Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 145 additions & 1 deletion packages/core/src/compiler/compositionScoping.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, expect, it } from "vitest";
import { describe, expect, it, vi } from "vitest";
import { parseHTML } from "linkedom";
import { scopeCssToComposition, wrapScopedCompositionScript } from "./compositionScoping";

Expand Down Expand Up @@ -89,4 +89,148 @@ window.__timelines.scene = tl;
expect(fakeWindow.__selectedRootTitle).toBe("Scene");
expect(gsapTargets).toEqual([["Scene"], ["Scene"]]);
});

it("reads scoped proxy accessors with the original target receiver", () => {
const root = {
contains(node: unknown) {
return node === root;
},
};
const body = { tagName: "BODY" };
const fakeDocument = {
querySelector(selector: string) {
return selector === '[data-composition-id="scene"]' ? root : null;
},
querySelectorAll() {
return [];
},
getElementById() {
return null;
},
get body() {
if (this !== fakeDocument) {
throw new TypeError("Illegal invocation");
}
return body;
},
};
const location = { href: "https://example.test/scene" };
const fakeUtils = {
get marker() {
if (this !== fakeUtils) {
throw new TypeError("Illegal invocation");
}
return "utils-ok";
},
};
const fakeGsap = {
utils: fakeUtils,
get version() {
if (this !== fakeGsap) {
throw new TypeError("Illegal invocation");
}
return "gsap-ok";
},
};
const fakeWindow = {
document: fakeDocument,
__bodyTag: "",
__href: "",
__windowSet: "",
__gsapVersion: "",
__utilsMarker: "",
__timelines: {},
gsap: fakeGsap,
get location() {
if (this !== fakeWindow) {
throw new TypeError("Illegal invocation");
}
return location;
},
set customValue(value: string) {
if (this !== fakeWindow) {
throw new TypeError("Illegal invocation");
}
this.__windowSet = value;
},
};
const wrapped = wrapScopedCompositionScript(
`
window.__bodyTag = document.body.tagName;
window.__href = window.location.href;
window.customValue = "window-set-ok";
window.__gsapVersion = gsap.version;
window.__utilsMarker = gsap.utils.marker;
`,
"scene",
);
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});

try {
new Function("window", "gsap", wrapped)(fakeWindow, fakeWindow.gsap);
} finally {
errorSpy.mockRestore();
}

expect(fakeWindow.__bodyTag).toBe("BODY");
expect(fakeWindow.__href).toBe("https://example.test/scene");
expect(fakeWindow.__windowSet).toBe("window-set-ok");
expect(fakeWindow.__gsapVersion).toBe("gsap-ok");
expect(fakeWindow.__utilsMarker).toBe("utils-ok");
expect(errorSpy).not.toHaveBeenCalled();
});

it("reads remapped timeline registry accessors with the original target receiver", () => {
let timeline = "initial";
const timelineRegistry = {
get host() {
if (this !== timelineRegistry) {
throw new TypeError("Illegal invocation");
}
return timeline;
},
set host(value: string) {
if (this !== timelineRegistry) {
throw new TypeError("Illegal invocation");
}
timeline = value;
},
};
const fakeWindow = {
document: {
querySelector() {
return null;
},
querySelectorAll() {
return [];
},
},
__timelines: timelineRegistry,
__beforeTimeline: "",
__afterTimeline: "",
gsap: {},
};
const wrapped = wrapScopedCompositionScript(
`
window.__beforeTimeline = window.__timelines.scene;
window.__timelines.scene = "updated";
window.__afterTimeline = window.__timelines.scene;
`,
"scene",
"[HyperFrames] composition script error:",
undefined,
"host",
);
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});

try {
new Function("window", "gsap", wrapped)(fakeWindow, fakeWindow.gsap);
} finally {
errorSpy.mockRestore();
}

expect(fakeWindow.__beforeTimeline).toBe("initial");
expect(fakeWindow.__afterTimeline).toBe("updated");
expect(errorSpy).not.toHaveBeenCalled();
});
});
14 changes: 7 additions & 7 deletions packages/core/src/compiler/compositionScoping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ export function wrapScopedCompositionScript(
return found && __hfContains(found) ? found : null;
};
}
var value = Reflect.get(target, prop, receiver);
var value = Reflect.get(target, prop, target);
return typeof value === "function" ? value.bind(target) : value;
},
})
Expand All @@ -166,10 +166,10 @@ export function wrapScopedCompositionScript(
if (!__hfTimelineRegistryProxy) {
__hfTimelineRegistryProxy = new Proxy(window.__timelines, {
get: function(target, prop, receiver) {
return Reflect.get(target, prop === __hfCompId ? __hfTimelineCompId : prop, receiver);
return Reflect.get(target, prop === __hfCompId ? __hfTimelineCompId : prop, target);
},
set: function(target, prop, value, receiver) {
return Reflect.set(target, prop === __hfCompId ? __hfTimelineCompId : prop, value, receiver);
return Reflect.set(target, prop === __hfCompId ? __hfTimelineCompId : prop, value, target);
},
});
}
Expand All @@ -179,7 +179,7 @@ export function wrapScopedCompositionScript(
? new Proxy(window, {
get: function(target, prop, receiver) {
if (prop === "__timelines") return __hfGetTimelineRegistry();
var value = Reflect.get(target, prop, receiver);
var value = Reflect.get(target, prop, target);
return typeof value === "function" ? value.bind(target) : value;
},
set: function(target, prop, value, receiver) {
Expand All @@ -188,7 +188,7 @@ export function wrapScopedCompositionScript(
__hfTimelineRegistryProxy = null;
return true;
}
return Reflect.set(target, prop, value, receiver);
return Reflect.set(target, prop, value, target);
},
})
: window;
Expand Down Expand Up @@ -252,12 +252,12 @@ export function wrapScopedCompositionScript(
};
};
}
var value = Reflect.get(utilsTarget, utilsProp, utilsReceiver);
var value = Reflect.get(utilsTarget, utilsProp, utilsTarget);
return typeof value === "function" ? value.bind(utilsTarget) : value;
},
});
}
var value = Reflect.get(target, prop, receiver);
var value = Reflect.get(target, prop, target);
return typeof value === "function" ? value.bind(target) : value;
},
});
Expand Down
Loading