Skip to content
Draft
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
32 changes: 32 additions & 0 deletions packages/react/cypress/component/LazyVideo.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,38 @@ describe("responsive video", () => {
cy.viewport(500, 600);
cy.get("video").its("[0].currentSrc").should("contain", "portrait");
});

it("handles duplicate video URLs for different media queries", () => {
// This test simulates the Contentful scenario where the same video asset
// is used for both portrait and landscape, which should result in deduplication
const sameVideoUrl = "https://github.com/BKWLD/react-visual/raw/refs/heads/main/packages/react/cypress/fixtures/300x200.mp4";

cy.mount(
<LazyVideo
src={{
portrait: sameVideoUrl,
landscape: sameVideoUrl,
}}
sourceMedia={["(orientation:landscape)", "(orientation:portrait)"]}
videoLoader={({ src, media }) => {
// Both media queries return the same URL, simulating Contentful behavior
return sameVideoUrl;
}}
alt="Duplicate video URL test"
/>,
);

// Video should load in portrait mode
cy.get("video").its("[0].currentSrc").should("contain", "300x200.mp4");

// Switch to landscape - video should still load
cy.viewport(500, 250);
cy.get("video").its("[0].currentSrc").should("contain", "300x200.mp4");

// Switch back to portrait - video should still load
cy.viewport(500, 600);
cy.get("video").its("[0].currentSrc").should("contain", "300x200.mp4");
});
});

describe("Accessibility controls", () => {
Expand Down
11 changes: 10 additions & 1 deletion packages/react/src/LazyVideo/LazyVideoServer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,17 @@ export default function LazyVideo(props: LazyVideoProps): ReactNode {
// If the array ended up empty, abort
if (mediaSrcEntries.filter(([url]) => !!url).length == 0) return null;

// Deduplicate entries to prevent conflicts when the same URL is returned
// for different media queries (e.g., same Contentful asset for portrait/landscape)
const deduplicatedEntries = mediaSrcEntries.reduce<[string, string][]>((acc, [url, media]) => {
if (!url || acc.some(([seenUrl]) => seenUrl === url)) {
return acc;
}
return [...acc, [url, media]];
}, []);

// Make the hash
mediaSrcs = Object.fromEntries(mediaSrcEntries);
mediaSrcs = Object.fromEntries(deduplicatedEntries);

// Make a simple string src url
} else {
Expand Down