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
25 changes: 25 additions & 0 deletions packages/lint/src/rules/media.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,31 @@ describe("media rules", () => {
expect(findings.every((finding) => finding.fixHint?.includes("data-var-src"))).toBe(true);
});

it.each([
{
script: `const target = document.getElementById("clip"); { const target = document.getElementById("poster"); target.src = "b.png"; } target.src = "b.mp4";`,
expected: ["clip"],
},
{
script: `const target = document.getElementById("clip"); function update(target) { target.src = "b.png"; }`,
expected: [],
},
{
script: `function updateVideo() { const target = document.getElementById("clip"); target.src = "b.mp4"; } function updateImage() { const target = document.getElementById("poster"); target.src = "b.png"; }`,
expected: ["clip"],
},
])("checks the media source write in its own scope ($expected)", async ({ script, expected }) => {
const html = `<html><body><div data-composition-id="c1" data-width="1920" data-height="1080">
<video id="clip" src="a.mp4"></video><img id="poster" src="a.png">
<script>${script}</script></div></body></html>`;
const result = await lintHyperframeHtml(html);
expect(
result.findings
.filter((finding) => finding.code === "media_runtime_src_mutation")
.map((finding) => finding.elementId),
).toEqual(expected);
});

it("does not flag img or script src mutations", async () => {
const html = `
<html><body>
Expand Down
113 changes: 113 additions & 0 deletions packages/parsers/src/mediaSrcMutation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { describe, expect, it } from "vitest";
import { extractMediaSrcMutations } from "./mediaSrcMutation.js";

describe("extractMediaSrcMutations", () => {
it.each([
{
label: "block",
script: `const media = document.getElementById("video"); { const media = document.getElementById("image"); media.src = "image.png"; } media.src = "new.mp4";`,
},
{
label: "sibling functions",
script: `function updateImage() { const media = document.getElementById("image"); media.src = "image.png"; } function updateVideo() { const media = document.getElementById("video"); media.src = "new.mp4"; }`,
},
{
label: "arrow function",
script: `const media = document.getElementById("video"); const updateImage = () => { const media = document.getElementById("image"); media.src = "image.png"; }; media.src = "new.mp4";`,
},
])("keeps $label bindings separate", ({ script }) => {
expect(extractMediaSrcMutations(script).map((mutation) => mutation.selector)).toEqual([
"#image",
"#video",
]);
});

it.each([
{
label: "parameter",
script: `const media = document.getElementById("video"); function update(media) { media.src = "image.png"; } media.src = "new.mp4";`,
},
{
label: "destructured parameter",
script: `const media = document.getElementById("video"); function update({ media }) { media.src = "image.png"; } media.src = "new.mp4";`,
},
{
label: "unknown local",
script: `const media = document.getElementById("video"); { const media = findImage(); media.src = "image.png"; } media.src = "new.mp4";`,
},
{
label: "destructured local",
script: `const media = document.getElementById("video"); { const { media } = assets; media.src = "image.png"; } media.src = "new.mp4";`,
},
{
label: "catch parameter",
script: `const media = document.getElementById("video"); try { update(); } catch(media) { media.src = "image.png"; } media.src = "new.mp4";`,
},
{
label: "loop variable",
script: `const media = document.getElementById("video"); for (const media of images) { media.src = "image.png"; } media.src = "new.mp4";`,
},
])("does not inherit an outer target through a $label", ({ script }) => {
expect(extractMediaSrcMutations(script)).toEqual([
{ selector: "#video", operation: "src_assignment", raw: `media.src = "new.mp4"` },
]);
});

it("resolves a closure and its scoped aliases", () => {
const script = `const media = document.getElementById("video"); { const alias = media; function update() { alias.setAttribute("src", "new.mp4"); } }`;
expect(extractMediaSrcMutations(script)).toEqual([
{
selector: "#video",
operation: "set_attribute",
raw: `alias.setAttribute("src", "new.mp4")`,
},
]);
});

it("keeps var declarations in their enclosing function", () => {
const script = `function update() { { var media = document.getElementById("video"); } media.src = "new.mp4"; }`;
expect(extractMediaSrcMutations(script).map((mutation) => mutation.selector)).toEqual([
"#video",
]);
});

it("retains a parameter that is redeclared and initialized with var", () => {
const script = `function update(media) { var media = document.getElementById("video"); media.src = "new.mp4"; }`;
expect(extractMediaSrcMutations(script).map((mutation) => mutation.selector)).toEqual([
"#video",
]);
});

it("does not inherit an outer target through a named class expression", () => {
const script = `const media = document.getElementById("video"); const View = class media { update() { media.src = "not-a-video"; } }; media.src = "new.mp4";`;
expect(extractMediaSrcMutations(script).map((mutation) => mutation.selector)).toEqual([
"#video",
]);
});

it("does not leak block declarations outside their scope", () => {
const script = `{ const media = document.getElementById("video"); } media.src = "new.mp4";`;
expect(extractMediaSrcMutations(script)).toEqual([]);
});

it("retains direct and computed DOM lookups", () => {
const script = `document["getElementById"]("video")["src"] = "new.mp4"; document.querySelector("#audio").setAttribute("SRC", "new.wav");`;
expect(extractMediaSrcMutations(script).map((mutation) => mutation.selector)).toEqual([
"#video",
"#audio",
]);
});

it("keeps module parsing and rejects malformed scripts", () => {
expect(
extractMediaSrcMutations(
`export const media = document.getElementById("video"); media.src = "new.mp4";`,
),
).toHaveLength(1);
expect(extractMediaSrcMutations("const media = ;")).toEqual([]);
});

it("does not recurse through circular aliases", () => {
expect(extractMediaSrcMutations(`let a = b; let b = a; a.src = "new.mp4";`)).toEqual([]);
});
});
Loading
Loading