@@ -419,3 +419,61 @@ describe("removeLabel", () => {
419419 expect ( result . forward ) . toHaveLength ( 0 ) ;
420420 } ) ;
421421} ) ;
422+
423+ // ─── removeElement GSAP cascade ──────────────────────────────────────────────
424+
425+ describe ( "removeElement — GSAP cascade" , ( ) => {
426+ it ( "removes animations targeting the removed element from the script" , ( ) => {
427+ const parsed = fresh ( ) ;
428+ const result = applyOp ( parsed , { type : "removeElement" , target : "hf-box" } ) ;
429+ // forward: [remove_element, replace_script]
430+ expect ( result . forward ) . toHaveLength ( 2 ) ;
431+ expect ( result . forward [ 0 ] ) . toEqual ( { op : "remove" , path : "/elements/hf-box" } ) ;
432+ const newScript = String ( result . forward [ 1 ] ?. value ?? "" ) ;
433+ expect ( newScript ) . not . toContain ( "hf-box" ) ;
434+ } ) ;
435+
436+ it ( "inverse restores element AND script" , ( ) => {
437+ const parsed = fresh ( ) ;
438+ const { inverse } = applyOp ( parsed , { type : "removeElement" , target : "hf-box" } ) ;
439+ // inverse[0] = restore element, inverse[1] = restore script
440+ expect ( inverse ) . toHaveLength ( 2 ) ;
441+ expect ( inverse [ 0 ] ?. op ) . toBe ( "add" ) ;
442+ expect ( inverse [ 0 ] ?. path ) . toBe ( "/elements/hf-box" ) ;
443+ expect ( inverse [ 1 ] ?. op ) . toBe ( "replace" ) ;
444+ expect ( inverse [ 1 ] ?. path ) . toBe ( "/script/gsap" ) ;
445+ const restoredScript = String ( inverse [ 1 ] ?. value ?? "" ) ;
446+ expect ( restoredScript ) . toContain ( "hf-box" ) ;
447+ } ) ;
448+
449+ it ( "applying inverse restores element and GSAP script to original" , ( ) => {
450+ const parsed = fresh ( ) ;
451+ const origScript = getScript ( parsed ) ;
452+ const { inverse } = applyOp ( parsed , { type : "removeElement" , target : "hf-box" } ) ;
453+ applyPatchesToDocument ( parsed , inverse ) ;
454+ expect ( parsed . document . querySelector ( '[data-hf-id="hf-box"]' ) ) . not . toBeNull ( ) ;
455+ expect ( getScript ( parsed ) ) . toBe ( origScript ) ;
456+ } ) ;
457+
458+ it ( "emits only element patch when composition has no GSAP script" , ( ) => {
459+ const noScriptHtml = `<div data-hf-id="hf-stage" data-hf-root style="width:1280px;height:720px">
460+ <div data-hf-id="hf-box"></div>
461+ </div>` . trim ( ) ;
462+ const parsed = parseMutable ( noScriptHtml ) ;
463+ const result = applyOp ( parsed , { type : "removeElement" , target : "hf-box" } ) ;
464+ expect ( result . forward ) . toHaveLength ( 1 ) ;
465+ expect ( result . forward [ 0 ] ?. op ) . toBe ( "remove" ) ;
466+ } ) ;
467+
468+ it ( "does not remove animations targeting other elements" , ( ) => {
469+ const twoTweenScript = `var tl = gsap.timeline({ paused: true });
470+ tl.to("[data-hf-id=\\"hf-box\\"]", { opacity: 1, duration: 0.5 }, 0);
471+ tl.to("[data-hf-id=\\"hf-stage\\"]", { scale: 1.05, duration: 1 }, 0);
472+ window.__timelines["t"] = tl;` ;
473+ const parsed = fresh ( twoTweenScript ) ;
474+ const result = applyOp ( parsed , { type : "removeElement" , target : "hf-box" } ) ;
475+ const newScript = String ( result . forward [ 1 ] ?. value ?? "" ) ;
476+ expect ( newScript ) . not . toContain ( "hf-box" ) ;
477+ expect ( newScript ) . toContain ( "hf-stage" ) ;
478+ } ) ;
479+ } ) ;
0 commit comments