From a622d9b90d8b85488e0b6ffe2571786af8a0bcbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Best?= Date: Wed, 11 Dec 2024 21:15:48 +0100 Subject: [PATCH] test: Move relevant e2e tests to be unit tests (#802) --- .vscode/settings.json | 2 + .../e2e/next/cypress/e2e/clearOnDefault.cy.js | 10 - packages/e2e/next/cypress/e2e/repro-599.cy.js | 18 -- .../next/src/app/app/clearOnDefault/page.tsx | 60 ----- .../e2e/next/src/app/app/repro-599/page.tsx | 27 -- packages/nuqs/package.json | 1 + packages/nuqs/src/useQueryState.test.ts | 162 ++++++++++++ packages/nuqs/src/useQueryStates.test.ts | 235 ++++++++++++++++++ packages/nuqs/src/useQueryStates.test.tsx | 95 ------- pnpm-lock.yaml | 153 +++++++++++- 10 files changed, 542 insertions(+), 221 deletions(-) delete mode 100644 packages/e2e/next/cypress/e2e/clearOnDefault.cy.js delete mode 100644 packages/e2e/next/cypress/e2e/repro-599.cy.js delete mode 100644 packages/e2e/next/src/app/app/clearOnDefault/page.tsx delete mode 100644 packages/e2e/next/src/app/app/repro-599/page.tsx create mode 100644 packages/nuqs/src/useQueryState.test.ts create mode 100644 packages/nuqs/src/useQueryStates.test.ts delete mode 100644 packages/nuqs/src/useQueryStates.test.tsx diff --git a/.vscode/settings.json b/.vscode/settings.json index ebac3d3b..1f95a140 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,6 @@ { + "editor.tabSize": 2, + "editor.insertSpaces": true, "githubPullRequests.queries": [ { "label": "Backlog", diff --git a/packages/e2e/next/cypress/e2e/clearOnDefault.cy.js b/packages/e2e/next/cypress/e2e/clearOnDefault.cy.js deleted file mode 100644 index 942322b8..00000000 --- a/packages/e2e/next/cypress/e2e/clearOnDefault.cy.js +++ /dev/null @@ -1,10 +0,0 @@ -/// - -it('Clears the URL when setting the default value when `clearOnDefault` is used', () => { - cy.visit( - '/app/clearOnDefault?a=a&b=b&array=1,2,3&json-ref={"egg":"spam"}&json-new={"egg":"spam"}&keepMe=init' - ) - cy.contains('#hydration-marker', 'hydrated').should('be.hidden') - cy.get('button').click() - cy.location('search').should('eq', '?a=&keepMe=') -}) diff --git a/packages/e2e/next/cypress/e2e/repro-599.cy.js b/packages/e2e/next/cypress/e2e/repro-599.cy.js deleted file mode 100644 index 01452371..00000000 --- a/packages/e2e/next/cypress/e2e/repro-599.cy.js +++ /dev/null @@ -1,18 +0,0 @@ -/// - -it('Reproduction for issue #599', () => { - // Start without encoding for most characters - cy.visit( - '/app/repro-599?a %26b%3Fc%3Dd%23e%f%2Bg"h\'i`jl(m)n*o,p.q:r;s/t=init' - ) - cy.contains('#hydration-marker', 'hydrated').should('be.hidden') - cy.get('input').should('have.value', 'init') - cy.get('p').should('have.text', 'init') - cy.get('button').click() - cy.get('input').should('have.value', 'works') - cy.get('p').should('have.text', 'works') - cy.location('search').should( - 'eq', - '?a%20%26b%3Fc%3Dd%23e%f%2Bg%22h%27i`j%3Ck%3El(m)n*o,p.q:r;s/t=works' - ) -}) diff --git a/packages/e2e/next/src/app/app/clearOnDefault/page.tsx b/packages/e2e/next/src/app/app/clearOnDefault/page.tsx deleted file mode 100644 index 42f5df88..00000000 --- a/packages/e2e/next/src/app/app/clearOnDefault/page.tsx +++ /dev/null @@ -1,60 +0,0 @@ -'use client' - -import { - parseAsArrayOf, - parseAsInteger, - parseAsJson, - parseAsString, - useQueryState -} from 'nuqs' -import { Suspense } from 'react' - -export default function Page() { - return ( - - - - ) -} - -const defaultJSON = { foo: 'bar' } -const runtimePassthrough = (x: unknown) => x - -function Client() { - const [, setA] = useQueryState('a') - const [, setB] = useQueryState('b', { - defaultValue: '' - }) - const [, setArray] = useQueryState( - 'array', - parseAsArrayOf(parseAsInteger).withDefault([]) - ) - const [, setJsonRef] = useQueryState( - 'json-ref', - parseAsJson(runtimePassthrough).withDefault(defaultJSON) - ) - const [, setJsonNew] = useQueryState( - 'json-new', - parseAsJson(runtimePassthrough).withDefault(defaultJSON) - ) - const [, keepMe] = useQueryState( - 'keepMe', - parseAsString.withDefault('').withOptions({ clearOnDefault: false }) - ) - return ( - <> - - - ) -} diff --git a/packages/e2e/next/src/app/app/repro-599/page.tsx b/packages/e2e/next/src/app/app/repro-599/page.tsx deleted file mode 100644 index 80e824b5..00000000 --- a/packages/e2e/next/src/app/app/repro-599/page.tsx +++ /dev/null @@ -1,27 +0,0 @@ -'use client' - -import { parseAsString, useQueryState, useQueryStates } from 'nuqs' -import { Suspense } from 'react' - -export default function Page() { - return ( - - - - ) -} - -const key = 'a &b?c=d#e%f+g"h\'i`jl(m)n*o,p.q:r;s/t' -const parser = parseAsString.withDefault('') - -function Client() { - const [a, setValue] = useQueryState(key, parser) - const [{ [key]: b }] = useQueryStates({ [key]: parser }) - return ( - <> - setValue(e.target.value)} /> -

{b}

- - - ) -} diff --git a/packages/nuqs/package.json b/packages/nuqs/package.json index 1f353b21..fd61df73 100644 --- a/packages/nuqs/package.json +++ b/packages/nuqs/package.json @@ -156,6 +156,7 @@ "@types/react": "catalog:react19", "@types/react-dom": "catalog:react19", "@vitejs/plugin-react": "^4.3.3", + "@vitest/coverage-v8": "^2.1.8", "next": "15.0.3", "react": "catalog:react19", "react-dom": "catalog:react19", diff --git a/packages/nuqs/src/useQueryState.test.ts b/packages/nuqs/src/useQueryState.test.ts new file mode 100644 index 00000000..99b0741b --- /dev/null +++ b/packages/nuqs/src/useQueryState.test.ts @@ -0,0 +1,162 @@ +import { act, renderHook } from '@testing-library/react' +import { describe, expect, it, vi } from 'vitest' +import { + withNuqsTestingAdapter, + type OnUrlUpdateFunction +} from './adapters/testing' +import { parseAsArrayOf, parseAsJson, parseAsString } from './parsers' +import { useQueryState } from './useQueryState' + +describe('useQueryState: referential equality', () => { + const defaults = { + str: 'foo', + obj: { initial: 'state' }, + arr: [ + { + initial: 'state' + } + ] + } + + const useTestHookWithDefaults = ( + { defaultValue } = { defaultValue: defaults.str } + ) => { + const str = useQueryState('str', parseAsString.withDefault(defaultValue)) + const obj = useQueryState( + 'obj', + parseAsJson(x => x).withDefault(defaults.obj) + ) + const arr = useQueryState( + 'arr', + parseAsArrayOf(parseAsJson(x => x)).withDefault(defaults.arr) + ) + return { str, obj, arr } + } + + it('should have referential equality on default values', () => { + const { result } = renderHook(useTestHookWithDefaults, { + wrapper: withNuqsTestingAdapter() + }) + const { str, obj, arr } = result.current + expect(str[0]).toBe(defaults.str) + expect(obj[0]).toBe(defaults.obj) + expect(arr[0]).toBe(defaults.arr) + expect(arr[0][0]).toBe(defaults.arr[0]) + }) + + it('should keep referential equality when resetting to defaults', async () => { + const { result } = renderHook(useTestHookWithDefaults, { + wrapper: withNuqsTestingAdapter({ + searchParams: { + str: 'foo', + obj: '{"hello":"world"}', + arr: '{"obj":true},{"arr":true}' + } + }) + }) + await act(() => { + const { str, arr, obj } = result.current + str[1](null) + obj[1](null) + return arr[1](null) + }) + const { str, arr, obj } = result.current + expect(str[0]).toBe(defaults.str) + expect(obj[0]).toBe(defaults.obj) + expect(arr[0]).toBe(defaults.arr) + expect(arr[0][0]).toBe(defaults.arr[0]) + }) + + it('should keep referential equality when unrelated keys change', async () => { + const { result } = renderHook(useTestHookWithDefaults, { + wrapper: withNuqsTestingAdapter({ + searchParams: { + str: 'foo', + obj: '{"hello":"world"}' + // Keep arr as default + } + }) + }) + const initialObj = result.current.obj[0] + const initialArr = result.current.arr[0] + await act(() => { + const { str } = result.current + return str[1]('bar') + }) + const { str, obj, arr } = result.current + expect(str[0]).toBe('bar') + expect(obj[0]).toBe(initialObj) + expect(arr[0]).toBe(initialArr) + }) + + it('should keep referential equality when default changes for another key', () => { + const { result, rerender } = renderHook(useTestHookWithDefaults, { + wrapper: withNuqsTestingAdapter() + }) + expect(result.current.str[0]).toBe('foo') + rerender({ defaultValue: 'b' }) + const { str, obj, arr } = result.current + expect(str[0]).toBe('b') + expect(obj[0]).toBe(defaults.obj) + expect(arr[0]).toBe(defaults.arr) + expect(arr[0][0]).toBe(defaults.arr[0]) + }) +}) + +describe('useQueryState: clearOnDefault', () => { + it('honors clearOnDefault: true by default', async () => { + const onUrlUpdate = vi.fn() + const { result } = renderHook( + () => useQueryState('test', parseAsString.withDefault('default')), + { + wrapper: withNuqsTestingAdapter({ + searchParams: '?test=init', + onUrlUpdate + }) + } + ) + await act(() => result.current[1]('default')) + expect(onUrlUpdate).toHaveBeenCalledOnce() + expect(onUrlUpdate.mock.calls[0]![0].queryString).toEqual('') + }) + + it('supports clearOnDefault: false (hook level)', async () => { + const onUrlUpdate = vi.fn() + const useTestHook = () => + useQueryState( + 'a', + parseAsString.withDefault('default').withOptions({ + clearOnDefault: false + }) + ) + const { result } = renderHook(useTestHook, { + wrapper: withNuqsTestingAdapter({ + searchParams: '?a=init', + onUrlUpdate + }) + }) + await act(() => result.current[1]('default')) + expect(onUrlUpdate).toHaveBeenCalledOnce() + expect(onUrlUpdate.mock.calls[0]![0].queryString).toEqual('?a=default') + }) + + it('supports clearOnDefault: false (call level)', async () => { + const onUrlUpdate = vi.fn() + const useTestHook = () => + useQueryState( + 'a', + parseAsString.withDefault('default').withOptions({ + clearOnDefault: true + }) + ) + const { result } = renderHook(useTestHook, { + wrapper: withNuqsTestingAdapter({ + searchParams: '?a=init', + onUrlUpdate + }) + }) + await act(() => result.current[1]('default', { clearOnDefault: false })) + expect(onUrlUpdate).toHaveBeenCalledOnce() + expect(onUrlUpdate.mock.calls[0]![0].queryString).toEqual('?a=default') + }) +}) diff --git a/packages/nuqs/src/useQueryStates.test.ts b/packages/nuqs/src/useQueryStates.test.ts new file mode 100644 index 00000000..a1b0958e --- /dev/null +++ b/packages/nuqs/src/useQueryStates.test.ts @@ -0,0 +1,235 @@ +import { act, renderHook } from '@testing-library/react' +import { describe, expect, it, vi } from 'vitest' +import { + withNuqsTestingAdapter, + type OnUrlUpdateFunction +} from './adapters/testing' +import { parseAsArrayOf, parseAsJson, parseAsString } from './parsers' +import { useQueryStates } from './useQueryStates' + +describe('useQueryStates: referential equality', () => { + const defaults = { + str: 'foo', + obj: { initial: 'state' }, + arr: [ + { + initial: 'state' + } + ] + } + + const useTestHookWithDefaults = ( + { defaultValue } = { defaultValue: defaults.str } + ) => { + return useQueryStates({ + str: parseAsString.withDefault(defaultValue), + obj: parseAsJson(x => x).withDefault(defaults.obj), + arr: parseAsArrayOf(parseAsJson(x => x)).withDefault(defaults.arr) + }) + } + + it('should have referential equality on default values', () => { + const { result } = renderHook(useTestHookWithDefaults, { + wrapper: withNuqsTestingAdapter() + }) + const [state] = result.current + expect(state.str).toBe(defaults.str) + expect(state.obj).toBe(defaults.obj) + expect(state.arr).toBe(defaults.arr) + expect(state.arr[0]).toBe(defaults.arr[0]) + }) + + it('should keep referential equality when resetting to defaults', async () => { + const { result } = renderHook(useTestHookWithDefaults, { + wrapper: withNuqsTestingAdapter({ + searchParams: { + str: 'foo', + obj: '{"hello":"world"}', + arr: '{"obj":true},{"arr":true}' + } + }) + }) + await act(() => result.current[1](null)) + const [state] = result.current + expect(state.str).toBe(defaults.str) + expect(state.obj).toBe(defaults.obj) + expect(state.arr).toBe(defaults.arr) + expect(state.arr[0]).toBe(defaults.arr[0]) + }) + + it('should keep referential equality when unrelated keys change', async () => { + const { result } = renderHook(useTestHookWithDefaults, { + wrapper: withNuqsTestingAdapter({ + searchParams: { + str: 'foo', + obj: '{"hello":"world"}' + // Keep arr as default + } + }) + }) + const [{ obj: initialObj, arr: initialArr }] = result.current + await act(() => result.current[1]({ str: 'bar' })) + const [{ str, obj, arr }] = result.current + expect(str).toBe('bar') + expect(obj).toBe(initialObj) + expect(arr).toBe(initialArr) + }) + + it('should keep referential equality when default changes for another key', () => { + const { result, rerender } = renderHook(useTestHookWithDefaults, { + wrapper: withNuqsTestingAdapter() + }) + expect(result.current[0].str).toBe('foo') + rerender({ defaultValue: 'b' }) + const [state] = result.current + expect(state.str).toBe('b') + expect(state.obj).toBe(defaults.obj) + expect(state.arr).toBe(defaults.arr) + expect(state.arr[0]).toBe(defaults.arr[0]) + }) +}) + +describe('useQueryStates: urlKeys remapping', () => { + it('uses the object key names by default', async () => { + const onUrlUpdate = vi.fn() + const useTestHook = () => + useQueryStates({ + foo: parseAsString, + bar: parseAsString + }) + const { result } = renderHook(useTestHook, { + wrapper: withNuqsTestingAdapter({ + searchParams: '?foo=init&bar=init', + onUrlUpdate + }) + }) + expect(result.current[0].foo).toEqual('init') + expect(result.current[0].bar).toEqual('init') + await act(() => result.current[1]({ foo: 'a', bar: 'b' })) + expect(onUrlUpdate).toHaveBeenCalledOnce() + expect(onUrlUpdate.mock.calls[0]![0].queryString).toEqual('?foo=a&bar=b') + }) + + it('allows remapping keys partially', async () => { + const onUrlUpdate = vi.fn() + const useTestHook = () => + useQueryStates( + { + foo: parseAsString, + bar: parseAsString + }, + { + urlKeys: { + foo: 'f' + } + } + ) + const { result } = renderHook(useTestHook, { + wrapper: withNuqsTestingAdapter({ + searchParams: '?f=foo&bar=bar', + onUrlUpdate + }) + }) + expect(result.current[0].foo).toEqual('foo') + expect(result.current[0].bar).toEqual('bar') + await act(() => result.current[1]({ foo: 'a', bar: 'b' })) + expect(onUrlUpdate).toHaveBeenCalledOnce() + expect(onUrlUpdate.mock.calls[0]![0].queryString).toEqual('?f=a&bar=b') + }) +}) + +describe('useQueryStates: clearOnDefault', () => { + it('honors clearOnDefault: true by default', async () => { + const onUrlUpdate = vi.fn() + const useTestHook = () => + useQueryStates({ + test: parseAsString.withDefault('default') + }) + const { result } = renderHook(useTestHook, { + wrapper: withNuqsTestingAdapter({ + searchParams: '?test=init', + onUrlUpdate + }) + }) + await act(() => result.current[1]({ test: 'default' })) + expect(onUrlUpdate).toHaveBeenCalledOnce() + expect(onUrlUpdate.mock.calls[0]![0].queryString).toEqual('') + }) + + it('supports clearOnDefault: false (parser level)', async () => { + const onUrlUpdate = vi.fn() + const useTestHook = () => + useQueryStates({ + a: parseAsString.withDefault('default').withOptions({ + clearOnDefault: false + }), + b: parseAsString.withDefault('default') + }) + const { result } = renderHook(useTestHook, { + wrapper: withNuqsTestingAdapter({ + searchParams: '?a=init&b=init', + onUrlUpdate + }) + }) + await act(() => result.current[1]({ a: 'default', b: 'default' })) + expect(onUrlUpdate).toHaveBeenCalledOnce() + expect(onUrlUpdate.mock.calls[0]![0].queryString).toEqual('?a=default') + }) + + it('supports clearOnDefault: false (hook level)', async () => { + const onUrlUpdate = vi.fn() + const useTestHook = () => + useQueryStates( + { + a: parseAsString.withDefault('default'), + b: parseAsString.withDefault('default').withOptions({ + clearOnDefault: true // overrides hook options + }) + }, + { + clearOnDefault: false + } + ) + const { result } = renderHook(useTestHook, { + wrapper: withNuqsTestingAdapter({ + searchParams: '?a=init&b=init', + onUrlUpdate + }) + }) + await act(() => result.current[1]({ a: 'default', b: 'default' })) + expect(onUrlUpdate).toHaveBeenCalledOnce() + expect(onUrlUpdate.mock.calls[0]![0].queryString).toEqual('?a=default') + }) + + it('supports clearOnDefault: false (call level)', async () => { + const onUrlUpdate = vi.fn() + const useTestHook = () => + useQueryStates( + { + a: parseAsString.withDefault('default'), + b: parseAsString.withDefault('default').withOptions({ + clearOnDefault: true // overrides hook options + }) + }, + { + clearOnDefault: false + } + ) + const { result } = renderHook(useTestHook, { + wrapper: withNuqsTestingAdapter({ + searchParams: '?a=init&b=init', + onUrlUpdate + }) + }) + await act(() => + result.current[1]( + { a: 'default', b: 'default' }, + { + clearOnDefault: true + } + ) + ) + expect(onUrlUpdate).toHaveBeenCalledOnce() + expect(onUrlUpdate.mock.calls[0]![0].queryString).toEqual('') + }) +}) diff --git a/packages/nuqs/src/useQueryStates.test.tsx b/packages/nuqs/src/useQueryStates.test.tsx deleted file mode 100644 index eda6b362..00000000 --- a/packages/nuqs/src/useQueryStates.test.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { act, renderHook } from '@testing-library/react' -import type { ReactNode } from 'react' -import React from 'react' -import { describe, expect, it } from 'vitest' -import { NuqsTestingAdapter } from './adapters/testing' -import { parseAsArrayOf, parseAsJson, parseAsString } from './parsers' -import { useQueryStates } from './useQueryStates' - -function withSearchParams( - searchParams?: string | URLSearchParams | Record -) { - return (props: { children: ReactNode }) => ( - - ) -} - -const defaults = { - str: 'foo', - obj: { initial: 'state' }, - arr: [ - { - initial: 'state' - } - ] -} - -const hook = ({ defaultValue } = { defaultValue: defaults.str }) => { - return useQueryStates({ - str: parseAsString.withDefault(defaultValue), - obj: parseAsJson(x => x).withDefault(defaults.obj), - arr: parseAsArrayOf(parseAsJson(x => x)).withDefault(defaults.arr) - }) -} - -describe('useQueryStates', () => { - it('should have referential equality on default values', () => { - const { result } = renderHook(hook, { - wrapper: NuqsTestingAdapter - }) - const [state] = result.current - expect(state.str).toBe(defaults.str) - expect(state.obj).toBe(defaults.obj) - expect(state.arr).toBe(defaults.arr) - expect(state.arr[0]).toBe(defaults.arr[0]) - }) - - it('should keep referential equality when resetting to defaults', () => { - const { result } = renderHook(hook, { - wrapper: withSearchParams({ - str: 'foo', - obj: '{"hello":"world"}', - arr: '{"obj":true},{"arr":true}' - }) - }) - act(() => { - result.current[1](null) - }) - const [state] = result.current - expect(state.str).toBe(defaults.str) - expect(state.obj).toBe(defaults.obj) - expect(state.arr).toBe(defaults.arr) - expect(state.arr[0]).toBe(defaults.arr[0]) - }) - - it('should keep referential equality when unrelated keys change', () => { - const { result } = renderHook(hook, { - wrapper: withSearchParams({ - str: 'foo', - obj: '{"hello":"world"}' - // Keep arr as default - }) - }) - const [{ obj: initialObj, arr: initialArr }] = result.current - act(() => { - result.current[1]({ str: 'bar' }) - }) - const [{ str, obj, arr }] = result.current - expect(str).toBe('bar') - expect(obj).toBe(initialObj) - expect(arr).toBe(initialArr) - }) - - it('should keep referential equality when default changes for another key', () => { - const { result, rerender } = renderHook(hook, { - wrapper: withSearchParams() - }) - expect(result.current[0].str).toBe('foo') - rerender({ defaultValue: 'b' }) - const [state] = result.current - expect(state.str).toBe('b') - expect(state.obj).toBe(defaults.obj) - expect(state.arr).toBe(defaults.arr) - expect(state.arr[0]).toBe(defaults.arr[0]) - }) -}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 59676037..1fa3de81 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -526,9 +526,12 @@ importers: '@vitejs/plugin-react': specifier: ^4.3.3 version: 4.3.3(vite@5.4.11(@types/node@22.9.0)(lightningcss@1.27.0)(terser@5.34.1)) + '@vitest/coverage-v8': + specifier: ^2.1.8 + version: 2.1.8(vitest@2.1.5(@types/node@22.9.0)(jsdom@25.0.1)(lightningcss@1.27.0)(terser@5.34.1)) next: specifier: 15.0.3 - version: 15.0.3(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.0.0-beta-a7bf2bd-20241110)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 15.0.3(@babel/core@7.25.7)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: specifier: catalog:react19 version: 19.0.0 @@ -752,6 +755,9 @@ packages: resolution: {integrity: sha512-vwIVdXG+j+FOpkwqHRcBgHLYNL7XMkufrlaFvL9o6Ai9sJn9+PdyIL5qa0XzTZw084c+u9LOls53eoZWP/W5WQ==} engines: {node: '>=6.9.0'} + '@bcoe/v8-coverage@0.2.3': + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + '@colors/colors@1.5.0': resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} @@ -1765,6 +1771,10 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} + '@istanbuljs/schema@0.1.3': + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + '@jest/schemas@29.6.3': resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -3524,6 +3534,15 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 + '@vitest/coverage-v8@2.1.8': + resolution: {integrity: sha512-2Y7BPlKH18mAZYAW1tYByudlCYrQyl5RGvnnDYJKW5tCiO5qg3KSAy3XAxcxKz900a0ZXxWtKrMuZLe3lKBpJw==} + peerDependencies: + '@vitest/browser': 2.1.8 + vitest: 2.1.8 + peerDependenciesMeta: + '@vitest/browser': + optional: true + '@vitest/expect@2.1.5': resolution: {integrity: sha512-nZSBTW1XIdpZvEJyoP/Sy8fUg0b8od7ZpGDkTUcfJ7wz/VoZAFzFfLyxVxGFhUjJzhYqSbIpfMtl/+k/dpWa3Q==} @@ -5471,6 +5490,9 @@ packages: resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} engines: {node: '>=18'} + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + html-void-elements@3.0.0: resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} @@ -5850,6 +5872,22 @@ packages: resolution: {integrity: sha512-3YZcUUR2Wt1WsapF+S/WiA2WmlW0cWAoPccMqne7AxEBhCdFeTPjfv/Axb8V2gyCgY3nRw+ksZ3xSUX+R47iAg==} engines: {node: ^18.17 || >=20.6.1} + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@5.0.6: + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} + engines: {node: '>=10'} + + istanbul-reports@3.1.7: + resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} + engines: {node: '>=8'} + iterator.prototype@1.1.3: resolution: {integrity: sha512-FW5iMbeQ6rBGm/oKgzq2aW4KvAGpxPzYES8N4g4xNXUKpL1mclMvOe+76AcLDTvD+Ze+sOpVhgdAQEKF4L9iGQ==} engines: {node: '>= 0.4'} @@ -6221,9 +6259,6 @@ packages: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true - magic-string@0.30.11: - resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==} - magic-string@0.30.12: resolution: {integrity: sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==} @@ -6231,6 +6266,13 @@ packages: resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==} engines: {node: '>=12'} + magicast@0.3.5: + resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} @@ -8352,6 +8394,10 @@ packages: engines: {node: '>=10'} hasBin: true + test-exclude@7.0.1: + resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} + engines: {node: '>=18'} + text-extensions@2.4.0: resolution: {integrity: sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==} engines: {node: '>=8'} @@ -9368,6 +9414,8 @@ snapshots: '@babel/helper-validator-identifier': 7.25.7 to-fast-properties: 2.0.0 + '@bcoe/v8-coverage@0.2.3': {} + '@colors/colors@1.5.0': optional: true @@ -10070,6 +10118,8 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 + '@istanbuljs/schema@0.1.3': {} + '@jest/schemas@29.6.3': dependencies: '@sinclair/typebox': 0.27.8 @@ -11414,7 +11464,7 @@ snapshots: estree-walker: 2.0.2 glob: 10.4.5 is-reference: 1.2.1 - magic-string: 0.30.11 + magic-string: 0.30.12 optionalDependencies: rollup: 3.29.5 @@ -12323,6 +12373,24 @@ snapshots: transitivePeerDependencies: - supports-color + '@vitest/coverage-v8@2.1.8(vitest@2.1.5(@types/node@22.9.0)(jsdom@25.0.1)(lightningcss@1.27.0)(terser@5.34.1))': + dependencies: + '@ampproject/remapping': 2.3.0 + '@bcoe/v8-coverage': 0.2.3 + debug: 4.3.7(supports-color@8.1.1) + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.1.7 + magic-string: 0.30.12 + magicast: 0.3.5 + std-env: 3.8.0 + test-exclude: 7.0.1 + tinyrainbow: 1.2.0 + vitest: 2.1.5(@types/node@22.9.0)(jsdom@25.0.1)(lightningcss@1.27.0)(terser@5.34.1) + transitivePeerDependencies: + - supports-color + '@vitest/expect@2.1.5': dependencies: '@vitest/spy': 2.1.5 @@ -14771,6 +14839,8 @@ snapshots: dependencies: whatwg-encoding: 3.1.1 + html-escaper@2.0.2: {} + html-void-elements@3.0.0: {} htmlparser2@8.0.2: @@ -15099,6 +15169,27 @@ snapshots: lodash.isstring: 4.0.1 lodash.uniqby: 4.7.0 + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@5.0.6: + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + debug: 4.3.7(supports-color@8.1.1) + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.1.7: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + iterator.prototype@1.1.3: dependencies: define-properties: 1.2.1 @@ -15438,10 +15529,6 @@ snapshots: lz-string@1.5.0: {} - magic-string@0.30.11: - dependencies: - '@jridgewell/sourcemap-codec': 1.5.0 - magic-string@0.30.12: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 @@ -15450,6 +15537,16 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 + magicast@0.3.5: + dependencies: + '@babel/parser': 7.25.7 + '@babel/types': 7.25.7 + source-map-js: 1.2.1 + + make-dir@4.0.0: + dependencies: + semver: 7.6.3 + make-error@1.3.6: {} map-obj@1.0.1: {} @@ -16393,6 +16490,32 @@ snapshots: react: 19.0.0 react-dom: 19.0.0(react@19.0.0) + next@15.0.3(@babel/core@7.25.7)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@next/env': 15.0.3 + '@swc/counter': 0.1.3 + '@swc/helpers': 0.5.13 + busboy: 1.6.0 + caniuse-lite: 1.0.30001667 + postcss: 8.4.31 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + styled-jsx: 5.1.6(@babel/core@7.25.7)(react@19.0.0) + optionalDependencies: + '@next/swc-darwin-arm64': 15.0.3 + '@next/swc-darwin-x64': 15.0.3 + '@next/swc-linux-arm64-gnu': 15.0.3 + '@next/swc-linux-arm64-musl': 15.0.3 + '@next/swc-linux-x64-gnu': 15.0.3 + '@next/swc-linux-x64-musl': 15.0.3 + '@next/swc-win32-arm64-msvc': 15.0.3 + '@next/swc-win32-x64-msvc': 15.0.3 + '@opentelemetry/api': 1.9.0 + sharp: 0.33.5 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + next@15.0.3(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.0.0-beta-a7bf2bd-20241110)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: '@next/env': 15.0.3 @@ -16403,7 +16526,7 @@ snapshots: postcss: 8.4.31 react: 19.0.0 react-dom: 19.0.0(react@19.0.0) - styled-jsx: 5.1.6(react@19.0.0) + styled-jsx: 5.1.6(@babel/core@7.25.7)(react@19.0.0) optionalDependencies: '@next/swc-darwin-arm64': 15.0.3 '@next/swc-darwin-x64': 15.0.3 @@ -17992,10 +18115,12 @@ snapshots: dependencies: inline-style-parser: 0.2.4 - styled-jsx@5.1.6(react@19.0.0): + styled-jsx@5.1.6(@babel/core@7.25.7)(react@19.0.0): dependencies: client-only: 0.0.1 react: 19.0.0 + optionalDependencies: + '@babel/core': 7.25.7 sucrase@3.35.0: dependencies: @@ -18126,6 +18251,12 @@ snapshots: commander: 2.20.3 source-map-support: 0.5.21 + test-exclude@7.0.1: + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 10.4.5 + minimatch: 9.0.5 + text-extensions@2.4.0: {} text-table@0.2.0: {}