From b78f4c181c6f67baf3679b4a766215863f8ce343 Mon Sep 17 00:00:00 2001 From: Taj Date: Tue, 5 Aug 2025 13:46:12 +0530 Subject: [PATCH 1/2] fix(tests): stabilize tests by mocking core and controlling timers Resolved two main issues causing test failures: 1. Race condition from `setTimeout(..., 0)` during player init. 2. Tight coupling to internal refs not exposed by the component. Fixes: - Mocked `@interactive-video-labs/core` to isolate component behavior. - Used `vi.useFakeTimers()` to control async timing. - Exposed `playerRef` via setup to improve test accessibility. --- package.json | 10 ++- pnpm-lock.yaml | 123 ++++++++++++++++++++++++++++++++++ src/index.ts | 102 ++++++++++++++++++++++++++++ test/InteractiveVideo.test.ts | 78 +++++++++++++++++++++ tsconfig.json | 2 +- vitest.config.ts | 6 +- 6 files changed, 317 insertions(+), 4 deletions(-) create mode 100644 test/InteractiveVideo.test.ts diff --git a/package.json b/package.json index d8f6b85..d1ef1aa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "@interactive-video-labs/vue", "version": "0.0.1", + "type": "module", "description": "Thin Vue wrapper for the @interactive-video-labs/core engine. Enables cue-based interactive video playback in Vue 3 applications.", "main": "dist/index.cjs", "module": "dist/index.mjs", @@ -45,13 +46,18 @@ }, "packageManager": "pnpm@10.13.1", "peerDependencies": { - "vue": "^3.0.0", - "@interactive-video-labs/core": "^0.1.2" + "@interactive-video-labs/core": "^0.1.2", + "vue": "^3.0.0" }, "dependencies": { "jsdom": "^26.1.0", "tsup": "^8.5.0", "typescript": "^5.8.3", "vitest": "^3.2.4" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.0.5", + "@vue/test-utils": "^2.4.6", + "vitest": "^1.6.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9b9cdd6..660db00 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,13 @@ importers: vue: specifier: ^3.0.0 version: 3.5.18(typescript@5.9.2) + devDependencies: + '@vitejs/plugin-vue': + specifier: ^5.0.5 + version: 5.2.4(vite@7.0.6)(vue@3.5.18(typescript@5.9.2)) + '@vue/test-utils': + specifier: ^2.4.6 + version: 2.4.6 packages: @@ -253,6 +260,9 @@ packages: '@jridgewell/trace-mapping@0.3.29': resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==} + '@one-ini/wasm@0.1.1': + resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -366,6 +376,13 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@vitejs/plugin-vue@5.2.4': + resolution: {integrity: sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==} + engines: {node: ^18.0.0 || >=20.0.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 + vue: ^3.2.25 + '@vitest/expect@3.2.4': resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} @@ -424,6 +441,13 @@ packages: '@vue/shared@3.5.18': resolution: {integrity: sha512-cZy8Dq+uuIXbxCZpuLd2GJdeSO/lIzIspC2WtkqIpje5QyFbvLaI5wZtdUjLHjGZrlVX6GilejatWwVYYRc8tA==} + '@vue/test-utils@2.4.6': + resolution: {integrity: sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==} + + abbrev@2.0.0: + resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + acorn@8.15.0: resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} engines: {node: '>=0.4.0'} @@ -491,6 +515,10 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + commander@10.0.1: + resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} + engines: {node: '>=14'} + commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -498,6 +526,9 @@ packages: confbox@0.1.8: resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + config-chain@1.1.13: + resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} + consola@3.4.2: resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} engines: {node: ^14.18.0 || >=16.10.0} @@ -536,6 +567,11 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + editorconfig@1.0.4: + resolution: {integrity: sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==} + engines: {node: '>=14'} + hasBin: true + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -608,6 +644,9 @@ packages: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} @@ -625,6 +664,15 @@ packages: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} + js-beautify@1.15.4: + resolution: {integrity: sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==} + engines: {node: '>=14'} + hasBin: true + + js-cookie@3.0.5: + resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} + engines: {node: '>=14'} + js-tokens@9.0.1: resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} @@ -660,6 +708,10 @@ packages: magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + minimatch@9.0.1: + resolution: {integrity: sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==} + engines: {node: '>=16 || 14 >=14.17'} + minimatch@9.0.5: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} @@ -682,6 +734,11 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + nopt@7.2.1: + resolution: {integrity: sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + hasBin: true + nwsapi@2.2.21: resolution: {integrity: sha512-o6nIY3qwiSXl7/LuOU0Dmuctd34Yay0yeuZRLFmDPrrdHpXKFndPj3hM+YEPVHYC5fx2otBx4Ilc/gyYSAUaIA==} @@ -746,6 +803,9 @@ packages: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} + proto-list@1.2.4: + resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -773,6 +833,11 @@ packages: resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} engines: {node: '>=v12.22.7'} + semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + engines: {node: '>=10'} + hasBin: true + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -795,6 +860,7 @@ packages: source-map@0.8.0-beta.0: resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} engines: {node: '>= 8'} + deprecated: The work that was done in this beta branch won't be included in future versions stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -983,6 +1049,9 @@ packages: jsdom: optional: true + vue-component-type-helpers@2.2.12: + resolution: {integrity: sha512-YbGqHZ5/eW4SnkPNR44mKVc6ZKQoRs/Rux1sxC6rdwXb4qpbOSYfDr9DsTHolOTGmIKgM9j141mZbBeg05R1pw==} + vue@3.5.18: resolution: {integrity: sha512-7W4Y4ZbMiQ3SEo+m9lnoNpV9xG7QVMLa+/0RFwwiAVkeYoyGXqWE85jabU4pllJNUzqfLShJ5YLptewhCWUgNA==} peerDependencies: @@ -1200,6 +1269,8 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.4 + '@one-ini/wasm@0.1.1': {} + '@pkgjs/parseargs@0.11.0': optional: true @@ -1271,6 +1342,11 @@ snapshots: '@types/estree@1.0.8': {} + '@vitejs/plugin-vue@5.2.4(vite@7.0.6)(vue@3.5.18(typescript@5.9.2))': + dependencies: + vite: 7.0.6 + vue: 3.5.18(typescript@5.9.2) + '@vitest/expect@3.2.4': dependencies: '@types/chai': 5.2.2 @@ -1367,6 +1443,13 @@ snapshots: '@vue/shared@3.5.18': {} + '@vue/test-utils@2.4.6': + dependencies: + js-beautify: 1.15.4 + vue-component-type-helpers: 2.2.12 + + abbrev@2.0.0: {} + acorn@8.15.0: {} agent-base@7.1.4: {} @@ -1418,10 +1501,17 @@ snapshots: color-name@1.1.4: {} + commander@10.0.1: {} + commander@4.1.1: {} confbox@0.1.8: {} + config-chain@1.1.13: + dependencies: + ini: 1.3.8 + proto-list: 1.2.4 + consola@3.4.2: {} cross-spawn@7.0.6: @@ -1452,6 +1542,13 @@ snapshots: eastasianwidth@0.2.0: {} + editorconfig@1.0.4: + dependencies: + '@one-ini/wasm': 0.1.1 + commander: 10.0.1 + minimatch: 9.0.1 + semver: 7.7.2 + emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} @@ -1548,6 +1645,8 @@ snapshots: dependencies: safer-buffer: 2.1.2 + ini@1.3.8: {} + is-fullwidth-code-point@3.0.0: {} is-potential-custom-element-name@1.0.1: {} @@ -1562,6 +1661,16 @@ snapshots: joycon@3.1.1: {} + js-beautify@1.15.4: + dependencies: + config-chain: 1.1.13 + editorconfig: 1.0.4 + glob: 10.4.5 + js-cookie: 3.0.5 + nopt: 7.2.1 + + js-cookie@3.0.5: {} + js-tokens@9.0.1: {} jsdom@26.1.0: @@ -1607,6 +1716,10 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.4 + minimatch@9.0.1: + dependencies: + brace-expansion: 2.0.2 + minimatch@9.0.5: dependencies: brace-expansion: 2.0.2 @@ -1630,6 +1743,10 @@ snapshots: nanoid@3.3.11: {} + nopt@7.2.1: + dependencies: + abbrev: 2.0.0 + nwsapi@2.2.21: {} object-assign@4.1.1: {} @@ -1675,6 +1792,8 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + proto-list@1.2.4: {} + punycode@2.3.1: {} readdirp@4.1.2: {} @@ -1715,6 +1834,8 @@ snapshots: dependencies: xmlchars: 2.2.0 + semver@7.7.2: {} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -1921,6 +2042,8 @@ snapshots: - tsx - yaml + vue-component-type-helpers@2.2.12: {} + vue@3.5.18(typescript@5.9.2): dependencies: '@vue/compiler-dom': 3.5.18 diff --git a/src/index.ts b/src/index.ts index e69de29..b581c70 100644 --- a/src/index.ts +++ b/src/index.ts @@ -0,0 +1,102 @@ +import { defineComponent, onMounted, onUnmounted, ref, watch, h, PropType } from 'vue'; +import { IVLabsPlayer, PlayerConfig, CuePoint, Translations, AnalyticsEvent, AnalyticsPayload } from '@interactive-video-labs/core'; + +// Define the props interface based on the React component's props +export interface InteractiveVideoProps extends Omit { + videoUrl: string; + onAnalyticsEvent?: (event: AnalyticsEvent, payload?: AnalyticsPayload) => void; + cues?: CuePoint[]; + translations?: Translations; +} + +export default defineComponent({ + name: 'InteractiveVideo', + props: { + videoUrl: { type: String, required: true }, + onAnalyticsEvent: { type: Function as PropType<(event: AnalyticsEvent, payload?: AnalyticsPayload) => void> }, + cues: { type: Array as PropType }, + translations: { type: Object as PropType }, + // It's better to explicitly define the props that can be passed + autoplay: { type: Boolean, default: false }, + loop: { type: Boolean, default: false }, + locale: { type: String, default: 'en' }, + // Add other PlayerConfig properties here as needed + }, + setup(props, { attrs, expose }) { + const containerRef = ref(null); + const playerRef = ref(null); + const uniqueId = `ivlabs-player-${Math.random().toString(36).substr(2, 9)}`; + + onMounted(() => { + if (containerRef.value) { + // Combine props and non-prop attributes to pass to the player + const playerConfig: PlayerConfig = { + ...attrs, + videoUrl: props.videoUrl, + autoplay: props.autoplay, + loop: props.loop, + locale: props.locale, + }; + + try { + // Use a timeout to ensure the element is in the DOM + setTimeout(() => { + if (containerRef.value) { + const player = new IVLabsPlayer(`#${uniqueId}`, playerConfig); + playerRef.value = player; + + if (props.onAnalyticsEvent) { + player.on('PLAYER_LOADED', (payload?: AnalyticsPayload) => (props.onAnalyticsEvent as Function)('PLAYER_LOADED', payload)); + player.on('VIDEO_STARTED', (payload?: AnalyticsPayload) => (props.onAnalyticsEvent as Function)('VIDEO_STARTED', payload)); + player.on('VIDEO_PAUSED', (payload?: AnalyticsPayload) => (props.onAnalyticsEvent as Function)('VIDEO_PAUSED', payload)); + player.on('VIDEO_ENDED', (payload?: AnalyticsPayload) => (props.onAnalyticsEvent as Function)('VIDEO_ENDED', payload)); + player.on('CUE_TRIGGERED', (payload?: AnalyticsPayload) => (props.onAnalyticsEvent as Function)('CUE_TRIGGERED', payload)); + player.on('INTERACTION_COMPLETED', (payload?: AnalyticsPayload) => (props.onAnalyticsEvent as Function)('INTERACTION_COMPLETED', payload)); + player.on('ERROR', (payload?: AnalyticsPayload) => (props.onAnalyticsEvent as Function)('ERROR', payload)); + } + + if (props.cues) { + player.loadCues(props.cues); + } + + if (props.translations) { + player.loadTranslations(props.locale, props.translations); + } + } + }, 0); + } catch (error) { + console.error('Error initializing IVLabsPlayer:', error); + } + } + }); + + onUnmounted(() => { + if (playerRef.value) { + playerRef.value.destroy(); + playerRef.value = null; + } + }); + + watch(() => props.cues, (newCues) => { + if (playerRef.value && newCues) { + playerRef.value.loadCues(newCues); + } + }, { deep: true }); + + watch(() => props.translations, (newTranslations) => { + if (playerRef.value && newTranslations) { + playerRef.value.loadTranslations(props.locale, newTranslations); + } + }, { deep: true }); + + expose({ playerRef }); + + // Use Vue's render function `h` to create the container div + return () => h('div', { + ref: containerRef, + id: uniqueId, + style: { width: '100%', height: 'auto' }, + 'data-testid': 'interactive-video-container', + }); + }, +}); \ No newline at end of file diff --git a/test/InteractiveVideo.test.ts b/test/InteractiveVideo.test.ts new file mode 100644 index 0000000..1c46f06 --- /dev/null +++ b/test/InteractiveVideo.test.ts @@ -0,0 +1,78 @@ +import { mount } from '@vue/test-utils'; +import InteractiveVideo from '../src/index'; +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { IVLabsPlayer } from '@interactive-video-labs/core'; + +// Mock the IVLabsPlayer +vi.mock('@interactive-video-labs/core', () => { + const mockPlayer = { + on: vi.fn(), + loadCues: vi.fn(), + loadTranslations: vi.fn(), + destroy: vi.fn(), + emit: vi.fn(), // Mock the emit method + }; + return { + IVLabsPlayer: vi.fn(() => mockPlayer), + }; +}); + +describe('InteractiveVideo', () => { + beforeEach(() => { + vi.useFakeTimers(); + vi.clearAllMocks(); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it('renders the video container', () => { + const wrapper = mount(InteractiveVideo, { + props: { + videoUrl: 'https://example.com/video.mp4', + }, + }); + expect(wrapper.find('[data-testid="interactive-video-container"]').exists()).toBe(true); + }); + + it('initializes the player on mount', async () => { + const wrapper = mount(InteractiveVideo, { + props: { + videoUrl: 'https://example.com/video.mp4', + }, + attachTo: document.body, // Attach to the DOM + }); + + // Fast-forward timers to trigger the player initialization + vi.runAllTimers(); + await wrapper.vm.$nextTick(); + + expect(IVLabsPlayer).toHaveBeenCalledTimes(1); + expect(wrapper.find('div').attributes('id')).toContain('ivlabs-player'); + }); + + it('calls onAnalyticsEvent when an event is fired', async () => { + const onAnalyticsEvent = vi.fn(); + const wrapper = mount(InteractiveVideo, { + props: { + videoUrl: 'https://example.com/video.mp4', + onAnalyticsEvent, + }, + attachTo: document.body, // Attach to the DOM + }); + + // Fast-forward timers to trigger the player initialization + vi.runAllTimers(); + await wrapper.vm.$nextTick(); + + // Get the mocked player instance + const mockPlayerInstance = (IVLabsPlayer as any).mock.results[0].value; + + // Simulate an event being fired by the player + const eventCallback = (mockPlayerInstance.on as any).mock.calls.find(call => call[0] === 'PLAYER_LOADED')[1]; + eventCallback({ some: 'payload' }); + + expect(onAnalyticsEvent).toHaveBeenCalledWith('PLAYER_LOADED', { some: 'payload' }); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index 2d6475e..d8e3c3d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "target": "ES2020", "module": "ESNext", - "moduleResolution": "Node", + "moduleResolution": "bundler", "lib": ["DOM", "ES2020"], "types": ["vitest/globals"], "allowJs": true, diff --git a/vitest.config.ts b/vitest.config.ts index 582eda0..4e93029 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,7 +1,11 @@ + import { defineConfig } from 'vitest/config'; +import vue from '@vitejs/plugin-vue'; export default defineConfig({ + plugins: [vue()], test: { + globals: true, environment: 'jsdom', }, -}); \ No newline at end of file +}); From 56bc41ea45bd7f7049b3445b59a93e76dd017739 Mon Sep 17 00:00:00 2001 From: Taj <87968438+taj54@users.noreply.github.com> Date: Tue, 5 Aug 2025 14:08:40 +0530 Subject: [PATCH 2/2] Update package.json Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index d1ef1aa..d01a185 100644 --- a/package.json +++ b/package.json @@ -52,8 +52,7 @@ "dependencies": { "jsdom": "^26.1.0", "tsup": "^8.5.0", - "typescript": "^5.8.3", - "vitest": "^3.2.4" + "typescript": "^5.8.3" }, "devDependencies": { "@vitejs/plugin-vue": "^5.0.5",