diff --git a/.github/actions/build/action.yaml b/.github/actions/build/action.yaml new file mode 100644 index 0000000..fe1c6ef --- /dev/null +++ b/.github/actions/build/action.yaml @@ -0,0 +1,19 @@ +name: "Build" +description: "Build the project." + +runs: + using: "composite" # This is the magic + steps: + - uses: pnpm/action-setup@v2 + with: + version: 7 + - uses: actions/setup-node@v3 + with: + node-version: 18 + cache: "pnpm" + registry-url: https://registry.npmjs.org/ + + - run: pnpm install --frozen-lockfile + shell: bash + - run: pnpm run build + shell: bash diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 14fd478..bef56aa 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -23,16 +23,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: pnpm/action-setup@v2 - with: - version: 7 - - uses: actions/setup-node@v3 - with: - node-version: 18 - cache: "pnpm" - - - run: pnpm install - - run: pnpm run build + - uses: ./.github/actions/build - uses: actions/configure-pages@v3 - uses: actions/upload-pages-artifact@v1 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..506c82d --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,18 @@ +name: Release + +on: + push: + tags: + - "v*.*.*" + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: ./.github/actions/build + + - run: pnpm test + - run: pnpm publish + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 406c7ac..3b511f7 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -14,14 +14,5 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: pnpm/action-setup@v2 - with: - version: 7 - - uses: actions/setup-node@v3 - with: - node-version: 18 - cache: "pnpm" - - - run: pnpm install --frozen-lockfile - - run: pnpm run compile + - uses: ./.github/actions/build - run: pnpm run test diff --git a/README.md b/README.md index af7fc04..689bfa7 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ ![Logo](https://raw.githubusercontent.com/cupcakearmy/formhero/master/.github/Logo.jpg) -![dependencies](https://badgen.net/david/dep/cupcakearmy/formhero) -![downloads badge](https://badgen.net/npm/dt/formhero) -![types badge](https://badgen.net/npm/types/formhero) ![version badge](https://badgen.net/npm/v/formhero) +![types badge](https://badgen.net/npm/types/formhero) +![downloads badge](https://badgen.net/npm/dt/formhero) +![dependencies](https://badgen.net//bundlephobia/dependency-count/formhero) ![minzip size badge](https://badgen.net/bundlephobia/minzip/formhero) **Fully customisable react form utility.** @@ -22,7 +22,7 @@ npm i formhero ``` -*Note:* Requires at least typescript version `3.5`, otherwise the error object will not have the right inherited types. +_Note:_ Requires at least typescript version `3.5`, otherwise the error object will not have the right inherited types. ### 👁 Demos @@ -99,7 +99,7 @@ const Form = () => { password: '', }, { - username: value => value.length > 3, + username: (value) => value.length > 3, email: { validator: /@/, message: 'Must contain an @', @@ -146,7 +146,7 @@ const Form = () => { return (
{ + onSubmit={(e) => { e.preventDefault() console.log(form) }} @@ -159,7 +159,7 @@ const Form = () => { {...field('awesome', { setter: 'checked', getter: 'onChange', - extractor: e => e.target.checked, + extractor: (e) => e.target.checked, })} /> Is it awesome? @@ -176,28 +176,28 @@ const Form = () => { Sometimes you don't know all the fields upfront. You can simply define a generic type and assign it to the initial object. Of course type assistance is limited in this case as formhero cannot be sure what keys are valid. ```typescript -import React from "react"; -import ReactDOM from "react-dom"; -import { useForm } from "formhero"; +import React from 'react' +import ReactDOM from 'react-dom' +import { useForm } from 'formhero' -type MyForm = { [field: string]: string | number }; +type MyForm = { [field: string]: string | number } const init: MyForm = { - username: "unicorn", - password: "" -}; + username: 'unicorn', + password: '', +} const Form: React.FC = () => { - const { field, form, errors } = useForm(init); + const { field, form, errors } = useForm(init) return ( - - - + + +
- ); -}; + ) +} ``` ## 📖 Documentation @@ -267,9 +267,9 @@ const validators = { message: 'My custom error message', }, /[\d]/, - async value => value.length > 0, + async (value) => value.length > 0, { - validator: value => true, + validator: (value) => true, message: 'Some other error', }, ], @@ -282,8 +282,8 @@ const validators = { const validators = { username: async (s: string) => { const taken = await API.isUsernameTaken(s) - return taken ? 'Username is taken': true - } + return taken ? 'Username is taken' : true + }, } ``` @@ -307,7 +307,7 @@ const validators = {} const options = { setter: 'value', // This is not stricly necessarry as 'value' would already be the default. getter: 'onChangeText', - extractor: text => text.toLowerCase(), + extractor: (text) => text.toLowerCase(), } export default () => { @@ -339,7 +339,7 @@ export default () => { {...field('username', { setter: 'value', // This is not stricly necessarry as 'value' would already be the default. getter: 'onChangeText', - extractor: text => text.toLowerCase(), + extractor: (text) => text.toLowerCase(), })} /> {form.username} diff --git a/lib/index.ts b/lib/index.ts index aaf7a11..0216a4d 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -1,60 +1,62 @@ import * as React from 'react' -import { useEffect, useState } from 'react' +import { useMemo, useState } from 'react' -export type FieldOptions = { - extractor?: useFormExtractor - getter: G - setter: S +// Possible future ideas +// TODO: Scroll to error field +// TODO: Focus on error field + +export type FieldOptions = { + extractor?: useFormExtractor | null + getter?: G + setter?: S } type RuleFunctionReturn = boolean | string -type RuleFunction = (value: I) => RuleFunctionReturn | Promise -type Rule = RuleFunction | RegExp -type RuleObject = Rule | { rule: Rule; message: string } -type RuleSet = RuleObject | RuleObject[] +type RuleFunction = (value: I, data: F) => RuleFunctionReturn | Promise +type Rule = RuleFunction | RegExp +type RuleObject = Rule | { rule: Rule; message: string } +type RuleSet = RuleObject | RuleObject[] -function isSimpleRule(obj: RuleObject): obj is Rule { +function isSimpleRule(obj: RuleObject): obj is Rule { return obj instanceof RegExp || typeof obj === 'function' } -export type useFormExtractor = (from: any) => any +export type useFormExtractor = (from: any) => T +export const NoExtractor: useFormExtractor = (v: unknown) => v export const HTMLInputExtractor: useFormExtractor = (e: React.FormEvent) => e.currentTarget.value export const HTMLCheckboxExtractor: useFormExtractor = (e: React.FormEvent) => e.currentTarget.checked export type FormOptions = { rules: R - // fields: FieldOptions } -// Form = Type of form +// F = Type of form // R = Rules, derived from F // E = Errors, derived from F -export const useForm =
}, E extends { [key in keyof R]?: RuleFunctionReturn }>(init: Form, options?: FormOptions) => { +export const useForm = }, E extends { [key in keyof R]?: RuleFunctionReturn }>(init: F, options?: FormOptions) => { const validators: R = options?.rules ?? ({} as R) - const [form, setForm] = useState(init) + const [form, setForm] = useState(init) const [errors, setErrors] = useState({} as E) - const [isValid, setIsValid] = useState(true) - - useEffect(() => { - setIsValid(!Object.values(errors).reduce((acc, cur) => acc || cur !== undefined, false)) + const isValid = useMemo(() => { + return !Object.values(errors).reduce((acc, cur) => acc || cur !== undefined, false) }, [errors]) - const setField = (key: A, value: Form[A]) => { + const setField = (key: A, value: F[A]) => { setForm({ ...form, [key]: value, }) } - async function applyRule(value: any, rule: Rule): Promise { - if (typeof rule === 'function') return await rule(value) + async function applyRule(value: any, rule: Rule): Promise { + if (typeof rule === 'function') return await rule(value, form) if (rule instanceof RegExp) return rule.test(value) throw new Error(`Unsupported validator: ${rule}`) } - async function validate(key: K, value: Form[K]) { - const set: RuleSet | undefined = validators[key] as any + async function validate(key: K, value: F[K]) { + const set: RuleSet | undefined = validators[key] as any if (!set) return const rules = Array.isArray(set) ? set : [set] @@ -74,18 +76,19 @@ export const useForm = (key: A, extractor?: (e: RAW) => Form[A]) { - return (value: RAW) => { - const extracted = extractor ? extractor(value) : HTMLInputExtractor(value) + // Internal use + function update(key: A, extractor?: useFormExtractor | null) { + return (value: any) => { + const extracted = extractor ? extractor(value) : extractor === undefined ? HTMLInputExtractor(value) : value setField(key, extracted) validate(key, extracted) } } - type FieldReturn = { [getter in G]: ReturnType> } & { [setter in S]: Form[K] } - function field(key: K): FieldReturn - function field(key: K, opts: FieldOptions): FieldReturn - function field(key: K, opts?: FieldOptions): FieldReturn { + type FieldReturn = { [getter in G]: ReturnType> } & { [setter in S]: F[K] } + function field(key: K): FieldReturn + function field(key: K, opts: FieldOptions): FieldReturn + function field(key: K, opts?: FieldOptions): FieldReturn { return { [opts?.getter || 'onChange']: update(key, opts?.extractor), [opts?.setter || 'value']: form[key], diff --git a/package.json b/package.json index 17a6f6a..c92aba3 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,12 @@ { "name": "formhero", - "version": "0.1.0", + "version": "1.0.0-rc.0", "type": "module", "module": "./dist/index.js", "types": "./dist/index.d.ts", + "files": [ + "dist" + ], "scripts": { "clean": "rm -rf ./dist", "prepublishOnly": "run-s clean build test", @@ -21,16 +24,16 @@ }, "devDependencies": { "@testing-library/react": "^13.4.0", - "@types/react": "^18.0.27", - "@types/react-dom": "^18.0.10", + "@types/react": "^18.0.28", + "@types/react-dom": "^18.0.11", "@vitejs/plugin-react": "^3.1.0", - "@vitest/coverage-c8": "^0.28.4", - "happy-dom": "^8.2.6", + "@vitest/coverage-c8": "^0.29.2", + "happy-dom": "^8.9.0", "npm-run-all": "^4.1.5", "react": "^18.2.0", "react-dom": "^18.2.0", "typescript": "^4.9.5", - "vite": "^4.1.1", - "vitest": "^0.28.4" + "vite": "^4.1.4", + "vitest": "^0.29.2" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f9e9973..4c337ff 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2,31 +2,31 @@ lockfileVersion: 5.4 specifiers: '@testing-library/react': ^13.4.0 - '@types/react': ^18.0.27 - '@types/react-dom': ^18.0.10 + '@types/react': ^18.0.28 + '@types/react-dom': ^18.0.11 '@vitejs/plugin-react': ^3.1.0 - '@vitest/coverage-c8': ^0.28.4 - happy-dom: ^8.2.6 + '@vitest/coverage-c8': ^0.29.2 + happy-dom: ^8.9.0 npm-run-all: ^4.1.5 react: ^18.2.0 react-dom: ^18.2.0 typescript: ^4.9.5 - vite: ^4.1.1 - vitest: ^0.28.4 + vite: ^4.1.4 + vitest: ^0.29.2 devDependencies: '@testing-library/react': 13.4.0_biqbaboplfbrettd7655fr4n2y - '@types/react': 18.0.27 - '@types/react-dom': 18.0.10 - '@vitejs/plugin-react': 3.1.0_vite@4.1.1 - '@vitest/coverage-c8': 0.28.4_happy-dom@8.2.6 - happy-dom: 8.2.6 + '@types/react': 18.0.28 + '@types/react-dom': 18.0.11 + '@vitejs/plugin-react': 3.1.0_vite@4.1.4 + '@vitest/coverage-c8': 0.29.2_vitest@0.29.2 + happy-dom: 8.9.0 npm-run-all: 4.1.5 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 typescript: 4.9.5 - vite: 4.1.1 - vitest: 0.28.4_happy-dom@8.2.6 + vite: 4.1.4 + vitest: 0.29.2_happy-dom@8.9.0 packages: @@ -45,25 +45,25 @@ packages: '@babel/highlight': 7.18.6 dev: true - /@babel/compat-data/7.20.14: - resolution: {integrity: sha512-0YpKHD6ImkWMEINCyDAD0HLLUH/lPCefG8ld9it8DJB2wnApraKuhgYTvTY1z7UFIfBTGy5LwncZ+5HWWGbhFw==} + /@babel/compat-data/7.21.0: + resolution: {integrity: sha512-gMuZsmsgxk/ENC3O/fRw5QY8A9/uxQbbCEypnLIiYYc/qVJtEV7ouxC3EllIIwNzMqAQee5tanFabWsUOutS7g==} engines: {node: '>=6.9.0'} dev: true - /@babel/core/7.20.12: - resolution: {integrity: sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg==} + /@babel/core/7.21.0: + resolution: {integrity: sha512-PuxUbxcW6ZYe656yL3EAhpy7qXKq0DmYsrJLpbB8XrsCP9Nm+XCg9XFMb5vIDliPD7+U/+M+QJlH17XOcB7eXA==} engines: {node: '>=6.9.0'} dependencies: '@ampproject/remapping': 2.2.0 '@babel/code-frame': 7.18.6 - '@babel/generator': 7.20.14 - '@babel/helper-compilation-targets': 7.20.7_@babel+core@7.20.12 - '@babel/helper-module-transforms': 7.20.11 - '@babel/helpers': 7.20.13 - '@babel/parser': 7.20.15 + '@babel/generator': 7.21.1 + '@babel/helper-compilation-targets': 7.20.7_@babel+core@7.21.0 + '@babel/helper-module-transforms': 7.21.2 + '@babel/helpers': 7.21.0 + '@babel/parser': 7.21.2 '@babel/template': 7.20.7 - '@babel/traverse': 7.20.13 - '@babel/types': 7.20.7 + '@babel/traverse': 7.21.2 + '@babel/types': 7.21.2 convert-source-map: 1.9.0 debug: 4.3.4 gensync: 1.0.0-beta.2 @@ -73,24 +73,25 @@ packages: - supports-color dev: true - /@babel/generator/7.20.14: - resolution: {integrity: sha512-AEmuXHdcD3A52HHXxaTmYlb8q/xMEhoRP67B3T4Oq7lbmSoqroMZzjnGj3+i1io3pdnF8iBYVu4Ilj+c4hBxYg==} + /@babel/generator/7.21.1: + resolution: {integrity: sha512-1lT45bAYlQhFn/BHivJs43AiW2rg3/UbLyShGfF3C0KmHvO5fSghWd5kBJy30kpRRucGzXStvnnCFniCR2kXAA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.20.7 + '@babel/types': 7.21.2 '@jridgewell/gen-mapping': 0.3.2 + '@jridgewell/trace-mapping': 0.3.17 jsesc: 2.5.2 dev: true - /@babel/helper-compilation-targets/7.20.7_@babel+core@7.20.12: + /@babel/helper-compilation-targets/7.20.7_@babel+core@7.21.0: resolution: {integrity: sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/compat-data': 7.20.14 - '@babel/core': 7.20.12 - '@babel/helper-validator-option': 7.18.6 + '@babel/compat-data': 7.21.0 + '@babel/core': 7.21.0 + '@babel/helper-validator-option': 7.21.0 browserslist: 4.21.5 lru-cache: 5.1.1 semver: 6.3.0 @@ -101,30 +102,30 @@ packages: engines: {node: '>=6.9.0'} dev: true - /@babel/helper-function-name/7.19.0: - resolution: {integrity: sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==} + /@babel/helper-function-name/7.21.0: + resolution: {integrity: sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==} engines: {node: '>=6.9.0'} dependencies: '@babel/template': 7.20.7 - '@babel/types': 7.20.7 + '@babel/types': 7.21.2 dev: true /@babel/helper-hoist-variables/7.18.6: resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.20.7 + '@babel/types': 7.21.2 dev: true /@babel/helper-module-imports/7.18.6: resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.20.7 + '@babel/types': 7.21.2 dev: true - /@babel/helper-module-transforms/7.20.11: - resolution: {integrity: sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg==} + /@babel/helper-module-transforms/7.21.2: + resolution: {integrity: sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==} engines: {node: '>=6.9.0'} dependencies: '@babel/helper-environment-visitor': 7.18.9 @@ -133,8 +134,8 @@ packages: '@babel/helper-split-export-declaration': 7.18.6 '@babel/helper-validator-identifier': 7.19.1 '@babel/template': 7.20.7 - '@babel/traverse': 7.20.13 - '@babel/types': 7.20.7 + '@babel/traverse': 7.21.2 + '@babel/types': 7.21.2 transitivePeerDependencies: - supports-color dev: true @@ -148,14 +149,14 @@ packages: resolution: {integrity: sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.20.7 + '@babel/types': 7.21.2 dev: true /@babel/helper-split-export-declaration/7.18.6: resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.20.7 + '@babel/types': 7.21.2 dev: true /@babel/helper-string-parser/7.19.4: @@ -168,18 +169,18 @@ packages: engines: {node: '>=6.9.0'} dev: true - /@babel/helper-validator-option/7.18.6: - resolution: {integrity: sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==} + /@babel/helper-validator-option/7.21.0: + resolution: {integrity: sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==} engines: {node: '>=6.9.0'} dev: true - /@babel/helpers/7.20.13: - resolution: {integrity: sha512-nzJ0DWCL3gB5RCXbUO3KIMMsBY2Eqbx8mBpKGE/02PgyRQFcPQLbkQ1vyy596mZLaP+dAfD+R4ckASzNVmW3jg==} + /@babel/helpers/7.21.0: + resolution: {integrity: sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==} engines: {node: '>=6.9.0'} dependencies: '@babel/template': 7.20.7 - '@babel/traverse': 7.20.13 - '@babel/types': 7.20.7 + '@babel/traverse': 7.21.2 + '@babel/types': 7.21.2 transitivePeerDependencies: - supports-color dev: true @@ -193,36 +194,36 @@ packages: js-tokens: 4.0.0 dev: true - /@babel/parser/7.20.15: - resolution: {integrity: sha512-DI4a1oZuf8wC+oAJA9RW6ga3Zbe8RZFt7kD9i4qAspz3I/yHet1VvC3DiSy/fsUvv5pvJuNPh0LPOdCcqinDPg==} + /@babel/parser/7.21.2: + resolution: {integrity: sha512-URpaIJQwEkEC2T9Kn+Ai6Xe/02iNaVCuT/PtoRz3GPVJVDpPd7mLo+VddTbhCRU9TXqW5mSrQfXZyi8kDKOVpQ==} engines: {node: '>=6.0.0'} hasBin: true dependencies: - '@babel/types': 7.20.7 + '@babel/types': 7.21.2 dev: true - /@babel/plugin-transform-react-jsx-self/7.18.6_@babel+core@7.20.12: - resolution: {integrity: sha512-A0LQGx4+4Jv7u/tWzoJF7alZwnBDQd6cGLh9P+Ttk4dpiL+J5p7NSNv/9tlEFFJDq3kjxOavWmbm6t0Gk+A3Ig==} + /@babel/plugin-transform-react-jsx-self/7.21.0_@babel+core@7.21.0: + resolution: {integrity: sha512-f/Eq+79JEu+KUANFks9UZCcvydOOGMgF7jBrcwjHa5jTZD8JivnhCJYvmlhR/WTXBWonDExPoW0eO/CR4QJirA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.20.12 + '@babel/core': 7.21.0 '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-transform-react-jsx-source/7.19.6_@babel+core@7.20.12: + /@babel/plugin-transform-react-jsx-source/7.19.6_@babel+core@7.21.0: resolution: {integrity: sha512-RpAi004QyMNisst/pvSanoRdJ4q+jMCWyk9zdw/CyLB9j8RXEahodR6l2GyttDRyEVWZtbN+TpLiHJ3t34LbsQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.20.12 + '@babel/core': 7.21.0 '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/runtime/7.20.13: - resolution: {integrity: sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==} + /@babel/runtime/7.21.0: + resolution: {integrity: sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==} engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.13.11 @@ -233,30 +234,30 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/code-frame': 7.18.6 - '@babel/parser': 7.20.15 - '@babel/types': 7.20.7 + '@babel/parser': 7.21.2 + '@babel/types': 7.21.2 dev: true - /@babel/traverse/7.20.13: - resolution: {integrity: sha512-kMJXfF0T6DIS9E8cgdLCSAL+cuCK+YEZHWiLK0SXpTo8YRj5lpJu3CDNKiIBCne4m9hhTIqUg6SYTAI39tAiVQ==} + /@babel/traverse/7.21.2: + resolution: {integrity: sha512-ts5FFU/dSUPS13tv8XiEObDu9K+iagEKME9kAbaP7r0Y9KtZJZ+NGndDvWoRAYNpeWafbpFeki3q9QoMD6gxyw==} engines: {node: '>=6.9.0'} dependencies: '@babel/code-frame': 7.18.6 - '@babel/generator': 7.20.14 + '@babel/generator': 7.21.1 '@babel/helper-environment-visitor': 7.18.9 - '@babel/helper-function-name': 7.19.0 + '@babel/helper-function-name': 7.21.0 '@babel/helper-hoist-variables': 7.18.6 '@babel/helper-split-export-declaration': 7.18.6 - '@babel/parser': 7.20.15 - '@babel/types': 7.20.7 + '@babel/parser': 7.21.2 + '@babel/types': 7.21.2 debug: 4.3.4 globals: 11.12.0 transitivePeerDependencies: - supports-color dev: true - /@babel/types/7.20.7: - resolution: {integrity: sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==} + /@babel/types/7.21.2: + resolution: {integrity: sha512-3wRZSs7jiFaB8AjxiiD+VqN5DTG2iRvJGQ+qYFrs/654lg6kGTQWIOFjlBo5RaXuAZjBmP3+OQH4dmhqiiyYxw==} engines: {node: '>=6.9.0'} dependencies: '@babel/helper-string-parser': 7.19.4 @@ -514,7 +515,7 @@ packages: engines: {node: '>=12'} dependencies: '@babel/code-frame': 7.18.6 - '@babel/runtime': 7.20.13 + '@babel/runtime': 7.21.0 '@types/aria-query': 5.0.1 aria-query: 5.1.3 chalk: 4.1.2 @@ -530,9 +531,9 @@ packages: react: ^18.0.0 react-dom: ^18.0.0 dependencies: - '@babel/runtime': 7.20.13 + '@babel/runtime': 7.21.0 '@testing-library/dom': 8.20.0 - '@types/react-dom': 18.0.10 + '@types/react-dom': 18.0.11 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 dev: true @@ -555,22 +556,22 @@ packages: resolution: {integrity: sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==} dev: true - /@types/node/18.11.19: - resolution: {integrity: sha512-YUgMWAQBWLObABqrvx8qKO1enAvBUdjZOAWQ5grBAkp5LQv45jBvYKZ3oFS9iKRCQyFjqw6iuEa1vmFqtxYLZw==} + /@types/node/18.14.5: + resolution: {integrity: sha512-CRT4tMK/DHYhw1fcCEBwME9CSaZNclxfzVMe7GsO6ULSwsttbj70wSiX6rZdIjGblu93sTJxLdhNIT85KKI7Qw==} dev: true /@types/prop-types/15.7.5: resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} dev: true - /@types/react-dom/18.0.10: - resolution: {integrity: sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg==} + /@types/react-dom/18.0.11: + resolution: {integrity: sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==} dependencies: - '@types/react': 18.0.27 + '@types/react': 18.0.28 dev: true - /@types/react/18.0.27: - resolution: {integrity: sha512-3vtRKHgVxu3Jp9t718R9BuzoD4NcQ8YJ5XRzsSKxNDiDonD2MXIT1TmSkenxuCycZJoQT5d2vE8LwWJxBC1gmA==} + /@types/react/18.0.28: + resolution: {integrity: sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==} dependencies: '@types/prop-types': 15.7.5 '@types/scheduler': 0.16.2 @@ -581,67 +582,57 @@ packages: resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==} dev: true - /@vitejs/plugin-react/3.1.0_vite@4.1.1: + /@vitejs/plugin-react/3.1.0_vite@4.1.4: resolution: {integrity: sha512-AfgcRL8ZBhAlc3BFdigClmTUMISmmzHn7sB2h9U1odvc5U/MjWXsAaz18b/WoppUTDBzxOJwo2VdClfUcItu9g==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: vite: ^4.1.0-beta.0 dependencies: - '@babel/core': 7.20.12 - '@babel/plugin-transform-react-jsx-self': 7.18.6_@babel+core@7.20.12 - '@babel/plugin-transform-react-jsx-source': 7.19.6_@babel+core@7.20.12 + '@babel/core': 7.21.0 + '@babel/plugin-transform-react-jsx-self': 7.21.0_@babel+core@7.21.0 + '@babel/plugin-transform-react-jsx-source': 7.19.6_@babel+core@7.21.0 magic-string: 0.27.0 react-refresh: 0.14.0 - vite: 4.1.1 + vite: 4.1.4 transitivePeerDependencies: - supports-color dev: true - /@vitest/coverage-c8/0.28.4_happy-dom@8.2.6: - resolution: {integrity: sha512-btelLBxaWhHnywXRQxDlrvPhGdnuIaD3XulsxcZRIcnpLPbFu39dNTT0IYu2QWP2ZZrV0AmNtdLIfD4c77zMAg==} + /@vitest/coverage-c8/0.29.2_vitest@0.29.2: + resolution: {integrity: sha512-NmD3WirQCeQjjKfHu4iEq18DVOBFbLn9TKVdMpyi5YW2EtnS+K22/WE+9/wRrepOhyeTxuEFgxUVkCAE1GhbnQ==} + peerDependencies: + vitest: '>=0.29.0 <1' dependencies: - c8: 7.12.0 + c8: 7.13.0 picocolors: 1.0.0 std-env: 3.3.2 - vitest: 0.28.4_happy-dom@8.2.6 - transitivePeerDependencies: - - '@edge-runtime/vm' - - '@vitest/browser' - - '@vitest/ui' - - happy-dom - - jsdom - - less - - sass - - stylus - - sugarss - - supports-color - - terser + vitest: 0.29.2_happy-dom@8.9.0 dev: true - /@vitest/expect/0.28.4: - resolution: {integrity: sha512-JqK0NZ4brjvOSL8hXAnIsfi+jxDF7rH/ZWCGCt0FAqRnVFc1hXsfwXksQvEnKqD84avRt3gmeXoK4tNbmkoVsQ==} + /@vitest/expect/0.29.2: + resolution: {integrity: sha512-wjrdHB2ANTch3XKRhjWZN0UueFocH0cQbi2tR5Jtq60Nb3YOSmakjdAvUa2JFBu/o8Vjhj5cYbcMXkZxn1NzmA==} dependencies: - '@vitest/spy': 0.28.4 - '@vitest/utils': 0.28.4 + '@vitest/spy': 0.29.2 + '@vitest/utils': 0.29.2 chai: 4.3.7 dev: true - /@vitest/runner/0.28.4: - resolution: {integrity: sha512-Q8UV6GjDvBSTfUoq0QXVCNpNOUrWu4P2qvRq7ssJWzn0+S0ojbVOxEjMt+8a32X6SdkhF8ak+2nkppsqV0JyNQ==} + /@vitest/runner/0.29.2: + resolution: {integrity: sha512-A1P65f5+6ru36AyHWORhuQBJrOOcmDuhzl5RsaMNFe2jEkoj0faEszQS4CtPU/LxUYVIazlUtZTY0OEZmyZBnA==} dependencies: - '@vitest/utils': 0.28.4 + '@vitest/utils': 0.29.2 p-limit: 4.0.0 pathe: 1.1.0 dev: true - /@vitest/spy/0.28.4: - resolution: {integrity: sha512-8WuhfXLlvCXpNXEGJW6Gc+IKWI32435fQJLh43u70HnZ1otJOa2Cmg2Wy2Aym47ZnNCP4NolF+8cUPwd0MigKQ==} + /@vitest/spy/0.29.2: + resolution: {integrity: sha512-Hc44ft5kaAytlGL2PyFwdAsufjbdOvHklwjNy/gy/saRbg9Kfkxfh+PklLm1H2Ib/p586RkQeNFKYuJInUssyw==} dependencies: - tinyspy: 1.0.2 + tinyspy: 1.1.1 dev: true - /@vitest/utils/0.28.4: - resolution: {integrity: sha512-l2QztOLdc2LkR+w/lP52RGh8hW+Ul4KESmCAgVE8q737I7e7bQoAfkARKpkPJ4JQtGpwW4deqlj1732VZD7TFw==} + /@vitest/utils/0.29.2: + resolution: {integrity: sha512-F14/Uc+vCdclStS2KEoXJlOLAEyqRhnw0gM27iXw9bMTcyKRPJrQ+rlC6XZ125GIPvvKYMPpVxNhiou6PsEeYQ==} dependencies: cli-truncate: 3.1.0 diff: 5.1.0 @@ -726,18 +717,14 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001450 - electron-to-chromium: 1.4.286 - node-releases: 2.0.9 + caniuse-lite: 1.0.30001460 + electron-to-chromium: 1.4.319 + node-releases: 2.0.10 update-browserslist-db: 1.0.10_browserslist@4.21.5 dev: true - /buffer-from/1.1.2: - resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - dev: true - - /c8/7.12.0: - resolution: {integrity: sha512-CtgQrHOkyxr5koX1wEUmN/5cfDa2ckbHRA4Gy5LAL0zaCFtVWJS5++n+w4/sr2GWGerBxgTjpKeDclk/Qk6W/A==} + /c8/7.13.0: + resolution: {integrity: sha512-/NL4hQTv1gBL6J6ei80zu3IiTrmePDKXKXOTLpHvcIWZTVYQlDhVWjjWvkhICylE8EwwnMVzDZugCvdx0/DIIA==} engines: {node: '>=10.12.0'} hasBin: true dependencies: @@ -750,7 +737,7 @@ packages: istanbul-reports: 3.1.5 rimraf: 3.0.2 test-exclude: 6.0.0 - v8-to-istanbul: 9.0.1 + v8-to-istanbul: 9.1.0 yargs: 16.2.0 yargs-parser: 20.2.9 dev: true @@ -767,8 +754,8 @@ packages: get-intrinsic: 1.2.0 dev: true - /caniuse-lite/1.0.30001450: - resolution: {integrity: sha512-qMBmvmQmFXaSxexkjjfMvD5rnDL0+m+dUMZKoDYsGG8iZN29RuYh9eRoMvKsT6uMAWlyUUGDEQGJJYjzCIO9ew==} + /caniuse-lite/1.0.30001460: + resolution: {integrity: sha512-Bud7abqjvEjipUkpLs4D7gR0l8hBYBHoa+tGtKJHvT2AYzLp1z7EmVkUT4ERpVUfca8S2HGIVs883D8pUH1ZzQ==} dev: true /chai/4.3.7: @@ -904,7 +891,7 @@ packages: es-get-iterator: 1.1.3 get-intrinsic: 1.2.0 is-arguments: 1.1.1 - is-array-buffer: 3.0.1 + is-array-buffer: 3.0.2 is-date-object: 1.0.5 is-regex: 1.1.4 is-shared-array-buffer: 1.0.2 @@ -919,8 +906,8 @@ packages: which-typed-array: 1.1.9 dev: true - /define-properties/1.1.4: - resolution: {integrity: sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==} + /define-properties/1.2.0: + resolution: {integrity: sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==} engines: {node: '>= 0.4'} dependencies: has-property-descriptors: 1.0.0 @@ -940,8 +927,8 @@ packages: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} dev: true - /electron-to-chromium/1.4.286: - resolution: {integrity: sha512-Vp3CVhmYpgf4iXNKAucoQUDcCrBQX3XLBtwgFqP9BUXuucgvAV9zWp1kYU7LL9j4++s9O+12cb3wMtN4SJy6UQ==} + /electron-to-chromium/1.4.319: + resolution: {integrity: sha512-WeoI6NwZUgteKB+Wmn692S35QycwwNxwgTomNnoCJ79znBAjtBi6C/cIW62JkXmpJRX5rKNYSLDBdAM8l5fH0w==} dev: true /emoji-regex/8.0.0: @@ -976,8 +963,8 @@ packages: has-property-descriptors: 1.0.0 has-proto: 1.0.1 has-symbols: 1.0.3 - internal-slot: 1.0.4 - is-array-buffer: 3.0.1 + internal-slot: 1.0.5 + is-array-buffer: 3.0.2 is-callable: 1.2.7 is-negative-zero: 2.0.2 is-regex: 1.1.4 @@ -1112,7 +1099,7 @@ packages: engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 - define-properties: 1.1.4 + define-properties: 1.2.0 es-abstract: 1.21.1 functions-have-names: 1.2.3 dev: true @@ -1171,7 +1158,7 @@ packages: resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} engines: {node: '>= 0.4'} dependencies: - define-properties: 1.1.4 + define-properties: 1.2.0 dev: true /gopd/1.0.1: @@ -1184,11 +1171,12 @@ packages: resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} dev: true - /happy-dom/8.2.6: - resolution: {integrity: sha512-s53VwyMFpQPEZdN00M82i3tFTzz0T4kBVD4tu8b+im99s1NkLK6tfKGCCl2Jmf3ZWfFCRwS+DV2qkR7S1wmIhQ==} + /happy-dom/8.9.0: + resolution: {integrity: sha512-JZwJuGdR7ko8L61136YzmrLv7LgTh5b8XaEM3P709mLjyQuXJ3zHTDXvUtBBahRjGlcYW0zGjIiEWizoTUGKfA==} dependencies: css.escape: 1.5.1 he: 1.2.0 + iconv-lite: 0.6.3 node-fetch: 2.6.9 webidl-conversions: 7.0.0 whatwg-encoding: 2.0.0 @@ -1272,8 +1260,8 @@ packages: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} dev: true - /internal-slot/1.0.4: - resolution: {integrity: sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ==} + /internal-slot/1.0.5: + resolution: {integrity: sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==} engines: {node: '>= 0.4'} dependencies: get-intrinsic: 1.2.0 @@ -1289,8 +1277,8 @@ packages: has-tostringtag: 1.0.0 dev: true - /is-array-buffer/3.0.1: - resolution: {integrity: sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==} + /is-array-buffer/3.0.2: + resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} dependencies: call-bind: 1.0.2 get-intrinsic: 1.2.0 @@ -1544,13 +1532,13 @@ packages: brace-expansion: 1.1.11 dev: true - /mlly/1.1.0: - resolution: {integrity: sha512-cwzBrBfwGC1gYJyfcy8TcZU1f+dbH/T+TuOhtYP2wLv/Fb51/uV7HJQfBPtEupZ2ORLRU1EKFS/QfS3eo9+kBQ==} + /mlly/1.1.1: + resolution: {integrity: sha512-Jnlh4W/aI4GySPo6+DyTN17Q75KKbLTyFK8BrGhjNP4rxuUjbRWhE6gHg3bs33URWAF44FRm7gdQA348i3XxRw==} dependencies: acorn: 8.8.2 pathe: 1.1.0 - pkg-types: 1.0.1 - ufo: 1.0.1 + pkg-types: 1.0.2 + ufo: 1.1.1 dev: true /ms/2.1.2: @@ -1579,8 +1567,8 @@ packages: whatwg-url: 5.0.0 dev: true - /node-releases/2.0.9: - resolution: {integrity: sha512-2xfmOrRkGogbTK9R6Leda0DGiXeY3p2NJpy4+gNCffdUvV6mdEJnaDEic1i3Ec2djAo8jWYoJMR5PB0MSMpxUA==} + /node-releases/2.0.10: + resolution: {integrity: sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==} dev: true /normalize-package-data/2.5.0: @@ -1617,7 +1605,7 @@ packages: engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 - define-properties: 1.1.4 + define-properties: 1.2.0 dev: true /object-keys/1.1.1: @@ -1630,7 +1618,7 @@ packages: engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 - define-properties: 1.1.4 + define-properties: 1.2.0 has-symbols: 1.0.3 object-keys: 1.1.1 dev: true @@ -1724,11 +1712,11 @@ packages: engines: {node: '>=4'} dev: true - /pkg-types/1.0.1: - resolution: {integrity: sha512-jHv9HB+Ho7dj6ItwppRDDl0iZRYBD0jsakHXtFgoLr+cHSF6xC+QL54sJmWxyGxOLYSHm0afhXhXcQDQqH9z8g==} + /pkg-types/1.0.2: + resolution: {integrity: sha512-hM58GKXOcj8WTqUXnsQyJYXdeAPbythQgEF3nTcEo+nkD49chjQ9IKm/QJy9xf6JakXptz86h7ecP2024rrLaQ==} dependencies: jsonc-parser: 3.2.0 - mlly: 1.1.0 + mlly: 1.1.1 pathe: 1.1.0 dev: true @@ -1794,7 +1782,7 @@ packages: engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 - define-properties: 1.1.4 + define-properties: 1.2.0 functions-have-names: 1.2.3 dev: true @@ -1819,8 +1807,8 @@ packages: glob: 7.2.3 dev: true - /rollup/3.14.0: - resolution: {integrity: sha512-o23sdgCLcLSe3zIplT9nQ1+r97okuaiR+vmAPZPTDYB7/f3tgWIYNyiQveMsZwshBT0is4eGax/HH83Q7CG+/Q==} + /rollup/3.18.0: + resolution: {integrity: sha512-J8C6VfEBjkvYPESMQYxKHxNOh4A5a3FlP+0BETGo34HEcE4eTlgCrO2+eWzlu2a/sHs2QUkZco+wscH7jhhgWg==} engines: {node: '>=14.18.0', npm: '>=8.0.0'} hasBin: true optionalDependencies: @@ -1912,13 +1900,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /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'} @@ -1958,7 +1939,7 @@ packages: resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==} engines: {node: '>= 0.4'} dependencies: - internal-slot: 1.0.4 + internal-slot: 1.0.5 dev: true /string-width/4.2.3: @@ -1984,7 +1965,7 @@ packages: engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 - define-properties: 1.1.4 + define-properties: 1.2.0 es-abstract: 1.21.1 dev: true @@ -1992,7 +1973,7 @@ packages: resolution: {integrity: sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==} dependencies: call-bind: 1.0.2 - define-properties: 1.1.4 + define-properties: 1.2.0 es-abstract: 1.21.1 dev: true @@ -2000,7 +1981,7 @@ packages: resolution: {integrity: sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==} dependencies: call-bind: 1.0.2 - define-properties: 1.1.4 + define-properties: 1.2.0 es-abstract: 1.21.1 dev: true @@ -2066,8 +2047,8 @@ packages: engines: {node: '>=14.0.0'} dev: true - /tinyspy/1.0.2: - resolution: {integrity: sha512-bSGlgwLBYf7PnUsQ6WOc6SJ3pGOcd+d8AA6EUnLDDM0kWEstC1JIlSZA3UNliDXhd9ABoS7hiRBDCu+XP/sf1Q==} + /tinyspy/1.1.1: + resolution: {integrity: sha512-UVq5AXt/gQlti7oxoIg5oi/9r0WpF7DGEVwXgqWSMmyN16+e3tl5lIvTaOpJ3TAtu5xFzWccFRM4R5NaWHF+4g==} engines: {node: '>=14.0.0'} dev: true @@ -2099,8 +2080,8 @@ packages: hasBin: true dev: true - /ufo/1.0.1: - resolution: {integrity: sha512-boAm74ubXHY7KJQZLlXrtMz52qFvpsbOxDcZOnw/Wf+LS4Mmyu7JxmzD4tDLtUQtmZECypJ0FrCz4QIe6dvKRA==} + /ufo/1.1.1: + resolution: {integrity: sha512-MvlCc4GHrmZdAllBc0iUDowff36Q9Ndw/UzqmEKyrfSzokTd9ZCy1i+IIk5hrYKkjoYVQyNbrw7/F8XJ2rEwTg==} dev: true /unbox-primitive/1.0.2: @@ -2123,8 +2104,8 @@ packages: picocolors: 1.0.0 dev: true - /v8-to-istanbul/9.0.1: - resolution: {integrity: sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==} + /v8-to-istanbul/9.1.0: + resolution: {integrity: sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==} engines: {node: '>=10.12.0'} dependencies: '@jridgewell/trace-mapping': 0.3.17 @@ -2139,19 +2120,17 @@ packages: spdx-expression-parse: 3.0.1 dev: true - /vite-node/0.28.4_@types+node@18.11.19: - resolution: {integrity: sha512-KM0Q0uSG/xHHKOJvVHc5xDBabgt0l70y7/lWTR7Q0pR5/MrYxadT+y32cJOE65FfjGmJgxpVEEY+69btJgcXOQ==} + /vite-node/0.29.2_@types+node@18.14.5: + resolution: {integrity: sha512-5oe1z6wzI3gkvc4yOBbDBbgpiWiApvuN4P55E8OI131JGrSuo4X3SOZrNmZYo4R8Zkze/dhi572blX0zc+6SdA==} engines: {node: '>=v14.16.0'} hasBin: true dependencies: cac: 6.7.14 debug: 4.3.4 - mlly: 1.1.0 + mlly: 1.1.1 pathe: 1.1.0 picocolors: 1.0.0 - source-map: 0.6.1 - source-map-support: 0.5.21 - vite: 4.1.1_@types+node@18.11.19 + vite: 4.1.4_@types+node@18.14.5 transitivePeerDependencies: - '@types/node' - less @@ -2162,8 +2141,8 @@ packages: - terser dev: true - /vite/4.1.1: - resolution: {integrity: sha512-LM9WWea8vsxhr782r9ntg+bhSFS06FJgCvvB0+8hf8UWtvaiDagKYWXndjfX6kGl74keHJUcpzrQliDXZlF5yg==} + /vite/4.1.4: + resolution: {integrity: sha512-3knk/HsbSTKEin43zHu7jTwYWv81f8kgAL99G5NWBcA1LKvtvcVAC4JjBH1arBunO9kQka+1oGbrMKOjk4ZrBg==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true peerDependencies: @@ -2190,13 +2169,13 @@ packages: esbuild: 0.16.17 postcss: 8.4.21 resolve: 1.22.1 - rollup: 3.14.0 + rollup: 3.18.0 optionalDependencies: fsevents: 2.3.2 dev: true - /vite/4.1.1_@types+node@18.11.19: - resolution: {integrity: sha512-LM9WWea8vsxhr782r9ntg+bhSFS06FJgCvvB0+8hf8UWtvaiDagKYWXndjfX6kGl74keHJUcpzrQliDXZlF5yg==} + /vite/4.1.4_@types+node@18.14.5: + resolution: {integrity: sha512-3knk/HsbSTKEin43zHu7jTwYWv81f8kgAL99G5NWBcA1LKvtvcVAC4JjBH1arBunO9kQka+1oGbrMKOjk4ZrBg==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true peerDependencies: @@ -2220,17 +2199,17 @@ packages: terser: optional: true dependencies: - '@types/node': 18.11.19 + '@types/node': 18.14.5 esbuild: 0.16.17 postcss: 8.4.21 resolve: 1.22.1 - rollup: 3.14.0 + rollup: 3.18.0 optionalDependencies: fsevents: 2.3.2 dev: true - /vitest/0.28.4_happy-dom@8.2.6: - resolution: {integrity: sha512-sfWIy0AdlbyGRhunm+TLQEJrFH9XuRPdApfubsyLcDbCRrUX717BRQKInTgzEfyl2Ipi1HWoHB84Nqtcwxogcg==} + /vitest/0.29.2_happy-dom@8.9.0: + resolution: {integrity: sha512-ydK9IGbAvoY8wkg29DQ4ivcVviCaUi3ivuPKfZEVddMTenFHUfB8EEDXQV8+RasEk1ACFLgMUqAaDuQ/Nk+mQA==} engines: {node: '>=v14.16.0'} hasBin: true peerDependencies: @@ -2253,17 +2232,17 @@ packages: dependencies: '@types/chai': 4.3.4 '@types/chai-subset': 1.3.3 - '@types/node': 18.11.19 - '@vitest/expect': 0.28.4 - '@vitest/runner': 0.28.4 - '@vitest/spy': 0.28.4 - '@vitest/utils': 0.28.4 + '@types/node': 18.14.5 + '@vitest/expect': 0.29.2 + '@vitest/runner': 0.29.2 + '@vitest/spy': 0.29.2 + '@vitest/utils': 0.29.2 acorn: 8.8.2 acorn-walk: 8.2.0 cac: 6.7.14 chai: 4.3.7 debug: 4.3.4 - happy-dom: 8.2.6 + happy-dom: 8.9.0 local-pkg: 0.4.3 pathe: 1.1.0 picocolors: 1.0.0 @@ -2272,9 +2251,9 @@ packages: strip-literal: 1.0.1 tinybench: 2.3.1 tinypool: 0.3.1 - tinyspy: 1.0.2 - vite: 4.1.1_@types+node@18.11.19 - vite-node: 0.28.4_@types+node@18.11.19 + tinyspy: 1.1.1 + vite: 4.1.4_@types+node@18.14.5 + vite-node: 0.29.2_@types+node@18.14.5 why-is-node-running: 2.2.2 transitivePeerDependencies: - less diff --git a/test/basic.test.tsx b/test/basic.test.tsx index e1aa707..f00165a 100644 --- a/test/basic.test.tsx +++ b/test/basic.test.tsx @@ -1,34 +1,15 @@ -import { act, cleanup, fireEvent, render, screen } from '@testing-library/react' -import React, { useEffect } from 'react' +import { act, cleanup, fireEvent, render } from '@testing-library/react' +import React from 'react' import { beforeEach, describe, expect, test } from 'vitest' import { useForm } from '../lib' +import { Insight, Util } from './shared' beforeEach(cleanup) -const Insight = { - Portal({ data }: { data: any }) { - return
{JSON.stringify(data)}
- }, - async verify(obj: any) { - const result = await screen.findByTestId('result') - const data = JSON.parse(result.innerText) - expect(data).toMatchObject(obj) - }, -} - -const Util = { - find(id: string) { - return screen.findByTestId(id) - }, - writeToField(node: HTMLInputElement, value: string) { - fireEvent.change(node, { target: { value } }) - }, -} - describe('Field', () => { test('Basic Form', async () => { - const BasicForm = () => { + function Component() { const form = useForm({ username: '', password: '' }) const { field } = form return ( @@ -47,7 +28,7 @@ describe('Field', () => { ) } - render() + render() async function inputIntoForm(id: string, value: string) { const node = await Util.find(id) await act(() => { @@ -60,27 +41,65 @@ describe('Field', () => { await inputIntoForm('password', 'bar') }) - test('setField', async () => { - const value = 'foo' - const Component = () => { - const { field, setField, form } = useForm({ username: '', password: '' }) - useEffect(() => setField('username', value), []) + test.skip('Checkbox', async () => { + function Component() { + const { field, form } = useForm({ cool: false }) return ( -
- + + e.target.checked, + })} + /> -
+ ) } + render() - const node = await screen.findByTestId('field') - expect(node.value).toBe(value) - Insight.verify({ username: value, password: '' }) + const field = await Util.find('field') + expect(field.checked).toBe(false) + await Insight.verify({ cool: false }) + await act(() => { + // Bugged for now + fireEvent.click(field) + }) + expect(field.checked).toBe(true) + await Insight.verify({ cool: true }) + }) + + test('Select', async () => { + function Component() { + const { form, field } = useForm({ letter: '' }) + return ( + <> + + + + ) + } + + render() + const field = await Util.find('field') + const value = 'b' + await act(() => { + fireEvent.change(field, { target: { value } }) + }) + expect(field.value).toBe(value) + await Insight.verify({ letter: value }) }) test('Field sync', async () => { const value = 'foo' - const Component = () => { + function Component() { const { field, form } = useForm({ name: '' }) return (
@@ -101,54 +120,3 @@ describe('Field', () => { expect(a.value).toBe(b.value) }) }) - -describe('Validation', () => { - test('Basic', async () => { - const Component = () => { - const { errors, field } = useForm({ password: '' }, { rules: { password: [(p) => p.length > 8] } }) - - return ( -
- - -
- ) - } - render() - const node = await Util.find('field') - await act(() => { - Util.writeToField(node, '123') - }) - Insight.verify({ password: true }) - }) - - test('Array of rules', async () => { - const Component = () => { - const { errors, field } = useForm({ password: '' }, { rules: { password: [(p) => p.length > 8, /#/] } }) - - return ( -
- - -
- ) - } - render() - const node = await Util.find('field') - await act(() => { - Util.writeToField(node, '12345678') - }) - Insight.verify({ password: true }) - await act(() => { - Util.writeToField(node, '1234#5678') - }) - Insight.verify({}) - }) -}) - -// Is valid -// Reset / setForm -// Set error -// Checkbox -// Extractor -// Custom extractor diff --git a/test/blocks.tsx b/test/blocks.tsx new file mode 100644 index 0000000..08d07c9 --- /dev/null +++ b/test/blocks.tsx @@ -0,0 +1,11 @@ +import React from 'react' + +// Custom field with non standard setter and getter. Emulate custom component from a library +export function NumberField(props: { number: number; update: (value: number) => void }) { + return props.update(parseInt(e.target.value))} /> +} + +// Component that needs a different extractor, as it's returning the actual value and not the event. +export function DirectReturnInput(props: { value: string; onChange: (v: string) => void }) { + return props.onChange(e.target.value)} /> +} diff --git a/test/options.test.tsx b/test/options.test.tsx new file mode 100644 index 0000000..ab14a69 --- /dev/null +++ b/test/options.test.tsx @@ -0,0 +1,57 @@ +import { act, cleanup, render } from '@testing-library/react' +import React from 'react' +import { beforeEach, describe, test } from 'vitest' + +import { useForm } from '../lib' +import { DirectReturnInput, NumberField } from './blocks' +import { Insight, Util } from './shared' + +beforeEach(cleanup) + +describe('Options', () => { + test('Custom component props', async () => { + function Component() { + const { form, field } = useForm({ foo: 5 }) + + return ( +
+ + +
+ ) + } + + render() + const node = await Util.find('field') + await act(() => { + Util.writeToField(node, '123') + }) + Insight.verify({ foo: 123 }) + }) + + test('Disable default extractor', async () => { + function Component() { + const { form, field } = useForm({ username: '' }) + + return ( +
+ + +
+ ) + } + + render() + const node = await Util.find('field') + await act(() => { + Util.writeToField(node, '123') + }) + Insight.verify({ username: '123' }) + }) +}) diff --git a/test/shared.tsx b/test/shared.tsx new file mode 100644 index 0000000..53fb3ab --- /dev/null +++ b/test/shared.tsx @@ -0,0 +1,23 @@ +import { fireEvent, screen } from '@testing-library/react' +import React from 'react' +import { expect } from 'vitest' + +export const Insight = { + Portal({ data }: { data: any }) { + return
{JSON.stringify(data)}
+ }, + async verify(obj: any) { + const result = await screen.findByTestId('result') + const data = JSON.parse(result.innerText) + expect(data).toMatchObject(obj) + }, +} + +export const Util = { + find(id: string) { + return screen.findByTestId(id) + }, + writeToField(node: HTMLInputElement, value: string) { + fireEvent.change(node, { target: { value } }) + }, +} diff --git a/test/utility.test.tsx b/test/utility.test.tsx new file mode 100644 index 0000000..fa8bc41 --- /dev/null +++ b/test/utility.test.tsx @@ -0,0 +1,53 @@ +import { cleanup, render } from '@testing-library/react' +import React, { useEffect } from 'react' +import { beforeEach, describe, expect, test } from 'vitest' + +import { useForm } from '../lib' +import { Insight, Util } from './shared' + +beforeEach(cleanup) + +describe('Utility', () => { + test('Manually set a single field', async () => { + const value = 'foo' + function Component() { + const { field, setField, form } = useForm({ username: '', password: '' }) + useEffect(() => setField('username', value), []) + return ( +
+ + +
+ ) + } + render() + const node = await Util.find('field') + expect(node.value).toBe(value) + Insight.verify({ username: value, password: '' }) + }) + + test('Manually set the form state later on', async () => { + const value = 'foo' + function Component() { + const { form, field, setForm } = useForm({ username: '' }) + + useEffect(() => { + setForm({ + username: value, + }) + }, []) + + return ( + + + + + ) + } + + render() + const node = await Util.find('username') + expect(node.value).toBe(value) + await Insight.verify({ username: value }) + }) +}) diff --git a/test/validation.test.tsx b/test/validation.test.tsx new file mode 100644 index 0000000..2f6d8e8 --- /dev/null +++ b/test/validation.test.tsx @@ -0,0 +1,109 @@ +import { act, cleanup, fireEvent, render } from '@testing-library/react' +import React from 'react' +import { beforeEach, describe, test } from 'vitest' + +import { useForm } from '../lib' +import { Insight, Util } from './shared' + +beforeEach(cleanup) + +describe('Validation', () => { + test('Basic', async () => { + function Component() { + const { errors, field } = useForm({ password: '' }, { rules: { password: [(p) => p.length > 8] } }) + + return ( +
+ + +
+ ) + } + render() + const node = await Util.find('field') + await act(() => { + Util.writeToField(node, '123') + }) + Insight.verify({ password: true }) + }) + + test('Array of rules', async () => { + function Component() { + const { errors, field } = useForm({ password: '' }, { rules: { password: [(p) => p.length > 8, /#/] } }) + + return ( +
+ + +
+ ) + } + render() + const node = await Util.find('field') + await act(() => { + Util.writeToField(node, '12345678') + }) + Insight.verify({ password: true }) + await act(() => { + Util.writeToField(node, '1234#5678') + }) + Insight.verify({}) + }) + + // https://github.com/testing-library/react-testing-library/issues/828 + test.skip('Invalid rule', async () => { + function Component() { + const { field } = useForm( + { username: '' }, + { + rules: { + username: [ + // @ts-ignore Give an invalid rules and expect to fail + 5, + ], + }, + } + ) + + return ( +
+ +
+ ) + } + + render() + const field = await Util.find('field') + await act(() => { + Util.writeToField(field, 'abc') + }) + }) + + test('Invalid dependency on other component', async () => { + function Component() { + const { errors, field } = useForm( + { min: 10, max: 20 }, + { + rules: { + max: (value, form) => value > form.min, + }, + } + ) + return ( +
+ + + + + ) + } + + render() + const field = await Util.find('max') + const value = 5 + await act(() => { + fireEvent.change(field, { target: { value } }) + }) + await Insight.verify({ max: true }) + }) +})