diff --git a/package.json b/package.json index d540a842fc..163d141818 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,6 @@ "@tanstack/typedoc-config": "0.3.1", "@tanstack/vite-config": "0.4.1", "@testing-library/jest-dom": "^6.8.0", - "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^14.6.1", "@testing-library/vue": "^8.1.0", "@types/node": "^24.1.0", diff --git a/packages/form-core/vite.config.ts b/packages/form-core/vite.config.ts index 94d53421f1..715e7060ab 100644 --- a/packages/form-core/vite.config.ts +++ b/packages/form-core/vite.config.ts @@ -7,7 +7,6 @@ const config = defineConfig({ name: packageJson.name, dir: './tests', watch: false, - environment: 'jsdom', coverage: { enabled: true, provider: 'istanbul', include: ['src/**/*'] }, typecheck: { enabled: true }, }, diff --git a/packages/react-form-nextjs/tests/test-setup.ts b/packages/react-form-nextjs/tests/test-setup.ts deleted file mode 100644 index 33e431061a..0000000000 --- a/packages/react-form-nextjs/tests/test-setup.ts +++ /dev/null @@ -1,6 +0,0 @@ -import '@testing-library/jest-dom/vitest' -import { cleanup } from '@testing-library/react' -import { afterEach } from 'vitest' - -// https://testing-library.com/docs/react-testing-library/api#cleanup -afterEach(() => cleanup()) diff --git a/packages/react-form-nextjs/vite.config.ts b/packages/react-form-nextjs/vite.config.ts index 15183ab228..10d048d1ce 100644 --- a/packages/react-form-nextjs/vite.config.ts +++ b/packages/react-form-nextjs/vite.config.ts @@ -9,8 +9,6 @@ const config = defineConfig({ name: packageJson.name, dir: './tests', watch: false, - environment: 'jsdom', - setupFiles: ['./tests/test-setup.ts'], coverage: { enabled: true, provider: 'istanbul', include: ['src/**/*'] }, typecheck: { enabled: true }, }, diff --git a/packages/react-form-remix/tests/test-setup.ts b/packages/react-form-remix/tests/test-setup.ts deleted file mode 100644 index 33e431061a..0000000000 --- a/packages/react-form-remix/tests/test-setup.ts +++ /dev/null @@ -1,6 +0,0 @@ -import '@testing-library/jest-dom/vitest' -import { cleanup } from '@testing-library/react' -import { afterEach } from 'vitest' - -// https://testing-library.com/docs/react-testing-library/api#cleanup -afterEach(() => cleanup()) diff --git a/packages/react-form-remix/vite.config.ts b/packages/react-form-remix/vite.config.ts index 15183ab228..10d048d1ce 100644 --- a/packages/react-form-remix/vite.config.ts +++ b/packages/react-form-remix/vite.config.ts @@ -9,8 +9,6 @@ const config = defineConfig({ name: packageJson.name, dir: './tests', watch: false, - environment: 'jsdom', - setupFiles: ['./tests/test-setup.ts'], coverage: { enabled: true, provider: 'istanbul', include: ['src/**/*'] }, typecheck: { enabled: true }, }, diff --git a/packages/react-form-start/tests/test-setup.ts b/packages/react-form-start/tests/test-setup.ts deleted file mode 100644 index 33e431061a..0000000000 --- a/packages/react-form-start/tests/test-setup.ts +++ /dev/null @@ -1,6 +0,0 @@ -import '@testing-library/jest-dom/vitest' -import { cleanup } from '@testing-library/react' -import { afterEach } from 'vitest' - -// https://testing-library.com/docs/react-testing-library/api#cleanup -afterEach(() => cleanup()) diff --git a/packages/react-form-start/vite.config.ts b/packages/react-form-start/vite.config.ts index 15183ab228..10d048d1ce 100644 --- a/packages/react-form-start/vite.config.ts +++ b/packages/react-form-start/vite.config.ts @@ -9,8 +9,6 @@ const config = defineConfig({ name: packageJson.name, dir: './tests', watch: false, - environment: 'jsdom', - setupFiles: ['./tests/test-setup.ts'], coverage: { enabled: true, provider: 'istanbul', include: ['src/**/*'] }, typecheck: { enabled: true }, }, diff --git a/packages/react-form/package.json b/packages/react-form/package.json index 872693cf5a..50ade6bfce 100644 --- a/packages/react-form/package.json +++ b/packages/react-form/package.json @@ -59,10 +59,12 @@ "@types/react": "~19.2.0", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^5.1.1", + "@vitest/browser-playwright": "^5.0.5", "eslint-plugin-react-compiler": "19.1.0-rc.2", "react": "19.1.0", "react-dom": "19.1.0", - "vite": "^7.2.2" + "vite": "^7.2.2", + "vitest-browser-react": "^2.3.0" }, "peerDependencies": { "react": "^17.0.0 || ^18.0.0 || ^19.0.0" diff --git a/packages/react-form/tests/createFormHook.test.tsx b/packages/react-form/tests/createFormHook.test.tsx index c93d0d725b..40dac54372 100644 --- a/packages/react-form/tests/createFormHook.test.tsx +++ b/packages/react-form/tests/createFormHook.test.tsx @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest' -import { render, waitFor } from '@testing-library/react' +import { render } from 'vitest-browser-react' import { formOptions } from '@tanstack/form-core' -import userEvent from '@testing-library/user-event' +import { userEvent } from 'vitest/browser' import { createFormHook, createFormHookContexts, useStore } from '../src' const user = userEvent.setup() @@ -44,7 +44,7 @@ const { useAppForm, withForm, withFieldGroup, useTypedAppFormContext } = }) describe('createFormHook', () => { - it('should allow to set default value', () => { + it('should allow to set default value', async () => { type Person = { firstName: string lastName: string @@ -68,12 +68,12 @@ describe('createFormHook', () => { ) } - const { getByLabelText } = render() - const input = getByLabelText('Testing') - expect(input).toHaveValue('FirstName') + const { getByLabelText } = await render() + const input = getByLabelText('Testing', { exact: true }) + await expect.element(input).toHaveValue('FirstName') }) - it('should handle withForm types properly', () => { + it('should handle withForm types properly', async () => { const formOpts = formOptions({ defaultValues: { firstName: 'John', @@ -111,13 +111,15 @@ describe('createFormHook', () => { return } - const { getByLabelText, getByText } = render() - const input = getByLabelText('First Name') - expect(input).toHaveValue('John') - expect(getByText('Testing')).toBeInTheDocument() + const { getByLabelText, getByText } = await render() + const input = getByLabelText('First Name', { exact: true }) + await expect.element(input).toHaveValue('John') + await expect + .element(getByText('Testing', { exact: true })) + .toBeInTheDocument() }) - it('should handle withFieldGroup types properly', () => { + it('should handle withFieldGroup types properly', async () => { const formOpts = formOptions({ defaultValues: { person: { @@ -157,13 +159,15 @@ describe('createFormHook', () => { return } - const { getByLabelText, getByText } = render() - const input = getByLabelText('First Name') - expect(input).toHaveValue('John') - expect(getByText('Testing')).toBeInTheDocument() + const { getByLabelText, getByText } = await render() + const input = getByLabelText('First Name', { exact: true }) + await expect.element(input).toHaveValue('John') + await expect + .element(getByText('Testing', { exact: true })) + .toBeInTheDocument() }) - it('should use the correct field name in Field with withFieldGroup', () => { + it('should use the correct field name in Field with withFieldGroup', async () => { const formOpts = formOptions({ defaultValues: { person: { @@ -234,17 +238,19 @@ describe('createFormHook', () => { ) } - const { getByLabelText, getByText } = render() - const inputField1 = getByLabelText('person.firstName') - const inputArray = getByLabelText('people[0].firstName') - const inputField2 = getByLabelText('people[1].firstName') - expect(inputField1).toHaveValue('John') - expect(inputArray).toHaveValue('Jane') - expect(inputField2).toHaveValue('Robert') - expect(getByText('Testing')).toBeInTheDocument() + const { getByLabelText, getByText } = await render() + const inputField1 = getByLabelText('person.firstName', { exact: true }) + const inputArray = getByLabelText('people[0].firstName', { exact: true }) + const inputField2 = getByLabelText('people[1].firstName', { exact: true }) + await expect.element(inputField1).toHaveValue('John') + await expect.element(inputArray).toHaveValue('Jane') + await expect.element(inputField2).toHaveValue('Robert') + await expect + .element(getByText('Testing', { exact: true })) + .toBeInTheDocument() }) - it('should forward Field and Subscribe to the form', () => { + it('should forward Field and Subscribe to the form', async () => { const formOpts = formOptions({ defaultValues: { person: { @@ -286,10 +292,10 @@ describe('createFormHook', () => { return } - const { getByLabelText, getByText } = render() - const input = getByLabelText('person.firstName') - expect(input).toHaveValue('John') - expect(getByText('Doe')).toBeInTheDocument() + const { getByLabelText, getByText } = await render() + const input = getByLabelText('person.firstName', { exact: true }) + await expect.element(input).toHaveValue('John') + await expect.element(getByText('Doe', { exact: true })).toBeInTheDocument() }) it('should not lose focus on update with withFieldGroup', async () => { @@ -339,19 +345,19 @@ describe('createFormHook', () => { return } - const { getByLabelText } = render() + const { getByLabelText } = await render() - const input = getByLabelText('person.firstName') - input.focus() - expect(input).toHaveFocus() + const input = getByLabelText('person.firstName', { exact: true }) + ;(input.element() as HTMLInputElement).focus() + await expect.element(input).toHaveFocus() await user.clear(input) await user.type(input, 'Something') - expect(input).toHaveFocus() + await expect.element(input).toHaveFocus() }) - it('should allow nesting withFieldGroup in other withFieldGroups', () => { + it('should allow nesting withFieldGroup in other withFieldGroups', async () => { type Nested = { firstName: string } @@ -404,12 +410,14 @@ describe('createFormHook', () => { return } - const { getByText } = render() + const { getByText } = await render() - expect(getByText('form.field.firstName')).toBeInTheDocument() + await expect + .element(getByText('form.field.firstName', { exact: true })) + .toBeInTheDocument() }) - it('should allow mapping withFieldGroup to different values', () => { + it('should allow mapping withFieldGroup to different values', async () => { const formOpts = formOptions({ defaultValues: { unrelated: 'John', @@ -447,12 +455,12 @@ describe('createFormHook', () => { ) } - const { getByLabelText } = render() - const inputField1 = getByLabelText('unrelated') - expect(inputField1).toHaveValue('John') + const { getByLabelText } = await render() + const inputField1 = getByLabelText('unrelated', { exact: true }) + await expect.element(inputField1).toHaveValue('John') }) - it('should remap FieldGroupApi.Field validators to the correct names', () => { + it('should remap FieldGroupApi.Field validators to the correct names', async () => { const FieldGroupString = withFieldGroup({ defaultValues: { password: '', confirmPassword: '' }, render: function Render({ group }) { @@ -533,7 +541,7 @@ describe('createFormHook', () => { ) } - render() + await render() }) it('should accept formId and return it', async () => { @@ -574,15 +582,15 @@ describe('createFormHook', () => { ) } - const { getByTestId } = render() + const { getByTestId } = await render() const target = getByTestId('formId-target') const result = getByTestId('formId-result') await user.click(target) - expect(result).toHaveTextContent('1') + await expect.element(result).toHaveTextContent('1') }) - it('should allow using typed app form', () => { + it('should allow using typed app form', async () => { type Person = { firstName: string lastName: string @@ -620,12 +628,12 @@ describe('createFormHook', () => { ) } - const { getByLabelText } = render() - const input = getByLabelText('Testing') - expect(input).toHaveValue('FirstName') + const { getByLabelText } = await render() + const input = getByLabelText('Testing', { exact: true }) + await expect.element(input).toHaveValue('FirstName') }) - it('should throw if `useTypedAppFormContext` is used without AppForm', () => { + it('should throw if `useTypedAppFormContext` is used without AppForm', async () => { type Person = { firstName: string lastName: string @@ -659,10 +667,10 @@ describe('createFormHook', () => { return } - expect(() => render()).toThrow() + await expect(render()).rejects.toThrow() }) - it('should allow using typed app form with form components', () => { + it('should allow using typed app form with form components', async () => { type Person = { firstName: string lastName: string @@ -695,13 +703,13 @@ describe('createFormHook', () => { ) } - const { getByText } = render() - const button = getByText('Testing') - expect(button).toBeInTheDocument() + const { getByText } = await render() + const button = getByText('Testing', { exact: true }) + await expect.element(button).toBeInTheDocument() }) describe('extendForm', () => { - it('should include both parent and extended field components', () => { + it('should include both parent and extended field components', async () => { function ExtendedTextField({ label }: { label: string }) { const field = useFieldContext() return ( @@ -746,12 +754,14 @@ describe('createFormHook', () => { ) } - const { getByLabelText, getByTestId } = render() - expect(getByLabelText('First Name')).toHaveValue('John') - expect(getByTestId('extended-input')).toHaveValue('Doe') + const { getByLabelText, getByTestId } = await render() + await expect + .element(getByLabelText('First Name', { exact: true })) + .toHaveValue('John') + await expect.element(getByTestId('extended-input')).toHaveValue('Doe') }) - it('should include both parent and extended form components', () => { + it('should include both parent and extended form components', async () => { function ExtendedSubmit({ label }: { label: string }) { const form = useFormContext() return ( @@ -791,14 +801,16 @@ describe('createFormHook', () => { ) } - const { getByText, getByTestId } = render() - expect(getByText('Submit')).toBeInTheDocument() - expect(getByTestId('extended-submit')).toHaveTextContent( - 'Extended Submit', - ) + const { getByText, getByTestId } = await render() + await expect + .element(getByText('Submit', { exact: true })) + .toBeInTheDocument() + await expect + .element(getByTestId('extended-submit')) + .toHaveTextContent('Extended Submit') }) - it('should support chaining multiple extendForm calls', () => { + it('should support chaining multiple extendForm calls', async () => { function FieldA({ label }: { label: string }) { const field = useFieldContext() return ( @@ -859,13 +871,19 @@ describe('createFormHook', () => { ) } - const { getByLabelText } = render() - expect(getByLabelText('A')).toHaveValue('valueA') - expect(getByLabelText('B')).toHaveValue('valueB') - expect(getByLabelText('C')).toHaveValue('valueC') + const { getByLabelText } = await render() + await expect + .element(getByLabelText('A', { exact: true })) + .toHaveValue('valueA') + await expect + .element(getByLabelText('B', { exact: true })) + .toHaveValue('valueB') + await expect + .element(getByLabelText('C', { exact: true })) + .toHaveValue('valueC') }) - it('should work with withForm after extendForm', () => { + it('should work with withForm after extendForm', async () => { function ExtendedTextField({ label }: { label: string }) { const field = useFieldContext() return ( @@ -916,12 +934,16 @@ describe('createFormHook', () => { return } - const { getByLabelText } = render() - expect(getByLabelText('First Name')).toHaveValue('Jane') - expect(getByLabelText('Last Name')).toHaveValue('Smith') + const { getByLabelText } = await render() + await expect + .element(getByLabelText('First Name', { exact: true })) + .toHaveValue('Jane') + await expect + .element(getByLabelText('Last Name', { exact: true })) + .toHaveValue('Smith') }) - it('should return a new extendForm from the extended hook', () => { + it('should return a new extendForm from the extended hook', async () => { function ExtendedTextField({ label }: { label: string }) { const field = useFieldContext() return ( @@ -999,14 +1021,14 @@ describe('createFormHook', () => { return } - const { getByTestId } = render() + const { getByTestId } = await render() const input = getByTestId('lastName') const stateLastName = getByTestId('state-lastName') - expect(stateLastName).toHaveTextContent('LastName') + await expect.element(stateLastName).toHaveTextContent('LastName') await user.clear(input) await user.type(input, 'Updated') - await waitFor(() => expect(stateLastName).toHaveTextContent('Updated')) + await expect.element(stateLastName).toHaveTextContent('Updated') }) }) diff --git a/packages/react-form/tests/onChangeListenTo.adapter.test.tsx b/packages/react-form/tests/onChangeListenTo.adapter.test.tsx index 79defa0b44..f5b6fd597d 100644 --- a/packages/react-form/tests/onChangeListenTo.adapter.test.tsx +++ b/packages/react-form/tests/onChangeListenTo.adapter.test.tsx @@ -1,5 +1,6 @@ import { describe, expect, it, vi } from 'vitest' -import { fireEvent, render } from '@testing-library/react' +import { userEvent } from 'vitest/browser' +import { render } from 'vitest-browser-react' import { useSelector } from '@tanstack/react-store' import { StrictMode } from 'react' import { useForm } from '../src/useForm' @@ -109,22 +110,22 @@ describe('React adapter - onChangeListenTo race', () => { ) } - const { getByTestId } = render( + const { getByTestId } = await render( , ) - const street = getByTestId('street') as HTMLInputElement - const houseNo = getByTestId('houseNo') as HTMLInputElement - const zipCode = getByTestId('zipCode') as HTMLInputElement - const city = getByTestId('city') as HTMLInputElement + const street = getByTestId('street') + const houseNo = getByTestId('houseNo') + const zipCode = getByTestId('zipCode') + const city = getByTestId('city') // Simulate rapid updates (autofill) - fireEvent.change(street, { target: { value: 'Foo Street' } }) - fireEvent.change(houseNo, { target: { value: '2' } }) - fireEvent.change(zipCode, { target: { value: '12345' } }) - fireEvent.change(city, { target: { value: 'Barrington' } }) + await userEvent.fill(street, 'Foo Street') + await userEvent.fill(houseNo, '2') + await userEvent.fill(zipCode, '12345') + await userEvent.fill(city, 'Barrington') // Run debounce + async validation await vi.runAllTimersAsync() @@ -132,11 +133,15 @@ describe('React adapter - onChangeListenTo race', () => { expect(validationFn).toHaveBeenCalledTimes(1) // Verify validation flags are not stuck - const isFieldsValidating = getByTestId('isFieldsValidating').textContent - const streetValidating = getByTestId('street-validating').textContent - const houseValidating = getByTestId('houseNo-validating').textContent - const zipValidating = getByTestId('zipCode-validating').textContent - const cityValidating = getByTestId('city-validating').textContent + const isFieldsValidating = + getByTestId('isFieldsValidating').element().textContent + const streetValidating = + getByTestId('street-validating').element().textContent + const houseValidating = + getByTestId('houseNo-validating').element().textContent + const zipValidating = + getByTestId('zipCode-validating').element().textContent + const cityValidating = getByTestId('city-validating').element().textContent expect(isFieldsValidating).toBe('false') expect(streetValidating).toBe('false') diff --git a/packages/react-form/tests/test-setup.ts b/packages/react-form/tests/test-setup.ts deleted file mode 100644 index 33e431061a..0000000000 --- a/packages/react-form/tests/test-setup.ts +++ /dev/null @@ -1,6 +0,0 @@ -import '@testing-library/jest-dom/vitest' -import { cleanup } from '@testing-library/react' -import { afterEach } from 'vitest' - -// https://testing-library.com/docs/react-testing-library/api#cleanup -afterEach(() => cleanup()) diff --git a/packages/react-form/tests/useField.test.tsx b/packages/react-form/tests/useField.test.tsx index 4454165947..d041f3bb54 100644 --- a/packages/react-form/tests/useField.test.tsx +++ b/packages/react-form/tests/useField.test.tsx @@ -1,7 +1,7 @@ /* eslint-disable react-compiler/react-compiler */ import { describe, expect, it, vi } from 'vitest' -import { render, waitFor, within } from '@testing-library/react' -import { userEvent } from '@testing-library/user-event' +import { render } from 'vitest-browser-react' +import { userEvent } from 'vitest/browser' import { StrictMode, useState } from 'react' import { useStore } from '@tanstack/react-store' import { useForm } from '../src/index' @@ -11,7 +11,7 @@ import type { AnyFieldApi } from '../src/index' const user = userEvent.setup() describe('useField', () => { - it('should allow to set default value', () => { + it('should allow to set default value', async () => { type Person = { firstName: string lastName: string @@ -44,12 +44,12 @@ describe('useField', () => { ) } - const { getByTestId } = render() + const { getByTestId } = await render() const input = getByTestId('fieldinput') - expect(input).toHaveValue('FirstName') + await expect.element(input).toHaveValue('FirstName') }) - it('should use field default value first', () => { + it('should use field default value first', async () => { type Person = { firstName: string lastName: string @@ -83,9 +83,9 @@ describe('useField', () => { ) } - const { getByTestId } = render() + const { getByTestId } = await render() const input = getByTestId('fieldinput') - expect(input).toHaveValue('otherName') + await expect.element(input).toHaveValue('otherName') }) it('should not validate on change if isTouched is false', async () => { @@ -131,10 +131,12 @@ describe('useField', () => { ) } - const { getByTestId, queryByText } = render() + const { getByTestId, getByText } = await render() const input = getByTestId('fieldinput') await user.type(input, 'other') - expect(queryByText(error)).not.toBeInTheDocument() + await expect + .element(getByText(error, { exact: true })) + .not.toBeInTheDocument() }) it('should validate on change if isTouched is true', async () => { @@ -177,11 +179,13 @@ describe('useField', () => { ) } - const { getByTestId, getByText, queryByText } = render() + const { getByTestId, getByText } = await render() const input = getByTestId('fieldinput') - expect(queryByText(error)).not.toBeInTheDocument() + await expect + .element(getByText(error, { exact: true })) + .not.toBeInTheDocument() await user.type(input, 'other') - expect(getByText(error)).toBeInTheDocument() + await expect.element(getByText(error, { exact: true })).toBeInTheDocument() }) it('should validate on change and on blur', async () => { @@ -229,14 +233,22 @@ describe('useField', () => { ) } - const { getByTestId, getByText, queryByText } = render() + const { getByTestId, getByText } = await render() const input = getByTestId('fieldinput') - expect(queryByText(onChangeError)).not.toBeInTheDocument() - expect(queryByText(onBlurError)).not.toBeInTheDocument() + await expect + .element(getByText(onChangeError, { exact: true })) + .not.toBeInTheDocument() + await expect + .element(getByText(onBlurError, { exact: true })) + .not.toBeInTheDocument() await user.type(input, 'other') - expect(getByText(onChangeError)).toBeInTheDocument() + await expect + .element(getByText(onChangeError, { exact: true })) + .toBeInTheDocument() await user.click(document.body) - expect(queryByText(onBlurError)).toBeInTheDocument() + await expect + .element(getByText(onBlurError, { exact: true })) + .toBeInTheDocument() }) it('should properly update conditionally rendered fields', async () => { @@ -311,21 +323,19 @@ describe('useField', () => { ) } - const { getByTestId } = render() + const { getByTestId } = await render() const showFirstFieldInput = getByTestId('show-first-field') await user.type(getByTestId('first-field'), 'hello') - expect((getByTestId('first-field') as HTMLInputElement).value).toBe('hello') + await expect.element(getByTestId('first-field')).toHaveValue('hello') await user.click(showFirstFieldInput) await user.type(getByTestId('second-field'), 'world') - expect((getByTestId('second-field') as HTMLInputElement).value).toBe( - 'world', - ) + await expect.element(getByTestId('second-field')).toHaveValue('world') await user.click(showFirstFieldInput) - expect((getByTestId('first-field') as HTMLInputElement).value).toBe('hello') + await expect.element(getByTestId('first-field')).toHaveValue('hello') }) it('should not keep hidden field submit errors after unmount', async () => { @@ -395,7 +405,7 @@ describe('useField', () => { ) } - const { getByTestId, queryByTestId } = render( + const { getByTestId } = await render( , @@ -407,26 +417,23 @@ describe('useField', () => { await user.type(getByTestId('last-name'), 'abc') await user.click(submitButton) - await waitFor(() => expect(submitButton).toBeDisabled()) + await expect.element(submitButton).toBeDisabled() expect(onSubmit).toHaveBeenCalledTimes(0) await user.clear(getByTestId('first-name')) await user.type(getByTestId('first-name'), 'b') - await waitFor(() => - expect(queryByTestId('last-name')).not.toBeInTheDocument(), - ) - await waitFor(() => expect(submitButton).toBeEnabled()) + await expect.element(getByTestId('last-name')).not.toBeInTheDocument() + await expect.element(submitButton).toBeEnabled() await user.click(submitButton) - await waitFor(() => expect(onSubmit).toHaveBeenCalledTimes(1)) + await vi.waitFor(() => expect(onSubmit).toHaveBeenCalledTimes(1)) await user.clear(getByTestId('first-name')) await user.type(getByTestId('first-name'), 'a') - const remountedLastName = await waitFor(() => getByTestId('last-name')) - expect((remountedLastName as HTMLInputElement).value).toBe('abc') - expect(submitButton).toBeEnabled() + await expect.element(getByTestId('last-name')).toHaveValue('abc') + await expect.element(submitButton).toBeEnabled() }) it('should call onUnmount listener when a field is conditionally removed', async () => { @@ -476,20 +483,20 @@ describe('useField', () => { ) } - const { getByTestId, queryByTestId } = render( + const { getByTestId } = await render( , ) - await waitFor(() => expect(getByTestId('name')).toBeInTheDocument()) + await expect.element(getByTestId('name')).toBeInTheDocument() const callsBefore = fieldUnmount.mock.calls.length const formCallsBefore = formFieldUnmount.mock.calls.length await user.click(getByTestId('toggle')) - await waitFor(() => expect(queryByTestId('name')).not.toBeInTheDocument()) + await expect.element(getByTestId('name')).not.toBeInTheDocument() expect(fieldUnmount).toHaveBeenCalledTimes(callsBefore + 1) expect(formFieldUnmount).toHaveBeenCalledTimes(formCallsBefore + 1) }) @@ -537,12 +544,13 @@ describe('useField', () => { ) } - const { getByTestId, getByText, queryByText } = render() + const { getByTestId, getByText } = await render() const input = getByTestId('fieldinput') - expect(queryByText(error)).not.toBeInTheDocument() + await expect + .element(getByText(error, { exact: true })) + .not.toBeInTheDocument() await user.type(input, 'other') - await waitFor(() => getByText(error)) - expect(getByText(error)).toBeInTheDocument() + await expect.element(getByText(error, { exact: true })).toBeInTheDocument() }) it('should validate async on change and async on blur', async () => { @@ -594,17 +602,23 @@ describe('useField', () => { ) } - const { getByTestId, getByText, queryByText } = render() + const { getByTestId, getByText } = await render() const input = getByTestId('fieldinput') - expect(queryByText(onChangeError)).not.toBeInTheDocument() - expect(queryByText(onBlurError)).not.toBeInTheDocument() + await expect + .element(getByText(onChangeError, { exact: true })) + .not.toBeInTheDocument() + await expect + .element(getByText(onBlurError, { exact: true })) + .not.toBeInTheDocument() await user.type(input, 'other') - await waitFor(() => getByText(onChangeError)) - expect(getByText(onChangeError)).toBeInTheDocument() + await expect + .element(getByText(onChangeError, { exact: true })) + .toBeInTheDocument() await user.click(document.body) - await waitFor(() => getByText(onBlurError)) - expect(getByText(onBlurError)).toBeInTheDocument() + await expect + .element(getByText(onBlurError, { exact: true })) + .toBeInTheDocument() }) it('should validate async on change with debounce', async () => { @@ -653,13 +667,12 @@ describe('useField', () => { ) } - const { getByTestId, getByText } = render() + const { getByTestId, getByText } = await render() const input = getByTestId('fieldinput') await user.type(input, 'other') // mockFn will have been called 5 times without onChangeAsyncDebounceMs expect(mockFn).toHaveBeenCalledTimes(0) - await waitFor(() => getByText(error)) - expect(getByText(error)).toBeInTheDocument() + await expect.element(getByText(error, { exact: true })).toBeInTheDocument() }) it('should handle strict mode properly with conditional fields', async () => { @@ -754,17 +767,21 @@ describe('useField', () => { ) } - const { getByText, findByText, queryByText } = render( + const { getByText } = await render( , ) - await user.click(getByText('Submit')) - expect(await findByText('A first name is required')).toBeInTheDocument() - await user.click(getByText('Hide field')) - await user.click(getByText('Submit')) - expect(queryByText('A first name is required')).not.toBeInTheDocument() + await user.click(getByText('Submit', { exact: true })) + await expect + .element(getByText('A first name is required', { exact: true })) + .toBeInTheDocument() + await user.click(getByText('Hide field', { exact: true })) + await user.click(getByText('Submit', { exact: true })) + await expect + .element(getByText('A first name is required', { exact: true })) + .not.toBeInTheDocument() }) it('should handle arrays with primitive values', async () => { @@ -837,29 +854,39 @@ describe('useField', () => { ) } - const { getByText, findByLabelText, queryByText, findByText } = render( - , - ) - - expect(queryByText('Name for person 0')).not.toBeInTheDocument() - expect(queryByText('Name for person 1')).not.toBeInTheDocument() - await user.click(getByText('Add person')) - const input = await findByLabelText('Name for person 0') - expect(input).toBeInTheDocument() + const { getByText, getByLabelText } = await render() + + await expect + .element(getByText('Name for person 0', { exact: true })) + .not.toBeInTheDocument() + await expect + .element(getByText('Name for person 1', { exact: true })) + .not.toBeInTheDocument() + await user.click(getByText('Add person', { exact: true })) + const input = getByLabelText('Name for person 0', { exact: true }) + await expect.element(input).toBeInTheDocument() await user.type(input, 'John') - await user.click(getByText('Add person')) - const input2 = await findByLabelText('Name for person 1') - expect(input).toBeInTheDocument() + await user.click(getByText('Add person', { exact: true })) + const input2 = getByLabelText('Name for person 1', { exact: true }) + await expect.element(input).toBeInTheDocument() await user.type(input2, 'Jack') - expect(queryByText('Name for person 0')).toBeInTheDocument() - expect(queryByText('Name for person 1')).toBeInTheDocument() - await user.click(getByText('Remove person 1')) - expect(queryByText('Name for person 0')).toBeInTheDocument() - expect(queryByText('Name for person 1')).not.toBeInTheDocument() - - await user.click(await findByText('Submit')) + await expect + .element(getByText('Name for person 0', { exact: true })) + .toBeInTheDocument() + await expect + .element(getByText('Name for person 1', { exact: true })) + .toBeInTheDocument() + await user.click(getByText('Remove person 1', { exact: true })) + await expect + .element(getByText('Name for person 0', { exact: true })) + .toBeInTheDocument() + await expect + .element(getByText('Name for person 1', { exact: true })) + .not.toBeInTheDocument() + + await user.click(getByText('Submit', { exact: true })) expect(fn).toHaveBeenCalledWith({ people: ['John'] }) }) @@ -936,29 +963,39 @@ describe('useField', () => { ) } - const { getByText, findByLabelText, queryByText, findByText } = render( - , - ) - - expect(queryByText('Name for person 0')).not.toBeInTheDocument() - expect(queryByText('Name for person 1')).not.toBeInTheDocument() - await user.click(getByText('Add person')) - const input = await findByLabelText('Name for person 0') - expect(input).toBeInTheDocument() + const { getByText, getByLabelText } = await render() + + await expect + .element(getByText('Name for person 0', { exact: true })) + .not.toBeInTheDocument() + await expect + .element(getByText('Name for person 1', { exact: true })) + .not.toBeInTheDocument() + await user.click(getByText('Add person', { exact: true })) + const input = getByLabelText('Name for person 0', { exact: true }) + await expect.element(input).toBeInTheDocument() await user.type(input, 'John') - await user.click(getByText('Add person')) - const input2 = await findByLabelText('Name for person 1') - expect(input).toBeInTheDocument() + await user.click(getByText('Add person', { exact: true })) + const input2 = getByLabelText('Name for person 1', { exact: true }) + await expect.element(input).toBeInTheDocument() await user.type(input2, 'Jack') - expect(queryByText('Name for person 0')).toBeInTheDocument() - expect(queryByText('Name for person 1')).toBeInTheDocument() - await user.click(getByText('Remove person 1')) - expect(queryByText('Name for person 0')).toBeInTheDocument() - expect(queryByText('Name for person 1')).not.toBeInTheDocument() - - await user.click(await findByText('Submit')) + await expect + .element(getByText('Name for person 0', { exact: true })) + .toBeInTheDocument() + await expect + .element(getByText('Name for person 1', { exact: true })) + .toBeInTheDocument() + await user.click(getByText('Remove person 1', { exact: true })) + await expect + .element(getByText('Name for person 0', { exact: true })) + .toBeInTheDocument() + await expect + .element(getByText('Name for person 1', { exact: true })) + .not.toBeInTheDocument() + + await user.click(getByText('Submit', { exact: true })) expect(fn).toHaveBeenCalledWith({ people: [{ name: 'John', age: 0 }] }) }) @@ -1024,21 +1061,27 @@ describe('useField', () => { ) } - const { getByText, findByLabelText } = render() + const { getByText, getByLabelText } = await render() - await user.click(getByText('Add section')) - await user.click(getByText('Add section')) + await user.click(getByText('Add section', { exact: true })) + await user.click(getByText('Add section', { exact: true })) - await findByLabelText('Title for section id-1') - await findByLabelText('Title for section id-2') + await expect + .element(getByLabelText('Title for section id-1', { exact: true })) + .toBeInTheDocument() + await expect + .element(getByLabelText('Title for section id-2', { exact: true })) + .toBeInTheDocument() observedValues.length = 0 - await user.click(getByText('Remove first section')) + await user.click(getByText('Remove first section', { exact: true })) // The surviving item (previously `sections[1]`) shifts to `sections[0]`. // Its value must remain "Section 2" and never render as `undefined`. - const survivingInput = await findByLabelText('Title for section id-2') - expect((survivingInput as HTMLInputElement).value).toBe('Section 2') + const survivingInput = getByLabelText('Title for section id-2', { + exact: true, + }) + await expect.element(survivingInput).toHaveValue('Section 2') expect(observedValues).not.toContain(undefined) }) @@ -1103,15 +1146,21 @@ describe('useField', () => { ) } - const { findByLabelText, queryByText, findByText } = render() + const { getByLabelText, getByText } = await render() - const passwordInput = await findByLabelText('Password') - const confirmPasswordInput = await findByLabelText('Confirm Password') + const passwordInput = getByLabelText('Password', { exact: true }) + const confirmPasswordInput = getByLabelText('Confirm Password', { + exact: true, + }) await user.type(passwordInput, 'password') await user.type(confirmPasswordInput, 'password') - expect(queryByText('Passwords do not match')).not.toBeInTheDocument() + await expect + .element(getByText('Passwords do not match', { exact: true })) + .not.toBeInTheDocument() await user.type(confirmPasswordInput, '1') - expect(await findByText('Passwords do not match')).toBeInTheDocument() + await expect + .element(getByText('Passwords do not match', { exact: true })) + .toBeInTheDocument() }) it('should handle deeply nested values in StrictMode', async () => { @@ -1130,14 +1179,16 @@ describe('useField', () => { ) } - const { queryByText, findByText } = render( + const { getByText } = await render( , ) - expect(queryByText('Test')).not.toBeInTheDocument() - expect(await findByText('User')).toBeInTheDocument() + await expect + .element(getByText('Test', { exact: true })) + .not.toBeInTheDocument() + await expect.element(getByText('User', { exact: true })).toBeInTheDocument() }) it('should validate async on submit without debounce', async () => { @@ -1188,12 +1239,11 @@ describe('useField', () => { ) } - const { getByRole, getByText } = render() + const { getByRole, getByText } = await render() await user.click(getByRole('button', { name: 'Submit' })) expect(mockFn).toHaveBeenCalledTimes(1) - await waitFor(() => getByText(error)) - expect(getByText(error)).toBeInTheDocument() + await expect.element(getByText(error, { exact: true })).toBeInTheDocument() }) it('should validate allow pushvalue to implicitly set a default value', async () => { @@ -1246,14 +1296,14 @@ describe('useField', () => { ) } - const { getByText, queryByText } = render( + const { getByText } = await render( , ) - expect(getByText('[]')).toBeInTheDocument() - await user.click(getByText('Add person')) - expect(getByText(`[""]`)).toBeInTheDocument() + await expect.element(getByText('[]', { exact: true })).toBeInTheDocument() + await user.click(getByText('Add person', { exact: true })) + await expect.element(getByText(`[""]`, { exact: true })).toBeInTheDocument() }) it('should validate allow pushvalue to implicitly set a pushed default value', async () => { @@ -1306,14 +1356,16 @@ describe('useField', () => { ) } - const { getByText, queryByText } = render( + const { getByText } = await render( , ) - expect(getByText('[]')).toBeInTheDocument() - await user.click(getByText('Add person')) - expect(getByText(`["Test"]`)).toBeInTheDocument() + await expect.element(getByText('[]', { exact: true })).toBeInTheDocument() + await user.click(getByText('Add person', { exact: true })) + await expect + .element(getByText(`["Test"]`, { exact: true })) + .toBeInTheDocument() }) it('should handle removing element from array', async () => { @@ -1384,7 +1436,7 @@ describe('useField', () => { ) } - const { getByText, queryByText, getByTestId } = render( + const { getByText, getByTestId } = await render( , @@ -1395,20 +1447,28 @@ describe('useField', () => { fakePeople.molly, fakePeople.george, ] - exisingPeople.forEach((person) => - expect(getByText(person.name)).toBeInTheDocument(), - ) + for (const person of exisingPeople) { + await expect + .element(getByText(person.name, { exact: true })) + .toBeInTheDocument() + } const container = getByTestId('container') - expect(within(container).getAllByRole('textbox')).toHaveLength(3) + expect(container.getByRole('textbox').elements()).toHaveLength(3) - await user.click(getByText('Remove person')) + await user.click(getByText('Remove person', { exact: true })) - expect(within(container).getAllByRole('textbox')).toHaveLength(2) + await expect + .poll(() => container.getByRole('textbox').elements()) + .toHaveLength(2) exisingPeople = [fakePeople.jack, fakePeople.george] - exisingPeople.forEach((person) => - expect(getByText(person.name)).toBeInTheDocument(), - ) - expect(queryByText(fakePeople.molly.name)).not.toBeInTheDocument() + for (const person of exisingPeople) { + await expect + .element(getByText(person.name, { exact: true })) + .toBeInTheDocument() + } + await expect + .element(getByText(fakePeople.molly.name, { exact: true })) + .not.toBeInTheDocument() }) it('should not rerender unrelated fields', async () => { @@ -1455,7 +1515,7 @@ describe('useField', () => { ) } - const { getByTestId } = render( + const { getByTestId } = await render( , @@ -1522,7 +1582,7 @@ describe('useField', () => { ) } - const { getByTestId } = render( + const { getByTestId } = await render( , @@ -1540,7 +1600,7 @@ describe('useField', () => { expect(renderCount.arrayField).toBe(arrayFieldInitialRender) // Verify typing still works - expect(getByTestId('person-0')).toHaveValue('Johnny') + await expect.element(getByTestId('person-0')).toHaveValue('Johnny') // Now add a new item - this SHOULD trigger array field re-render const arrayFieldBeforeAdd = renderCount.arrayField @@ -1589,21 +1649,21 @@ describe('useField', () => { ) } - const { getByTestId } = render( + const { getByTestId } = await render( , ) - expect(getByTestId('item-0')).toHaveTextContent('John') - expect(getByTestId('item-1')).toHaveTextContent('Jane') + await expect.element(getByTestId('item-0')).toHaveTextContent('John') + await expect.element(getByTestId('item-1')).toHaveTextContent('Jane') const before = renderCount.arrayField await user.click(getByTestId('swap')) expect(renderCount.arrayField).toBeGreaterThan(before) - expect(getByTestId('item-0')).toHaveTextContent('Jane') - expect(getByTestId('item-1')).toHaveTextContent('John') + await expect.element(getByTestId('item-0')).toHaveTextContent('Jane') + await expect.element(getByTestId('item-1')).toHaveTextContent('John') }) it('should rerender array field when async defaultValues resolve', async () => { @@ -1636,12 +1696,14 @@ describe('useField', () => { ) } - const { getByTestId, rerender } = render() - expect(getByTestId('list').children).toHaveLength(0) + const { getByTestId, rerender } = await render() + expect(getByTestId('list').element().children).toHaveLength(0) - rerender() - await waitFor(() => expect(getByTestId('list').children).toHaveLength(1)) - expect(getByTestId('item-0')).toHaveTextContent('Alice') + await rerender() + await expect + .poll(() => getByTestId('list').element().children) + .toHaveLength(1) + await expect.element(getByTestId('item-0')).toHaveTextContent('Alice') }) it('should handle defaultValue without setstate-in-render error', async () => { @@ -1684,7 +1746,7 @@ describe('useField', () => { ) } - const { getByTestId } = render() + const { getByTestId } = await render() await user.type(getByTestId('fieldOne'), 'John') // Should not log an error @@ -1709,9 +1771,11 @@ describe('useField', () => { ) } - const { queryByText } = render() + const { getByText } = await render() - expect(queryByText('a')).toBeInTheDocument() - expect(queryByText('never')).not.toBeInTheDocument() + await expect.element(getByText('a', { exact: true })).toBeInTheDocument() + await expect + .element(getByText('never', { exact: true })) + .not.toBeInTheDocument() }) }) diff --git a/packages/react-form/tests/useForm.test.tsx b/packages/react-form/tests/useForm.test.tsx index b71094cddc..9ce4639dc9 100644 --- a/packages/react-form/tests/useForm.test.tsx +++ b/packages/react-form/tests/useForm.test.tsx @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from 'vitest' -import { render, waitFor } from '@testing-library/react' -import { userEvent } from '@testing-library/user-event' +import { render } from 'vitest-browser-react' +import { userEvent } from 'vitest/browser' import { useStore } from '@tanstack/react-store' import { useCallback, useEffect, useReducer, useState } from 'react' import { mergeForm, useForm } from '../src/index' @@ -40,11 +40,13 @@ describe('useForm', () => { ) } - const { getByTestId, queryByText } = render() + const { getByTestId, getByText } = await render() const input = getByTestId('fieldinput') - expect(queryByText('FirstName')).not.toBeInTheDocument() + await expect + .element(getByText('FirstName', { exact: true })) + .not.toBeInTheDocument() await user.type(input, 'FirstName') - expect(input).toHaveValue('FirstName') + await expect.element(input).toHaveValue('FirstName') }) it('should allow default values to be set', async () => { @@ -73,9 +75,13 @@ describe('useForm', () => { ) } - const { findByText, queryByText } = render() - expect(await findByText('FirstName')).toBeInTheDocument() - expect(queryByText('LastName')).not.toBeInTheDocument() + const { getByText } = await render() + await expect + .element(getByText('FirstName', { exact: true })) + .toBeInTheDocument() + await expect + .element(getByText('LastName', { exact: true })) + .not.toBeInTheDocument() }) it('should handle submitting properly', async () => { @@ -114,14 +120,14 @@ describe('useForm', () => { ) } - const { findByPlaceholderText, getByText } = render() - const input = await findByPlaceholderText('First name') + const { getByPlaceholder, getByText } = await render() + const input = getByPlaceholder('First name') await user.clear(input) await user.type(input, 'OtherName') - await user.click(getByText('Submit')) - await waitFor(() => - expect(getByText('Submitted data: OtherName')).toBeInTheDocument(), - ) + await user.click(getByText('Submit', { exact: true })) + await expect + .element(getByText('Submitted data: OtherName', { exact: true })) + .toBeInTheDocument() }) it('should run on form mount', async () => { @@ -154,9 +160,11 @@ describe('useForm', () => { ) } - const { getByText } = render() - await user.click(getByText('Mount form')) - await waitFor(() => expect(getByText('Form mounted')).toBeInTheDocument()) + const { getByText } = await render() + await user.click(getByText('Mount form', { exact: true })) + await expect + .element(getByText('Form mounted', { exact: true })) + .toBeInTheDocument() }) it('should validate async on change for the form', async () => { @@ -198,12 +206,13 @@ describe('useForm', () => { ) } - const { getByTestId, getByText, queryByText } = render() + const { getByTestId, getByText } = await render() const input = getByTestId('fieldinput') - expect(queryByText(error)).not.toBeInTheDocument() + await expect + .element(getByText(error, { exact: true })) + .not.toBeInTheDocument() await user.type(input, 'other') - await waitFor(() => getByText(error)) - expect(getByText(error)).toBeInTheDocument() + await expect.element(getByText(error, { exact: true })).toBeInTheDocument() }) it('should not validate on change if isTouched is false', async () => { @@ -251,10 +260,12 @@ describe('useForm', () => { ) } - const { getByTestId, queryByText } = render() + const { getByTestId, getByText } = await render() const input = getByTestId('fieldinput') await user.type(input, 'other') - expect(queryByText(error)).not.toBeInTheDocument() + await expect + .element(getByText(error, { exact: true })) + .not.toBeInTheDocument() }) it('should validate on change if isTouched is true', async () => { @@ -298,11 +309,13 @@ describe('useForm', () => { ) } - const { getByTestId, getByText, queryByText } = render() + const { getByTestId, getByText } = await render() const input = getByTestId('fieldinput') - expect(queryByText(error)).not.toBeInTheDocument() + await expect + .element(getByText(error, { exact: true })) + .not.toBeInTheDocument() await user.type(input, 'other') - expect(getByText(error)).toBeInTheDocument() + await expect.element(getByText(error, { exact: true })).toBeInTheDocument() }) it('should validate on change and on blur', async () => { @@ -349,14 +362,22 @@ describe('useForm', () => { ) } - const { getByTestId, getByText, queryByText } = render() + const { getByTestId, getByText } = await render() const input = getByTestId('fieldinput') - expect(queryByText(onChangeError)).not.toBeInTheDocument() - expect(queryByText(onBlurError)).not.toBeInTheDocument() + await expect + .element(getByText(onChangeError, { exact: true })) + .not.toBeInTheDocument() + await expect + .element(getByText(onBlurError, { exact: true })) + .not.toBeInTheDocument() await user.type(input, 'other') - expect(getByText(onChangeError)).toBeInTheDocument() + await expect + .element(getByText(onChangeError, { exact: true })) + .toBeInTheDocument() await user.click(document.body) - expect(queryByText(onBlurError)).toBeInTheDocument() + await expect + .element(getByText(onBlurError, { exact: true })) + .toBeInTheDocument() }) it("should set field errors from the field's validators", async () => { @@ -410,24 +431,24 @@ describe('useForm', () => { ) } - const { getByTestId } = render() + const { getByTestId } = await render() - expect(getByTestId('form-onchange')).toHaveTextContent('') + await expect.element(getByTestId('form-onchange')).toHaveTextContent('') const input = getByTestId('fieldinput') await user.type(input, 'other') - expect(getByTestId('form-onchange')).toHaveTextContent( - 'Something went wrong', - ) - expect(getByTestId('field-onchange')).toHaveTextContent( - 'Please enter a different value (onChangeError)', - ) + await expect + .element(getByTestId('form-onchange')) + .toHaveTextContent('Something went wrong') + await expect + .element(getByTestId('field-onchange')) + .toHaveTextContent('Please enter a different value (onChangeError)') await user.click(document.body) - expect(getByTestId('form-onblur')).toHaveTextContent( - 'Please enter a different value (onBlurError)', - ) + await expect + .element(getByTestId('form-onblur')) + .toHaveTextContent('Please enter a different value (onBlurError)') }) it('should validate async on change', async () => { @@ -473,12 +494,13 @@ describe('useForm', () => { ) } - const { getByTestId, getByText, queryByText } = render() + const { getByTestId, getByText } = await render() const input = getByTestId('fieldinput') - expect(queryByText(error)).not.toBeInTheDocument() + await expect + .element(getByText(error, { exact: true })) + .not.toBeInTheDocument() await user.type(input, 'other') - await waitFor(() => getByText(error)) - expect(getByText(error)).toBeInTheDocument() + await expect.element(getByText(error, { exact: true })).toBeInTheDocument() }) it("should set field errors from the form's onChangeAsync validator", async () => { @@ -531,23 +553,23 @@ describe('useForm', () => { ) } - const { getByTestId } = render() + const { getByTestId } = await render() const input = getByTestId('fieldinput') const firstNameErrorElement = getByTestId('field-error') const formErrorElement = getByTestId('form-error') - expect(firstNameErrorElement).toBeEmptyDOMElement() - expect(formErrorElement).toBeEmptyDOMElement() + await expect.element(firstNameErrorElement).toBeEmptyDOMElement() + await expect.element(formErrorElement).toBeEmptyDOMElement() await user.type(input, 'other') - await waitFor(() => { - expect(firstNameErrorElement).not.toBeEmptyDOMElement() - }) - expect(firstNameErrorElement).toHaveTextContent( - 'First name cannot be "other"', - ) - expect(formErrorElement).toHaveTextContent('Invalid form values') + await expect.element(firstNameErrorElement).not.toBeEmptyDOMElement() + await expect + .element(firstNameErrorElement) + .toHaveTextContent('First name cannot be "other"') + await expect + .element(formErrorElement) + .toHaveTextContent('Invalid form values') }) it('should validate async on change and async on blur', async () => { @@ -600,17 +622,23 @@ describe('useForm', () => { ) } - const { getByTestId, getByText, queryByText } = render() + const { getByTestId, getByText } = await render() const input = getByTestId('fieldinput') - expect(queryByText(onChangeError)).not.toBeInTheDocument() - expect(queryByText(onBlurError)).not.toBeInTheDocument() + await expect + .element(getByText(onChangeError, { exact: true })) + .not.toBeInTheDocument() + await expect + .element(getByText(onBlurError, { exact: true })) + .not.toBeInTheDocument() await user.type(input, 'other') - await waitFor(() => getByText(onChangeError)) - expect(getByText(onChangeError)).toBeInTheDocument() + await expect + .element(getByText(onChangeError, { exact: true })) + .toBeInTheDocument() await user.click(document.body) - await waitFor(() => getByText(onBlurError)) - expect(getByText(onBlurError)).toBeInTheDocument() + await expect + .element(getByText(onBlurError, { exact: true })) + .toBeInTheDocument() }) it('should validate async on change with debounce', async () => { @@ -660,13 +688,12 @@ describe('useForm', () => { ) } - const { getByTestId, getByText } = render() + const { getByTestId, getByText } = await render() const input = getByTestId('fieldinput') await user.type(input, 'other') // mockFn will have been called 5 times without onChangeAsyncDebounceMs expect(mockFn).toHaveBeenCalledTimes(0) - await waitFor(() => getByText(error)) - expect(getByText(error)).toBeInTheDocument() + await expect.element(getByText(error, { exact: true })).toBeInTheDocument() }) it("should set errors on the fields from the form's onChange validator", async () => { @@ -716,26 +743,28 @@ describe('useForm', () => { ) } - const { getByTestId, queryByText } = render() + const { getByTestId, getByText } = await render() const input = getByTestId('fieldinput') const fieldError = getByTestId('field-error') const formError = getByTestId('form-error') - expect( - queryByText('Please enter a different value'), - ).not.toBeInTheDocument() - expect(queryByText('Something went wrong')).not.toBeInTheDocument() + await expect + .element(getByText('Please enter a different value', { exact: true })) + .not.toBeInTheDocument() + await expect + .element(getByText('Something went wrong', { exact: true })) + .not.toBeInTheDocument() await user.type(input, 'Tom') - await waitFor(() => - expect(fieldError.textContent).toBe('Please enter a different value'), - ) - await waitFor(() => - expect(formError.textContent).toBe('Something went wrong'), - ) + await expect + .poll(() => fieldError.element().textContent) + .toBe('Please enter a different value') + await expect + .poll(() => formError.element().textContent) + .toBe('Something went wrong') }) - it('should not cause infinite re-renders when listening to state.errors', () => { + it('should not cause infinite re-renders when listening to state.errors', async () => { const fn = vi.fn() function Comp() { @@ -761,12 +790,12 @@ describe('useForm', () => { return null } - render() + await render() expect(fn).toHaveBeenCalledTimes(1) }) - it('should not cause infinite re-renders when listening to state', () => { + it('should not cause infinite re-renders when listening to state', async () => { const fn = vi.fn() function Comp() { @@ -790,7 +819,7 @@ describe('useForm', () => { return null } - render() + await render() expect(fn).toHaveBeenCalledTimes(1) }) @@ -839,20 +868,20 @@ describe('useForm', () => { ) } - const { getByTestId } = render() + const { getByTestId } = await render() const input = getByTestId('fieldinput') const submit = getByTestId('submit') const reset = getByTestId('reset') await user.type(input, 'test') - await waitFor(() => expect(input).toHaveValue('test')) + await expect.element(input).toHaveValue('test') await user.click(reset) - await waitFor(() => expect(input).toHaveValue('')) + await expect.element(input).toHaveValue('') await user.type(input, 'another-test') await user.click(submit) - await waitFor(() => expect(input).toHaveValue('another-test')) + await expect.element(input).toHaveValue('another-test') }) it('should accept formId and return it', async () => { @@ -885,12 +914,12 @@ describe('useForm', () => { ) } - const { getByTestId } = render() + const { getByTestId } = await render() const target = getByTestId('formId-target') const result = getByTestId('formId-result') await user.click(target) - expect(result).toHaveTextContent('1') + await expect.element(result).toHaveTextContent('1') }) it('should allow custom component keys for arrays', async () => { @@ -932,7 +961,7 @@ describe('useForm', () => { ) } - const { getByTestId } = render() + const { getByTestId } = await render() const target = getByTestId('removeField') await user.click(target) @@ -996,7 +1025,7 @@ describe('useForm', () => { ) } - const { getByTestId } = render() + const { getByTestId } = await render() const removeButton = getByTestId('remove') const input = getByTestId('input') @@ -1034,8 +1063,10 @@ describe('useForm', () => { ) } - const { getByText } = render() - getByText('What 1') + const { getByText } = await render() + await expect + .element(getByText('What 1', { exact: true })) + .toBeInTheDocument() }) it('should handle stable transforms to update the base form on subsequent renders', async () => { @@ -1070,10 +1101,14 @@ describe('useForm', () => { ) } - const { findByText, getByText } = render() - await findByText('First') - await user.click(getByText('Rerender')) - await findByText('Another') + const { getByText } = await render() + await expect + .element(getByText('First', { exact: true })) + .toBeInTheDocument() + await user.click(getByText('Rerender', { exact: true })) + await expect + .element(getByText('Another', { exact: true })) + .toBeInTheDocument() }) it('should render Subscribe without selector (default identity)', async () => { @@ -1106,14 +1141,14 @@ describe('useForm', () => { ) } - const { getByTestId } = render() + const { getByTestId } = await render() const input = getByTestId('input') const stateValue = getByTestId('state-value') - expect(stateValue).toHaveTextContent('test') + await expect.element(stateValue).toHaveTextContent('test') await user.clear(input) await user.type(input, 'updated') - await waitFor(() => expect(stateValue).toHaveTextContent('updated')) + await expect.element(stateValue).toHaveTextContent('updated') }) }) diff --git a/packages/react-form/tests/useFormGroup.test.tsx b/packages/react-form/tests/useFormGroup.test.tsx index d8eabf34f3..82f3c73cba 100644 --- a/packages/react-form/tests/useFormGroup.test.tsx +++ b/packages/react-form/tests/useFormGroup.test.tsx @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from 'vitest' -import { render, waitFor } from '@testing-library/react' -import { userEvent } from '@testing-library/user-event' +import { render } from 'vitest-browser-react' +import { userEvent } from 'vitest/browser' import { useForm } from '../src/index' const user = userEvent.setup() @@ -48,10 +48,10 @@ describe('form.FormGroup', () => { ) } - const { getByTestId } = render() + const { getByTestId } = await render() await user.click(getByTestId('submit-group')) - await waitFor(() => expect(onGroupSubmit).toHaveBeenCalledTimes(1)) + await vi.waitFor(() => expect(onGroupSubmit).toHaveBeenCalledTimes(1)) expect(onSubmit).not.toHaveBeenCalled() }) @@ -87,15 +87,17 @@ describe('form.FormGroup', () => { ) } - const { getByTestId } = render() - expect(getByTestId('group-value').textContent).toBe('{"name":"initial"}') + const { getByTestId } = await render() + expect(getByTestId('group-value').element().textContent).toBe( + '{"name":"initial"}', + ) await user.clear(getByTestId('step1-name')) await user.type(getByTestId('step1-name'), 'updated') - await waitFor(() => - expect(getByTestId('group-value').textContent).toBe('{"name":"updated"}'), - ) + await expect + .poll(() => getByTestId('group-value').element().textContent) + .toBe('{"name":"updated"}') }) it('should call onGroupSubmitInvalid when group-level validation fails', async () => { @@ -152,15 +154,17 @@ describe('form.FormGroup', () => { ) } - const { getByTestId } = render() + const { getByTestId } = await render() await user.click(getByTestId('submit-group')) - await waitFor(() => expect(onGroupSubmitInvalid).toHaveBeenCalledTimes(1)) + await vi.waitFor(() => + expect(onGroupSubmitInvalid).toHaveBeenCalledTimes(1), + ) expect(onGroupSubmit).not.toHaveBeenCalled() expect(onSubmit).not.toHaveBeenCalled() - await waitFor(() => - expect(getByTestId('group-error').textContent).toBe('Name is required'), - ) + await expect + .poll(() => getByTestId('group-error').element().textContent) + .toBe('Name is required') }) it('should ignore form-level field errors outside the group when submitting the group', async () => { @@ -215,10 +219,10 @@ describe('form.FormGroup', () => { ) } - const { getByTestId } = render() + const { getByTestId } = await render() await user.click(getByTestId('submit-group')) - await waitFor(() => expect(onGroupSubmit).toHaveBeenCalledTimes(1)) + await vi.waitFor(() => expect(onGroupSubmit).toHaveBeenCalledTimes(1)) expect(onGroupSubmitInvalid).not.toHaveBeenCalled() }) @@ -252,10 +256,10 @@ describe('form.FormGroup', () => { ) } - const { getByTestId } = render() + const { getByTestId } = await render() await user.click(getByTestId('submit-group')) - await waitFor(() => expect(onGroupSubmit).toHaveBeenCalledTimes(1)) + await vi.waitFor(() => expect(onGroupSubmit).toHaveBeenCalledTimes(1)) expect(onGroupSubmit).toHaveBeenCalledWith( expect.objectContaining({ value: { name: 'test' }, @@ -304,20 +308,20 @@ describe('form.FormGroup', () => { ) } - const { getByTestId } = render() - const button = getByTestId('submit-group') as HTMLButtonElement - expect(button.textContent).toBe('Continue') - expect(button.disabled).toBe(false) + const { getByTestId } = await render() + const button = getByTestId('submit-group') + await expect.element(button).toHaveTextContent('Continue') + await expect.element(button).toBeEnabled() await user.click(button) - await waitFor(() => expect(button.textContent).toBe('Saving...')) - expect(button.disabled).toBe(true) + await expect.element(button).toHaveTextContent('Saving...') + await expect.element(button).toBeDisabled() resolveSubmit() - await waitFor(() => expect(button.textContent).toBe('Continue')) - expect(button.disabled).toBe(false) + await expect.element(button).toHaveTextContent('Continue') + await expect.element(button).toBeEnabled() expect(onGroupSubmit).toHaveBeenCalledTimes(1) }) }) diff --git a/packages/react-form/tsconfig.json b/packages/react-form/tsconfig.json index f65fd3d402..99e8c0b59a 100644 --- a/packages/react-form/tsconfig.json +++ b/packages/react-form/tsconfig.json @@ -3,6 +3,7 @@ "compilerOptions": { "jsx": "react-jsx", "moduleResolution": "Bundler", + "types": ["vitest/browser"], "paths": { "@tanstack/form-core": ["../form-core/src"] } diff --git a/packages/react-form/vite.config.ts b/packages/react-form/vite.config.ts index 8e68669a0c..23949c95ac 100644 --- a/packages/react-form/vite.config.ts +++ b/packages/react-form/vite.config.ts @@ -1,6 +1,7 @@ import { defineConfig, mergeConfig } from 'vitest/config' import { tanstackViteConfig } from '@tanstack/vite-config' import react from '@vitejs/plugin-react' +import { playwright } from '@vitest/browser-playwright' import packageJson from './package.json' const config = defineConfig({ @@ -9,8 +10,14 @@ const config = defineConfig({ name: packageJson.name, dir: './tests', watch: false, - environment: 'jsdom', - setupFiles: ['./tests/test-setup.ts'], + setupFiles: ['vitest-browser-react'], + browser: { + enabled: true, + provider: playwright( + process.env.CI ? { launchOptions: { channel: 'chrome' } } : {}, + ), + instances: [{ browser: 'chromium', headless: true }], + }, coverage: { enabled: true, provider: 'istanbul', include: ['src/**/*'] }, typecheck: { enabled: true }, }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8932ccbf97..cad7b4c986 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -193,9 +193,6 @@ importers: '@testing-library/jest-dom': specifier: ^6.8.0 version: 6.9.1 - '@testing-library/react': - specifier: ^16.3.0 - version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@testing-library/user-event': specifier: ^14.6.1 version: 14.6.1(@testing-library/dom@10.4.1) @@ -264,7 +261,7 @@ importers: version: 7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0) vitest: specifier: ^5.0.5 - version: 5.0.5(@types/node@24.12.4)(@vitest/coverage-istanbul@5.0.5)(jsdom@27.4.0(supports-color@7.2.0))(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0)) + version: 5.0.5(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-istanbul@5.0.5)(@vitest/ui@5.0.5)(jsdom@27.4.0(supports-color@7.2.0))(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0)) examples/angular/array: dependencies: @@ -1906,6 +1903,9 @@ importers: '@vitejs/plugin-react': specifier: ^5.1.1 version: 5.2.0(supports-color@8.1.1)(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0)) + '@vitest/browser-playwright': + specifier: ^5.0.5 + version: 5.0.5(playwright@1.64.1)(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0))(vitest@5.0.5) eslint-plugin-react-compiler: specifier: 19.1.0-rc.2 version: 19.1.0-rc.2(eslint@9.36.0(jiti@2.7.0)(supports-color@8.1.1))(supports-color@8.1.1) @@ -1918,6 +1918,9 @@ importers: vite: specifier: ^7.2.2 version: 7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0) + vitest-browser-react: + specifier: ^2.3.0 + version: 2.3.0(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vitest@5.0.5) packages/react-form-devtools: dependencies: @@ -3262,6 +3265,9 @@ packages: resolution: {integrity: sha512-iXSSZAKQWmaeAwGtFw10v9fg5hUiUs0L+w+Z2K11kym3pDNK+SF0VcAQPsEFvWcTSZ7LMgClMXDBvTbH0IlICg==} engines: {node: ^22.18.0 || >=24.11.0} + '@blazediff/core@1.10.0': + resolution: {integrity: sha512-AOQff0zgR7cGsZL+4E7hVkmujoPUpm0J9xzWGWZj5wCjd3gmxESXAPfKyuzs93VdpQNFhHlBhfOjrcZ+XTERtQ==} + '@changesets/apply-release-plan@8.1.1': resolution: {integrity: sha512-nk2Hbq3M9NeHMdaIRBDvawKrQJzXB9sGyBUE5yWnIKtvdcNCPj75zBekJYr0gTT2ecJH3eFeTrc8eisPImcv0Q==} engines: {node: ^22.11 || ^24 || >=26} @@ -5966,6 +5972,9 @@ packages: resolution: {integrity: sha512-pOr5+q1fLYKwFN3LAJuGZEnfXDcQ73zqgDHMtGy+K+uIoUqyY+6MeDCWFwfu+4EFuq76I5EPFofoNAI+Bmmq4A==} engines: {node: '>=22.13'} + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + '@popperjs/core@2.11.8': resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} @@ -7326,21 +7335,6 @@ packages: peerDependencies: preact: '>=10 || ^10.0.0-alpha.0 || ^10.0.0-beta.0' - '@testing-library/react@16.3.2': - resolution: {integrity: sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==} - 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 - '@testing-library/svelte-core@1.0.0': resolution: {integrity: sha512-VkUePoLV6oOYwSUvX6ShA8KLnJqZiYMIbP2JW2t0GLWLkJxKGvuH5qrrZBV/X7cXFnLGuFQEC7RheYiZOW68KQ==} engines: {node: '>=16'} @@ -7809,6 +7803,17 @@ packages: vite: ^5.0.0 || ^6.0.0 vue: ^3.2.25 + '@vitest/browser-playwright@5.0.5': + resolution: {integrity: sha512-lcdThFUUcMV9mGGV1SUiZXPxfjgW4CssA7o3STxzh8WK0tT2r526kc12tZIBRZfoLMwkhxjLmMXkWXONzdsDQw==} + peerDependencies: + playwright: '*' + vitest: 5.0.5 + + '@vitest/browser@5.0.5': + resolution: {integrity: sha512-IDMoZX/iZ8rkilw7weaSgN8tJ7iZ9hUv0wYoKXyiyjWjWFwLSWAY707DcVTgxTYOudWC1E3HA9LYn7kVa9a7uQ==} + peerDependencies: + vitest: 5.0.5 + '@vitest/coverage-istanbul@5.0.5': resolution: {integrity: sha512-nmbE0+e1JSH9kYEoHGp/iKCQq6+TqwvQ4EcH2zKPULOIrY7rPHxDHsipYGGr+0xMDdzCy46F8zj3edz8dsqvdg==} peerDependencies: @@ -7841,9 +7846,20 @@ packages: vite: optional: true + '@vitest/pretty-format@5.0.5': + resolution: {integrity: sha512-24sSdIlMA9DVPXnYiB9siwU0bTd887Y9jX3+vabztCSsvHwo7ipvJ/a2L3Rljw8+2rK1f5fCj3h5U9I/Jsqgbw==} + '@vitest/spy@5.0.5': resolution: {integrity: sha512-lwJRExuH417257Q5wv9QYugN9IMJkoxcrKrYxGPQkBzl7wlNpTUxD/5yNX4ESKVUIShKPgkGchYPFMWOOk0wzw==} + '@vitest/ui@5.0.5': + resolution: {integrity: sha512-dCoGUOdgmCaSB2g+WQ/spPCBkytAWdNUkFSXY4z+eYp2d8JEOY8v0uDRaJTG9qf/kTlTVZ4CuYMYMz/f1HS/qQ==} + peerDependencies: + vitest: 5.0.5 + + '@vitest/utils@5.0.5': + resolution: {integrity: sha512-/rBu7gzLDQI/SC79Tn98b5DwOgaHU/Gt4I9OVeP3YofzNaYtbImRCOJQmT1S6vOJg9gEIlWC+r3QxKkjuSa1nQ==} + '@volar/language-core@2.4.15': resolution: {integrity: sha512-3VHw+QZU0ZG9IuQmzT68IyN4hZNd9GchGPhbD9+pa8CVv7rnoOZwo7T8weIbrRmihqy3ATpdfXFnqRrfPVK6CA==} @@ -9934,6 +9950,9 @@ packages: fetchdts@0.1.7: resolution: {integrity: sha512-YoZjBdafyLIop9lSxXVI33oLD5kN31q4Td+CasofLLYeLXRFeOsuOw0Uo+XNRi9PZlbfdlN2GmRtm4tCEQ9/KA==} + fflate@0.8.3: + resolution: {integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==} + figures@3.2.0: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} engines: {node: '>=8'} @@ -9992,6 +10011,9 @@ packages: flatted@3.4.2: resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + flatted@3.4.4: + resolution: {integrity: sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==} + flow-enums-runtime@0.0.6: resolution: {integrity: sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==} @@ -12332,6 +12354,16 @@ packages: resolution: {integrity: sha512-emEcLuomt2j03vxD54giVB4SxTjnsqkU692xZOZXHDVoYyypEm+b3jpiTcc+Cf+myooc+/Ly0z01jqeNHVgJGw==} engines: {node: '>=16.0.0'} + playwright-core@1.64.1: + resolution: {integrity: sha512-BC4s8y6SEVA6Q1rSxZTto8hI1KUI3xXlHGZgvGqP4IqPdI/a6FX9H29IDNz7vKo9l21b6LfYosScPL6M3xxyDQ==} + engines: {node: '>=20'} + hasBin: true + + playwright@1.64.1: + resolution: {integrity: sha512-HCrVhHr5QQuwYoswEUGTb7gGp522yflbv/ST8nF4sFWGuLjQCt17Q/EubpGGSNKYn1tsTCPZxqHhHCfVURu7UA==} + engines: {node: '>=20'} + hasBin: true + plist@3.1.1: resolution: {integrity: sha512-ZIfcLJC+7E7FBFnDxm9MPmt7D+DidyQ26lewieO75AdhA2ayMtsJSES0iWzqJQbcVRSrTufQoy0DR94xHue0oA==} engines: {node: '>=10.4.0'} @@ -12340,6 +12372,10 @@ packages: resolution: {integrity: sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==} engines: {node: '>=4.0.0'} + pngjs@7.0.0: + resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==} + engines: {node: '>=14.19.0'} + possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} @@ -13343,6 +13379,10 @@ packages: simple-swizzle@0.2.4: resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==} + sirv@3.0.2: + resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} + engines: {node: '>=18'} + sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} @@ -13851,6 +13891,10 @@ packages: toml@3.0.0: resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==} + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + tough-cookie@6.0.1: resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==} engines: {node: '>=16'} @@ -14510,6 +14554,20 @@ packages: vite: optional: true + vitest-browser-react@2.3.0: + resolution: {integrity: sha512-S5vdGHHA93j2Z6dbmIJI1wtxT3AXPTRlmC1Meo+ekG011yNnEutMAnHrwyj8qFlGhgBYA8ENGK6+o1Q+vnFz6A==} + peerDependencies: + '@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 + vitest: ^4.0.0 || ^5.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + vitest@5.0.5: resolution: {integrity: sha512-cPODOMF8YnkiiYtDdHo6n4wWmBrOBBbOIYH2d33M4l3CdNn72h+qMmO6uXffUN6A5AZFXNND/d+krS9mgkXRfg==} engines: {node: ^22.12.0 || ^24.0.0 || >=26.0.0} @@ -14828,6 +14886,18 @@ packages: utf-8-validate: optional: true + ws@8.22.0: + resolution: {integrity: sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==} + 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 + wsl-utils@0.1.0: resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} engines: {node: '>=18'} @@ -15071,7 +15141,7 @@ snapshots: '@analogjs/vite-plugin-angular': 2.9.2(81ea192f493565263f8b896c3e692134) '@angular-devkit/architect': 0.2102.12(chokidar@5.0.0) '@angular-devkit/schematics': 21.2.12(chokidar@5.0.0) - vitest: 5.0.5(@types/node@24.12.4)(@vitest/coverage-istanbul@5.0.5)(jsdom@27.4.0(supports-color@8.1.1))(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0)) + vitest: 5.0.5(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-istanbul@5.0.5)(@vitest/ui@5.0.5)(jsdom@27.4.0(supports-color@8.1.1))(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0)) optionalDependencies: zone.js: 0.15.1 @@ -15548,7 +15618,7 @@ snapshots: lmdb: 3.5.1 ng-packagr: 21.2.3(@angular/compiler-cli@21.2.14(@angular/compiler@21.2.14)(supports-color@8.1.1)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3) postcss: 8.5.12 - vitest: 5.0.5(@types/node@24.12.4)(@vitest/coverage-istanbul@5.0.5)(jsdom@27.4.0(supports-color@8.1.1))(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0)) + vitest: 5.0.5(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-istanbul@5.0.5)(@vitest/ui@5.0.5)(jsdom@27.4.0(supports-color@8.1.1))(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0)) transitivePeerDependencies: - '@emnapi/core' - '@emnapi/runtime' @@ -15605,7 +15675,7 @@ snapshots: lmdb: 3.5.1 ng-packagr: 21.2.3(@angular/compiler-cli@21.2.14(@angular/compiler@21.2.14)(supports-color@8.1.1)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3) postcss: 8.5.12 - vitest: 5.0.5(@types/node@24.12.4)(@vitest/coverage-istanbul@5.0.5)(jsdom@27.4.0(supports-color@7.2.0))(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0)) + vitest: 5.0.5(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-istanbul@5.0.5)(@vitest/ui@5.0.5)(jsdom@27.4.0(supports-color@7.2.0))(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0)) transitivePeerDependencies: - '@emnapi/core' - '@emnapi/runtime' @@ -15661,7 +15731,7 @@ snapshots: lmdb: 3.5.1 ng-packagr: 21.2.3(@angular/compiler-cli@21.2.14(@angular/compiler@21.2.14)(supports-color@8.1.1)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3) postcss: 8.5.12 - vitest: 5.0.5(@types/node@24.12.4)(@vitest/coverage-istanbul@5.0.5)(jsdom@27.4.0(supports-color@7.2.0))(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0)) + vitest: 5.0.5(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-istanbul@5.0.5)(@vitest/ui@5.0.5)(jsdom@27.4.0(supports-color@7.2.0))(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0)) transitivePeerDependencies: - '@emnapi/core' - '@emnapi/runtime' @@ -15717,7 +15787,7 @@ snapshots: lmdb: 3.5.1 ng-packagr: 21.2.3(@angular/compiler-cli@21.2.14(@angular/compiler@21.2.14)(supports-color@8.1.1)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3) postcss: 8.5.12 - vitest: 5.0.5(@types/node@24.12.4)(@vitest/coverage-istanbul@5.0.5)(jsdom@27.4.0(supports-color@7.2.0))(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0)) + vitest: 5.0.5(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-istanbul@5.0.5)(@vitest/ui@5.0.5)(jsdom@27.4.0(supports-color@7.2.0))(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0)) transitivePeerDependencies: - '@emnapi/core' - '@emnapi/runtime' @@ -15773,7 +15843,7 @@ snapshots: lmdb: 3.5.1 ng-packagr: 21.2.3(@angular/compiler-cli@21.2.14(@angular/compiler@21.2.14)(supports-color@8.1.1)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3) postcss: 8.5.15 - vitest: 5.0.5(@types/node@24.12.4)(@vitest/coverage-istanbul@5.0.5)(jsdom@27.4.0(supports-color@8.1.1))(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0)) + vitest: 5.0.5(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-istanbul@5.0.5)(@vitest/ui@5.0.5)(jsdom@27.4.0(supports-color@8.1.1))(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0)) transitivePeerDependencies: - '@emnapi/core' - '@emnapi/runtime' @@ -17228,6 +17298,8 @@ snapshots: '@babel/helper-string-parser': 8.0.8 '@babel/helper-validator-identifier': 8.0.6 + '@blazediff/core@1.10.0': {} + '@changesets/apply-release-plan@8.1.1': dependencies: '@changesets/config': 4.0.1 @@ -19772,6 +19844,8 @@ snapshots: '@pnpm/deps.graph-sequencer@1100.0.1': {} + '@polka/url@1.0.0-next.29': {} + '@popperjs/core@2.11.8': {} '@preact/preset-vite@2.10.5(@babel/core@7.29.7(supports-color@8.1.1))(preact@10.29.2)(rollup@4.60.4)(supports-color@8.1.1)(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0))': @@ -21273,16 +21347,6 @@ snapshots: '@testing-library/dom': 8.20.1 preact: 10.29.2 - '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': - dependencies: - '@babel/runtime': 7.29.7 - '@testing-library/dom': 10.4.1 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@testing-library/svelte-core@1.0.0(svelte@5.55.9(@typescript-eslint/types@8.59.4))': dependencies: svelte: 5.55.9(@typescript-eslint/types@8.59.4) @@ -21294,7 +21358,7 @@ snapshots: svelte: 5.55.9(@typescript-eslint/types@8.59.4) optionalDependencies: vite: 7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0) - vitest: 5.0.5(@types/node@24.12.4)(@vitest/coverage-istanbul@5.0.5)(jsdom@27.4.0(supports-color@8.1.1))(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0)) + vitest: 5.0.5(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-istanbul@5.0.5)(@vitest/ui@5.0.5)(jsdom@27.4.0(supports-color@8.1.1))(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0)) '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)': dependencies: @@ -21900,6 +21964,37 @@ snapshots: vite: 7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0) vue: 3.5.34(typescript@5.9.3) + '@vitest/browser-playwright@5.0.5(playwright@1.64.1)(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0))(vitest@5.0.5)': + dependencies: + '@vitest/browser': 5.0.5(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0))(vitest@5.0.5) + '@vitest/mocker': 5.0.5(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0)) + playwright: 1.64.1 + tinyrainbow: 3.2.0 + vitest: 5.0.5(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-istanbul@5.0.5)(@vitest/ui@5.0.5)(jsdom@27.4.0(supports-color@7.2.0))(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0)) + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite + + '@vitest/browser@5.0.5(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0))(vitest@5.0.5)': + dependencies: + '@blazediff/core': 1.10.0 + '@vitest/mocker': 5.0.5(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0)) + '@vitest/ui': 5.0.5(vitest@5.0.5) + '@vitest/utils': 5.0.5 + magic-string: 1.4.5 + pngjs: 7.0.0 + sirv: 3.0.2 + tinyrainbow: 3.2.0 + vitest: 5.0.5(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-istanbul@5.0.5)(@vitest/ui@5.0.5)(jsdom@27.4.0(supports-color@7.2.0))(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0)) + ws: 8.22.0 + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite + '@vitest/coverage-istanbul@5.0.5(vitest@5.0.5)': dependencies: '@jridgewell/gen-mapping': 0.3.13 @@ -21911,7 +22006,7 @@ snapshots: magicast: 0.5.5 obug: 3.0.0 tinyrainbow: 3.2.0 - vitest: 5.0.5(@types/node@24.12.4)(@vitest/coverage-istanbul@5.0.5)(jsdom@27.4.0(supports-color@7.2.0))(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0)) + vitest: 5.0.5(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-istanbul@5.0.5)(@vitest/ui@5.0.5)(jsdom@27.4.0(supports-color@7.2.0))(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0)) '@vitest/istanbul-lib-coverage@1.0.2': {} @@ -21942,8 +22037,28 @@ snapshots: optionalDependencies: vite: 7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0) + '@vitest/pretty-format@5.0.5': + dependencies: + tinyrainbow: 3.2.0 + '@vitest/spy@5.0.5': {} + '@vitest/ui@5.0.5(vitest@5.0.5)': + dependencies: + '@vitest/utils': 5.0.5 + fflate: 0.8.3 + flatted: 3.4.4 + pathe: 2.0.3 + sirv: 3.0.2 + tinyrainbow: 3.2.0 + vitest: 5.0.5(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-istanbul@5.0.5)(@vitest/ui@5.0.5)(jsdom@27.4.0(supports-color@7.2.0))(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0)) + + '@vitest/utils@5.0.5': + dependencies: + '@vitest/pretty-format': 5.0.5 + convert-source-map: 2.0.0 + tinyrainbow: 3.2.0 + '@volar/language-core@2.4.15': dependencies: '@volar/source-map': 2.4.15 @@ -24767,6 +24882,8 @@ snapshots: fetchdts@0.1.7: {} + fflate@0.8.3: {} + figures@3.2.0: dependencies: escape-string-regexp: 1.0.5 @@ -24844,6 +24961,8 @@ snapshots: flatted@3.4.2: {} + flatted@3.4.4: {} + flow-enums-runtime@0.0.6: {} flru@1.0.2: {} @@ -28001,6 +28120,12 @@ snapshots: pvutils: 1.1.5 tslib: 2.8.1 + playwright-core@1.64.1: {} + + playwright@1.64.1: + dependencies: + playwright-core: 1.64.1 + plist@3.1.1: dependencies: '@xmldom/xmldom': 0.9.10 @@ -28009,6 +28134,8 @@ snapshots: pngjs@3.4.0: {} + pngjs@7.0.0: {} + possible-typed-array-names@1.1.0: {} postcss-discard-duplicates@5.1.0(postcss@8.5.6): @@ -29217,6 +29344,12 @@ snapshots: dependencies: is-arrayish: 0.3.4 + sirv@3.0.2: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + sisteransi@1.0.5: {} slash@3.0.0: {} @@ -29775,6 +29908,8 @@ snapshots: toml@3.0.0: {} + totalist@3.0.1: {} + tough-cookie@6.0.1: dependencies: tldts: 7.4.0 @@ -30497,7 +30632,16 @@ snapshots: optionalDependencies: vite: 7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0) - vitest@5.0.5(@types/node@24.12.4)(@vitest/coverage-istanbul@5.0.5)(jsdom@27.4.0(supports-color@7.2.0))(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0)): + vitest-browser-react@2.3.0(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vitest@5.0.5): + dependencies: + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + vitest: 5.0.5(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-istanbul@5.0.5)(@vitest/ui@5.0.5)(jsdom@27.4.0(supports-color@8.1.1))(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0)) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + vitest@5.0.5(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-istanbul@5.0.5)(@vitest/ui@5.0.5)(jsdom@27.4.0(supports-color@7.2.0))(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0)): dependencies: '@types/chai': 5.2.3 '@vitest/mocker': 5.0.5(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0)) @@ -30515,12 +30659,14 @@ snapshots: why-is-node-running: 3.2.1 optionalDependencies: '@types/node': 24.12.4 + '@vitest/browser-playwright': 5.0.5(playwright@1.64.1)(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0))(vitest@5.0.5) '@vitest/coverage-istanbul': 5.0.5(vitest@5.0.5) + '@vitest/ui': 5.0.5(vitest@5.0.5) jsdom: 27.4.0(supports-color@7.2.0) transitivePeerDependencies: - msw - vitest@5.0.5(@types/node@24.12.4)(@vitest/coverage-istanbul@5.0.5)(jsdom@27.4.0(supports-color@8.1.1))(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0)): + vitest@5.0.5(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-istanbul@5.0.5)(@vitest/ui@5.0.5)(jsdom@27.4.0(supports-color@8.1.1))(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0)): dependencies: '@types/chai': 5.2.3 '@vitest/mocker': 5.0.5(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0)) @@ -30538,7 +30684,9 @@ snapshots: why-is-node-running: 3.2.1 optionalDependencies: '@types/node': 24.12.4 + '@vitest/browser-playwright': 5.0.5(playwright@1.64.1)(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.9.0))(vitest@5.0.5) '@vitest/coverage-istanbul': 5.0.5(vitest@5.0.5) + '@vitest/ui': 5.0.5(vitest@5.0.5) jsdom: 27.4.0(supports-color@8.1.1) transitivePeerDependencies: - msw @@ -31009,6 +31157,8 @@ snapshots: ws@8.21.0: {} + ws@8.22.0: {} + wsl-utils@0.1.0: dependencies: is-wsl: 3.1.1