From ddbabb4df15d97a83bc052ea7fce86a5d0a98271 Mon Sep 17 00:00:00 2001 From: nicojav Date: Thu, 21 Aug 2025 15:10:35 -0300 Subject: [PATCH 1/9] fixed autoplay prop + test --- src/lib/index.test.tsx | 323 ++++++++++++++++++++++++----------------- src/lib/index.tsx | 2 +- 2 files changed, 187 insertions(+), 138 deletions(-) diff --git a/src/lib/index.test.tsx b/src/lib/index.test.tsx index 0d71c2b..4e03c2a 100644 --- a/src/lib/index.test.tsx +++ b/src/lib/index.test.tsx @@ -1,311 +1,360 @@ -import * as React from 'react'; -import { render, screen, fireEvent } from '@testing-library/react'; -import '@testing-library/jest-dom'; -import { vi } from 'vitest'; -import LiteYouTubeEmbed from './index'; +import * as React from "react"; +import { render, screen, fireEvent } from "@testing-library/react"; +import "@testing-library/jest-dom"; +import { vi } from "vitest"; +import LiteYouTubeEmbed from "./index"; +import { imgResolution } from "./useYoutubeThumbnail"; -describe('LiteYouTubeEmbed', () => { +describe("LiteYouTubeEmbed", () => { const defaultProps = { - id: 'dQw4w9WgXcQ', - title: 'Rick Astley - Never Gonna Give You Up', + id: "dQw4w9WgXcQ", + title: "Rick Astley - Never Gonna Give You Up", }; - test('renders with default props', () => { + test("renders with default props", () => { const { container } = render(); - + // Check if the component renders with the correct title as data attribute - const article = container.querySelector('article'); - expect(article).toHaveAttribute('data-title', defaultProps.title); - + const article = container.querySelector("article"); + expect(article).toHaveAttribute("data-title", defaultProps.title); + // Check if the play button is rendered - const playButton = screen.getByRole('button'); + const playButton = screen.getByRole("button"); expect(playButton).toBeInTheDocument(); - expect(playButton).toHaveAttribute('aria-label', `Watch ${defaultProps.title}`); - + expect(playButton).toHaveAttribute( + "aria-label", + `Watch ${defaultProps.title}` + ); + // Check if iframe is not rendered initially const iframe = screen.queryByTitle(defaultProps.title); expect(iframe).not.toBeInTheDocument(); }); - test('loads iframe when clicked', () => { + test("loads iframe when clicked", () => { const { container } = render(); - + // Click the play button - const playButton = screen.getByRole('button'); + const playButton = screen.getByRole("button"); fireEvent.click(playButton); - + // Check if iframe is rendered after click - const iframe = container.querySelector('iframe'); + const iframe = container.querySelector("iframe"); expect(iframe).not.toBeNull(); - expect(iframe).toHaveAttribute('src', expect.stringContaining(defaultProps.id)); - expect(iframe).toHaveAttribute('src', expect.stringContaining('autoplay=1')); + expect(iframe).toHaveAttribute( + "src", + expect.stringContaining(defaultProps.id) + ); + expect(iframe).toHaveAttribute( + "src", + expect.stringContaining("autoplay=1") + ); }); - test('preconnects when hovered', () => { + test("preconnects when hovered", () => { const { container } = render(); - + // Hover over the container - const article = container.querySelector('article'); + const article = container.querySelector("article"); expect(article).not.toBeNull(); if (article) { fireEvent.pointerOver(article); - + // Check if preconnect links are added - const preconnectLinks = document.querySelectorAll('link[rel="preconnect"]'); + const preconnectLinks = document.querySelectorAll( + 'link[rel="preconnect"]' + ); expect(preconnectLinks.length).toBeGreaterThan(0); - + // Check if YouTube domain is preconnected - const ytPreconnect = document.querySelector('link[rel="preconnect"][href="https://www.youtube-nocookie.com"]'); + const ytPreconnect = document.querySelector( + 'link[rel="preconnect"][href="https://www.youtube-nocookie.com"]' + ); expect(ytPreconnect).toBeInTheDocument(); } }); - test('renders with custom poster resolution', () => { + test("renders with custom poster resolution", () => { const customProps = { ...defaultProps, - poster: 'maxresdefault', + poster: "maxresdefault" as imgResolution, }; - + const { container } = render(); - + // Check if the background image URL contains the custom resolution - const article = container.querySelector('article'); + const article = container.querySelector("article"); expect(article).toHaveStyle({ - backgroundImage: expect.stringContaining('maxresdefault.jpg'), + backgroundImage: expect.stringContaining("maxresdefault.jpg"), }); }); - test('renders with webp format', () => { + test("renders with webp format", () => { const webpProps = { ...defaultProps, webp: true, }; - + const { container } = render(); - + // Check if the background image URL uses webp format - const article = container.querySelector('article'); + const article = container.querySelector("article"); expect(article).toHaveStyle({ - backgroundImage: expect.stringContaining('.webp'), + backgroundImage: expect.stringContaining(".webp"), }); }); - test('renders with custom thumbnail', () => { + test("renders with custom thumbnail", () => { const customThumbnailProps = { ...defaultProps, - thumbnail: 'https://example.com/custom-thumbnail.jpg', + thumbnail: "https://example.com/custom-thumbnail.jpg", }; - - const { container } = render(); - + + const { container } = render( + + ); + // Check if the background image URL is the custom thumbnail - const article = container.querySelector('article'); + const article = container.querySelector("article"); expect(article).toHaveStyle({ - backgroundImage: 'url(https://example.com/custom-thumbnail.jpg)', + backgroundImage: "url(https://example.com/custom-thumbnail.jpg)", }); }); - test('renders with custom classes', () => { + test("renders with custom classes", () => { const customClassProps = { ...defaultProps, - wrapperClass: 'custom-wrapper', - playerClass: 'custom-player', - iframeClass: 'custom-iframe', - activatedClass: 'custom-activated', + wrapperClass: "custom-wrapper", + playerClass: "custom-player", + iframeClass: "custom-iframe", + activatedClass: "custom-activated", }; - + const { container } = render(); - + // Check if custom wrapper class is applied - const wrapper = container.querySelector('.custom-wrapper'); + const wrapper = container.querySelector(".custom-wrapper"); expect(wrapper).not.toBeNull(); - + // Check if custom player class is applied - const player = container.querySelector('.custom-player'); + const player = container.querySelector(".custom-player"); expect(player).not.toBeNull(); - + if (player) { // Click to load iframe fireEvent.click(player); - + // Check if custom iframe class is applied - const iframe = container.querySelector('.custom-iframe'); + const iframe = container.querySelector(".custom-iframe"); expect(iframe).not.toBeNull(); - + // Check if activated class is applied after click - expect(wrapper).toHaveClass('custom-activated'); + expect(wrapper).toHaveClass("custom-activated"); } }); - test('renders with custom aspect ratio', () => { + test("renders with custom aspect ratio", () => { const aspectRatioProps = { ...defaultProps, aspectHeight: 4, aspectWidth: 3, }; - + const { container } = render(); - + // Check if custom aspect ratio is applied - const article = container.querySelector('article'); + const article = container.querySelector("article"); expect(article).toHaveStyle({ - '--aspect-ratio': '133.33333333333331%', + "--aspect-ratio": "133.33333333333331%", }); }); - test('renders with adNetwork enabled', () => { - const { container } = render(); - + test("renders with adNetwork enabled", () => { + const { container } = render( + + ); + // Trigger preconnect - const article = container.querySelector('article'); + const article = container.querySelector("article"); expect(article).not.toBeNull(); if (article) { fireEvent.pointerOver(article); - + // Check if ad network domains are preconnected - const doubleClickPreconnect = document.querySelector('link[rel="preconnect"][href="https://static.doubleclick.net"]'); + const doubleClickPreconnect = document.querySelector( + 'link[rel="preconnect"][href="https://static.doubleclick.net"]' + ); expect(doubleClickPreconnect).toBeInTheDocument(); - - const googleAdsPreconnect = document.querySelector('link[rel="preconnect"][href="https://googleads.g.doubleclick.net"]'); + + const googleAdsPreconnect = document.querySelector( + 'link[rel="preconnect"][href="https://googleads.g.doubleclick.net"]' + ); expect(googleAdsPreconnect).toBeInTheDocument(); } }); - test('renders with playlist mode', () => { + test("renders with playlist mode", () => { const playlistProps = { ...defaultProps, playlist: true, - playlistCoverId: 'dQw4w9WgXcQ', + playlistCoverId: "dQw4w9WgXcQ", }; - + render(); - + // Click to load iframe - const playButton = screen.getByRole('button'); + const playButton = screen.getByRole("button"); fireEvent.click(playButton); - + // Check if iframe src contains videoseries const iframe = screen.getByTitle(playlistProps.title); - expect(iframe).toHaveAttribute('src', expect.stringContaining('videoseries')); - expect(iframe).toHaveAttribute('src', expect.stringContaining('list=dQw4w9WgXcQ')); + expect(iframe).toHaveAttribute( + "src", + expect.stringContaining("videoseries") + ); + expect(iframe).toHaveAttribute( + "src", + expect.stringContaining("list=dQw4w9WgXcQ") + ); }); - test('renders with always load iframe', () => { + test("renders with always load iframe", () => { render(); - + // Check if iframe is rendered initially without clicking const iframe = screen.getByTitle(defaultProps.title); expect(iframe).toBeInTheDocument(); - + // Check that autoplay is not set when alwaysLoadIframe is true - expect(iframe).toHaveAttribute('src', expect.not.stringContaining('autoplay=1')); + expect(iframe).toHaveAttribute( + "src", + expect.not.stringContaining("autoplay=1") + ); }); - test('calls onIframeAdded callback when iframe is added', () => { + test("calls onIframeAdded callback when iframe is added", () => { const onIframeAdded = vi.fn(); - render(); - + render( + + ); + // Click to load iframe - const playButton = screen.getByRole('button'); + const playButton = screen.getByRole("button"); fireEvent.click(playButton); - + // Check if callback was called expect(onIframeAdded).toHaveBeenCalledTimes(1); }); - test('renders with custom container element', () => { - const { container } = render(); - + test("renders with custom container element", () => { + const { container } = render( + + ); + // Check if section element is used instead of default article - const section = container.querySelector('section'); + const section = container.querySelector("section"); expect(section).toBeInTheDocument(); }); - test('renders with custom style', () => { + test("renders with custom style", () => { const customStyle = { - border: '2px solid red', - borderRadius: '10px', + border: "2px solid red", + borderRadius: "10px", }; - - const { container } = render(); - + + const { container } = render( + + ); + // Check if custom styles are applied - const article = container.querySelector('article'); + const article = container.querySelector("article"); expect(article).toHaveStyle({ - border: '2px solid red', - borderRadius: '10px', + border: "2px solid red", + borderRadius: "10px", }); }); - test('renders with muted parameter', () => { + test("renders with muted parameter", () => { render(); - + // Click to load iframe - const playButton = screen.getByRole('button'); + const playButton = screen.getByRole("button"); fireEvent.click(playButton); - + // Check if iframe src contains mute=1 const iframe = screen.getByTitle(defaultProps.title); - expect(iframe).toHaveAttribute('src', expect.stringContaining('mute=1')); + expect(iframe).toHaveAttribute("src", expect.stringContaining("mute=1")); }); - test('renders with enableJsApi parameter', () => { + test("renders with enableJsApi parameter", () => { render(); - + // Click to load iframe - const playButton = screen.getByRole('button'); + const playButton = screen.getByRole("button"); fireEvent.click(playButton); - + // Check if iframe src contains enablejsapi=1 const iframe = screen.getByTitle(defaultProps.title); - expect(iframe).toHaveAttribute('src', expect.stringContaining('enablejsapi=1')); + expect(iframe).toHaveAttribute( + "src", + expect.stringContaining("enablejsapi=1") + ); }); - test('renders with cookie parameter', () => { + test("renders with cookie parameter", () => { render(); - + // Click to load iframe - const playButton = screen.getByRole('button'); + const playButton = screen.getByRole("button"); fireEvent.click(playButton); - + // Check if iframe src uses youtube.com instead of youtube-nocookie.com const iframe = screen.getByTitle(defaultProps.title); - expect(iframe).toHaveAttribute('src', expect.stringContaining('https://www.youtube.com/embed/')); + expect(iframe).toHaveAttribute( + "src", + expect.stringContaining("https://www.youtube.com/embed/") + ); }); - test('renders with noCookie parameter', () => { + test("renders with noCookie parameter", () => { render(); - + // Click to load iframe - const playButton = screen.getByRole('button'); + const playButton = screen.getByRole("button"); fireEvent.click(playButton); - + // Check if iframe src uses youtube-nocookie.com const iframe = screen.getByTitle(defaultProps.title); - expect(iframe).toHaveAttribute('src', expect.stringContaining('https://www.youtube-nocookie.com/embed/')); + expect(iframe).toHaveAttribute( + "src", + expect.stringContaining("https://www.youtube-nocookie.com/embed/") + ); }); - test('renders with custom announce text', () => { + test("renders with custom announce text", () => { render(); - + // Check if play button has custom announce text - const playButton = screen.getByRole('button'); - expect(playButton).toHaveAttribute('aria-label', `Play ${defaultProps.title}`); + const playButton = screen.getByRole("button"); + expect(playButton).toHaveAttribute( + "aria-label", + `Play ${defaultProps.title}` + ); }); - test('forwards ref to iframe element', () => { + test("forwards ref to iframe element", () => { const ref = React.createRef(); render(); - + // Initially ref should be null as iframe is not rendered expect(ref.current).toBeNull(); - + // Click to load iframe - const playButton = screen.getByRole('button'); + const playButton = screen.getByRole("button"); fireEvent.click(playButton); - + // Now ref should point to iframe expect(ref.current).not.toBeNull(); - expect(ref.current.tagName).toBe('IFRAME'); + expect(ref.current!.tagName).toBe("IFRAME"); }); -}); \ No newline at end of file +}); diff --git a/src/lib/index.tsx b/src/lib/index.tsx index fa09950..436b4d0 100644 --- a/src/lib/index.tsx +++ b/src/lib/index.tsx @@ -24,7 +24,7 @@ export interface LiteYouTubeProps { wrapperClass?: string; onIframeAdded?: () => void; muted?: boolean; - autoplay: boolean; + autoplay?: boolean; thumbnail?: string; rel?: string; containerElement?: keyof React.JSX.IntrinsicElements; From 9d29424188364be9e0a3be3595085cd5c6ff921d Mon Sep 17 00:00:00 2001 From: nicojav Date: Thu, 21 Aug 2025 15:13:51 -0300 Subject: [PATCH 2/9] Added build step on test + publish --- .github/workflows/release.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 315c0b5..c6c1fd5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,6 +35,9 @@ jobs: - name: Install dependencies run: npm install + - name: Build the project + run: npm run build + - name: Run the test suite run: npm run test @@ -52,6 +55,10 @@ jobs: registry-url: 'https://registry.npmjs.org' - run: npm install + + - name: Build the project + run: npm run build + - run: npm publish env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} From 28010508e3f94473fa1a03d642d5fb8b78e92835 Mon Sep 17 00:00:00 2001 From: nicojav Date: Thu, 21 Aug 2025 15:14:43 -0300 Subject: [PATCH 3/9] testing release 2.5.5-rc1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ccbad63..b6c5f92 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-lite-youtube-embed", - "version": "2.5.4", + "version": "2.5.5-rc1", "description": "A private by default, faster and cleaner YouTube embed component for React applications", "type": "module", "main": "dist/index.js", From 94acd79d5eb1ab0f1258e56801e48d5892552fb1 Mon Sep 17 00:00:00 2001 From: nicojav Date: Thu, 21 Aug 2025 15:46:35 -0300 Subject: [PATCH 4/9] testing local publish --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index b6c5f92..22eb521 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "react-lite-youtube-embed", - "version": "2.5.5-rc1", + "name": "@nicojav/react-lite-youtube-embed", + "version": "2.5.5-rc2", "description": "A private by default, faster and cleaner YouTube embed component for React applications", "type": "module", "main": "dist/index.js", From 543b5cdbf2b361eadcdeddb5c4665700cd484714 Mon Sep 17 00:00:00 2001 From: nicojav Date: Thu, 21 Aug 2025 15:54:04 -0300 Subject: [PATCH 5/9] added public access --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index 22eb521..0eb6636 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,9 @@ "YouTube Embed", "Adaptive Loading" ], + "publishConfig": { + "access": "public" + }, "peerDependencies": { "react": ">=18.2.0", "react-dom": ">=18.2.0" From bb6a721441432e508b3888d11c46c7b6b944fec6 Mon Sep 17 00:00:00 2001 From: nicojav Date: Thu, 21 Aug 2025 15:54:18 -0300 Subject: [PATCH 6/9] bump 2.5.5-rc4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0eb6636..8b8ec2c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@nicojav/react-lite-youtube-embed", - "version": "2.5.5-rc2", + "version": "2.5.5-rc3", "description": "A private by default, faster and cleaner YouTube embed component for React applications", "type": "module", "main": "dist/index.js", From 8efa655f08ed80b83ad98e0abec6df4e2ea6b8b6 Mon Sep 17 00:00:00 2001 From: nicojav Date: Fri, 22 Aug 2025 09:57:51 -0300 Subject: [PATCH 7/9] fixed conditional exports --- package.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 8b8ec2c..09e14a5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@nicojav/react-lite-youtube-embed", - "version": "2.5.5-rc3", + "version": "2.5.5-rc4", "description": "A private by default, faster and cleaner YouTube embed component for React applications", "type": "module", "main": "dist/index.js", @@ -33,6 +33,15 @@ "YouTube Embed", "Adaptive Loading" ], + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.es.js", + "require": "./dist/index.js", + "default": "./dist/index.es.js" + }, + "./dist/LiteYouTubeEmbed.css": "./dist/LiteYouTubeEmbed.css" + }, "publishConfig": { "access": "public" }, From 03dbdad446ba87efde06ae0ab8ce693417fab5de Mon Sep 17 00:00:00 2001 From: nicojav Date: Fri, 22 Aug 2025 10:07:05 -0300 Subject: [PATCH 8/9] bumped 2.5.5 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 09e14a5..4b42131 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "@nicojav/react-lite-youtube-embed", - "version": "2.5.5-rc4", + "name": "react-lite-youtube-embed", + "version": "2.5.5", "description": "A private by default, faster and cleaner YouTube embed component for React applications", "type": "module", "main": "dist/index.js", From b1828cc7f44eee9342c745e980128d797c50b60d Mon Sep 17 00:00:00 2001 From: nicojav Date: Fri, 22 Aug 2025 10:12:22 -0300 Subject: [PATCH 9/9] removed access public --- package.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/package.json b/package.json index 4b42131..db4fae0 100644 --- a/package.json +++ b/package.json @@ -42,9 +42,6 @@ }, "./dist/LiteYouTubeEmbed.css": "./dist/LiteYouTubeEmbed.css" }, - "publishConfig": { - "access": "public" - }, "peerDependencies": { "react": ">=18.2.0", "react-dom": ">=18.2.0"