(parsed: ParsedDocument, op: EditOp)
| 1410 | /** Dry-run validation — returns CanResult for the given op against current document state. */ |
| 1411 | // fallow-ignore-next-line complexity |
| 1412 | export function validateOp(parsed: ParsedDocument, op: EditOp): CanResult { |
| 1413 | switch (op.type) { |
| 1414 | case "setStyle": |
| 1415 | case "setText": |
| 1416 | case "setAttribute": |
| 1417 | case "setTiming": |
| 1418 | case "setHold": |
| 1419 | case "moveElement": |
| 1420 | case "removeElement": { |
| 1421 | const ids = targets(op.target); |
| 1422 | if (ids.length === 0) return canErr("E_TARGET_NOT_FOUND", "No target ids provided."); |
| 1423 | const missing = ids.filter((id) => resolveScoped(parsed.document, id) === null); |
| 1424 | if (missing.length > 0) |
| 1425 | return canErr( |
| 1426 | "E_TARGET_NOT_FOUND", |
| 1427 | `Element(s) not found: ${missing.join(", ")}.`, |
| 1428 | "Verify the id against comp.getElements() or comp.find().", |
| 1429 | ); |
| 1430 | return CAN_OK; |
| 1431 | } |
| 1432 | case "addElement": { |
| 1433 | if (op.parent !== null && resolveScoped(parsed.document, op.parent) === null) |
| 1434 | return canErr( |
| 1435 | "E_TARGET_NOT_FOUND", |
| 1436 | `Parent element not found: "${op.parent}".`, |
| 1437 | "Verify the parent id against comp.getElements() or comp.find().", |
| 1438 | ); |
| 1439 | if (op.index < 0) return canErr("E_INVALID_ARGS", `index must be >= 0 (got ${op.index}).`); |
| 1440 | if (!op.html || op.html.trim().length === 0) |
| 1441 | return canErr("E_INVALID_HTML", "html must not be empty."); |
| 1442 | // Parse to check for <script> and zero-element fragments. |
| 1443 | // Use the same temp-div pattern as apply-patches.ts for consistency. |
| 1444 | const tmp = parsed.document.createElement("div"); |
| 1445 | tmp.innerHTML = op.html; |
| 1446 | if (tmp.firstElementChild === null) |
| 1447 | return canErr("E_INVALID_HTML", "html parses to zero element nodes."); |
| 1448 | if (tmp.querySelector("script") !== null) |
| 1449 | return canErr( |
| 1450 | "E_INVALID_HTML", |
| 1451 | "<script> elements are not permitted in addElement html.", |
| 1452 | "GSAP is managed by the composition's single script block; add tweens via addGsapTween.", |
| 1453 | ); |
| 1454 | return CAN_OK; |
| 1455 | } |
| 1456 | case "reorderElements": { |
| 1457 | if (op.entries.length === 0) return CAN_OK; |
| 1458 | const missing = op.entries |
| 1459 | .map((e) => e.target) |
| 1460 | .filter((id) => resolveScoped(parsed.document, id) === null); |
| 1461 | if (missing.length > 0) |
| 1462 | return canErr( |
| 1463 | "E_TARGET_NOT_FOUND", |
| 1464 | `Element(s) not found: ${missing.join(", ")}.`, |
| 1465 | "Verify the id against comp.getElements() or comp.find().", |
| 1466 | ); |
| 1467 | return CAN_OK; |
| 1468 | } |
| 1469 | case "setVariableValue": |
no test coverage detected