diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..c844038 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,33 @@ +name: Tests + +on: + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install + + - name: Build packages + run: pnpm build + + - name: Install Playwright browsers + run: pnpm exec playwright install --with-deps chromium + + - name: Run tests + run: pnpm test:all diff --git a/.gitignore b/.gitignore index 30ed3b4..9b03044 100644 --- a/.gitignore +++ b/.gitignore @@ -63,3 +63,6 @@ vite.config.ts.timestamp-* .vite/ .turbo/ *.DS_Store + +# Test artifacts +__screenshots__/ diff --git a/package.json b/package.json index 1a9ce51..83476ec 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,11 @@ "private": true, "type": "module", "scripts": { - "check": "pnpm lint", + "test": "vitest run", + "test:watch": "vitest", + "test:e2e": "vitest run --config vitest.e2e.config.ts", + "test:all": "pnpm test && pnpm test:e2e", + "check": "pnpm lint && pnpm test", "build": "pnpm -r --filter './packages/*' run build", "lint": "pnpm -r --filter './packages/*' run lint", "clean": "pnpm -r --filter './{packages,examples}/*' run clean && rimraf node_modules", @@ -12,16 +16,20 @@ "release:prepare": "node scripts/release.js", "release": "pnpm release:preflight && pnpm publish -r --access public" }, - "packageManager": "pnpm@10.23.0", + "packageManager": "pnpm@10.27.0", "devDependencies": { "@eslint/js": "^9.39.1", "@stylistic/eslint-plugin": "^5.6.1", + "@testing-library/react": "^16.3.1", "@types/node": "^20.0.0", + "@vitest/browser": "^4.0.16", + "@vitest/browser-playwright": "^4.0.16", "eslint": "^9.39.1", "globals": "^17.0.0", + "playwright": "^1.57.0", "rimraf": "^6.1.2", "typescript": "^5.9.3", "typescript-eslint": "^8.48.0", - "vitest": "^4.0.14" + "vitest": "^4.0.16" } } diff --git a/packages/grid-lite-react/src/__tests__/Grid.test.tsx b/packages/grid-lite-react/src/__tests__/Grid.test.tsx new file mode 100644 index 0000000..0d0caad --- /dev/null +++ b/packages/grid-lite-react/src/__tests__/Grid.test.tsx @@ -0,0 +1,23 @@ +import { createGridTests } from '@highcharts/grid-shared-react/src/test/createGridTests'; +import { GridLite, GridOptions } from '../index'; + +createGridTests( + 'GridLite', + GridLite, + { + dataTable: { + columns: { + name: ['Alice', 'Bob'], + age: [30, 25] + } + } + }, + { + dataTable: { + columns: { + name: ['Charlie', 'Diana'], + age: [40, 35] + } + } + } +); diff --git a/packages/grid-pro-react/src/__tests__/Grid.test.tsx b/packages/grid-pro-react/src/__tests__/Grid.test.tsx new file mode 100644 index 0000000..5d72560 --- /dev/null +++ b/packages/grid-pro-react/src/__tests__/Grid.test.tsx @@ -0,0 +1,23 @@ +import { createGridTests } from '@highcharts/grid-shared-react/src/test/createGridTests'; +import { GridPro, GridOptions } from '../index'; + +createGridTests( + 'GridPro', + GridPro, + { + dataTable: { + columns: { + name: ['Alice', 'Bob'], + age: [30, 25] + } + } + }, + { + dataTable: { + columns: { + name: ['Charlie', 'Diana'], + age: [40, 35] + } + } + } +); diff --git a/packages/grid-shared-react/src/hooks/useGrid.test.tsx b/packages/grid-shared-react/src/hooks/useGrid.test.tsx new file mode 100644 index 0000000..774a6a1 --- /dev/null +++ b/packages/grid-shared-react/src/hooks/useGrid.test.tsx @@ -0,0 +1,71 @@ +import { render, waitFor } from '@testing-library/react'; +import { StrictMode } from 'react'; +import { describe, it, expect } from 'vitest'; +import { BaseGrid } from '../components/BaseGrid'; +import type { GridType, GridInstance } from './useGrid'; + +type TestOptions = { label?: string }; + +interface DeferredInit { + id: number; + resolve: () => Promise; +} + +function createDeferredGrid(initQueue: DeferredInit[]): GridType { + let nextId = 0; + + return { + grid(container, _options, async) { + const id = ++nextId; + const grid: GridInstance = { + destroy: () => { + container.innerHTML = ''; + }, + update: () => {} + }; + + if (!async) { + container.innerHTML = `
grid
`; + return grid; + } + + return new Promise((resolve) => { + const resolveInit = async () => { + container.innerHTML = `
grid
`; + resolve(grid); + await Promise.resolve(); + }; + + initQueue.push({ + id, + resolve: resolveInit + }); + }); + } + }; +} + +describe('useGrid', () => { + it('keeps the active grid when StrictMode double-inits', async () => { + const initQueue: DeferredInit[] = []; + const Grid = createDeferredGrid(initQueue); + + const { container } = render( + + + + ); + + await waitFor(() => { + expect(initQueue).toHaveLength(1); + }); + + const [firstInit] = initQueue; + + await firstInit.resolve(); + + await waitFor(() => { + expect(container.querySelector('[data-grid-id="1"]')).not.toBeNull(); + }); + }); +}); diff --git a/packages/grid-shared-react/src/hooks/useGrid.ts b/packages/grid-shared-react/src/hooks/useGrid.ts index 5dafa5b..327e3fb 100644 --- a/packages/grid-shared-react/src/hooks/useGrid.ts +++ b/packages/grid-shared-react/src/hooks/useGrid.ts @@ -39,52 +39,90 @@ export function useGrid({ callback }: UseGridOptions) { const currGridRef = useRef | null>(null); - const isInitializingRef = useRef(false); + const callbackRef = useRef(callback); + const pendingOptionsRef = useRef(null); + const initStartedRef = useRef(false); + // StrictMode runs effects twice: mount → cleanup → mount. + // This ref tracks if cleanup ran while async init was in-flight. + // The second mount resets it to false, allowing the init to complete + // and commit. Without this, the grid would be destroyed immediately + // after creation due to the cleanup setting this flag. + const destroyOnInitRef = useRef(false); + + // Keep callback ref in sync + callbackRef.current = callback; + + // Effect for initialization - only depends on container and Grid useEffect(() => { const container = containerRef.current; if (!container) { return; } - // Update grid if it already exists - if (currGridRef.current) { - currGridRef.current.update(options, true); - return; - } + // StrictMode cleanup runs before re-mount; allow init to complete if re-mounted. + destroyOnInitRef.current = false; // Prevent double initialization - if (isInitializingRef.current) { + if (initStartedRef.current || currGridRef.current) { return; } - - isInitializingRef.current = true; + initStartedRef.current = true; const initGrid = async () => { try { - const grid = await Grid.grid(container, options, true); + // Use pending options if available (from rapid updates during init) + const initOptions = pendingOptionsRef.current ?? options; + pendingOptionsRef.current = null; + + const grid = await Grid.grid(container, initOptions, true); + + if (destroyOnInitRef.current) { + // Component unmounted while we were initializing - destroy immediately + grid.destroy(); + return; + } + currGridRef.current = grid; - callback?.(grid); + + // Apply any pending options that came in while we were initializing + if (pendingOptionsRef.current) { + grid.update(pendingOptionsRef.current, true); + pendingOptionsRef.current = null; + } + + callbackRef.current?.(grid); + } catch (error) { + // Re-throw unless we've been cleaned up (component unmounted) + if (!destroyOnInitRef.current) { + throw error; + } } finally { - isInitializingRef.current = false; + initStartedRef.current = false; } }; initGrid(); return () => { - currGridRef.current?.destroy(); - isInitializingRef.current = false; + destroyOnInitRef.current = true; + if (currGridRef.current) { + currGridRef.current.destroy(); + currGridRef.current = null; + } }; - }, [options, containerRef, Grid]); + }, [containerRef, Grid]); - // Cleanup on unmount + // Effect for options updates - separate from init useEffect(() => { - return () => { - currGridRef.current?.destroy(); - currGridRef.current = null; - }; - }, []); + if (currGridRef.current) { + // Grid exists, update it directly + currGridRef.current.update(options, true); + } else { + // Grid still initializing, queue the update + pendingOptionsRef.current = options; + } + }, [options]); return currGridRef; } diff --git a/packages/grid-shared-react/src/test/createGridTests.tsx b/packages/grid-shared-react/src/test/createGridTests.tsx new file mode 100644 index 0000000..5492516 --- /dev/null +++ b/packages/grid-shared-react/src/test/createGridTests.tsx @@ -0,0 +1,141 @@ +import { render, waitFor, fireEvent } from '@testing-library/react'; +import { useRef, useState, ComponentType } from 'react'; +import { describe, it, expect, vi } from 'vitest'; +import { GridProps, GridRefHandle } from '../components/BaseGrid'; +import { GridInstance } from '../hooks/useGrid'; + +type CellValue = string | number | boolean | null; + +interface TestOptions { + dataTable?: { + columns?: Record; + }; +} + +/** + * Creates a standard test suite for a Grid component. + * Use this to avoid duplicating tests between grid-lite-react and grid-pro-react. + */ +export function createGridTests( + name: string, + GridComponent: ComponentType>, + testOptions: TOptions, + updatedOptions: TOptions +) { + + describe(name, () => { + it('renders a container div and initializes grid', async () => { + let gridInstance: GridInstance | null = null; + + const onGridReady = (grid: GridInstance) => { + gridInstance = grid; + }; + + const { container } = render( + + ); + + expect(container.firstChild).toBeInstanceOf(HTMLDivElement); + + await waitFor(() => { + expect(gridInstance).not.toBeNull(); + }); + }); + + it('provides grid instance via ref', async () => { + let gridRef: React.RefObject | null>; + let initialized = false; + + function TestComponent() { + gridRef = useRef>(null); + return ( + { initialized = true; }} + /> + ); + } + + render(); + + await waitFor(() => { + expect(initialized).toBe(true); + expect(gridRef.current?.grid).toBeDefined(); + }); + }); + + it('calls callback when grid is initialized', async () => { + const callback = vi.fn(); + render(); + + await waitFor(() => { + expect(callback).toHaveBeenCalled(); + }); + }); + + it('updates grid when options change', async () => { + let gridInstance: GridInstance | null = null; + + function TestComponent() { + const [opts, setOpts] = useState(testOptions); + + const onGridReady = (grid: GridInstance) => { + gridInstance = grid; + }; + + return ( + <> + + + + ); + } + + const { getByTestId, container } = render(); + + // Wait for initial grid creation + await waitFor(() => { + expect(gridInstance).not.toBeNull(); + }); + + // Trigger options change + fireEvent.click(getByTestId('update-options')); + + // Wait for the grid to update with new data + await waitFor(() => { + const cells = container.querySelectorAll('td[data-value]'); + const values = Array.from(cells).map(c => c.getAttribute('data-value')); + expect(values).toContain('Charlie'); + }); + }); + + it('calls destroy() on unmount', async () => { + let destroySpy: ReturnType | null = null; + + const onGridReady = (grid: GridInstance) => { + destroySpy = vi.spyOn(grid, 'destroy'); + }; + + const { unmount } = render( + + ); + + // Wait for grid to initialize + await waitFor(() => { + expect(destroySpy).not.toBeNull(); + }); + + // Unmount and verify destroy was called + unmount(); + + expect(destroySpy).toHaveBeenCalledTimes(1); + }); + + }); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4d20455..b861401 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,15 +14,27 @@ importers: '@stylistic/eslint-plugin': specifier: ^5.6.1 version: 5.6.1(eslint@9.39.1) + '@testing-library/react': + specifier: ^16.3.1 + version: 16.3.1(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@types/node': specifier: ^20.0.0 version: 20.19.26 + '@vitest/browser': + specifier: ^4.0.16 + version: 4.0.16(vite@7.2.7(@types/node@20.19.26))(vitest@4.0.16) + '@vitest/browser-playwright': + specifier: ^4.0.16 + version: 4.0.16(playwright@1.57.0)(vite@7.2.7(@types/node@20.19.26))(vitest@4.0.16) eslint: specifier: ^9.39.1 version: 9.39.1 globals: specifier: ^17.0.0 version: 17.0.0 + playwright: + specifier: ^1.57.0 + version: 1.57.0 rimraf: specifier: ^6.1.2 version: 6.1.2 @@ -33,8 +45,8 @@ importers: specifier: ^8.48.0 version: 8.49.0(eslint@9.39.1)(typescript@5.9.3) vitest: - specifier: ^4.0.14 - version: 4.0.15(@types/node@20.19.26) + specifier: ^4.0.16 + version: 4.0.16(@types/node@20.19.26)(@vitest/browser-playwright@4.0.16)(jsdom@27.4.0) examples/grid-lite/minimal-nextjs: dependencies: @@ -266,6 +278,18 @@ importers: packages: + '@acemir/cssom@0.9.30': + resolution: {integrity: sha512-9CnlMCI0LmCIq0olalQqdWrJHPzm0/tw3gzOA9zJSgvFX7Xau3D24mAGa4BtwxwY69nsuJW6kQqqCzf/mEcQgg==} + + '@asamuzakjp/css-color@4.1.1': + resolution: {integrity: sha512-B0Hv6G3gWGMn0xKJ0txEi/jM5iFpT3MfDxmhZFb4W047GvytCf1DHQ1D69W3zHI4yWe2aTZAA0JnbMZ7Xc8DuQ==} + + '@asamuzakjp/dom-selector@6.7.6': + resolution: {integrity: sha512-hBaJER6A9MpdG3WgdlOolHmbOYvSk46y7IQN/1+iqiCuUu6iWdQrs9DGKF8ocqsEqWujWf/V7b7vaDgiUmIvUg==} + + '@asamuzakjp/nwsapi@2.3.9': + resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} + '@babel/code-frame@7.27.1': resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} @@ -337,6 +361,10 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/runtime@7.28.4': + resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} + engines: {node: '>=6.9.0'} + '@babel/template@7.27.2': resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} @@ -349,6 +377,38 @@ packages: resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} engines: {node: '>=6.9.0'} + '@csstools/color-helpers@5.1.0': + resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} + engines: {node: '>=18'} + + '@csstools/css-calc@2.1.4': + resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-color-parser@3.1.0': + resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-parser-algorithms@3.0.5': + resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-syntax-patches-for-csstree@1.0.22': + resolution: {integrity: sha512-qBcx6zYlhleiFfdtzkRgwNC7VVoAwfK76Vmsw5t+PbvtdknO9StgRk7ROvq9so1iqbdW4uLIDAsXRsTfUrIoOw==} + engines: {node: '>=18'} + + '@csstools/css-tokenizer@3.0.4': + resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} + engines: {node: '>=18'} + '@esbuild/aix-ppc64@0.21.5': resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} @@ -681,6 +741,15 @@ packages: resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@exodus/bytes@1.8.0': + resolution: {integrity: sha512-8JPn18Bcp8Uo1T82gR8lh2guEOa5KKU/IEKvvdp0sgmi7coPBWf1Doi1EXsGZb2ehc8ym/StJCjffYV+ne7sXQ==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@exodus/crypto': ^1.0.0-rc.4 + peerDependenciesMeta: + '@exodus/crypto': + optional: true + '@highcharts/grid-lite@2.1.1': resolution: {integrity: sha512-ZOlX32apWXiwcaq6T5aaeZBNDtAJnCKb93rA0YskhOrBpU9g5G2g0WWdheyawml2+YuSq7inNx+Yhl/8UWxX9w==} @@ -784,6 +853,9 @@ packages: cpu: [x64] os: [win32] + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + '@rolldown/pluginutils@1.0.0-beta.27': resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} @@ -952,6 +1024,28 @@ packages: '@swc/helpers@0.5.5': resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==} + '@testing-library/dom@10.4.1': + resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} + engines: {node: '>=18'} + + '@testing-library/react@16.3.1': + resolution: {integrity: sha512-gr4KtAWqIOQoucWYD/f6ki+j5chXfcPc74Col/6poTyqTmn7zRmodWahWRCp8tYd+GMqBonw6hstNzqjbs6gjw==} + engines: {node: '>=18'} + peerDependencies: + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -1055,11 +1149,22 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 - '@vitest/expect@4.0.15': - resolution: {integrity: sha512-Gfyva9/GxPAWXIWjyGDli9O+waHDC0Q0jaLdFP1qPAUUfo1FEXPXUfUkp3eZA0sSq340vPycSyOlYUeM15Ft1w==} + '@vitest/browser-playwright@4.0.16': + resolution: {integrity: sha512-I2Fy/ANdphi1yI46d15o0M1M4M0UJrUiVKkH5oKeRZZCdPg0fw/cfTKZzv9Ge9eobtJYp4BGblMzXdXH0vcl5g==} + peerDependencies: + playwright: '*' + vitest: 4.0.16 - '@vitest/mocker@4.0.15': - resolution: {integrity: sha512-CZ28GLfOEIFkvCFngN8Sfx5h+Se0zN+h4B7yOsPVCcgtiO7t5jt9xQh2E1UkFep+eb9fjyMfuC5gBypwb07fvQ==} + '@vitest/browser@4.0.16': + resolution: {integrity: sha512-t4toy8X/YTnjYEPoY0pbDBg3EvDPg1elCDrfc+VupPHwoN/5/FNQ8Z+xBYIaEnOE2vVEyKwqYBzZ9h9rJtZVcg==} + peerDependencies: + vitest: 4.0.16 + + '@vitest/expect@4.0.16': + resolution: {integrity: sha512-eshqULT2It7McaJkQGLkPjPjNph+uevROGuIMJdG3V+0BSR2w9u6J9Lwu+E8cK5TETlfou8GRijhafIMhXsimA==} + + '@vitest/mocker@4.0.16': + resolution: {integrity: sha512-yb6k4AZxJTB+q9ycAvsoxGn+j/po0UaPgajllBgt1PzoMAAmJGYFdDk0uCcRcxb3BrME34I6u8gHZTQlkqSZpg==} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0-0 @@ -1069,20 +1174,20 @@ packages: vite: optional: true - '@vitest/pretty-format@4.0.15': - resolution: {integrity: sha512-SWdqR8vEv83WtZcrfLNqlqeQXlQLh2iilO1Wk1gv4eiHKjEzvgHb2OVc3mIPyhZE6F+CtfYjNlDJwP5MN6Km7A==} + '@vitest/pretty-format@4.0.16': + resolution: {integrity: sha512-eNCYNsSty9xJKi/UdVD8Ou16alu7AYiS2fCPRs0b1OdhJiV89buAXQLpTbe+X8V9L6qrs9CqyvU7OaAopJYPsA==} - '@vitest/runner@4.0.15': - resolution: {integrity: sha512-+A+yMY8dGixUhHmNdPUxOh0la6uVzun86vAbuMT3hIDxMrAOmn5ILBHm8ajrqHE0t8R9T1dGnde1A5DTnmi3qw==} + '@vitest/runner@4.0.16': + resolution: {integrity: sha512-VWEDm5Wv9xEo80ctjORcTQRJ539EGPB3Pb9ApvVRAY1U/WkHXmmYISqU5E79uCwcW7xYUV38gwZD+RV755fu3Q==} - '@vitest/snapshot@4.0.15': - resolution: {integrity: sha512-A7Ob8EdFZJIBjLjeO0DZF4lqR6U7Ydi5/5LIZ0xcI+23lYlsYJAfGn8PrIWTYdZQRNnSRlzhg0zyGu37mVdy5g==} + '@vitest/snapshot@4.0.16': + resolution: {integrity: sha512-sf6NcrYhYBsSYefxnry+DR8n3UV4xWZwWxYbCJUt2YdvtqzSPR7VfGrY0zsv090DAbjFZsi7ZaMi1KnSRyK1XA==} - '@vitest/spy@4.0.15': - resolution: {integrity: sha512-+EIjOJmnY6mIfdXtE/bnozKEvTC4Uczg19yeZ2vtCz5Yyb0QQ31QWVQ8hswJ3Ysx/K2EqaNsVanjr//2+P3FHw==} + '@vitest/spy@4.0.16': + resolution: {integrity: sha512-4jIOWjKP0ZUaEmJm00E0cOBLU+5WE0BpeNr3XN6TEF05ltro6NJqHWxXD0kA8/Zc8Nh23AT8WQxwNG+WeROupw==} - '@vitest/utils@4.0.15': - resolution: {integrity: sha512-HXjPW2w5dxhTD0dLwtYHDnelK3j8sR8cWIaLxr22evTyY6q8pRCjZSmhRWVjBaOVXChQd6AwMzi9pucorXCPZA==} + '@vitest/utils@4.0.16': + resolution: {integrity: sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA==} acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} @@ -1094,16 +1199,31 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} @@ -1115,6 +1235,9 @@ packages: resolution: {integrity: sha512-D5vIoztZOq1XM54LUdttJVc96ggEsIfju2JBvht06pSzpckp3C7HReun67Bghzrtdsq9XdMGbSSB3v3GhMNmAA==} hasBin: true + bidi-js@1.0.3: + resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} + brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} @@ -1168,9 +1291,21 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + css-tree@3.1.0: + resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + cssstyle@5.3.6: + resolution: {integrity: sha512-legscpSpgSAeGEe0TNcai97DKt9Vd9AsAdOL7Uoetb52Ar/8eJm3LIa39qpv8wWzLFlNG4vVvppQM+teaMPj3A==} + engines: {node: '>=20'} + csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + data-urls@6.0.0: + resolution: {integrity: sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==} + engines: {node: '>=20'} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -1180,6 +1315,9 @@ packages: supports-color: optional: true + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -1187,9 +1325,20 @@ packages: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + electron-to-chromium@1.5.267: resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} @@ -1296,6 +1445,11 @@ packages: flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -1335,6 +1489,18 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + html-encoding-sniffer@6.0.0: + resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -1366,6 +1532,9 @@ packages: is-module@1.0.0: resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-reference@1.2.1: resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} @@ -1379,6 +1548,15 @@ packages: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true + jsdom@27.4.0: + resolution: {integrity: sha512-mjzqwWRD9Y1J1KUi7W97Gja1bwOOM5Ug0EZ6UDK3xS7j7mndrkwozHtSblfomlzyB4NepioNt+B2sOSzczVgtQ==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} @@ -1419,9 +1597,16 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + mdn-data@2.12.2: + resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==} + minimatch@10.1.1: resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} engines: {node: 20 || >=22} @@ -1437,6 +1622,10 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -1492,6 +1681,9 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parse5@8.0.0: + resolution: {integrity: sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -1517,6 +1709,24 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + pixelmatch@7.1.0: + resolution: {integrity: sha512-1wrVzJ2STrpmONHKBy228LM1b84msXDUoAzVEl0R8Mz4Ce6EPr+IVtxm8+yvrqLYMHswREkjYFaMxnyGnaY3Ng==} + hasBin: true + + playwright-core@1.57.0: + resolution: {integrity: sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.57.0: + resolution: {integrity: sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==} + engines: {node: '>=18'} + hasBin: true + + pngjs@7.0.0: + resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==} + engines: {node: '>=14.19.0'} + postcss@8.4.31: resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} @@ -1529,6 +1739,10 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -1538,6 +1752,9 @@ packages: peerDependencies: react: ^19.2.1 + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + react-refresh@0.17.0: resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} engines: {node: '>=0.10.0'} @@ -1546,6 +1763,10 @@ packages: resolution: {integrity: sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==} engines: {node: '>=0.10.0'} + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -1572,6 +1793,10 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} @@ -1595,6 +1820,10 @@ packages: siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + sirv@3.0.2: + resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} + engines: {node: '>=18'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -1634,6 +1863,9 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -1649,6 +1881,25 @@ packages: resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} engines: {node: '>=14.0.0'} + tldts-core@7.0.19: + resolution: {integrity: sha512-lJX2dEWx0SGH4O6p+7FPwYmJ/bu1JbcGJ8RLaG9b7liIgZ85itUVEPbMtWRVrde/0fnDPEPHW10ZsKW3kVsE9A==} + + tldts@7.0.19: + resolution: {integrity: sha512-8PWx8tvC4jDB39BQw1m4x8y5MH1BcQ5xHeL2n7UVFulMPH/3Q0uiamahFJ3lXA0zO2SUyRXuVVbWSDmstlt9YA==} + hasBin: true + + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + + tough-cookie@6.0.0: + resolution: {integrity: sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==} + engines: {node: '>=16'} + + tr46@6.0.0: + resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} + engines: {node: '>=20'} + ts-api-utils@2.1.0: resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} engines: {node: '>=18.12'} @@ -1757,18 +2008,18 @@ packages: yaml: optional: true - vitest@4.0.15: - resolution: {integrity: sha512-n1RxDp8UJm6N0IbJLQo+yzLZ2sQCDyl1o0LeugbPWf8+8Fttp29GghsQBjYJVmWq3gBFfe9Hs1spR44vovn2wA==} + vitest@4.0.16: + resolution: {integrity: sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.0.15 - '@vitest/browser-preview': 4.0.15 - '@vitest/browser-webdriverio': 4.0.15 - '@vitest/ui': 4.0.15 + '@vitest/browser-playwright': 4.0.16 + '@vitest/browser-preview': 4.0.16 + '@vitest/browser-webdriverio': 4.0.16 + '@vitest/ui': 4.0.16 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -1791,6 +2042,22 @@ packages: jsdom: optional: true + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + + webidl-conversions@8.0.1: + resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} + engines: {node: '>=20'} + + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + + whatwg-url@15.1.0: + resolution: {integrity: sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==} + engines: {node: '>=20'} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -1805,6 +2072,25 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + ws@8.18.3: + resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -1814,6 +2100,30 @@ packages: snapshots: + '@acemir/cssom@0.9.30': + optional: true + + '@asamuzakjp/css-color@4.1.1': + dependencies: + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + lru-cache: 11.2.4 + optional: true + + '@asamuzakjp/dom-selector@6.7.6': + dependencies: + '@asamuzakjp/nwsapi': 2.3.9 + bidi-js: 1.0.3 + css-tree: 3.1.0 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.2.4 + optional: true + + '@asamuzakjp/nwsapi@2.3.9': + optional: true + '@babel/code-frame@7.27.1': dependencies: '@babel/helper-validator-identifier': 7.28.5 @@ -1903,6 +2213,8 @@ snapshots: '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 + '@babel/runtime@7.28.4': {} + '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 @@ -1926,6 +2238,34 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@csstools/color-helpers@5.1.0': + optional: true + + '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + optional: true + + '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/color-helpers': 5.1.0 + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + optional: true + + '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-tokenizer': 3.0.4 + optional: true + + '@csstools/css-syntax-patches-for-csstree@1.0.22': + optional: true + + '@csstools/css-tokenizer@3.0.4': + optional: true + '@esbuild/aix-ppc64@0.21.5': optional: true @@ -2119,6 +2459,9 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 + '@exodus/bytes@1.8.0': + optional: true + '@highcharts/grid-lite@2.1.1': {} '@highcharts/grid-pro@2.1.1': {} @@ -2188,6 +2531,8 @@ snapshots: '@next/swc-win32-x64-msvc@14.2.33': optional: true + '@polka/url@1.0.0-next.29': {} + '@rolldown/pluginutils@1.0.0-beta.27': {} '@rollup/plugin-commonjs@29.0.0(rollup@4.53.3)': @@ -2314,6 +2659,29 @@ snapshots: '@swc/counter': 0.1.3 tslib: 2.8.1 + '@testing-library/dom@10.4.1': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/runtime': 7.28.4 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 + + '@testing-library/react@16.3.1(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@babel/runtime': 7.28.4 + '@testing-library/dom': 10.4.1 + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + + '@types/aria-query@5.0.4': {} + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.28.5 @@ -2463,43 +2831,73 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitest/expect@4.0.15': + '@vitest/browser-playwright@4.0.16(playwright@1.57.0)(vite@7.2.7(@types/node@20.19.26))(vitest@4.0.16)': + dependencies: + '@vitest/browser': 4.0.16(vite@7.2.7(@types/node@20.19.26))(vitest@4.0.16) + '@vitest/mocker': 4.0.16(vite@7.2.7(@types/node@20.19.26)) + playwright: 1.57.0 + tinyrainbow: 3.0.3 + vitest: 4.0.16(@types/node@20.19.26)(@vitest/browser-playwright@4.0.16)(jsdom@27.4.0) + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite + + '@vitest/browser@4.0.16(vite@7.2.7(@types/node@20.19.26))(vitest@4.0.16)': + dependencies: + '@vitest/mocker': 4.0.16(vite@7.2.7(@types/node@20.19.26)) + '@vitest/utils': 4.0.16 + magic-string: 0.30.21 + pixelmatch: 7.1.0 + pngjs: 7.0.0 + sirv: 3.0.2 + tinyrainbow: 3.0.3 + vitest: 4.0.16(@types/node@20.19.26)(@vitest/browser-playwright@4.0.16)(jsdom@27.4.0) + ws: 8.18.3 + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite + + '@vitest/expect@4.0.16': dependencies: '@standard-schema/spec': 1.0.0 '@types/chai': 5.2.3 - '@vitest/spy': 4.0.15 - '@vitest/utils': 4.0.15 + '@vitest/spy': 4.0.16 + '@vitest/utils': 4.0.16 chai: 6.2.1 tinyrainbow: 3.0.3 - '@vitest/mocker@4.0.15(vite@7.2.7(@types/node@20.19.26))': + '@vitest/mocker@4.0.16(vite@7.2.7(@types/node@20.19.26))': dependencies: - '@vitest/spy': 4.0.15 + '@vitest/spy': 4.0.16 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: vite: 7.2.7(@types/node@20.19.26) - '@vitest/pretty-format@4.0.15': + '@vitest/pretty-format@4.0.16': dependencies: tinyrainbow: 3.0.3 - '@vitest/runner@4.0.15': + '@vitest/runner@4.0.16': dependencies: - '@vitest/utils': 4.0.15 + '@vitest/utils': 4.0.16 pathe: 2.0.3 - '@vitest/snapshot@4.0.15': + '@vitest/snapshot@4.0.16': dependencies: - '@vitest/pretty-format': 4.0.15 + '@vitest/pretty-format': 4.0.16 magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/spy@4.0.15': {} + '@vitest/spy@4.0.16': {} - '@vitest/utils@4.0.15': + '@vitest/utils@4.0.16': dependencies: - '@vitest/pretty-format': 4.0.15 + '@vitest/pretty-format': 4.0.16 tinyrainbow: 3.0.3 acorn-jsx@5.3.2(acorn@8.15.0): @@ -2508,6 +2906,9 @@ snapshots: acorn@8.15.0: {} + agent-base@7.1.4: + optional: true + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -2515,18 +2916,31 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + ansi-regex@5.0.1: {} + ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 + ansi-styles@5.2.0: {} + argparse@2.0.1: {} + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + assertion-error@2.0.1: {} balanced-match@1.0.2: {} baseline-browser-mapping@2.9.5: {} + bidi-js@1.0.3: + dependencies: + require-from-string: 2.0.2 + optional: true + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 @@ -2579,18 +2993,48 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + css-tree@3.1.0: + dependencies: + mdn-data: 2.12.2 + source-map-js: 1.2.1 + optional: true + + cssstyle@5.3.6: + dependencies: + '@asamuzakjp/css-color': 4.1.1 + '@csstools/css-syntax-patches-for-csstree': 1.0.22 + css-tree: 3.1.0 + lru-cache: 11.2.4 + optional: true + csstype@3.2.3: {} + data-urls@6.0.0: + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 15.1.0 + optional: true + debug@4.4.3: dependencies: ms: 2.1.3 + decimal.js@10.6.0: + optional: true + deep-is@0.1.4: {} deepmerge@4.3.1: {} + dequal@2.0.3: {} + + dom-accessibility-api@0.5.16: {} + electron-to-chromium@1.5.267: {} + entities@6.0.1: + optional: true + es-module-lexer@1.7.0: {} esbuild@0.21.5: @@ -2752,6 +3196,9 @@ snapshots: flatted@3.3.3: {} + fsevents@2.3.2: + optional: true + fsevents@2.3.3: optional: true @@ -2781,6 +3228,29 @@ snapshots: dependencies: function-bind: 1.1.2 + html-encoding-sniffer@6.0.0: + dependencies: + '@exodus/bytes': 1.8.0 + transitivePeerDependencies: + - '@exodus/crypto' + optional: true + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + optional: true + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + optional: true + ignore@5.3.2: {} ignore@7.0.5: {} @@ -2804,6 +3274,9 @@ snapshots: is-module@1.0.0: {} + is-potential-custom-element-name@1.0.1: + optional: true + is-reference@1.2.1: dependencies: '@types/estree': 1.0.8 @@ -2816,6 +3289,35 @@ snapshots: dependencies: argparse: 2.0.1 + jsdom@27.4.0: + dependencies: + '@acemir/cssom': 0.9.30 + '@asamuzakjp/dom-selector': 6.7.6 + '@exodus/bytes': 1.8.0 + cssstyle: 5.3.6 + data-urls: 6.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 6.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + parse5: 8.0.0 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 6.0.0 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 8.0.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 15.1.0 + ws: 8.18.3 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - '@exodus/crypto' + - bufferutil + - supports-color + - utf-8-validate + optional: true + jsesc@3.1.0: {} json-buffer@3.0.1: {} @@ -2847,10 +3349,15 @@ snapshots: dependencies: yallist: 3.1.1 + lz-string@1.5.0: {} + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + mdn-data@2.12.2: + optional: true + minimatch@10.1.1: dependencies: '@isaacs/brace-expansion': 5.0.0 @@ -2865,6 +3372,8 @@ snapshots: minipass@7.1.2: {} + mrmime@2.0.1: {} + ms@2.1.3: {} nanoid@3.3.11: {} @@ -2923,6 +3432,11 @@ snapshots: dependencies: callsites: 3.1.0 + parse5@8.0.0: + dependencies: + entities: 6.0.1 + optional: true + path-exists@4.0.0: {} path-key@3.1.1: {} @@ -2940,6 +3454,20 @@ snapshots: picomatch@4.0.3: {} + pixelmatch@7.1.0: + dependencies: + pngjs: 7.0.0 + + playwright-core@1.57.0: {} + + playwright@1.57.0: + dependencies: + playwright-core: 1.57.0 + optionalDependencies: + fsevents: 2.3.2 + + pngjs@7.0.0: {} + postcss@8.4.31: dependencies: nanoid: 3.3.11 @@ -2954,6 +3482,12 @@ snapshots: prelude-ls@1.2.1: {} + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + punycode@2.3.1: {} react-dom@19.2.1(react@19.2.1): @@ -2961,10 +3495,15 @@ snapshots: react: 19.2.1 scheduler: 0.27.0 + react-is@17.0.2: {} + react-refresh@0.17.0: {} react@19.2.1: {} + require-from-string@2.0.2: + optional: true + resolve-from@4.0.0: {} resolve@1.22.11: @@ -3014,6 +3553,11 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.53.3 fsevents: 2.3.3 + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + optional: true + scheduler@0.27.0: {} semver@6.3.1: {} @@ -3028,6 +3572,12 @@ snapshots: siginfo@2.0.0: {} + sirv@3.0.2: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + source-map-js@1.2.1: {} stackback@0.0.2: {} @@ -3049,6 +3599,9 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + symbol-tree@3.2.4: + optional: true + tinybench@2.9.0: {} tinyexec@1.0.2: {} @@ -3060,6 +3613,26 @@ snapshots: tinyrainbow@3.0.3: {} + tldts-core@7.0.19: + optional: true + + tldts@7.0.19: + dependencies: + tldts-core: 7.0.19 + optional: true + + totalist@3.0.1: {} + + tough-cookie@6.0.0: + dependencies: + tldts: 7.0.19 + optional: true + + tr46@6.0.0: + dependencies: + punycode: 2.3.1 + optional: true + ts-api-utils@2.1.0(typescript@5.9.3): dependencies: typescript: 5.9.3 @@ -3116,15 +3689,15 @@ snapshots: '@types/node': 20.19.26 fsevents: 2.3.3 - vitest@4.0.15(@types/node@20.19.26): + vitest@4.0.16(@types/node@20.19.26)(@vitest/browser-playwright@4.0.16)(jsdom@27.4.0): dependencies: - '@vitest/expect': 4.0.15 - '@vitest/mocker': 4.0.15(vite@7.2.7(@types/node@20.19.26)) - '@vitest/pretty-format': 4.0.15 - '@vitest/runner': 4.0.15 - '@vitest/snapshot': 4.0.15 - '@vitest/spy': 4.0.15 - '@vitest/utils': 4.0.15 + '@vitest/expect': 4.0.16 + '@vitest/mocker': 4.0.16(vite@7.2.7(@types/node@20.19.26)) + '@vitest/pretty-format': 4.0.16 + '@vitest/runner': 4.0.16 + '@vitest/snapshot': 4.0.16 + '@vitest/spy': 4.0.16 + '@vitest/utils': 4.0.16 es-module-lexer: 1.7.0 expect-type: 1.3.0 magic-string: 0.30.21 @@ -3140,6 +3713,8 @@ snapshots: why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 20.19.26 + '@vitest/browser-playwright': 4.0.16(playwright@1.57.0)(vite@7.2.7(@types/node@20.19.26))(vitest@4.0.16) + jsdom: 27.4.0 transitivePeerDependencies: - jiti - less @@ -3153,6 +3728,23 @@ snapshots: - tsx - yaml + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + optional: true + + webidl-conversions@8.0.1: + optional: true + + whatwg-mimetype@4.0.0: + optional: true + + whatwg-url@15.1.0: + dependencies: + tr46: 6.0.0 + webidl-conversions: 8.0.1 + optional: true + which@2.0.2: dependencies: isexe: 2.0.0 @@ -3164,6 +3756,14 @@ snapshots: word-wrap@1.2.5: {} + ws@8.18.3: {} + + xml-name-validator@5.0.0: + optional: true + + xmlchars@2.2.0: + optional: true + yallist@3.1.1: {} yocto-queue@0.1.0: {} diff --git a/tests/e2e.test.ts b/tests/e2e.test.ts new file mode 100644 index 0000000..36e7650 --- /dev/null +++ b/tests/e2e.test.ts @@ -0,0 +1,123 @@ +/** + * E2E tests for demo applications. + * These tests start the actual demo servers and verify the grid renders. + * Run with: pnpm test:e2e + */ +import { test, expect } from 'vitest'; +import { chromium, Browser, Page } from 'playwright'; +import { spawn, ChildProcess } from 'child_process'; +import { resolve } from 'path'; +import { fileURLToPath } from 'url'; +import { dirname } from 'path'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const rootDir = resolve(__dirname, '..'); + +interface DemoServer { + name: string; + cwd: string; + port: number; + args: string[]; + process?: ChildProcess; +} + +const demos: DemoServer[] = [ + { + name: 'grid-pro React (Vite)', + cwd: resolve(rootDir, 'examples/grid-pro/minimal-react'), + port: 51731, + args: ['dev', '--port', '51731', '--strictPort'] + }, + { + name: 'grid-pro Next.js', + cwd: resolve(rootDir, 'examples/grid-pro/minimal-nextjs'), + port: 51732, + args: ['dev', '--port', '51732'] + } +]; + +async function waitForServer(port: number, timeout = 30000): Promise { + const start = Date.now(); + while (Date.now() - start < timeout) { + try { + const response = await fetch(`http://localhost:${port}`); + if (response.ok) return; + } catch { + // Server not ready yet + } + await new Promise(r => setTimeout(r, 500)); + } + throw new Error(`Server on port ${port} did not start within ${timeout}ms`); +} + +function startServer(demo: DemoServer): ChildProcess { + const proc = spawn('pnpm', demo.args, { + cwd: demo.cwd, + stdio: ['ignore', 'pipe', 'pipe'], + shell: true, + detached: false + }); + + proc.stdout?.on('data', (data) => { + console.log(`[${demo.name}] ${data}`); + }); + + proc.stderr?.on('data', (data) => { + console.error(`[${demo.name}] ${data}`); + }); + + return proc; +} + +let browser: Browser; +let servers: ChildProcess[] = []; + +test.beforeAll(async () => { + // Start all demo servers + for (const demo of demos) { + console.log(`Starting ${demo.name} at ${demo.cwd}...`); + demo.process = startServer(demo); + servers.push(demo.process); + } + + // Wait for all servers to be ready + console.log('Waiting for servers to be ready...'); + await Promise.all(demos.map(d => waitForServer(d.port))); + console.log('All servers ready'); + + // Launch browser + browser = await chromium.launch({ headless: true }); +}, 90000); + +test.afterAll(async () => { + await browser?.close(); + + // Kill all servers + for (const server of servers) { + server.kill('SIGTERM'); + } +}); + +for (const demo of demos) { + test(`${demo.name}: renders grid with data`, async () => { + const page: Page = await browser.newPage(); + + try { + await page.goto(`http://localhost:${demo.port}`); + + // Wait for grid table to appear + await page.waitForSelector('table', { timeout: 10000 }); + + // Verify data is rendered - wait for text to be visible + await page.locator('text=Alice').waitFor({ state: 'visible', timeout: 5000 }); + await page.locator('text=Bob').waitFor({ state: 'visible', timeout: 5000 }); + + // Verify grid structure exists + const cells = await page.locator('td').count(); + expect(cells).toBeGreaterThan(0); + } finally { + await page.close(); + } + }, 30000); +} diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..8ad4fee --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,18 @@ +import { defineConfig } from 'vitest/config'; +import { playwright } from '@vitest/browser-playwright'; + +export default defineConfig({ + test: { + include: ['packages/*/src/**/*.test.{ts,tsx}'], + globals: true, + css: true, + browser: { + enabled: true, + provider: playwright(), + instances: [ + { browser: 'chromium' } + ], + headless: true + } + } +}); diff --git a/vitest.e2e.config.ts b/vitest.e2e.config.ts new file mode 100644 index 0000000..5252f53 --- /dev/null +++ b/vitest.e2e.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + include: ['tests/e2e.test.ts'], + globals: true, + testTimeout: 60000, + hookTimeout: 60000 + } +});