From 0dfe9aa6bb5001e6fccf6d2cb6c00323d44df970 Mon Sep 17 00:00:00 2001 From: TheoryOfNekomata Date: Sun, 17 Sep 2023 13:35:26 +0800 Subject: [PATCH] Refactor, add tests Extract client components and organize code base. We also add tests for the form component. --- packages/iceform-next/package.json | 9 +- packages/iceform-next/src/client.tsx | 162 ----------- packages/iceform-next/src/client/common.ts | 26 ++ .../src/client/components/Form.tsx | 94 +++++++ .../src/client/hooks/useFormFetch.ts | 89 ++++++ .../src/client/hooks/useResponse.ts | 26 ++ packages/iceform-next/src/client/index.ts | 3 + packages/iceform-next/src/index.ts | 2 +- packages/iceform-next/src/server.ts | 2 +- .../src/utils/{body.ts => request.ts} | 0 .../iceform-next/src/utils/serialization.ts | 2 +- packages/iceform-next/test/client.test.tsx | 61 ++++ packages/iceform-next/test/index.test.tsx | 13 - pnpm-lock.yaml | 261 +++++++++++------- 14 files changed, 476 insertions(+), 274 deletions(-) delete mode 100644 packages/iceform-next/src/client.tsx create mode 100644 packages/iceform-next/src/client/common.ts create mode 100644 packages/iceform-next/src/client/components/Form.tsx create mode 100644 packages/iceform-next/src/client/hooks/useFormFetch.ts create mode 100644 packages/iceform-next/src/client/hooks/useResponse.ts create mode 100644 packages/iceform-next/src/client/index.ts rename packages/iceform-next/src/utils/{body.ts => request.ts} (100%) create mode 100644 packages/iceform-next/test/client.test.tsx delete mode 100644 packages/iceform-next/test/index.test.tsx diff --git a/packages/iceform-next/package.json b/packages/iceform-next/package.json index 60effa2..065c2be 100644 --- a/packages/iceform-next/package.json +++ b/packages/iceform-next/package.json @@ -13,13 +13,16 @@ "pridepack" ], "devDependencies": { - "@testing-library/jest-dom": "^5.16.5", - "@testing-library/react": "^13.4.0", + "@types/testing-library__jest-dom": "^5.14.9", + "@testing-library/jest-dom": "^5.16.5", + "@testing-library/react": "^13.4.0", + "@testing-library/user-event": "^14.4.3", "@types/busboy": "^1.5.1", "@types/cookie": "^0.5.2", "@types/express": "^4.17.17", "@types/node": "^18.14.1", "@types/react": "^18.0.27", + "@vitest/coverage-v8": "^0.33.0", "eslint": "^8.35.0", "eslint-config-lxsmnsyc": "^0.5.0", "express": "^4.18.2", @@ -31,7 +34,7 @@ "react-test-renderer": "^18.2.0", "tslib": "^2.5.0", "typescript": "^4.9.5", - "vitest": "^0.28.1" + "vitest": "^0.34.1" }, "peerDependencies": { "next": "13.4.19", diff --git a/packages/iceform-next/src/client.tsx b/packages/iceform-next/src/client.tsx deleted file mode 100644 index 0e7695c..0000000 --- a/packages/iceform-next/src/client.tsx +++ /dev/null @@ -1,162 +0,0 @@ -import * as React from 'react'; -import fetchPonyfill from 'fetch-ponyfill'; -import { NextPage as DefaultNextPage } from 'next'; -import { - NextApiResponse, - NextApiRequest, - ENCTYPE_APPLICATION_JSON, - ENCTYPE_MULTIPART_FORM_DATA, -} from './common'; -import { - DEFAULT_ENCTYPE_SERIALIZERS, - EncTypeSerializerMap, serializeBody, - SerializerOptions, -} from './utils/serialization'; - -const FormDerivedElementComponent = 'form' as const; - -type FormDerivedElement = HTMLElementTagNameMap[typeof FormDerivedElementComponent]; - -const ALLOWED_SERVER_METHODS = ['get', 'post'] as const; - -type AllowedServerMethod = typeof ALLOWED_SERVER_METHODS[number]; - -const ALLOWED_CLIENT_METHODS = [ - ...ALLOWED_SERVER_METHODS, - 'put', - 'patch', - 'delete', -] as const; - -type AllowedClientMethod = typeof ALLOWED_CLIENT_METHODS[number]; - -export interface FormProps extends Omit, 'action' | 'method'> { - action?: string; - method?: AllowedServerMethod; - clientAction?: string; - clientHeaders?: HeadersInit; - clientMethod?: AllowedClientMethod; - invalidate?: (...args: unknown[]) => unknown; - refresh?: (response: Response) => void; - encTypeSerializers?: EncTypeSerializerMap; - responseEncType?: string; - serializerOptions?: SerializerOptions; -} - -export const useResponse = (res: NextApiResponse) => { - const [response, setResponse] = React.useState( - res.body ? new Response(res.body as unknown as BodyInit) : undefined, - ); - - const invalidate = React.useCallback(() => { - setResponse(undefined); - }, []); - - const refresh = React.useCallback((newResponse: Response) => { - setResponse(newResponse); - }, []); - - return React.useMemo(() => ({ - response, - refresh, - invalidate, - }), [ - response, - refresh, - invalidate, - ]); -}; - -export const Form = React.forwardRef(({ - children, - onSubmit, - action, - method = 'get', - clientAction = action, - clientMethod = method, - clientHeaders, - encType = ENCTYPE_MULTIPART_FORM_DATA, - invalidate, - refresh, - encTypeSerializers = DEFAULT_ENCTYPE_SERIALIZERS, - responseEncType = ENCTYPE_APPLICATION_JSON, - serializerOptions, - ...etcProps -}, forwardedRef) => { - const handleSubmit: React.FormEventHandler = async (event) => { - event.preventDefault(); - const nativeEvent = event.nativeEvent as unknown as { submitter?: HTMLElement }; - - if (clientAction) { - invalidate?.(); - const { fetch } = fetchPonyfill(); - const headers: HeadersInit = { - ...(clientHeaders ?? {}), - Accept: responseEncType, - }; - if (encType !== ENCTYPE_MULTIPART_FORM_DATA) { - // browser automatically generates content-type header for multipart/form-data - (headers as unknown as Record)['Content-Type'] = encType; - } - - const fetchInit: RequestInit = { - method: clientMethod.toUpperCase(), - headers, - }; - - if (!['GET', 'HEAD'].includes(clientMethod.toUpperCase())) { - fetchInit.body = serializeBody({ - form: event.currentTarget, - encType, - serializers: encTypeSerializers, - options: { - ...serializerOptions, - submitter: nativeEvent.submitter, - }, - }); - } - const response = await fetch(clientAction, fetchInit); - refresh?.(response); - } - - onSubmit?.(event); - }; - - const serverMethodRaw = method.toLowerCase(); - const serverMethod = serverMethodRaw === 'get' ? 'get' : 'post'; - - return ( - - {children} - - ); -}); - -Form.displayName = 'Form' as const; - -Form.defaultProps = { - action: undefined, - clientAction: undefined, - clientHeaders: undefined, - clientMethod: undefined, - encTypeSerializers: DEFAULT_ENCTYPE_SERIALIZERS, - invalidate: undefined, - method: 'get' as const, - refresh: undefined, - responseEncType: ENCTYPE_APPLICATION_JSON, - serializerOptions: undefined, -}; - -export type NextPage, U = T> = DefaultNextPage< - T & { - res: NextApiResponse; - req: NextApiRequest; - }, U -> diff --git a/packages/iceform-next/src/client/common.ts b/packages/iceform-next/src/client/common.ts new file mode 100644 index 0000000..d3ec078 --- /dev/null +++ b/packages/iceform-next/src/client/common.ts @@ -0,0 +1,26 @@ +import { NextPage as DefaultNextPage } from 'next/types'; +import { NextApiRequest, NextApiResponse } from '../common'; + +export const FormDerivedElementComponent = 'form' as const; + +export type FormDerivedElement = HTMLElementTagNameMap[typeof FormDerivedElementComponent]; + +export const ALLOWED_SERVER_METHODS = ['get', 'post'] as const; + +export type AllowedServerMethod = typeof ALLOWED_SERVER_METHODS[number]; + +export const ALLOWED_CLIENT_METHODS = [ + ...ALLOWED_SERVER_METHODS, + 'put', + 'patch', + 'delete', +] as const; + +export type AllowedClientMethod = typeof ALLOWED_CLIENT_METHODS[number]; + +export type NextPage, U = T> = DefaultNextPage< + T & { + res: NextApiResponse; + req: NextApiRequest; +}, U +> diff --git a/packages/iceform-next/src/client/components/Form.tsx b/packages/iceform-next/src/client/components/Form.tsx new file mode 100644 index 0000000..8ecdefa --- /dev/null +++ b/packages/iceform-next/src/client/components/Form.tsx @@ -0,0 +1,94 @@ +import * as React from 'react'; +import { + ENCTYPE_APPLICATION_JSON, + ENCTYPE_MULTIPART_FORM_DATA, +} from '../../common'; +import { + DEFAULT_ENCTYPE_SERIALIZERS, + EncTypeSerializerMap, + SerializerOptions, +} from '../../utils/serialization'; +import { + AllowedClientMethod, + AllowedServerMethod, + FormDerivedElement, + FormDerivedElementComponent, +} from '../common'; +import { useFormFetch } from '../hooks/useFormFetch'; + +export interface FormProps extends Omit, 'action' | 'method'> { + action?: string; + method?: AllowedServerMethod; + clientAction?: string; + clientHeaders?: HeadersInit; + clientMethod?: AllowedClientMethod; + invalidate?: (...args: unknown[]) => unknown; + refresh?: (response: Response) => void; + encTypeSerializers?: EncTypeSerializerMap; + responseEncType?: string; + serializerOptions?: SerializerOptions; + disableFetch?: boolean; +} + +export const Form = React.forwardRef(({ + children, + onSubmit, + action, + method = 'get' as const, + clientAction = action, + clientMethod = method, + clientHeaders, + encType = ENCTYPE_MULTIPART_FORM_DATA, + invalidate, + refresh, + encTypeSerializers = DEFAULT_ENCTYPE_SERIALIZERS, + responseEncType = ENCTYPE_APPLICATION_JSON, + serializerOptions, + disableFetch = false, + ...etcProps +}, forwardedRef) => { + const { handleSubmit } = useFormFetch({ + clientAction, + onSubmit, + clientMethod, + clientHeaders, + encType, + invalidate, + refresh, + encTypeSerializers, + responseEncType, + serializerOptions, + }); + + const serverMethodRaw = method.toLowerCase(); + const serverMethod = serverMethodRaw === 'get' ? 'get' : 'post'; + + return ( + + {children} + + ); +}); + +Form.displayName = 'Form' as const; + +Form.defaultProps = { + action: undefined, + clientAction: undefined, + clientHeaders: undefined, + clientMethod: undefined, + encTypeSerializers: DEFAULT_ENCTYPE_SERIALIZERS, + disableFetch: false as const, + invalidate: undefined, + method: 'get' as const, + refresh: undefined, + responseEncType: ENCTYPE_APPLICATION_JSON, + serializerOptions: undefined, +}; diff --git a/packages/iceform-next/src/client/hooks/useFormFetch.ts b/packages/iceform-next/src/client/hooks/useFormFetch.ts new file mode 100644 index 0000000..a258195 --- /dev/null +++ b/packages/iceform-next/src/client/hooks/useFormFetch.ts @@ -0,0 +1,89 @@ +import * as React from 'react'; +import fetchPonyfill from 'fetch-ponyfill'; +import { EncTypeSerializerMap, serializeBody, SerializerOptions } from '../../utils/serialization'; +import { ENCTYPE_MULTIPART_FORM_DATA } from '../../common'; +import { AllowedClientMethod, FormDerivedElement } from '../common'; + +export interface UseFormFetchParams { + clientAction?: string; + clientHeaders?: HeadersInit; + clientMethod: AllowedClientMethod; + encType: string; + invalidate?: (...args: unknown[]) => unknown; + refresh?: (response: Response) => void; + encTypeSerializers?: EncTypeSerializerMap; + responseEncType: string; + serializerOptions?: SerializerOptions; + onSubmit?: React.FormEventHandler; +} + +export const useFormFetch = ({ + clientAction, + invalidate, + clientHeaders, + responseEncType, + encType, + clientMethod, + encTypeSerializers, + serializerOptions, + refresh, + onSubmit, +}: UseFormFetchParams) => { + const handleSubmit: React.FormEventHandler< + FormDerivedElement + > = React.useCallback(async (event) => { + event.preventDefault(); + const { submitter } = event.nativeEvent as unknown as { submitter?: HTMLElement }; + + if (clientAction) { + invalidate?.(); + const { fetch } = fetchPonyfill(); + const headers: HeadersInit = { + ...(clientHeaders ?? {}), + Accept: responseEncType, + }; + if (encType !== ENCTYPE_MULTIPART_FORM_DATA) { + // browser automatically generates content-type header for multipart/form-data + (headers as unknown as Record)['Content-Type'] = encType; + } + + const fetchInit: RequestInit = { + method: clientMethod.toUpperCase(), + headers, + }; + + if (!['GET', 'HEAD'].includes(clientMethod.toUpperCase())) { + fetchInit.body = serializeBody({ + form: event.currentTarget, + encType, + serializers: encTypeSerializers, + options: { + ...serializerOptions, + submitter, + }, + }); + } + const response = await fetch(clientAction, fetchInit); + refresh?.(response); + } + + onSubmit?.(event); + }, [ + clientAction, + invalidate, + clientHeaders, + responseEncType, + encType, + clientMethod, + encTypeSerializers, + serializerOptions, + refresh, + onSubmit, + ]); + + return React.useMemo(() => ({ + handleSubmit, + }), [ + handleSubmit, + ]); +}; diff --git a/packages/iceform-next/src/client/hooks/useResponse.ts b/packages/iceform-next/src/client/hooks/useResponse.ts new file mode 100644 index 0000000..57e9bd4 --- /dev/null +++ b/packages/iceform-next/src/client/hooks/useResponse.ts @@ -0,0 +1,26 @@ +import * as React from 'react'; +import { NextApiResponse } from '../../common'; + +export const useResponse = (res: NextApiResponse) => { + const [response, setResponse] = React.useState( + res.body ? new Response(res.body as unknown as BodyInit) : undefined, + ); + + const invalidate = React.useCallback(() => { + setResponse(undefined); + }, []); + + const refresh = React.useCallback((newResponse: Response) => { + setResponse(newResponse); + }, []); + + return React.useMemo(() => ({ + response, + refresh, + invalidate, + }), [ + response, + refresh, + invalidate, + ]); +}; diff --git a/packages/iceform-next/src/client/index.ts b/packages/iceform-next/src/client/index.ts new file mode 100644 index 0000000..a1a514e --- /dev/null +++ b/packages/iceform-next/src/client/index.ts @@ -0,0 +1,3 @@ +export * from './hooks/useResponse'; +export * from './components/Form'; +export * from './common'; diff --git a/packages/iceform-next/src/index.ts b/packages/iceform-next/src/index.ts index 989d4da..c26e119 100644 --- a/packages/iceform-next/src/index.ts +++ b/packages/iceform-next/src/index.ts @@ -1,3 +1,3 @@ -export * from './common'; +export type * from './common'; export * from './client'; export * from './server'; diff --git a/packages/iceform-next/src/server.ts b/packages/iceform-next/src/server.ts index 445d32a..9b2bb4c 100644 --- a/packages/iceform-next/src/server.ts +++ b/packages/iceform-next/src/server.ts @@ -12,7 +12,7 @@ import { ENCTYPE_APPLICATION_JSON, ENCTYPE_APPLICATION_OCTET_STREAM, } from './common'; -import { getBody } from './utils/body'; +import { getBody } from './utils/request'; import { CookieManager, BODY_COOKIE_KEY, diff --git a/packages/iceform-next/src/utils/body.ts b/packages/iceform-next/src/utils/request.ts similarity index 100% rename from packages/iceform-next/src/utils/body.ts rename to packages/iceform-next/src/utils/request.ts diff --git a/packages/iceform-next/src/utils/serialization.ts b/packages/iceform-next/src/utils/serialization.ts index 34ed4ab..bbc7f7f 100644 --- a/packages/iceform-next/src/utils/serialization.ts +++ b/packages/iceform-next/src/utils/serialization.ts @@ -5,7 +5,7 @@ import { ENCTYPE_MULTIPART_FORM_DATA, ENCTYPE_X_WWW_FORM_URLENCODED, } from '../common'; -import { getBody, parseMultipartFormData } from './body'; +import { getBody, parseMultipartFormData } from './request'; export type EncTypeSerializer = (data: unknown) => string; diff --git a/packages/iceform-next/test/client.test.tsx b/packages/iceform-next/test/client.test.tsx new file mode 100644 index 0000000..71b1471 --- /dev/null +++ b/packages/iceform-next/test/client.test.tsx @@ -0,0 +1,61 @@ +import * as React from 'react'; +import { + vi, + describe, + it, + expect, + afterEach, +} from 'vitest'; +import userEvent from '@testing-library/user-event'; +import { render, screen, cleanup } from '@testing-library/react'; +import matchers from '@testing-library/jest-dom/matchers'; +import { Form } from '../src'; + +expect.extend(matchers); + +describe('Form', () => { + afterEach(() => { + cleanup(); + }); + + it('renders a form', () => { + render(
); + + const form = screen.getByRole('form'); + + expect(form).toBeInTheDocument(); + }); + + it('calls the submit handler on submit', async () => { + const onSubmit = vi.fn(); + + render( + + +
, + ); + + const button = screen.getByRole('button', { name: 'Submit' }); + + await userEvent.click(button); + + expect(onSubmit).toHaveBeenCalled(); + }); + + it.skip('calls the submit handler on submit with a custom component', async () => { + // TODO + const onSubmit = vi.fn(); + + render( +
+ +
, + ); + + const button = screen.getByRole('button', { name: 'Submit' }); + + await userEvent.click(button); + + expect(onSubmit).toHaveBeenCalled(); + }); +}); diff --git a/packages/iceform-next/test/index.test.tsx b/packages/iceform-next/test/index.test.tsx deleted file mode 100644 index 3d88bc9..0000000 --- a/packages/iceform-next/test/index.test.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; -import { render, screen } from '@testing-library/react'; -import '@testing-library/jest-dom'; -import Hello from '../src'; - -describe('Example', () => { - it('should have the expected content', () => { - const greeting = 'World'; - render(); - - expect(screen.getByText(`Hello ${greeting}`)).toBeInTheDocument(); - }); -}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a78c785..d6c7b01 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,6 +30,9 @@ importers: '@testing-library/react': specifier: ^13.4.0 version: 13.4.0(react-dom@18.2.0)(react@18.2.0) + '@testing-library/user-event': + specifier: ^14.4.3 + version: 14.4.3(@testing-library/dom@8.20.1) '@types/busboy': specifier: ^1.5.1 version: 1.5.1 @@ -45,6 +48,12 @@ importers: '@types/react': specifier: ^18.0.27 version: 18.2.21 + '@types/testing-library__jest-dom': + specifier: ^5.14.9 + version: 5.14.9 + '@vitest/coverage-v8': + specifier: ^0.33.0 + version: 0.33.0(vitest@0.34.1) eslint: specifier: ^8.35.0 version: 8.49.0 @@ -79,8 +88,8 @@ importers: specifier: ^4.9.5 version: 4.9.5 vitest: - specifier: ^0.28.1 - version: 0.28.1(jsdom@21.1.0) + specifier: ^0.34.1 + version: 0.34.1(jsdom@21.1.0) packages/iceform-next-sandbox: dependencies: @@ -373,6 +382,10 @@ packages: '@babel/helper-validator-identifier': 7.22.19 to-fast-properties: 2.0.0 + /@bcoe/v8-coverage@0.2.3: + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + dev: true + /@esbuild/android-arm64@0.17.19: resolution: {integrity: sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==} engines: {node: '>=12'} @@ -819,6 +832,11 @@ packages: /@humanwhocodes/object-schema@1.2.1: resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} + /@istanbuljs/schema@0.1.3: + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + dev: true + /@jest/expect-utils@29.7.0: resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -1039,6 +1057,15 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: true + /@testing-library/user-event@14.4.3(@testing-library/dom@8.20.1): + resolution: {integrity: sha512-kCUc5MEwaEMakkO5x7aoD+DLi02ehmEM2QCGWvNqAS1dV/fAvORWEjnjsEIvml59M7Y5kCkWN6fCCyPOe8OL6Q==} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@testing-library/dom': '>=7.21.4' + dependencies: + '@testing-library/dom': 8.20.1 + dev: true + /@theoryofnekomata/formxtra@1.0.3: resolution: {integrity: sha512-xOzE07Slttpx7vbOWqXfatJ+k44TN4zUjI57A5/sNqUDtHzp3pz94A+AVPGVoBY0QXiwzMjeN4DPMp6U1qlkyg==} engines: {node: '>=10'} @@ -1425,36 +1452,63 @@ packages: eslint-visitor-keys: 3.4.3 dev: false - /@vitest/expect@0.28.1: - resolution: {integrity: sha512-BOvWjBoocKrrTTTC0opIvzOEa7WR/Ovx4++QYlbjYKjnQJfWRSEQkTpAIEfOURtZ/ICcaLk5jvsRshXvjarZew==} + /@vitest/coverage-v8@0.33.0(vitest@0.34.1): + resolution: {integrity: sha512-Rj5IzoLF7FLj6yR7TmqsfRDSeaFki6NAJ/cQexqhbWkHEV2htlVGrmuOde3xzvFsCbLCagf4omhcIaVmfU8Okg==} + peerDependencies: + vitest: '>=0.32.0 <1' dependencies: - '@vitest/spy': 0.28.1 - '@vitest/utils': 0.28.1 + '@ampproject/remapping': 2.2.1 + '@bcoe/v8-coverage': 0.2.3 + istanbul-lib-coverage: 3.2.0 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 4.0.1 + istanbul-reports: 3.1.6 + magic-string: 0.30.3 + picocolors: 1.0.0 + std-env: 3.4.3 + test-exclude: 6.0.0 + v8-to-istanbul: 9.1.0 + vitest: 0.34.1(jsdom@21.1.0) + transitivePeerDependencies: + - supports-color + dev: true + + /@vitest/expect@0.34.1: + resolution: {integrity: sha512-q2CD8+XIsQ+tHwypnoCk8Mnv5e6afLFvinVGCq3/BOT4kQdVQmY6rRfyKkwcg635lbliLPqbunXZr+L1ssUWiQ==} + dependencies: + '@vitest/spy': 0.34.1 + '@vitest/utils': 0.34.1 chai: 4.3.8 dev: true - /@vitest/runner@0.28.1: - resolution: {integrity: sha512-kOdmgiNe+mAxZhvj2eUTqKnjfvzzknmrcS+SZXV7j6VgJuWPFAMCv3TWOe03nF9dkqDfVLCDRw/hwFuCzmzlQg==} + /@vitest/runner@0.34.1: + resolution: {integrity: sha512-YfQMpYzDsYB7yqgmlxZ06NI4LurHWfrH7Wy3Pvf/z/vwUSgq1zLAb1lWcItCzQG+NVox+VvzlKQrYEXb47645g==} dependencies: - '@vitest/utils': 0.28.1 + '@vitest/utils': 0.34.1 p-limit: 4.0.0 pathe: 1.1.1 dev: true - /@vitest/spy@0.28.1: - resolution: {integrity: sha512-XGlD78cG3IxXNnGwEF121l0MfTNlHSdI25gS2ik0z6f/D9wWUOru849QkJbuNl4CMlZCtNkx3b5IS6MRwKGKuA==} + /@vitest/snapshot@0.34.1: + resolution: {integrity: sha512-0O9LfLU0114OqdF8lENlrLsnn024Tb1CsS9UwG0YMWY2oGTQfPtkW+B/7ieyv0X9R2Oijhi3caB1xgGgEgclSQ==} dependencies: - tinyspy: 1.1.1 + magic-string: 0.30.3 + pathe: 1.1.1 + pretty-format: 29.7.0 dev: true - /@vitest/utils@0.28.1: - resolution: {integrity: sha512-a7cV1fs5MeU+W+8sn8gM9gV+q7V/wYz3/4y016w/icyJEKm9AMdSHnrzxTWaElJ07X40pwU6m5353Jlw6Rbd8w==} + /@vitest/spy@0.34.1: + resolution: {integrity: sha512-UT4WcI3EAPUNO8n6y9QoEqynGGEPmmRxC+cLzneFFXpmacivjHZsNbiKD88KUScv5DCHVDgdBsLD7O7s1enFcQ==} dependencies: - cli-truncate: 3.1.0 - diff: 5.1.0 + tinyspy: 2.1.1 + dev: true + + /@vitest/utils@0.34.1: + resolution: {integrity: sha512-/ql9dsFi4iuEbiNcjNHQWXBum7aL8pyhxvfnD9gNtbjR9fUKAjxhj4AA3yfLXg6gJpMGGecvtF8Au2G9y3q47Q==} + dependencies: + diff-sequences: 29.6.3 loupe: 2.3.6 - picocolors: 1.0.0 - pretty-format: 27.5.1 + pretty-format: 29.7.0 dev: true /abab@2.0.6: @@ -1536,11 +1590,6 @@ packages: engines: {node: '>=10'} dev: true - /ansi-styles@6.2.1: - resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} - engines: {node: '>=12'} - dev: true - /any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} dev: false @@ -1761,10 +1810,6 @@ packages: node-releases: 2.0.13 update-browserslist-db: 1.0.11(browserslist@4.21.10) - /buffer-from@1.1.2: - resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - dev: true - /buffer@6.0.3: resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} dependencies: @@ -1888,14 +1933,6 @@ packages: engines: {node: '>=6'} dev: true - /cli-truncate@3.1.0: - resolution: {integrity: sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dependencies: - slice-ansi: 5.0.0 - string-width: 5.1.2 - dev: true - /client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} @@ -2185,11 +2222,6 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dev: true - /diff@5.1.0: - resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==} - engines: {node: '>=0.3.1'} - dev: true - /dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -2235,10 +2267,6 @@ packages: engines: {node: '>=12'} dev: true - /eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - dev: true - /ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} dev: true @@ -3355,6 +3383,10 @@ packages: whatwg-encoding: 2.0.0 dev: true + /html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + dev: true + /http-errors@2.0.0: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} @@ -3525,11 +3557,6 @@ packages: engines: {node: '>=8'} dev: true - /is-fullwidth-code-point@4.0.0: - resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} - engines: {node: '>=12'} - dev: true - /is-generator-function@1.0.10: resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} engines: {node: '>= 0.4'} @@ -3644,6 +3671,39 @@ packages: /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + /istanbul-lib-coverage@3.2.0: + resolution: {integrity: sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==} + engines: {node: '>=8'} + dev: true + + /istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + dependencies: + istanbul-lib-coverage: 3.2.0 + make-dir: 4.0.0 + supports-color: 7.2.0 + dev: true + + /istanbul-lib-source-maps@4.0.1: + resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} + engines: {node: '>=10'} + dependencies: + debug: 4.3.4 + istanbul-lib-coverage: 3.2.0 + source-map: 0.6.1 + transitivePeerDependencies: + - supports-color + dev: true + + /istanbul-reports@3.1.6: + resolution: {integrity: sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==} + engines: {node: '>=8'} + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + dev: true + /iterator.prototype@1.1.2: resolution: {integrity: sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==} dependencies: @@ -3912,6 +3972,13 @@ packages: hasBin: true dev: true + /magic-string@0.30.3: + resolution: {integrity: sha512-B7xGbll2fG/VjP+SWg4sX3JynwIU0mjoTc6MPpKNuIvftk6u6vqhDnk1R80b8C2GBR6ywqy+1DcKBrevBg+bmw==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + /make-dir@3.1.0: resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} engines: {node: '>=8'} @@ -3919,6 +3986,13 @@ packages: semver: 6.3.1 dev: true + /make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + dependencies: + semver: 7.5.4 + dev: true + /media-typer@0.3.0: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} @@ -4847,25 +4921,10 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} - /slice-ansi@5.0.0: - resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} - engines: {node: '>=12'} - dependencies: - ansi-styles: 6.2.1 - is-fullwidth-code-point: 4.0.0 - dev: true - /source-map-js@1.0.2: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} - /source-map-support@0.5.21: - resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} - dependencies: - buffer-from: 1.1.2 - source-map: 0.6.1 - dev: true - /source-map@0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} @@ -4918,15 +4977,6 @@ packages: strip-ansi: 6.0.1 dev: true - /string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} - dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 - strip-ansi: 7.1.0 - dev: true - /string.prototype.matchall@4.0.10: resolution: {integrity: sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==} dependencies: @@ -5093,6 +5143,15 @@ packages: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} + /test-exclude@6.0.0: + resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + engines: {node: '>=8'} + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 7.2.3 + minimatch: 3.1.2 + dev: true + /text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} @@ -5113,13 +5172,13 @@ packages: resolution: {integrity: sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==} dev: true - /tinypool@0.3.1: - resolution: {integrity: sha512-zLA1ZXlstbU2rlpA4CIeVaqvWq41MTWqLY3FfsAXgC8+f7Pk7zroaJQxDgxn1xNudKW6Kmj4808rPFShUlIRmQ==} + /tinypool@0.7.0: + resolution: {integrity: sha512-zSYNUlYSMhJ6Zdou4cJwo/p7w5nmAH17GRfU/ui3ctvjXFErXXkruT4MWW6poDeXgCaIBlGLrfU6TbTXxyGMww==} engines: {node: '>=14.0.0'} dev: true - /tinyspy@1.1.1: - resolution: {integrity: sha512-UVq5AXt/gQlti7oxoIg5oi/9r0WpF7DGEVwXgqWSMmyN16+e3tl5lIvTaOpJ3TAtu5xFzWccFRM4R5NaWHF+4g==} + /tinyspy@2.1.1: + resolution: {integrity: sha512-XPJL2uSzcOyBMky6OFrusqWlzfFrXtE0hPuMgW8A2HmaqrPo4ZQHRN/V0QXN3FSjKxpsbRrFc5LI7KOwBsT1/w==} engines: {node: '>=14.0.0'} dev: true @@ -5331,14 +5390,23 @@ packages: engines: {node: '>= 0.4.0'} dev: true + /v8-to-istanbul@9.1.0: + resolution: {integrity: sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==} + engines: {node: '>=10.12.0'} + dependencies: + '@jridgewell/trace-mapping': 0.3.19 + '@types/istanbul-lib-coverage': 2.0.4 + convert-source-map: 1.9.0 + dev: true + /vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} dev: true - /vite-node@0.28.1(@types/node@18.14.1): - resolution: {integrity: sha512-Mmab+cIeElkVn4noScCRjy8nnQdh5LDIR4QCH/pVWtY15zv5Z1J7u6/471B9JZ2r8CEIs42vTbngaamOVkhPLA==} - engines: {node: '>=v14.16.0'} + /vite-node@0.34.1(@types/node@18.14.1): + resolution: {integrity: sha512-odAZAL9xFMuAg8aWd7nSPT+hU8u2r9gU3LRm9QKjxBEF2rRdWpMuqkrkjvyVQEdNFiBctqr2Gg4uJYizm5Le6w==} + engines: {node: '>=v14.18.0'} hasBin: true dependencies: cac: 6.7.14 @@ -5346,8 +5414,6 @@ packages: mlly: 1.4.2 pathe: 1.1.1 picocolors: 1.0.0 - source-map: 0.6.1 - source-map-support: 0.5.21 vite: 4.4.9(@types/node@18.14.1) transitivePeerDependencies: - '@types/node' @@ -5396,9 +5462,9 @@ packages: fsevents: 2.3.3 dev: true - /vitest@0.28.1(jsdom@21.1.0): - resolution: {integrity: sha512-F6wAO3K5+UqJCCGt0YAl3Ila2f+fpBrJhl9n7qWEhREwfzQeXlMkkCqGqGtzBxCSa8kv5QHrkshX8AaPTXYACQ==} - engines: {node: '>=v14.16.0'} + /vitest@0.34.1(jsdom@21.1.0): + resolution: {integrity: sha512-G1PzuBEq9A75XSU88yO5G4vPT20UovbC/2osB2KEuV/FisSIIsw7m5y2xMdB7RsAGHAfg2lPmp2qKr3KWliVlQ==} + engines: {node: '>=v14.18.0'} hasBin: true peerDependencies: '@edge-runtime/vm': '*' @@ -5406,6 +5472,9 @@ packages: '@vitest/ui': '*' happy-dom: '*' jsdom: '*' + playwright: '*' + safaridriver: '*' + webdriverio: '*' peerDependenciesMeta: '@edge-runtime/vm': optional: true @@ -5417,14 +5486,21 @@ packages: optional: true jsdom: optional: true + playwright: + optional: true + safaridriver: + optional: true + webdriverio: + optional: true dependencies: '@types/chai': 4.3.6 '@types/chai-subset': 1.3.3 '@types/node': 18.14.1 - '@vitest/expect': 0.28.1 - '@vitest/runner': 0.28.1 - '@vitest/spy': 0.28.1 - '@vitest/utils': 0.28.1 + '@vitest/expect': 0.34.1 + '@vitest/runner': 0.34.1 + '@vitest/snapshot': 0.34.1 + '@vitest/spy': 0.34.1 + '@vitest/utils': 0.34.1 acorn: 8.10.0 acorn-walk: 8.2.0 cac: 6.7.14 @@ -5432,16 +5508,15 @@ packages: debug: 4.3.4 jsdom: 21.1.0 local-pkg: 0.4.3 + magic-string: 0.30.3 pathe: 1.1.1 picocolors: 1.0.0 - source-map: 0.6.1 std-env: 3.4.3 strip-literal: 1.3.0 tinybench: 2.5.1 - tinypool: 0.3.1 - tinyspy: 1.1.1 + tinypool: 0.7.0 vite: 4.4.9(@types/node@18.14.1) - vite-node: 0.28.1(@types/node@18.14.1) + vite-node: 0.34.1(@types/node@18.14.1) why-is-node-running: 2.2.2 transitivePeerDependencies: - less