diff --git a/packages/react/cypress/component/LazyVideo.cy.tsx b/packages/react/cypress/component/LazyVideo.cy.tsx index 7916405..a26d90c 100644 --- a/packages/react/cypress/component/LazyVideo.cy.tsx +++ b/packages/react/cypress/component/LazyVideo.cy.tsx @@ -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( + { + // 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", () => { diff --git a/packages/react/src/LazyVideo/LazyVideoServer.tsx b/packages/react/src/LazyVideo/LazyVideoServer.tsx index d746aae..89d23d6 100644 --- a/packages/react/src/LazyVideo/LazyVideoServer.tsx +++ b/packages/react/src/LazyVideo/LazyVideoServer.tsx @@ -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 {