diff --git a/.storybook/main.js b/.storybook/main.js index c2e1d6a..424ac08 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -1,5 +1,5 @@ export default { stories: ['../stories/**/*.stories.@(js|jsx|ts|tsx|mdx)'], - addons: ['@storybook/addon-links'], + addons: ['@storybook/addon-links', '@storybook/addon-vitest'], framework: '@storybook/web-components-vite', }; diff --git a/.storybook/preview.js b/.storybook/preview.js index 47e288c..656a21b 100644 --- a/.storybook/preview.js +++ b/.storybook/preview.js @@ -1,3 +1,18 @@ +import i18next from 'i18next'; + +// Initialize i18next for Storybook environment +i18next.init({ + lng: 'en', + resources: { + en: { + translation: { + 'Load more': 'Load more', + 'Load all': 'Load all', + }, + }, + }, +}); + const preview = { parameters: { controls: { diff --git a/.storybook/vitest.setup.ts b/.storybook/vitest.setup.ts new file mode 100644 index 0000000..a58536e --- /dev/null +++ b/.storybook/vitest.setup.ts @@ -0,0 +1,7 @@ +import { setProjectAnnotations } from '@storybook/web-components'; +import { beforeAll } from 'vitest'; +import * as previewAnnotations from './preview'; + +const annotations = setProjectAnnotations([previewAnnotations]); + +beforeAll(annotations.beforeAll); diff --git a/package-lock.json b/package-lock.json index 47c5d18..7798db6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,26 +28,27 @@ "@commitlint/config-conventional": "^20.0.0", "@eslint/eslintrc": "^2.0.0", "@neovici/cfg": "^2.8.0", - "@neovici/testing": "^2.0.0", - "@open-wc/testing": "^4.0.0", - "@open-wc/testing-helpers": "^3.0.1", + "@neovici/testing": "^2.2.0", "@semantic-release/changelog": "^6.0.0", "@semantic-release/git": "^10.0.0", "@storybook/addon-links": "^10.0.0", + "@storybook/addon-vitest": "^10.0.0", "@storybook/web-components": "^10.0.0", "@storybook/web-components-vite": "^10.0.0", - "@types/mocha": "^10.0.6", "@types/node": "^22.10.2", + "@types/react": "^19.2.13", "@types/split.js": "^1.6.0", - "@web/dev-server-esbuild": "^1.0.4", - "@web/test-runner-playwright": "^0.11.1", + "@vitest/browser": "^4.0.0", + "@vitest/browser-playwright": "^4.0.0", "esbuild": "^0.27.0", "http-server": "^14.1.1", "husky": "^9.0.11", + "jsdom": "^26.0.0", + "playwright": "^1.52.0", "semantic-release": "^25.0.0", - "sinon": "^19.0.0", "storybook": "^10.0.0", - "typescript": "^5.4.3" + "typescript": "^5.4.3", + "vitest": "^4.0.0" } }, "node_modules/@actions/core": { @@ -106,6 +107,27 @@ "dev": true, "license": "MIT" }, + "node_modules/@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/@babel/code-frame": { "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", @@ -408,6 +430,123 @@ "node": ">=v18" } }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + } + }, "node_modules/@emnapi/core": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", @@ -1052,16 +1191,6 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@esm-bundle/chai": { - "version": "4.3.4-fix.0", - "resolved": "https://registry.npmjs.org/@esm-bundle/chai/-/chai-4.3.4-fix.0.tgz", - "integrity": "sha512-26SKdM4uvDWlY8/OOOxSB1AqQWeBosCX3wRYUZO7enTAj03CtVxIiCimYVG2WpULcyV51qapK4qTovwkUr5Mlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/chai": "^4.2.12" - } - }, "node_modules/@floating-ui/core": { "version": "1.7.4", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.4.tgz", @@ -1608,13 +1737,12 @@ "license": "MIT" }, "node_modules/@neovici/testing": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@neovici/testing/-/testing-2.1.1.tgz", - "integrity": "sha512-mZLicbQeWbeWqrO+L8JbBlqq/szb6C5P6ed6YJmnZ5VKngOOi4Jot1VUMCqKtDdCM7z+oLTL3FUEtDDVqAFtFQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@neovici/testing/-/testing-2.2.0.tgz", + "integrity": "sha512-933xOi5sYOTFrTC4omR27OKdtD3KY1quIXY6Y4t+vORbtT/DESDSd44tmy30eeICSmX7aEErxmDuL8Qq6JeAFA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@open-wc/testing": "^4.0.0", "@pionjs/pion": "^2.0.0", "lit-html": "^3.0.0" } @@ -1814,62 +1942,6 @@ "@octokit/openapi-types": "^27.0.0" } }, - "node_modules/@open-wc/dedupe-mixin": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@open-wc/dedupe-mixin/-/dedupe-mixin-2.0.1.tgz", - "integrity": "sha512-+R4VxvceUxHAUJXJQipkkoV9fy10vNo+OnUnGKZnVmcwxMl460KLzytnUM4S35SI073R0yZQp9ra0MbPUwVcEA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@open-wc/scoped-elements": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@open-wc/scoped-elements/-/scoped-elements-3.0.6.tgz", - "integrity": "sha512-w1ayJaUUmBw8tALtqQ6cBueld+op+bufujzbrOdH0uCTXnSQkONYZzOH+9jyQ8auVgKLqcxZ8oU6SzfqQhQkPg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@open-wc/dedupe-mixin": "^2.0.0", - "lit": "^3.0.0" - } - }, - "node_modules/@open-wc/semantic-dom-diff": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@open-wc/semantic-dom-diff/-/semantic-dom-diff-0.20.1.tgz", - "integrity": "sha512-mPF/RPT2TU7Dw41LEDdaeP6eyTOWBD4z0+AHP4/d0SbgcfJZVRymlIB6DQmtz0fd2CImIS9kszaMmwMt92HBPA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/chai": "^4.3.1", - "@web/test-runner-commands": "^0.9.0" - } - }, - "node_modules/@open-wc/testing": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@open-wc/testing/-/testing-4.0.0.tgz", - "integrity": "sha512-KI70O0CJEpBWs3jrTju4BFCy7V/d4tFfYWkg8pMzncsDhD7TYNHLw5cy+s1FHXIgVFetnMDhPpwlKIPvtTQW7w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@esm-bundle/chai": "^4.3.4-fix.0", - "@open-wc/semantic-dom-diff": "^0.20.0", - "@open-wc/testing-helpers": "^3.0.0", - "@types/chai-dom": "^1.11.0", - "@types/sinon-chai": "^3.2.3", - "chai-a11y-axe": "^1.5.0" - } - }, - "node_modules/@open-wc/testing-helpers": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@open-wc/testing-helpers/-/testing-helpers-3.0.1.tgz", - "integrity": "sha512-hyNysSatbgT2FNxHJsS3rGKcLEo6+HwDFu1UQL6jcSQUabp/tj3PyX7UnXL3H5YGv0lJArdYLSnvjLnjn3O2fw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@open-wc/scoped-elements": "^3.0.2", - "lit": "^2.0.0 || ^3.0.0", - "lit-html": "^2.0.0 || ^3.0.0" - } - }, "node_modules/@pionjs/pion": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/@pionjs/pion/-/pion-2.11.0.tgz", @@ -1940,6 +2012,13 @@ "node": ">=12" } }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, "node_modules/@polymer/iron-flex-layout": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@polymer/iron-flex-layout/-/iron-flex-layout-3.0.1.tgz", @@ -3135,53 +3214,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "13.0.5", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", - "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1" - } - }, - "node_modules/@sinonjs/samsam": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.3.tgz", - "integrity": "sha512-hw6HbX+GyVZzmaYNh82Ecj1vdGZrqVIn/keDTg63IgAwiQPO+xCz99uG6Woqgb4tM0mUiFENKZ4cqd7IX94AXQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1", - "type-detect": "^4.1.0" - } - }, - "node_modules/@sinonjs/samsam/node_modules/type-detect": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", - "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/@sinonjs/text-encoding": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz", - "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==", + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", "dev": true, - "license": "(Unlicense OR Apache-2.0)" + "license": "MIT" }, "node_modules/@storybook/addon-links": { "version": "10.2.1", @@ -3206,6 +3244,42 @@ } } }, + "node_modules/@storybook/addon-vitest": { + "version": "10.2.7", + "resolved": "https://registry.npmjs.org/@storybook/addon-vitest/-/addon-vitest-10.2.7.tgz", + "integrity": "sha512-pVFEZRSi7APkVcAdwuTL1crXa65fJ1ME/uVXajRR832UssIk8YwOGle3P7ciqPEL/8KSsn+GbDGQXLsKN2/oFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@storybook/global": "^5.0.0", + "@storybook/icons": "^2.0.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "@vitest/browser": "^3.0.0 || ^4.0.0", + "@vitest/browser-playwright": "^4.0.0", + "@vitest/runner": "^3.0.0 || ^4.0.0", + "storybook": "^10.2.7", + "vitest": "^3.0.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/runner": { + "optional": true + }, + "vitest": { + "optional": true + } + } + }, "node_modules/@storybook/builder-vite": { "version": "10.2.1", "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-10.2.1.tgz", @@ -3431,23 +3505,6 @@ "@types/node": "*" } }, - "node_modules/@types/chai": { - "version": "4.3.20", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.20.tgz", - "integrity": "sha512-/pC9HAB5I/xMlc5FP77qjCnI16ChlJfW0tGa0IUcFn38VJrTV6DeZ60NU5KZBtaOZqjdpwTWohz5HU1RrhiYxQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/chai-dom": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/@types/chai-dom/-/chai-dom-1.11.3.tgz", - "integrity": "sha512-EUEZI7uID4ewzxnU7DJXtyvykhQuwe+etJ1wwOiJyQRTH/ifMWKX+ghiXkxCUvNJ6IQDodf0JXhuP6zZcy2qXQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/chai": "*" - } - }, "node_modules/@types/co-body": { "version": "6.1.3", "resolved": "https://registry.npmjs.org/@types/co-body/-/co-body-6.1.3.tgz", @@ -3648,13 +3705,6 @@ "@types/koa": "*" } }, - "node_modules/@types/mocha": { - "version": "10.0.10", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", - "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/node": { "version": "22.19.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.7.tgz", @@ -3694,6 +3744,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/react": { + "version": "19.2.13", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.13.tgz", + "integrity": "sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, "node_modules/@types/resolve": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", @@ -3722,34 +3782,6 @@ "@types/node": "*" } }, - "node_modules/@types/sinon": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-21.0.0.tgz", - "integrity": "sha512-+oHKZ0lTI+WVLxx1IbJDNmReQaIsQJjN2e7UUrJHEeByG7bFeKJYsv1E75JxTQ9QKJDp21bAa/0W2Xo4srsDnw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/sinonjs__fake-timers": "*" - } - }, - "node_modules/@types/sinon-chai": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/@types/sinon-chai/-/sinon-chai-3.2.12.tgz", - "integrity": "sha512-9y0Gflk3b0+NhQZ/oxGtaAJDvRywCa5sIyaVnounqLvmf93yBF4EgIRspePtkMs3Tr844nCclYMlcCNmLCvjuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/chai": "*", - "@types/sinon": "*" - } - }, - "node_modules/@types/sinonjs__fake-timers": { - "version": "15.0.1", - "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-15.0.1.tgz", - "integrity": "sha512-Ko2tjWJq8oozHzHV+reuvS5KYIRAokHnGbDwGh/J64LntgpbuylF74ipEL24HCyRjf9FOlBiBHWBR1RlVKsI1w==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/split.js": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/@types/split.js/-/split.js-1.6.0.tgz", @@ -4314,6 +4346,124 @@ "win32" ] }, + "node_modules/@vitest/browser": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-4.0.18.tgz", + "integrity": "sha512-gVQqh7paBz3gC+ZdcCmNSWJMk70IUjDeVqi+5m5vYpEHsIwRgw3Y545jljtajhkekIpIp5Gg8oK7bctgY0E2Ng==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@vitest/mocker": "4.0.18", + "@vitest/utils": "4.0.18", + "magic-string": "^0.30.21", + "pixelmatch": "7.1.0", + "pngjs": "^7.0.0", + "sirv": "^3.0.2", + "tinyrainbow": "^3.0.3", + "ws": "^8.18.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": "4.0.18" + } + }, + "node_modules/@vitest/browser-playwright": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-4.0.18.tgz", + "integrity": "sha512-gfajTHVCiwpxRj1qh0Sh/5bbGLG4F/ZH/V9xvFVoFddpITfMta9YGow0W6ZpTTORv2vdJuz9TnrNSmjKvpOf4g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@vitest/browser": "4.0.18", + "@vitest/mocker": "4.0.18", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "playwright": "*", + "vitest": "4.0.18" + }, + "peerDependenciesMeta": { + "playwright": { + "optional": false + } + } + }, + "node_modules/@vitest/browser-playwright/node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@vitest/browser/node_modules/@vitest/pretty-format": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz", + "integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/browser/node_modules/@vitest/utils": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz", + "integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.18", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/browser/node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@vitest/browser/node_modules/ws": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "dev": true, + "license": "MIT", + "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 + } + } + }, "node_modules/@vitest/expect": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", @@ -4342,6 +4492,53 @@ "assertion-error": "^2.0.1" } }, + "node_modules/@vitest/mocker": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz", + "integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.0.18", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/mocker/node_modules/@vitest/spy": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz", + "integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/@vitest/pretty-format": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", @@ -4355,6 +4552,96 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/@vitest/runner": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz", + "integrity": "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@vitest/utils": "4.0.18", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner/node_modules/@vitest/pretty-format": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz", + "integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner/node_modules/@vitest/utils": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz", + "integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.18", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner/node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz", + "integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.18", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot/node_modules/@vitest/pretty-format": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz", + "integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot/node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@vitest/spy": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", @@ -5533,16 +5820,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/axe-core": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.1.tgz", - "integrity": "sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A==", - "dev": true, - "license": "MPL-2.0", - "engines": { - "node": ">=4" - } - }, "node_modules/b4a": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", @@ -5863,16 +6140,6 @@ "node": ">=18" } }, - "node_modules/chai-a11y-axe": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/chai-a11y-axe/-/chai-a11y-axe-1.5.0.tgz", - "integrity": "sha512-V/Vg/zJDr9aIkaHJ2KQu7lGTQQm5ZOH4u1k5iTMvIXuSVlSuUo0jcSpSqf9wUn9zl6oQXa4e4E0cqH18KOgKlQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "axe-core": "^4.3.3" - } - }, "node_modules/chalk": { "version": "5.6.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", @@ -6596,6 +6863,27 @@ "dev": true, "license": "MIT" }, + "node_modules/cssstyle": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, "node_modules/dargs": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/dargs/-/dargs-8.1.0.tgz", @@ -6619,6 +6907,20 @@ "node": ">= 14" } }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", @@ -6708,6 +7010,13 @@ } } }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, "node_modules/deep-eql": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", @@ -7480,6 +7789,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -8236,6 +8546,16 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/extract-zip": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", @@ -9926,6 +10246,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -10246,6 +10573,134 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdom": { + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", + "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssstyle": "^4.2.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.5.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.16", + "parse5": "^7.2.1", + "rrweb-cssom": "^0.8.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.1.1", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.1.1", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/jsdom/node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/jsdom/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jsdom/node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/jsdom/node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/jsdom/node_modules/ws": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "dev": true, + "license": "MIT", + "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 + } + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -10334,13 +10789,6 @@ "node": "*" } }, - "node_modules/just-extend": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", - "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", - "dev": true, - "license": "MIT" - }, "node_modules/keygrip": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", @@ -10748,6 +11196,16 @@ "lz-string": "bin/bin.js" } }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, "node_modules/make-asynchronous": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/make-asynchronous/-/make-asynchronous-1.0.1.tgz", @@ -11046,6 +11504,16 @@ "node": ">=10" } }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -11148,20 +11616,6 @@ "node": ">= 0.4.0" } }, - "node_modules/nise": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/nise/-/nise-6.1.1.tgz", - "integrity": "sha512-aMSAzLVY7LyeM60gvBS423nBmIPP+Wy7St7hsb+8/fc1HmeoHJfLO8CKse4u3BtOZvQLJghYPI2i/1WZrEj5/g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1", - "@sinonjs/fake-timers": "^13.0.1", - "@sinonjs/text-encoding": "^0.7.3", - "just-extend": "^6.2.0", - "path-to-regexp": "^8.1.0" - } - }, "node_modules/node-emoji": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz", @@ -13322,6 +13776,13 @@ "inBundle": true, "license": "ISC" }, + "node_modules/nwsapi": { + "version": "2.2.23", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz", + "integrity": "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==", + "dev": true, + "license": "MIT" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -13429,6 +13890,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -13824,17 +14296,6 @@ "dev": true, "license": "MIT" }, - "node_modules/path-to-regexp": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", - "dev": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -13845,6 +14306,13 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, "node_modules/pathval": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", @@ -13892,6 +14360,19 @@ "node": ">=4" } }, + "node_modules/pixelmatch": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-7.1.0.tgz", + "integrity": "sha512-1wrVzJ2STrpmONHKBy228LM1b84msXDUoAzVEl0R8Mz4Ce6EPr+IVtxm8+yvrqLYMHswREkjYFaMxnyGnaY3Ng==", + "dev": true, + "license": "ISC", + "dependencies": { + "pngjs": "^7.0.0" + }, + "bin": { + "pixelmatch": "bin/pixelmatch" + } + }, "node_modules/pkg-conf": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.1.0.tgz", @@ -13975,6 +14456,7 @@ "integrity": "sha512-2SVA0sbPktiIY/MCOPX8e86ehA/e+tDNq+e5Y8qjKYti2Z/JG7xnronT/TXTIkKbYGWlCbuucZ6dziEgkoEjQQ==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "playwright-core": "1.58.0" }, @@ -14001,6 +14483,16 @@ "node": ">=18" } }, + "node_modules/pngjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", + "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.19.0" + } + }, "node_modules/portfinder": { "version": "1.0.38", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.38.tgz", @@ -14879,6 +15371,13 @@ "fsevents": "~2.3.2" } }, + "node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true, + "license": "MIT" + }, "node_modules/run-applescript": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", @@ -14985,6 +15484,19 @@ "dev": true, "license": "MIT" }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/scheduler": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", @@ -15575,6 +16087,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -15688,33 +16207,19 @@ "node": ">=4" } }, - "node_modules/sinon": { - "version": "19.0.5", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-19.0.5.tgz", - "integrity": "sha512-r15s9/s+ub/d4bxNXqIUmwp6imVSdTorIRaxoecYjqTVLZ8RuoXr/4EDGwIBo6Waxn7f2gnURX9zuhAfCwaF6Q==", + "node_modules/sirv": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "dependencies": { - "@sinonjs/commons": "^3.0.1", - "@sinonjs/fake-timers": "^13.0.5", - "@sinonjs/samsam": "^8.0.1", - "diff": "^7.0.0", - "nise": "^6.1.1", - "supports-color": "^7.2.0" + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/sinon" - } - }, - "node_modules/sinon/node_modules/diff": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", - "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", - "dev": true, - "license": "BSD-3-Clause", "engines": { - "node": ">=0.3.1" + "node": ">=18" } }, "node_modules/skin-tone": { @@ -15888,6 +16393,13 @@ "node": ">=12.0.0" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, "node_modules/statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", @@ -15898,6 +16410,13 @@ "node": ">= 0.6" } }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", @@ -15913,9 +16432,9 @@ } }, "node_modules/storybook": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/storybook/-/storybook-10.2.1.tgz", - "integrity": "sha512-hgiiwT4ZWJ/yrRpoXnHpCzWOsUvLUwQqgM/ws6mCIDsKJ7Gc7irL6DjWpi8G7l1Uq5VXYsQjXQo5ydb8Pyajdg==", + "version": "10.2.7", + "resolved": "https://registry.npmjs.org/storybook/-/storybook-10.2.7.tgz", + "integrity": "sha512-LFKSuZyF6EW2/Kkl5d7CvqgwhXXfuWv+aLBuoc616boLKJ3mxXuea+GxIgfk02NEyTKctJ0QsnSh5pAomf6Qkg==", "dev": true, "license": "MIT", "peer": true, @@ -16240,6 +16759,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, "node_modules/table-layout": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-4.1.1.tgz", @@ -16446,6 +16972,13 @@ "dev": true, "license": "MIT" }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, "node_modules/tinyexec": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", @@ -16525,6 +17058,26 @@ "node": ">=14.0.0" } }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "dev": true, + "license": "MIT" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -16548,6 +17101,29 @@ "node": ">=0.6" } }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/tr46": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", @@ -16649,16 +17225,6 @@ "node": ">= 0.8.0" } }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -17116,6 +17682,7 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -17232,6 +17799,197 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/vitest": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz", + "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@vitest/expect": "4.0.18", + "@vitest/mocker": "4.0.18", + "@vitest/pretty-format": "4.0.18", + "@vitest/runner": "4.0.18", + "@vitest/snapshot": "4.0.18", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^3.10.0", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.18", + "@vitest/browser-preview": "4.0.18", + "@vitest/browser-webdriverio": "4.0.18", + "@vitest/ui": "4.0.18", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/vitest/node_modules/@vitest/expect": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz", + "integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "chai": "^6.2.1", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest/node_modules/@vitest/pretty-format": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz", + "integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest/node_modules/@vitest/spy": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz", + "integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest/node_modules/@vitest/utils": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz", + "integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.18", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest/node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest/node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/web-worker": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.2.0.tgz", @@ -17290,6 +18048,16 @@ "node": ">=0.10.0" } }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/whatwg-url": { "version": "14.2.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", @@ -17409,6 +18177,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -17512,6 +18297,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index 2568c87..63285df 100644 --- a/package.json +++ b/package.json @@ -29,8 +29,10 @@ "lint": "tsc && eslint --cache .", "build": "tsc -p tsconfig.build.json", "start": "wds", - "test": "wtr --coverage", - "test:watch": "wtr --watch", + "test": "vitest --run", + "test:unit": "vitest --project=unit --run", + "test:storybook": "vitest --project=storybook --run", + "test:watch": "vitest", "check:duplicates": "check-duplicate-components", "dev": "npm run storybook:start", "storybook:start": "storybook dev -p 8000", @@ -91,26 +93,27 @@ "@commitlint/config-conventional": "^20.0.0", "@eslint/eslintrc": "^2.0.0", "@neovici/cfg": "^2.8.0", - "@neovici/testing": "^2.0.0", - "@open-wc/testing": "^4.0.0", - "@open-wc/testing-helpers": "^3.0.1", + "@neovici/testing": "^2.2.0", "@semantic-release/changelog": "^6.0.0", "@semantic-release/git": "^10.0.0", "@storybook/addon-links": "^10.0.0", + "@storybook/addon-vitest": "^10.0.0", "@storybook/web-components": "^10.0.0", "@storybook/web-components-vite": "^10.0.0", - "@types/mocha": "^10.0.6", "@types/node": "^22.10.2", + "@types/react": "^19.2.13", "@types/split.js": "^1.6.0", - "@web/dev-server-esbuild": "^1.0.4", - "@web/test-runner-playwright": "^0.11.1", + "@vitest/browser": "^4.0.0", + "@vitest/browser-playwright": "^4.0.0", "esbuild": "^0.27.0", "http-server": "^14.1.1", "husky": "^9.0.11", + "jsdom": "^26.0.0", + "playwright": "^1.52.0", "semantic-release": "^25.0.0", - "sinon": "^19.0.0", "storybook": "^10.0.0", - "typescript": "^5.4.3" + "typescript": "^5.4.3", + "vitest": "^4.0.0" }, "overrides": { "conventional-changelog-conventionalcommits": ">= 8.0.0", diff --git a/src/list/more/render-more.ts b/src/list/more/render-more.ts index 5a5509c..fca4745 100644 --- a/src/list/more/render-more.ts +++ b/src/list/more/render-more.ts @@ -3,7 +3,6 @@ import { css } from '@pionjs/pion'; import { t } from 'i18next'; import { html, nothing } from 'lit-html'; import { until } from 'lit-html/directives/until.js'; -import { when } from 'lit-html/directives/when.js'; export const style = css` .more { @@ -21,30 +20,39 @@ export const style = css` } `; +const renderSpinner = (loading?: boolean, data$?: PromiseLike) => { + if (loading) return html``; + if (data$) { + return until( + data$.then( + () => nothing, + () => nothing, + ), + html``, + ); + } + return nothing; +}; + export const renderLoadMore = ({ loading, data$, onMore, + onAll, }: { + /** @deprecated Use data$ instead */ loading?: boolean; data$?: PromiseLike; onMore?: () => void; -}) => - html``; + onAll?: () => void; +}) => html` + + ${renderSpinner(loading, data$)} + + + +`; diff --git a/src/list/more/use-more.ts b/src/list/more/use-more.ts index 1825541..053469e 100644 --- a/src/list/more/use-more.ts +++ b/src/list/more/use-more.ts @@ -47,6 +47,28 @@ export const useMore = ({ hasMore ? () => setData((s) => ({ ...s, page: s.page + 1 })) : undefined, [hasMore], ); + + const loadAll = useMemo( + () => + hasMore + ? () => + setData((s) => ({ + ...s, + page: 0, + data$: list$({ + params: s.params, + page: 0, + pageSize: s.totalAvailable, + }).then((data) => { + setTotalAvailable(data.total); + setData((d) => ({ ...d, totalAvailable: data.total })); + return data.items; + }), + })) + : undefined, + [hasMore, list$, setTotalAvailable], + ); + useEffect( () => setData((d) => ({ @@ -81,5 +103,5 @@ export const useMore = ({ })); }, [page, params, list$, pageSize]); - return { data$, loadMore }; + return { data$, loadMore, loadAll }; }; diff --git a/src/list/render-list-core.ts b/src/list/render-list-core.ts index c86fa53..e4b2ca8 100644 --- a/src/list/render-list-core.ts +++ b/src/list/render-list-core.ts @@ -59,6 +59,7 @@ export const renderListCore = ({ content, loadMore, + loadAll, }: RenderListCore) => [ html`({ renderActions({ open, items: selectedItems, slot: 'actions' }), ), content?.({ selectedItems }), - renderLoadMore({ data$, onMore: loadMore }), + renderLoadMore({ data$, onMore: loadMore, onAll: loadAll }), ]}`, formDialog(dialog), diff --git a/src/list/use-list-core.ts b/src/list/use-list-core.ts index eb13a30..0a5a4aa 100644 --- a/src/list/use-list-core.ts +++ b/src/list/use-list-core.ts @@ -34,6 +34,7 @@ export interface UseListCoreResult< data$: PromiseLike; columns: TColumns; loadMore: (() => void) | undefined; + loadAll: (() => void) | undefined; } export const useListCore = < @@ -67,7 +68,7 @@ export const useListCore = < [_params, filters, descending, sortOn, columns, rtkn], ); const list$ = useCallback(..._list$); - const { data$, loadMore } = useMore({ + const { data$, loadMore, loadAll } = useMore({ list$, setTotalAvailable, params, @@ -81,5 +82,6 @@ export const useListCore = < dialog, open, loadMore, + loadAll, }; }; diff --git a/src/queue/test/__snapshots__/render.test.snap.js b/src/queue/test/__snapshots__/render.test.snap.js deleted file mode 100644 index af225cf..0000000 --- a/src/queue/test/__snapshots__/render.test.snap.js +++ /dev/null @@ -1,68 +0,0 @@ -/* @web/test-runner snapshot v1 */ -export const snapshots = {}; - -snapshots['queue > render renderNav'] = ` -`; -/* end snapshot queue > render renderNav */ - -snapshots['queue > render renderPagination'] = `
- - -
-`; -/* end snapshot queue > render renderPagination */ -snapshots['queue > render renderNav'] = ` -`; -/* end snapshot queue > render renderNav */ - -snapshots['queue > render renderPagination'] = `
- - -
-`; -/* end snapshot queue > render renderPagination */ -snapshots['queue > render renderNav'] = ` -`; -/* end snapshot queue > render renderNav */ - -snapshots['queue > render renderPagination'] = `
- - -
-`; -/* end snapshot queue > render renderPagination */ diff --git a/src/queue/test/item-click.test.ts b/src/queue/test/item-click.test.ts deleted file mode 100644 index 72d205f..0000000 --- a/src/queue/test/item-click.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { assert, oneEvent } from '@open-wc/testing'; -import { oneDefaultPreventedEvent } from '@open-wc/testing-helpers'; -import { itemClick } from '../item-click'; - -describe('item-click', () => { - it('fires event', async () => { - const el = document.createElement('div'); - el.addEventListener('click', itemClick({ index: 2, activate: 'queue' })); - setTimeout(() => el.click()); - const { detail } = await oneEvent(el, 'omnitable-item-click'); - assert.equal(detail.index, 2); - assert.equal(detail.activate, 'queue'); - }); - - it('prevents default', async () => { - const el = document.createElement('div'); - const ev = new MouseEvent('click'); - el.addEventListener('click', itemClick({ index: 3, activate: 'list' })); - setTimeout(() => el.dispatchEvent(ev)); - const { detail } = await oneDefaultPreventedEvent( - el, - 'omnitable-item-click', - ); - assert.equal(detail.index, 3); - assert.equal(detail.activate, 'list'); - }); - - it('does not fire event', async () => { - const el = document.createElement('div'); - const ev = new MouseEvent('click', { ctrlKey: true }); - el.addEventListener('click', itemClick({ index: 3, activate: 'list' })); - setTimeout(() => el.dispatchEvent(ev)); - }); -}); diff --git a/src/queue/test/render.test.ts b/src/queue/test/render.test.ts deleted file mode 100644 index 211664e..0000000 --- a/src/queue/test/render.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { expect, fixture } from '@open-wc/testing'; -import { nothing, TemplateResult } from 'lit-html'; -import { spy } from 'sinon'; -import { renderNav, renderPagination } from '../render'; - -describe('queue > render', () => { - it('renderNav', async () => { - const el = await fixture(renderNav({})); - await expect(el).dom.to.equalSnapshot(); - }); - - it('renderPagination nothing', async () => { - expect(renderPagination()).to.equal(nothing); - }); - - it('renderPagination', async () => { - const onPage = spy(); - const el = await fixture( - renderPagination({ - totalPages: 10, - pageNumber: 3, - onPage, - }) as TemplateResult, - ); - await expect(el).dom.to.equalSnapshot(); - el.querySelector('.page-next')?.click(); - expect(onPage).to.have.been.calledWith(4); - onPage.resetHistory(); - el.querySelector('.page-prev')?.click(); - expect(onPage).to.have.been.calledWith(2); - }); -}); diff --git a/src/queue/test/use-pref.test.ts b/src/queue/test/use-pref.test.ts deleted file mode 100644 index 12403bc..0000000 --- a/src/queue/test/use-pref.test.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { renderHook } from '@neovici/testing'; -import { assert } from '@open-wc/testing'; -import { usePref } from '../use-pref'; - -describe('use-pref', () => { - it('default pref', async () => { - const { result } = await renderHook(() => usePref('some', 'asdad')); - assert.equal(result.current[0], 'asdad'); - }); - - it('update pref', async () => { - const { result, nextUpdate } = await renderHook(() => - usePref('somethingelse'), - ); - assert.equal(result.current[0], undefined); - setTimeout(() => result.current[1]('dads')); - await nextUpdate(); - assert.equal(result.current[0], 'dads'); - }); -}); diff --git a/src/util/test/path.test.ts b/src/util/path.test.ts similarity index 60% rename from src/util/test/path.test.ts rename to src/util/path.test.ts index ad380d2..f5ba20d 100644 --- a/src/util/test/path.test.ts +++ b/src/util/path.test.ts @@ -1,104 +1,99 @@ -import { assert } from '@open-wc/testing'; -import { get, normalize, split } from '../path'; +import { describe, expect, it } from 'vitest'; +import { get, normalize, split } from './path'; describe('path', () => { describe('normalize', () => { it('returns string path as-is', () => { - assert.equal(normalize('foo.bar.0.baz'), 'foo.bar.0.baz'); + expect(normalize('foo.bar.0.baz')).toBe('foo.bar.0.baz'); }); it('returns empty string as-is', () => { - assert.equal(normalize(''), ''); + expect(normalize('')).toBe(''); }); it('converts array path to flattened string', () => { - assert.equal(normalize(['foo.bar', 0, 'baz']), 'foo.bar.0.baz'); + expect(normalize(['foo.bar', 0, 'baz'])).toBe('foo.bar.0.baz'); }); it('handles array with single element', () => { - assert.equal(normalize(['foo']), 'foo'); + expect(normalize(['foo'])).toBe('foo'); }); it('handles array with dotted strings and numbers', () => { - assert.equal(normalize(['a.b', 1, 'c.d', 2]), 'a.b.1.c.d.2'); + expect(normalize(['a.b', 1, 'c.d', 2])).toBe('a.b.1.c.d.2'); }); it('handles empty array', () => { - assert.equal(normalize([]), ''); + expect(normalize([])).toBe(''); }); }); describe('split', () => { it('splits string path into array', () => { - assert.deepEqual(split('foo.bar.0.baz'), ['foo', 'bar', '0', 'baz']); + expect(split('foo.bar.0.baz')).toEqual(['foo', 'bar', '0', 'baz']); }); it('splits array path into flat array', () => { - assert.deepEqual(split(['foo.bar', 0, 'baz']), [ - 'foo', - 'bar', - '0', - 'baz', - ]); + expect(split(['foo.bar', 0, 'baz'])).toEqual(['foo', 'bar', '0', 'baz']); }); it('handles single segment string', () => { - assert.deepEqual(split('foo'), ['foo']); + expect(split('foo')).toEqual(['foo']); }); it('handles array with single element', () => { - assert.deepEqual(split(['foo']), ['foo']); + expect(split(['foo'])).toEqual(['foo']); }); it('handles empty string', () => { - assert.deepEqual(split(''), ['']); + expect(split('')).toEqual(['']); }); }); describe('get', () => { it('retrieves nested value with string path', () => { const obj = { foo: { bar: { baz: 'value' } } }; - assert.equal(get(obj, 'foo.bar.baz'), 'value'); + expect(get(obj, 'foo.bar.baz')).toBe('value'); }); it('retrieves nested value with array path', () => { const obj = { foo: { bar: { baz: 'value' } } }; - assert.equal(get(obj, ['foo', 'bar', 'baz']), 'value'); + expect(get(obj, ['foo', 'bar', 'baz'])).toBe('value'); }); it('retrieves nested value with dotted string in array path', () => { const obj = { foo: { bar: { baz: 'value' } } }; - assert.equal(get(obj, ['foo.bar', 'baz']), 'value'); + expect(get(obj, ['foo.bar', 'baz'])).toBe('value'); }); it('retrieves array element by index', () => { const obj = { items: ['a', 'b', 'c'] }; - assert.equal(get(obj, 'items.1'), 'b'); + expect(get(obj, 'items.1')).toBe('b'); }); it('retrieves array element with array path', () => { const obj = { items: ['a', 'b', 'c'] }; - assert.equal(get(obj, ['items', 0]), 'a'); + expect(get(obj, ['items', 0])).toBe('a'); }); it('returns undefined for non-existent path', () => { const obj = { foo: { bar: 'value' } }; - assert.equal(get(obj, 'foo.baz.qux'), undefined); + expect(get(obj, 'foo.baz.qux')).toBeUndefined(); }); it('returns undefined when intermediate property is undefined', () => { const obj = { foo: undefined }; - assert.equal(get(obj, 'foo.bar'), undefined); + expect(get(obj, 'foo.bar')).toBeUndefined(); }); it('returns undefined when intermediate property is null', () => { const obj = { foo: null }; - assert.equal(get(obj, 'foo.bar'), undefined); + expect(get(obj, 'foo.bar')).toBeUndefined(); }); it('returns undefined with empty string path', () => { const obj = { foo: 'bar' }; - assert.equal(get(obj, ''), undefined); + expect(get(obj, '')).toBeUndefined(); }); it('handles complex nested structure', () => { @@ -108,15 +103,15 @@ describe('path', () => { { name: 'Bob', address: { city: 'LA' } }, ], }; - assert.equal(get(obj, 'users.1.address.city'), 'LA'); + expect(get(obj, 'users.1.address.city')).toBe('LA'); }); it('returns undefined for null root', () => { - assert.equal(get(null, 'foo'), undefined); + expect(get(null, 'foo')).toBeUndefined(); }); it('returns undefined for undefined root', () => { - assert.equal(get(undefined, 'foo'), undefined); + expect(get(undefined, 'foo')).toBeUndefined(); }); }); }); diff --git a/stories/item-click.stories.ts b/stories/item-click.stories.ts new file mode 100644 index 0000000..5001ac2 --- /dev/null +++ b/stories/item-click.stories.ts @@ -0,0 +1,103 @@ +import type { Meta, StoryObj } from '@storybook/web-components'; +import { html } from 'lit-html'; +import { expect, userEvent } from 'storybook/test'; +import { itemClick } from '../src/queue/item-click'; + +const meta: Meta = { + title: 'Tests/ItemClick', +}; + +export default meta; + +export const FiresEvent: StoryObj = { + render: () => html` + + `, + async play({ canvasElement }) { + const button = canvasElement.querySelector( + '#test-button', + ) as HTMLButtonElement; + + let eventDetail: { index: number; activate: string } | null = null; + button.addEventListener('omnitable-item-click', ((e: CustomEvent) => { + eventDetail = e.detail; + }) as EventListener); + + await userEvent.click(button); + + expect(eventDetail).not.toBeNull(); + expect(eventDetail!.index).toBe(2); + expect(eventDetail!.activate).toBe('queue'); + }, +}; + +export const PreventsDefault: StoryObj = { + render: () => html` + + `, + async play({ canvasElement }) { + const button = canvasElement.querySelector( + '#test-button', + ) as HTMLButtonElement; + + let eventDetail: { index: number; activate: string } | null = null; + let wasDefaultPrevented = false; + + button.addEventListener('omnitable-item-click', ((e: CustomEvent) => { + eventDetail = e.detail; + e.preventDefault(); + }) as EventListener); + + button.addEventListener('click', (e: MouseEvent) => { + wasDefaultPrevented = e.defaultPrevented; + }); + + await userEvent.click(button); + + expect(eventDetail).not.toBeNull(); + expect(eventDetail!.index).toBe(3); + expect(eventDetail!.activate).toBe('list'); + expect(wasDefaultPrevented).toBe(true); + }, +}; + +export const DoesNotFireWithCtrlKey: StoryObj = { + render: () => html` + + `, + async play({ canvasElement }) { + const button = canvasElement.querySelector( + '#test-button', + ) as HTMLButtonElement; + + let eventFired = false; + button.addEventListener('omnitable-item-click', () => { + eventFired = true; + }); + + // Simulate ctrl+click using native MouseEvent + const ctrlClickEvent = new MouseEvent('click', { + bubbles: true, + cancelable: true, + ctrlKey: true, + }); + button.dispatchEvent(ctrlClickEvent); + + expect(eventFired).toBe(false); + }, +}; diff --git a/stories/load-more.stories.ts b/stories/load-more.stories.ts new file mode 100644 index 0000000..d76af18 --- /dev/null +++ b/stories/load-more.stories.ts @@ -0,0 +1,257 @@ +import { html, render } from 'lit-html'; +import { expect, userEvent } from 'storybook/test'; +import { renderLoadMore, style } from '../src/list/more/render-more'; + +export default { + title: 'Components/LoadMore', +}; + +interface Item { + id: string; + name: string; +} + +// Simulates paginated data fetching +const createMockList = ( + totalItems: number, + delay = 300, +): ((props: { + page: number; + pageSize: number; +}) => Promise<{ items: Item[]; total: number }>) => { + const allItems = Array.from({ length: totalItems }, (_, i) => ({ + id: `item-${i + 1}`, + name: `Item ${i + 1}`, + })); + + return async ({ page, pageSize }) => { + await new Promise((resolve) => setTimeout(resolve, delay)); + const start = page * pageSize; + const items = allItems.slice(start, start + pageSize); + return { items, total: totalItems }; + }; +}; + +// Helper to create a stateful demo +const createLoadMoreDemo = ( + container: HTMLElement, + options: { totalItems: number; pageSize: number }, +) => { + const { totalItems, pageSize } = options; + const list$ = createMockList(totalItems); + + let state = { + items: [] as Item[], + page: 0, + data$: undefined as Promise | undefined, + totalAvailable: Infinity, + }; + + const renderDemo = () => { + const hasMore = + state.totalAvailable < Infinity && + state.totalAvailable > state.items.length; + + const loadMore = hasMore + ? () => { + state.data$ = list$({ page: state.page, pageSize }).then((result) => { + state = { + ...state, + items: [...state.items, ...result.items], + page: state.page + 1, + totalAvailable: result.total, + data$: undefined, + }; + renderDemo(); + return result.items; + }); + renderDemo(); + } + : undefined; + + const loadAll = hasMore + ? () => { + state.data$ = list$({ page: 0, pageSize: state.totalAvailable }).then( + (result) => { + state = { + ...state, + items: result.items, + totalAvailable: result.total, + data$: undefined, + }; + renderDemo(); + return result.items; + }, + ); + renderDemo(); + } + : undefined; + + render( + html` + +
+
+ Showing ${state.items.length} of ${state.totalAvailable} items +
+
+ ${state.items.map( + (item) => html`
${item.name}
`, + )} +
+
+ ${renderLoadMore({ + data$: state.data$, + onMore: loadMore, + onAll: loadAll, + })} +
+
+ `, + container, + ); + }; + + // Initial load + state.data$ = list$({ page: 0, pageSize }).then((result) => { + state = { + ...state, + items: result.items, + page: 1, + totalAvailable: result.total, + data$: undefined, + }; + renderDemo(); + return result.items; + }); + renderDemo(); +}; + +// Stories +export const Default = { + render: () => html`
`, + play: async ({ canvasElement }: { canvasElement: HTMLElement }) => { + const container = canvasElement.querySelector( + '#demo-container', + ) as HTMLElement; + createLoadMoreDemo(container, { totalItems: 100, pageSize: 10 }); + }, +}; + +export const WithCallbacks = { + render: () => html`
`, + play: async ({ canvasElement }: { canvasElement: HTMLElement }) => { + const container = canvasElement.querySelector( + '#demo-container', + ) as HTMLElement; + createLoadMoreDemo(container, { totalItems: 100, pageSize: 10 }); + + // Wait for initial load + await new Promise((resolve) => setTimeout(resolve, 500)); + + // Find buttons + const loadMoreButton = container.querySelector( + 'button.more:not([hidden])', + ) as HTMLButtonElement; + expect(loadMoreButton).not.toBeNull(); + expect(loadMoreButton.textContent).toContain('Load more'); + + // Click "Load more" + await userEvent.click(loadMoreButton); + + // Wait for load to complete + await new Promise((resolve) => setTimeout(resolve, 500)); + + // Verify more items loaded + const stats = container.querySelector( + '[data-testid="stats"]', + ) as HTMLElement; + expect(stats.textContent).toContain('Showing 20 of 100 items'); + }, +}; + +export const LoadAllTest = { + render: () => html`
`, + play: async ({ canvasElement }: { canvasElement: HTMLElement }) => { + const container = canvasElement.querySelector( + '#demo-container', + ) as HTMLElement; + createLoadMoreDemo(container, { totalItems: 100, pageSize: 10 }); + + // Wait for initial load + await new Promise((resolve) => setTimeout(resolve, 500)); + + // Find Load all button (second .more button) + const buttons = container.querySelectorAll('button.more:not([hidden])'); + const loadAllButton = Array.from(buttons).find((b) => + b.textContent?.includes('Load all'), + ) as HTMLButtonElement; + expect(loadAllButton).not.toBeNull(); + + // Click "Load all" + await userEvent.click(loadAllButton); + + // Wait for load to complete + await new Promise((resolve) => setTimeout(resolve, 500)); + + // Verify all items loaded + const stats = container.querySelector( + '[data-testid="stats"]', + ) as HTMLElement; + expect(stats.textContent).toContain('Showing 100 of 100 items'); + + // Buttons should be hidden now + const visibleButtons = container.querySelectorAll( + 'button.more:not([hidden])', + ); + expect(visibleButtons.length).toBe(0); + }, +}; + +export const AllDataLoaded = { + render: () => html`
`, + play: async ({ canvasElement }: { canvasElement: HTMLElement }) => { + const container = canvasElement.querySelector( + '#demo-container', + ) as HTMLElement; + createLoadMoreDemo(container, { totalItems: 5, pageSize: 10 }); + + // Wait for initial load + await new Promise((resolve) => setTimeout(resolve, 500)); + + // Buttons should be hidden when all data is already loaded + const visibleButtons = container.querySelectorAll( + 'button.more:not([hidden])', + ); + expect(visibleButtons.length).toBe(0); + }, +}; diff --git a/stories/render.stories.ts b/stories/render.stories.ts new file mode 100644 index 0000000..c6579e7 --- /dev/null +++ b/stories/render.stories.ts @@ -0,0 +1,105 @@ +import type { Meta, StoryObj } from '@storybook/web-components'; +import { html, nothing } from 'lit-html'; +import { expect, fn, userEvent } from 'storybook/test'; +import { renderNav, renderPagination } from '../src/queue/render'; + +const meta: Meta = { + title: 'Tests/Render', +}; + +export default meta; + +export const RenderNavTest: StoryObj = { + render: () => html`
${renderNav({})}
`, + async play({ canvasElement }) { + const container = canvasElement.querySelector('#test-container'); + + // Both buttons should be disabled when no callbacks are provided + const buttons = container?.querySelectorAll('button.button-nav'); + expect(buttons).toHaveLength(2); + + const prevButton = container?.querySelector('.button-nav.prev'); + const nextButton = container?.querySelector('.button-nav.next'); + expect(prevButton).toHaveAttribute('disabled'); + expect(nextButton).toHaveAttribute('disabled'); + }, +}; + +export const RenderNavWithCallbacks: StoryObj = { + args: { + prev: fn(), + next: fn(), + }, + render: (args) => + html`
+ ${renderNav(args as Parameters[0])} +
`, + async play({ args, canvasElement }) { + const container = canvasElement.querySelector('#test-container'); + + const prevButton = container?.querySelector( + '.button-nav.prev', + ) as HTMLButtonElement; + const nextButton = container?.querySelector( + '.button-nav.next', + ) as HTMLButtonElement; + + // Buttons should not be disabled when callbacks are provided + expect(prevButton).not.toHaveAttribute('disabled'); + expect(nextButton).not.toHaveAttribute('disabled'); + + // Test clicking + await userEvent.click(prevButton); + expect(args.prev).toHaveBeenCalled(); + + await userEvent.click(nextButton); + expect(args.next).toHaveBeenCalled(); + }, +}; + +export const RenderPaginationNothing: StoryObj = { + render: () => + html`
+ ${renderPagination() === nothing ? 'nothing' : renderPagination()} +
`, + async play({ canvasElement }) { + const container = canvasElement.querySelector('#test-container'); + expect(container?.textContent?.trim()).toBe('nothing'); + }, +}; + +export const RenderPaginationTest: StoryObj = { + args: { + onPage: fn(), + }, + render: (args) => + html`
+ ${renderPagination({ + totalPages: 10, + pageNumber: 3, + onPage: args.onPage as (page: number) => void, + })} +
`, + async play({ args, canvasElement }) { + const container = canvasElement.querySelector('#test-container'); + + const prevButton = container?.querySelector( + '.page-prev', + ) as HTMLButtonElement; + const nextButton = container?.querySelector( + '.page-next', + ) as HTMLButtonElement; + + expect(prevButton).toBeTruthy(); + expect(nextButton).toBeTruthy(); + + // Click next page + await userEvent.click(nextButton); + expect(args.onPage).toHaveBeenCalledWith(4); + + // Reset and click prev page + (args.onPage as ReturnType).mockClear(); + await userEvent.click(prevButton); + expect(args.onPage).toHaveBeenCalledWith(2); + }, +}; diff --git a/stories/use-pref.stories.ts b/stories/use-pref.stories.ts new file mode 100644 index 0000000..ddadfba --- /dev/null +++ b/stories/use-pref.stories.ts @@ -0,0 +1,44 @@ +import { renderHook } from '@neovici/testing'; +import type { Meta, StoryObj } from '@storybook/web-components'; +import { html } from 'lit-html'; +import { expect } from 'storybook/test'; +import { usePref } from '../src/queue/use-pref'; + +const meta: Meta = { + title: 'Tests/UsePref', +}; + +export default meta; + +export const DefaultPref: StoryObj = { + render: () => html`
`, + async play() { + localStorage.removeItem('pref-some'); + const { result, unmount } = await renderHook(() => + usePref('some', 'asdad'), + ); + + expect(result.current[0]).toBe('asdad'); + unmount(); + }, +}; + +export const UpdatePref: StoryObj = { + render: () => html`
`, + async play() { + localStorage.removeItem('pref-update'); + const { result, nextUpdate, unmount } = await renderHook(() => + usePref('update'), + ); + + // Initial value should be undefined + expect(result.current[0]).toBeUndefined(); + + // Update the pref + result.current[1]('dads'); + await nextUpdate(); + + expect(result.current[0]).toBe('dads'); + unmount(); + }, +}; diff --git a/test/fetch.test.ts b/test/fetch.test.ts index 5112121..db1892c 100644 --- a/test/fetch.test.ts +++ b/test/fetch.test.ts @@ -1,27 +1,34 @@ -import { expect } from '@open-wc/testing'; -import { stub, type SinonStub } from 'sinon'; +import { + afterEach, + beforeEach, + describe, + expect, + it, + vi, + type MockInstance, +} from 'vitest'; import { fetch, setBaseInit } from '../src/util/fetch/fetch'; describe('fetch', () => { - let fetchStub: SinonStub; + let fetchSpy: MockInstance; beforeEach(() => { - fetchStub = stub(window, 'fetch').resolves(new Response()); + fetchSpy = vi.spyOn(window, 'fetch').mockResolvedValue(new Response()); }); afterEach(() => { - fetchStub.restore(); + fetchSpy.mockRestore(); }); describe('getHeaders callback', () => { it('invokes getHeaders on each request', async () => { - const getHeaders = stub().returns({ 'X-Dynamic': 'value1' }); + const getHeaders = vi.fn().mockReturnValue({ 'X-Dynamic': 'value1' }); setBaseInit({ getHeaders }); await fetch('/api/test1'); await fetch('/api/test2'); - expect(getHeaders.callCount).to.equal(2); + expect(getHeaders).toHaveBeenCalledTimes(2); }); it('includes dynamic headers in request', async () => { @@ -31,8 +38,8 @@ describe('fetch', () => { await fetch('/api/test'); - const [, opts] = fetchStub.firstCall.args; - expect(opts.headers).to.have.property('X-Dynamic', 'dynamic-value'); + const [, opts] = fetchSpy.mock.calls[0]; + expect(opts.headers).toHaveProperty('X-Dynamic', 'dynamic-value'); }); it('dynamic headers override static headers', async () => { @@ -43,8 +50,8 @@ describe('fetch', () => { await fetch('/api/test'); - const [, opts] = fetchStub.firstCall.args; - expect(opts.headers).to.have.property('X-Header', 'dynamic'); + const [, opts] = fetchSpy.mock.calls[0]; + expect(opts.headers).toHaveProperty('X-Header', 'dynamic'); }); it('per-request headers override dynamic headers', async () => { @@ -56,8 +63,8 @@ describe('fetch', () => { headers: { 'X-Header': 'per-request' }, }); - const [, opts] = fetchStub.firstCall.args; - expect(opts.headers).to.have.property('X-Header', 'per-request'); + const [, opts] = fetchSpy.mock.calls[0]; + expect(opts.headers).toHaveProperty('X-Header', 'per-request'); }); it('per-request headers override both static and dynamic headers', async () => { @@ -70,8 +77,8 @@ describe('fetch', () => { headers: { 'X-Header': 'per-request' }, }); - const [, opts] = fetchStub.firstCall.args; - expect(opts.headers).to.have.property('X-Header', 'per-request'); + const [, opts] = fetchSpy.mock.calls[0]; + expect(opts.headers).toHaveProperty('X-Header', 'per-request'); }); it('returns different values on subsequent calls', async () => { @@ -83,10 +90,10 @@ describe('fetch', () => { await fetch('/api/test1'); await fetch('/api/test2'); - const [, opts1] = fetchStub.firstCall.args; - const [, opts2] = fetchStub.secondCall.args; - expect(opts1.headers).to.have.property('X-Request-Id', '1'); - expect(opts2.headers).to.have.property('X-Request-Id', '2'); + const [, opts1] = fetchSpy.mock.calls[0]; + const [, opts2] = fetchSpy.mock.calls[1]; + expect(opts1.headers).toHaveProperty('X-Request-Id', '1'); + expect(opts2.headers).toHaveProperty('X-Request-Id', '2'); }); it('merges static, dynamic, and per-request headers', async () => { @@ -99,10 +106,10 @@ describe('fetch', () => { headers: { 'X-Request': 'request-value' }, }); - const [, opts] = fetchStub.firstCall.args; - expect(opts.headers).to.have.property('X-Static', 'static-value'); - expect(opts.headers).to.have.property('X-Dynamic', 'dynamic-value'); - expect(opts.headers).to.have.property('X-Request', 'request-value'); + const [, opts] = fetchSpy.mock.calls[0]; + expect(opts.headers).toHaveProperty('X-Static', 'static-value'); + expect(opts.headers).toHaveProperty('X-Dynamic', 'dynamic-value'); + expect(opts.headers).toHaveProperty('X-Request', 'request-value'); }); }); }); diff --git a/tsconfig.json b/tsconfig.json index b8da42e..ed23677 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,17 +1,18 @@ { - "compilerOptions": { - "allowSyntheticDefaultImports": true, - "noEmit": true, - "module": "esnext", - "moduleResolution": "bundler", - "strict": true, - "target": "esnext", - "allowJs": true, - "types": ["node", "mocha"], - "baseUrl": ".", - "paths": { - "@neovici/cosmoz-queue/*": ["./src/*"] - } - }, - "include": ["src/**/*", "test/**/*", "stories/**/*"] + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "noEmit": true, + "module": "esnext", + "moduleResolution": "bundler", + "strict": true, + "target": "esnext", + "allowJs": true, + "types": ["node"], + "baseUrl": ".", + "paths": { + "@neovici/cosmoz-queue/*": ["./src/*"] + }, + "skipLibCheck": true + }, + "include": ["src/**/*", "test/**/*", "stories/**/*"] } diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..e637f30 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,29 @@ +import { storybookTest } from '@storybook/addon-vitest/vitest-plugin'; +import { playwright } from '@vitest/browser-playwright'; +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + projects: [ + { + test: { + name: 'unit', + include: ['test/**/*.test.ts', 'src/**/*.test.ts'], + environment: 'jsdom', + }, + }, + { + plugins: [storybookTest({ configDir: '.storybook' })], + test: { + name: 'storybook', + browser: { + enabled: true, + provider: playwright(), + instances: [{ browser: 'chromium' }], + }, + setupFiles: ['.storybook/vitest.setup.ts'], + }, + }, + ], + }, +}); diff --git a/web-test-runner.config.mjs b/web-test-runner.config.mjs deleted file mode 100644 index 359824d..0000000 --- a/web-test-runner.config.mjs +++ /dev/null @@ -1,15 +0,0 @@ -import { esbuildPlugin } from '@web/dev-server-esbuild'; -import { playwrightLauncher } from '@web/test-runner-playwright'; - -export default { - nodeResolve: true, - files: ['test/**/*.test.ts', 'src/**/*.test.ts'], - plugins: [esbuildPlugin({ ts: true, target: 'auto' })], - browsers: [playwrightLauncher({ product: 'chromium' })], - testFramework: { config: { ui: 'bdd' } }, - coverage: true, - coverageConfig: { - include: ['src/**/*.ts'], - exclude: ['**/*.test.ts', '**/*.spec.ts'], - }, -};