diff --git a/.claude/rules/testing.md b/.claude/rules/testing.md index 777a01ffe..67bf14f5a 100644 --- a/.claude/rules/testing.md +++ b/.claude/rules/testing.md @@ -33,8 +33,15 @@ Jest unit/component tests are co-located in a component's `__tests__/` folder. `.spec.tsx` specs use a table-driven pattern: * Import `test` and `expect` from `tests/playwright`, never directly from - `@playwright/experimental-ct-react`; its `mount` moves the pointer out of the - viewport so components do not get into `:hover` state. + `@playwright/test`; its `mount` moves the pointer out of the viewport so + components do not get into `:hover` state. +* Mount stories by ID (`/`, e.g. + `Button/ButtonForTest`) through the Playwright Component Testing page in + [tests/playwright/ct/](../../tests/playwright/ct), passing plain data props + only. Describe elements passed in props (icons, nodes) with `element()` from + `tests/playwright`. Callbacks are asserted through `*SpyForTest` stories + wrapped with `withSpy` that register the callbacks with `useSpy`; specs read + the recorded values with `expect.poll(() => component.getSpyValue(name))`. * Import arrays of test cases from `_propTests/` directories and from shared `tests/playwright/propTests/`. * Each test case is `{ name, props, onBeforeTest?, onBeforeSnapshot? }`; custom @@ -47,10 +54,13 @@ Jest unit/component tests are co-located in a component's `__tests__/` folder. `.spec.tsx-snapshots/`. **Story components** (`.story.tsx`) wrap the real component in a minimal fixture -(sometimes inside a context provider) and are imported only by `.spec.tsx` -files. Type them with the real component props; when the story fills in a -default for a required prop, use `StoryProps` from -`tests/playwright` instead of hand-written `Omit & { key?: … }` types. Naming convention: `ForTest`, `ForRefTest`, +(sometimes inside a context provider) and are mounted by ID from `.spec.tsx` +files. They run in the browser, so they import `withSpy` / `useSpy` from +`tests/playwright/utils/spy`, not from `tests/playwright`. Type them with the +real component props; when the story fills in a default for a required prop, use +`StoryProps` from `tests/playwright` instead of hand-written +`Omit & { key?: … }` types. Naming convention: `ForTest`, +`ForRefTest`, `SpyForTest`, `ForFormLayoutTests` — the FormLayout story component is always last. diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 14625a063..f8c9f43ec 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -10,7 +10,7 @@ jobs: name: Playwright runs-on: ubuntu-24.04 container: - image: mcr.microsoft.com/playwright:v1.62.1-noble + image: mcr.microsoft.com/playwright:v1.63.0-noble steps: - name: Clone repository uses: actions/checkout@v4 diff --git a/docker/playwright/Dockerfile b/docker/playwright/Dockerfile index 55039c62e..c83f73408 100644 --- a/docker/playwright/Dockerfile +++ b/docker/playwright/Dockerfile @@ -1,3 +1,3 @@ -FROM mcr.microsoft.com/playwright:v1.62.1 +FROM mcr.microsoft.com/playwright:v1.63.0 RUN mkdir /workspace WORKDIR /workspace diff --git a/package-lock.json b/package-lock.json index b8d5e2bc4..a2aa333ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,12 +18,13 @@ "@babel/preset-react": "^8.0.1", "@babel/preset-typescript": "^8.0.1", "@happy-dom/jest-environment": "^20.14.5", - "@playwright/experimental-ct-react": "^1.62.1", + "@playwright/test": "~1.63.0", "@react-ui-org/eslint-config": "^1.1.0", "@react-ui-org/stylelint-config": "^1.0.0", "@testing-library/dom": "^10.4.2", "@testing-library/jest-dom": "^7.0.1", "@testing-library/react": "^16.3.3", + "@testing-library/user-event": "^14.6.7", "@types/jest": "^30.0.0", "@types/node": "^26.5.1", "@types/prop-types": "^15.7.15", @@ -56,6 +57,7 @@ "typescript": "npm:@typescript/typescript6@^6.0.2", "webpack": "^5.111.0", "webpack-cli": "^7.2.3", + "webpack-dev-server": "^6.0.0", "webpack-visualizer-plugin2": "^2.0.0" }, "engines": { @@ -4836,6 +4838,437 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/buffers": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-17.67.0.tgz", + "integrity": "sha512-tfExRpYxBvi32vPs9ZHaTjSP4fHAfzSmcahOfNxtvGHcyJel+aibkPlGeBB+7AoC6hL7lXIE++8okecBxx7lcw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/codegen": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz", + "integrity": "sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-core": { + "version": "4.78.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-core/-/fs-core-4.78.1.tgz", + "integrity": "sha512-UUGcxEah6eb6PyrF8RDEuVgR0Cs2K6XTiLH7YxNR3DknL3wZCNx1nU5m2Gq5Wq8ipy4uHDay3xt/50KyoTN6Zg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-node-builtins": "4.78.1", + "@jsonjoy.com/fs-node-utils": "4.78.1", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-fsa": { + "version": "4.78.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-fsa/-/fs-fsa-4.78.1.tgz", + "integrity": "sha512-PaoXI4HAELQ/DYxCtqHne7ijrTE0BIEFYXI/qTy4f6pThXiYT5QyJkk96bzpcjdo2D+OM0pUv7mkwffSAl/GSQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-core": "4.78.1", + "@jsonjoy.com/fs-node-builtins": "4.78.1", + "@jsonjoy.com/fs-node-utils": "4.78.1", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node": { + "version": "4.78.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node/-/fs-node-4.78.1.tgz", + "integrity": "sha512-3z6o4qVdKUZTHNXiV+3bKAUkQSD5a2kFwwwag8fe6VvgZ4WNfDbMzWdGvoGx+oRpOcr1iWA9WYwVeVMjSir4mA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-core": "4.78.1", + "@jsonjoy.com/fs-node-builtins": "4.78.1", + "@jsonjoy.com/fs-node-utils": "4.78.1", + "@jsonjoy.com/fs-print": "4.78.1", + "@jsonjoy.com/fs-snapshot": "4.78.1", + "glob-to-regex.js": "^1.3.1", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node-builtins": { + "version": "4.78.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-builtins/-/fs-node-builtins-4.78.1.tgz", + "integrity": "sha512-yaGM1l77yYANFl+w2gdixRZhcQhKodtbI4FZgJg2ApfTTSJcxdYEekU+Ta92sCqSwrOtY+ru4IP81tgJxWXIyA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node-to-fsa": { + "version": "4.78.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-to-fsa/-/fs-node-to-fsa-4.78.1.tgz", + "integrity": "sha512-/Z00TkjLD8zPRimbINaiAQRqpE9HZmiGfxQFMqNcHncmhijS3JsAW21gcNO/16jujoDwXOyEaSSeK+pBtv8QHQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-fsa": "4.78.1", + "@jsonjoy.com/fs-node-builtins": "4.78.1", + "@jsonjoy.com/fs-node-utils": "4.78.1" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node-utils": { + "version": "4.78.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-utils/-/fs-node-utils-4.78.1.tgz", + "integrity": "sha512-wm0PVK6XpjHH6GLGVi60+ubD7pNv91KwQ8SBWwn+orRTYdfEIci59/VlOddIGhUSde0t9W9gLNetlPKQHQJsSQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-node-builtins": "4.78.1", + "glob-to-regex.js": "^1.3.1" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-print": { + "version": "4.78.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-print/-/fs-print-4.78.1.tgz", + "integrity": "sha512-NUwSD+BOz0TUpBc+riQpI1G1vOhViSZePeqUPxKznelsk3Iwotu6T/02UAY9WdaGcLdlDX8pllEIyxehnYo9Ng==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-node-utils": "4.78.1", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot": { + "version": "4.78.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-snapshot/-/fs-snapshot-4.78.1.tgz", + "integrity": "sha512-gejpLh45sgYGEHWZP/LP2etxaUOYawDfbFVckl9xDDEvgSAtZf700MzCAGbPBPBzRL+qW5UDDwacIgYXHY3C9Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/buffers": "^17.65.0", + "@jsonjoy.com/fs-node-utils": "4.78.1", + "@jsonjoy.com/json-pack": "^17.65.0", + "@jsonjoy.com/util": "^17.65.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/base64": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-17.67.0.tgz", + "integrity": "sha512-5SEsJGsm15aP8TQGkDfJvz9axgPwAEm98S5DxOuYe8e1EbfajcDmgeXXzccEjh+mLnjqEKrkBdjHWS5vFNwDdw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/codegen": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-17.67.0.tgz", + "integrity": "sha512-idnkUplROpdBOV0HMcwhsCUS5TRUi9poagdGs70A6S4ux9+/aPuKbh8+UYRTLYQHtXvAdNfQWXDqZEx5k4Dj2Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pack": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-17.67.0.tgz", + "integrity": "sha512-t0ejURcGaZsn1ClbJ/3kFqSOjlryd92eQY465IYrezsXmPcfHPE/av4twRSxf6WE+TkZgLY+71vCZbiIiFKA/w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/base64": "17.67.0", + "@jsonjoy.com/buffers": "17.67.0", + "@jsonjoy.com/codegen": "17.67.0", + "@jsonjoy.com/json-pointer": "17.67.0", + "@jsonjoy.com/util": "17.67.0", + "hyperdyperid": "^1.2.0", + "thingies": "^2.5.0", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pointer": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-17.67.0.tgz", + "integrity": "sha512-+iqOFInH+QZGmSuaybBUNdh7yvNrXvqR+h3wjXm0N/3JK1EyyFAeGJvqnmQL61d1ARLlk/wJdFKSL+LHJ1eaUA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/util": "17.67.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/util": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-17.67.0.tgz", + "integrity": "sha512-6+8xBaz1rLSohlGh68D1pdw3AwDi9xydm8QNlAFkvnavCJYSze+pxoW2VKP8p308jtlMRLs5NTHfPlZLd4w7ew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/buffers": "17.67.0", + "@jsonjoy.com/codegen": "17.67.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.21.0.tgz", + "integrity": "sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/base64": "^1.1.2", + "@jsonjoy.com/buffers": "^1.2.0", + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/json-pointer": "^1.0.2", + "@jsonjoy.com/util": "^1.9.0", + "hyperdyperid": "^1.2.0", + "thingies": "^2.5.0", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack/node_modules/@jsonjoy.com/buffers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", + "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pointer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz", + "integrity": "sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/util": "^1.9.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.9.0.tgz", + "integrity": "sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/buffers": "^1.0.0", + "@jsonjoy.com/codegen": "^1.0.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util/node_modules/@jsonjoy.com/buffers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", + "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, "node_modules/@keyv/serialize": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz", @@ -4843,6 +5276,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "dev": true, + "license": "MIT" + }, "node_modules/@napi-rs/wasm-runtime": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.3.tgz", @@ -4912,6 +5352,19 @@ "node": ">=8.6.0" } }, + "node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -4950,16 +5403,6 @@ "node": ">= 8" } }, - "node_modules/@oxc-project/types": { - "version": "0.148.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.148.0.tgz", - "integrity": "sha512-Nm4s/jB+4FpFsPhWGEC4h7rzksesmtnMXomo6rCMcg/b8zLQuOziRgkCS1fxDCXOlJB/6Q8oABOZ/OP6RIPj9A==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/oxc-project" - } - }, "node_modules/@parcel/watcher": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.6.0.tgz", @@ -5278,425 +5721,327 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "node_modules/@peculiar/asn1-cms": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.9.4.tgz", + "integrity": "sha512-cben7oxmQsUGZqotus7yt0srYdncOT6RNWcTQ77T2RFOXejYVYkXadrfePdRcrVpO9K95IRLKKglG2k38jKXuw==", "dev": true, "license": "MIT", - "optional": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.9.4", + "@peculiar/asn1-x509": "^2.9.4", + "@peculiar/asn1-x509-attr": "^2.9.4", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, "engines": { "node": ">=14" } }, - "node_modules/@pkgr/core": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.3.6.tgz", - "integrity": "sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==", + "node_modules/@peculiar/asn1-csr": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-csr/-/asn1-csr-2.9.4.tgz", + "integrity": "sha512-xd4YN4vpRjkDAQWVfZZkeu12IEND7DOpkqaHSIHxZl1uggUNa9Ju0QxY2jHvDAS9pP0zhRBytg8ifsnGo3V0jw==", "dev": true, "license": "MIT", - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/pkgr" - } - }, - "node_modules/@playwright/experimental-ct-core": { - "version": "1.62.1", - "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-core/-/experimental-ct-core-1.62.1.tgz", - "integrity": "sha512-GvaAlFMG6kZ+/00QyGOemjBkyDTk7OC1wGcVmO27i4Ek+x51ACjIBMyyyZrTGXHtQ1QvTFhaQrBhrXIrXEjn5g==", - "dev": true, - "license": "Apache-2.0", "dependencies": { - "playwright": "1.62.1", - "playwright-core": "1.62.1", - "vite": "^8.1.0" + "@peculiar/asn1-schema": "^2.9.4", + "@peculiar/asn1-x509": "^2.9.4", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" }, "engines": { - "node": ">=20" + "node": ">=14" } }, - "node_modules/@playwright/experimental-ct-react": { - "version": "1.62.1", - "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-react/-/experimental-ct-react-1.62.1.tgz", - "integrity": "sha512-J1mI20nCPIBHh2htGIXaLHwva5a5lhlX0yJo5Z8fTX9eMEd1wMhgvuUr0EzpGI6oVg6opOFGgb+URUqhSyQd3w==", + "node_modules/@peculiar/asn1-ecc": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.9.4.tgz", + "integrity": "sha512-JJXefFshRAuVAjWQo/39bkg1ywc1VaiO44S8RRC+Ykvf/u2KDmYffoDb0ZBPCR5uJy4AGKQhl8mX+Q8ShcWaXQ==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@playwright/experimental-ct-core": "1.62.1", - "@vitejs/plugin-react": "^6.0.3" - }, - "bin": { - "playwright": "cli.js" + "@peculiar/asn1-schema": "^2.9.4", + "@peculiar/asn1-x509": "^2.9.4", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" }, "engines": { - "node": ">=20" + "node": ">=14" } }, - "node_modules/@playwright/experimental-ct-react/node_modules/@vitejs/plugin-react": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.1.1.tgz", - "integrity": "sha512-yxLaQV9gkhS8ezJqCM6+ndU7mDY6gqAg75NQ+0IjwEI8IYOmQCgkRwHKVSfWXW076DsqMo0Dk+0FK1U+M5RgFw==", + "node_modules/@peculiar/asn1-pfx": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pfx/-/asn1-pfx-2.9.4.tgz", + "integrity": "sha512-khuGzHTzNzk4GDlIBEILyIs6Lce0yn0ZBdoI9v93kmNncfZRhD+AQ5ODFqdhvoE8cMJF/JMTQ8yA+t1D14kqCw==", "dev": true, "license": "MIT", "dependencies": { - "@rolldown/pluginutils": "^1.0.1" + "@peculiar/asn1-cms": "^2.9.4", + "@peculiar/asn1-pkcs8": "^2.9.4", + "@peculiar/asn1-rsa": "^2.9.4", + "@peculiar/asn1-schema": "^2.9.4", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" }, "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "peerDependencies": { - "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", - "babel-plugin-react-compiler": "^1.0.0", - "oxc-transform-react": "^0.145.0", - "vite": "^8.0.0" - }, - "peerDependenciesMeta": { - "@rolldown/plugin-babel": { - "optional": true - }, - "babel-plugin-react-compiler": { - "optional": true - }, - "oxc-transform-react": { - "optional": true - } + "node": ">=14" } }, - "node_modules/@react-ui-org/eslint-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@react-ui-org/eslint-config/-/eslint-config-1.1.0.tgz", - "integrity": "sha512-iQXC2Rkm+CuK2FW5r9X7fa92AKjJgffymkwpHgVnp/hHSWf4Ux95dkQhgyXCqNUpUwLuMe/OdwL2lZcK4UreVA==", + "node_modules/@peculiar/asn1-pkcs8": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.9.4.tgz", + "integrity": "sha512-duRdotlUx9eDZe6QrQpQKl61RbWykCHBCkKayP8V8XdEFwlKHZ8qGGDMyS6Pye7OX7nLFttTTpRkJeet78ckwQ==", "dev": true, "license": "MIT", "dependencies": { - "eslint-config-airbnb-extended": "^3.2.0", - "eslint-plugin-perfectionist": "^5.11.0", - "eslint-plugin-promise": "^7.3.0", - "globals": "^17.8.0", - "typescript-eslint": "^8.70.0" + "@peculiar/asn1-schema": "^2.9.4", + "@peculiar/asn1-x509": "^2.9.4", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" }, "engines": { - "node": ">=22.11.0" - }, - "peerDependencies": { - "eslint": "^9.22.0", - "typescript": ">=5.0.0 <6.1.0" - } - }, - "node_modules/@react-ui-org/eslint-config/node_modules/globals": { - "version": "17.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.12.0.tgz", - "integrity": "sha512-cezEd/DTyyht9cvSSURyygXPfy04GtWO/5e6ZPvH7fCtjKz9PYOmuawphw1Ctd1f6C+5JypXfGD7ahNMXvevBA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=14" } }, - "node_modules/@react-ui-org/stylelint-config": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@react-ui-org/stylelint-config/-/stylelint-config-1.0.0.tgz", - "integrity": "sha512-lzgPl7kADCIiuYOD4us0HdVTMsukhDl97pbvaKKbTgQoZekzsBJV12Kfi9XI1Vyz3pi5NczAbrWN+XtRS8iQdA==", + "node_modules/@peculiar/asn1-pkcs9": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.9.4.tgz", + "integrity": "sha512-kaL4cNxBpdQE2dKlyZBqz4ygCrwffO+8wfoxTEqM1Z8RadvCeELBRzcv0dzM8aY9azHMwODO5nxU65zXmhToOQ==", "dev": true, "license": "MIT", "dependencies": { - "@stylistic/stylelint-config": "^5.0.0", - "stylelint-config-standard": "^40.0.0", - "stylelint-config-standard-scss": "^17.0.0", - "stylelint-order": "^8.1.1" - }, - "engines": { - "node": ">=22.11.0" + "@peculiar/asn1-cms": "^2.9.4", + "@peculiar/asn1-pfx": "^2.9.4", + "@peculiar/asn1-pkcs8": "^2.9.4", + "@peculiar/asn1-schema": "^2.9.4", + "@peculiar/asn1-x509": "^2.9.4", + "@peculiar/asn1-x509-attr": "^2.9.4", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" }, - "peerDependencies": { - "stylelint": "^17.6.0" - } - }, - "node_modules/@rolldown/binding-android-arm-eabi": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.7.tgz", - "integrity": "sha512-EypzgnYCwyVY4NDHKzGmNJT5b+XaQEBniHxsMdeIQLB/tcCzZnhqrzHpZFbX9iaxx+5RiB8caATBtfvZP7zVxQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=14" } }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.7.tgz", - "integrity": "sha512-l17HE9EweWaqJZhuUuNBN/FzM62xw+DECVnJyvMsxn8vJFAGLy5QfLDoYAcronkAN8VxKZHezDpulHDPx95vFw==", - "cpu": [ - "arm64" - ], + "node_modules/@peculiar/asn1-rsa": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.9.4.tgz", + "integrity": "sha512-pZ96eD1PptovcWQ/GSmuNFXd/7EQJNlKfDaNCyE2rx3W0v6QFelkzquVqRSRyyDXXCYD69ZXJDzZ8GhIiQzKoA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "dependencies": { + "@peculiar/asn1-schema": "^2.9.4", + "@peculiar/asn1-x509": "^2.9.4", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=14" } }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.7.tgz", - "integrity": "sha512-8ED8ELFvHXc6OCETIn4gXObPiaR6bckM/ipXtbzlPVDRMBfEGjCKgO90F9YtfdpDatVx/ZQw7aZ1vUMf/+T3Mw==", - "cpu": [ - "arm64" - ], + "node_modules/@peculiar/asn1-schema": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.9.4.tgz", + "integrity": "sha512-GjzePcT9Iw8NzeOPf73iNS9xM+TBhd/FilAfP+RQGkTMQJTVWtytN3JHJACCjf/ABNau5S7mS3g+DcuxmRgYEg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=14" } }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.7.tgz", - "integrity": "sha512-/WPripjtiAIZ2tWY7ddijORT0Ujg87wxWW/qcoFVCKAWVDPhtY0xr7Dj0M3GyNGz60jGwTElhro/mkF9dT7dDQ==", - "cpu": [ - "x64" - ], + "node_modules/@peculiar/asn1-x509": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.9.4.tgz", + "integrity": "sha512-CxhBo/RdEbMMob7T31ZdQjGuoyRFLVwrDzTn25bihzBasRg9kRm/0IxIPvhgQtcK/9dNcO1XQL2fuPugwELL0Q==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "@peculiar/asn1-schema": "^2.9.4", + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=14" } }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.7.tgz", - "integrity": "sha512-14DI4NcqpvbICxSnGLx3PmtDaWqRP/KGSGb6C+JLLVPeZRl6dKdHba3pGsqT3vpdTqhEYIPG0MMQ8c0xYqoJxA==", - "cpu": [ - "x64" - ], + "node_modules/@peculiar/asn1-x509-attr": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.9.4.tgz", + "integrity": "sha512-ehQXbpQaQYycgu8OrvigwSPTFfVRcu0ECNYCWw+yzBp02Lw5paRqzzhUpfOgO2K38+WfFZuEz/0RPtam5g0OMg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], + "dependencies": { + "@peculiar/asn1-schema": "^2.9.4", + "@peculiar/asn1-x509": "^2.9.4", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=14" } }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.7.tgz", - "integrity": "sha512-bxrWIRvHWQvbJwi+VIie/kDJmQxcNE6xxWwZdqF/ExVAigtHkv54WTLQPb+QsZdnFy18fg7JPfWGL0RH6vwIlQ==", - "cpu": [ - "arm" - ], + "node_modules/@peculiar/utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@peculiar/utils/-/utils-2.0.3.tgz", + "integrity": "sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "dependencies": { + "tslib": "^2.8.1" } }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.7.tgz", - "integrity": "sha512-toOY2BChBZyuxU7OYX6Tn389di4IzAqPTycVcci0O7FSfBqzRB3RZn+K5Is6ANf4tmgRd/K1yZTsNTXbkXsnLg==", - "cpu": [ - "arm64" - ], + "node_modules/@peculiar/x509": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@peculiar/x509/-/x509-1.14.3.tgz", + "integrity": "sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==", "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@peculiar/asn1-cms": "^2.6.0", + "@peculiar/asn1-csr": "^2.6.0", + "@peculiar/asn1-ecc": "^2.6.0", + "@peculiar/asn1-pkcs9": "^2.6.0", + "@peculiar/asn1-rsa": "^2.6.0", + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "pvtsutils": "^1.3.6", + "reflect-metadata": "^0.2.2", + "tslib": "^2.8.1", + "tsyringe": "^4.10.0" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=20.0.0" } }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.7.tgz", - "integrity": "sha512-lAIXTH/aiLRLxsTgQvfhjo4K1ydWIp00+V0voOr9beb/9ZmkUFrSIb03dXNFRgMNvkE6oGsF10ioQ6UsI+vS5Q==", - "cpu": [ - "arm64" - ], + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, - "os": [ - "linux" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=14" } }, - "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.7.tgz", - "integrity": "sha512-kdnwS28Pkenp/mZMRwjXXXwxQ7pIsm+bF919LUK93BOyhcLsrVKdP2p9fxpiPNPAbNuch8ypQt0pm2P2LYCAGg==", - "cpu": [ - "ppc64" - ], + "node_modules/@pkgr/core": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.3.6.tgz", + "integrity": "sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==", "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" } }, - "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.7.tgz", - "integrity": "sha512-516OdsyLdr5E65paF3yBF55t8mfm9+gmtCsK3xI7XKXIT7EfRlHhxL8K/NR6Hu8BWSgF5+1w74lTL0+nxcc8Qw==", - "cpu": [ - "s390x" - ], + "node_modules/@playwright/test": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.63.0.tgz", + "integrity": "sha512-oxMK4vllB9RK5NQ2l1pq1IfOf2AvnEuj/vYGDj0H2nMtmtZpKtCwt/l00GEO6xjGfpBNAvjovvYdCm50dRQkpQ==", "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.63.0" + }, + "bin": { + "playwright": "cli.js" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=20" } }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.7.tgz", - "integrity": "sha512-r8/z8n7GFaYRln3xmP1Cxy0HH/HLM0uBUPkEuSVEfKGDA89M0FsZRZJRSwe/tJjRx+fpH/gjorfhB8tmEbSFLA==", - "cpu": [ - "x64" - ], + "node_modules/@playwright/test/node_modules/playwright": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.63.0.tgz", + "integrity": "sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==", "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.63.0" + }, + "bin": { + "playwright": "cli.js" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=20" } }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.7.tgz", - "integrity": "sha512-pAsE8iiDxUg1xBqdhrTfg45AVDVpirjz00sblEYClGNNcMnDb+e8beQgqIAw6LvauX/APvgxUnwrgun/YYGBhw==", - "cpu": [ - "x64" - ], + "node_modules/@playwright/test/node_modules/playwright-core": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.63.0.tgz", + "integrity": "sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==", "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=20" } }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.7.tgz", - "integrity": "sha512-lTcIYmmnQQA8Or/2DatS6oSqcdLHvendjS+zLu+FwgToynWMRSmQdpM65fTANJgIS4mjbMOo5KT2lnT9SAb96w==", - "cpu": [ - "arm64" - ], + "node_modules/@react-ui-org/eslint-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@react-ui-org/eslint-config/-/eslint-config-1.1.0.tgz", + "integrity": "sha512-iQXC2Rkm+CuK2FW5r9X7fa92AKjJgffymkwpHgVnp/hHSWf4Ux95dkQhgyXCqNUpUwLuMe/OdwL2lZcK4UreVA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], + "dependencies": { + "eslint-config-airbnb-extended": "^3.2.0", + "eslint-plugin-perfectionist": "^5.11.0", + "eslint-plugin-promise": "^7.3.0", + "globals": "^17.8.0", + "typescript-eslint": "^8.70.0" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=22.11.0" + }, + "peerDependencies": { + "eslint": "^9.22.0", + "typescript": ">=5.0.0 <6.1.0" } }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.7.tgz", - "integrity": "sha512-e3Gu3WxbNk/UqQhxqU7YIYO+9ZBvWNz3U+h/qRFosscMFzdRPbXYSaSWgSnklv2fz1TgzBTcti2z35c/7irsHw==", - "cpu": [ - "arm64" - ], + "node_modules/@react-ui-org/eslint-config/node_modules/globals": { + "version": "17.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.12.0.tgz", + "integrity": "sha512-cezEd/DTyyht9cvSSURyygXPfy04GtWO/5e6ZPvH7fCtjKz9PYOmuawphw1Ctd1f6C+5JypXfGD7ahNMXvevBA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.7.tgz", - "integrity": "sha512-W/jg5qoRSqjsEv0+dZi4e687mcHqmVuU0P4fK6qS/xjetW2Gmc1W8j//z5nAeNcC8Ttm0hV46IjcYeuVwYhuiw==", - "cpu": [ - "x64" - ], + "node_modules/@react-ui-org/stylelint-config": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@react-ui-org/stylelint-config/-/stylelint-config-1.0.0.tgz", + "integrity": "sha512-lzgPl7kADCIiuYOD4us0HdVTMsukhDl97pbvaKKbTgQoZekzsBJV12Kfi9XI1Vyz3pi5NczAbrWN+XtRS8iQdA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "dependencies": { + "@stylistic/stylelint-config": "^5.0.0", + "stylelint-config-standard": "^40.0.0", + "stylelint-config-standard-scss": "^17.0.0", + "stylelint-order": "^8.1.1" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=22.11.0" + }, + "peerDependencies": { + "stylelint": "^17.6.0" } }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", - "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", - "dev": true, - "license": "MIT" - }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -5910,6 +6255,20 @@ } } }, + "node_modules/@testing-library/user-event": { + "version": "14.6.7", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.7.tgz", + "integrity": "sha512-MPCpX8bxe8zS+JmmTwLp8jd0dy1rAm60Te/SL8JrQM3qvQJcBOs1d7IefJMyZzqM3EWBrDn/LWDt1BCGu4ASfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, "node_modules/@tybys/wasm-util": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", @@ -5973,6 +6332,48 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, "node_modules/@types/debug": { "version": "4.1.13", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", @@ -5990,6 +6391,31 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/express": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^2" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.3.tgz", + "integrity": "sha512-dPfW8NFiOF4wOHc7+N/QSxlY9cfSsenewGbAz8C8U/MULPd/YZ27LvJUIlzaXie7e6Ove9YunJGgC9tbHD2cKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, "node_modules/@types/gensync": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/gensync/-/gensync-1.0.5.tgz", @@ -5997,6 +6423,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -6116,6 +6549,20 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/react": { "version": "19.3.0", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz", @@ -6136,6 +6583,37 @@ "@types/react": "^19.3.0" } }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-index": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*" + } + }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", @@ -7346,6 +7824,51 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/accepts/node_modules/negotiator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz", + "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "content-type": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/acorn": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", @@ -7457,6 +7980,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "license": "Apache-2.0", + "bin": { + "ansi-html": "bin/ansi-html" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -7684,6 +8220,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/asn1js": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.10.tgz", + "integrity": "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.5", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/ast-types-flow": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", @@ -7959,6 +8510,63 @@ "node": ">=6.0.0" } }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true, + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", + "on-finished": "^2.4.1", + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/bonjour-service": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.4.4.tgz", + "integrity": "sha512-jCZcVv7eoc4QesRscwEZtSROBen+6LpKAmBIsQYQrsAeVHLyMXWX/t6eIV5KiRZYNUBl8eVqImEEMQ8L5+c/Kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, "node_modules/brace-expansion": { "version": "5.0.9", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", @@ -8049,6 +8657,42 @@ "node": ">=4.0" } }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/bytestreamjs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/bytestreamjs/-/bytestreamjs-2.0.1.tgz", + "integrity": "sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/cacheable": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.5.0.tgz", @@ -8406,6 +9050,60 @@ "node": ">= 12.0.0" } }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.2.tgz", + "integrity": "sha512-o8vI5RE5A6EVVOd9o41jKp41aJom+QTEO/Bx8MYNjexMo/Bv2WOjUfZr+aL0WnYSgymUy6zeguqLTsIhV0gMvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "destroy": "1.2.0", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -8420,6 +9118,40 @@ "dev": true, "license": "MIT" }, + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -8427,6 +9159,26 @@ "dev": true, "license": "MIT" }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, "node_modules/core-js": { "version": "3.50.0", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.50.0.tgz", @@ -8706,6 +9458,36 @@ "node": ">=0.10.0" } }, + "node_modules/default-browser": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.1.tgz", + "integrity": "sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -8724,6 +9506,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/define-properties": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", @@ -8742,6 +9537,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -8752,6 +9557,17 @@ "node": ">=6" } }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -8799,6 +9615,19 @@ "node": ">=8" } }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -8871,6 +9700,13 @@ "dev": true, "license": "MIT" }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true, + "license": "MIT" + }, "node_modules/electron-to-chromium": { "version": "1.5.422", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.422.tgz", @@ -8908,6 +9744,16 @@ "node": ">=14" } }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/enhanced-resolve": { "version": "5.25.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.25.1.tgz", @@ -9184,6 +10030,13 @@ "node": ">=6" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, + "license": "MIT" + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -10257,6 +11110,16 @@ "node": ">=0.10.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -10319,6 +11182,50 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -10430,6 +11337,28 @@ "node": ">=8" } }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -10524,6 +11453,16 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/fraction.js": { "version": "5.3.4", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", @@ -10538,19 +11477,14 @@ "url": "https://github.com/sponsors/rawify" } }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "dev": true, - "hasInstallScript": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">= 0.8" } }, "node_modules/function-bind": { @@ -10764,6 +11698,23 @@ "node": ">= 6" } }, + "node_modules/glob-to-regex.js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/glob-to-regex.js/-/glob-to-regex.js-1.3.1.tgz", + "integrity": "sha512-zhWhMRsgnOym+1txhUmYJGD6eUsbUdrZv55gCYKuJX8t2p3+M8My23PD09bEIsreGUs5pnSa/idmcfp2/IlqGg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, "node_modules/global-modules": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", @@ -11081,6 +12032,51 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-proxy-middleware": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-4.2.0.tgz", + "integrity": "sha512-ZA+oNOoM+GLoFTIzhkJptVQov73Srep2LBqhF8hG8CIPKO3nam1jonXVQ/QUH8RbwsmaaVz2SOJdzBNBHNtKbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "httpxy": "^0.5.4", + "is-glob": "^4.0.3", + "is-plain-obj": "^4.1.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": "^22.15.0 || ^24.0.0 || >=26.0.0" + } + }, + "node_modules/httpxy": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/httpxy/-/httpxy-0.5.5.tgz", + "integrity": "sha512-uDjmnPyp1q4Sgzf3w+J/Fc6UqcCEj0x4Wjp7OqK5dGhNeDgpyrAmnS6ey8QWrX3SWDon2DMKf9sBa5X9+CVyMA==", + "dev": true, + "license": "MIT" + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -11091,6 +12087,33 @@ "node": ">=10.17.0" } }, + "node_modules/hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.18" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/icss-utils": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", @@ -11281,6 +12304,13 @@ "node": ">=8" } }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, "node_modules/ini": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", @@ -11313,6 +12343,16 @@ "node": ">=10.13.0" } }, + "node_modules/ipaddr.js": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.5.0.tgz", + "integrity": "sha512-aq+t5NAc+cS6rZQQVWC2x98CPqGtKKTMDd4Gaodv0wShnItdKg/51djkGJ1hqH+Oy0ivDftCbSLCQob8zso01w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/is-alphabetical": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", @@ -11515,6 +12555,22 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-document.all": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-document.all/-/is-document.all-1.0.0.tgz", @@ -11621,6 +12677,38 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-in-ssh": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz", + "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-map": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", @@ -11647,6 +12735,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-network-error": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.2.tgz", + "integrity": "sha512-PhBY86zaxNZUuWP6h13Vu5oFe0XY6/UlKzQnYFELzGVHygP3MxmvTfYSG7GN3aIab/iWudSMgjSnG9Dq+nHrgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -11687,6 +12788,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-plain-object": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.1.0.tgz", @@ -11697,6 +12811,13 @@ "node": ">=0.10.0" } }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "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", @@ -11855,6 +12976,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", @@ -13669,364 +14806,102 @@ ], "license": "MIT", "dependencies": { - "commander": "^8.3.0" - }, - "bin": { - "katex": "cli.js" - } - }, - "node_modules/katex/node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/known-css-properties": { - "version": "0.37.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.37.0.tgz", - "integrity": "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/language-subtag-registry": { - "version": "0.3.23", - "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", - "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", - "dev": true, - "license": "CC0-1.0" - }, - "node_modules/language-tags": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", - "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", - "dev": true, - "license": "MIT", - "dependencies": { - "language-subtag-registry": "^0.3.20" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lightningcss": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", - "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", - "dev": true, - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.33.0", - "lightningcss-darwin-arm64": "1.33.0", - "lightningcss-darwin-x64": "1.33.0", - "lightningcss-freebsd-x64": "1.33.0", - "lightningcss-linux-arm-gnueabihf": "1.33.0", - "lightningcss-linux-arm64-gnu": "1.33.0", - "lightningcss-linux-arm64-musl": "1.33.0", - "lightningcss-linux-x64-gnu": "1.33.0", - "lightningcss-linux-x64-musl": "1.33.0", - "lightningcss-win32-arm64-msvc": "1.33.0", - "lightningcss-win32-x64-msvc": "1.33.0" - } - }, - "node_modules/lightningcss-android-arm64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", - "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", - "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", - "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", - "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", - "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" + "commander": "^8.3.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "bin": { + "katex": "cli.js" } }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", - "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", - "cpu": [ - "arm64" - ], + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", "dev": true, - "libc": [ - "glibc" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], + "license": "MIT", "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node": ">= 12" } }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", - "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", - "cpu": [ - "arm64" - ], + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, - "libc": [ - "musl" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" } }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", - "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", - "cpu": [ - "x64" - ], + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true, - "libc": [ - "glibc" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], + "license": "MIT", "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node": ">=0.10.0" } }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", - "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", - "cpu": [ - "x64" - ], + "node_modules/known-css-properties": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.37.0.tgz", + "integrity": "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==", "dev": true, - "libc": [ - "musl" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" + "license": "MIT" + }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "license": "MIT", + "dependencies": { + "language-subtag-registry": "^0.3.20" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "engines": { + "node": ">=0.10" } }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", - "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", - "cpu": [ - "arm64" - ], + "node_modules/launch-editor": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.14.1.tgz", + "integrity": "sha512-QWBrQsMpH7gPr965dsKD/3cKWiNoTjpATQf++Xq63N6sKRGMwlVXz41O1IZTMfZQgBctD/K5Zt06+/I6pP6+HA==", "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "license": "MIT", + "dependencies": { + "picocolors": "^1.1.1", + "shell-quote": "^1.8.4" } }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", - "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", - "cpu": [ - "x64" - ], + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], + "license": "MIT", "engines": { - "node": ">= 12.0.0" + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "engines": { + "node": ">= 0.8.0" } }, "node_modules/lines-and-columns": { @@ -14293,6 +15168,47 @@ "dev": true, "license": "MIT" }, + "node_modules/media-typer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/memfs": { + "version": "4.78.1", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.78.1.tgz", + "integrity": "sha512-SkSlK6DZbzekBoiu0mcHUx9RRPnsBdaKQ36u8F3HvYZm/lRiD34YabQ2Xr76r+zYENzRPFKBsanAZPSl+r3Nqg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-core": "4.78.1", + "@jsonjoy.com/fs-fsa": "4.78.1", + "@jsonjoy.com/fs-node": "4.78.1", + "@jsonjoy.com/fs-node-builtins": "4.78.1", + "@jsonjoy.com/fs-node-to-fsa": "4.78.1", + "@jsonjoy.com/fs-node-utils": "4.78.1", + "@jsonjoy.com/fs-print": "4.78.1", + "@jsonjoy.com/fs-snapshot": "4.78.1", + "@jsonjoy.com/json-pack": "^1.11.0", + "@jsonjoy.com/util": "^1.9.0", + "glob-to-regex.js": "^1.3.1", + "thingies": "^2.5.0", + "tree-dump": "^1.0.3", + "tslib": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + } + }, "node_modules/meow": { "version": "14.1.0", "resolved": "https://registry.npmjs.org/meow/-/meow-14.1.0.tgz", @@ -14306,6 +15222,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -14883,6 +15812,23 @@ "node": ">= 0.6" } }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -15084,6 +16030,20 @@ "dev": true, "license": "MIT" }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dev": true, + "license": "MIT", + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, "node_modules/nanoid": { "version": "3.3.18", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", @@ -15136,6 +16096,16 @@ "node": ">=18" } }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/node-addon-api": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", @@ -15345,6 +16315,39 @@ "node": ">=12.20.0" } }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/onetime": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", @@ -15361,6 +16364,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/open": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/open/-/open-11.0.4.tgz", + "integrity": "sha512-++Zlftm0kVLPmzC06t6epuWmcRMDbI4z5P3NNX979WA/k23+NtSOynEGzsVfZwguKw2mi5umVgnBlJQMwRz4Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.5.1", + "define-lazy-prop": "^3.0.0", + "is-in-ssh": "^1.0.0", + "is-inside-container": "^1.0.0", + "powershell-utils": "^0.2.1", + "wsl-utils": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -15421,10 +16445,26 @@ "dev": true, "license": "MIT", "dependencies": { - "p-limit": "^3.0.2" + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-8.0.1.tgz", + "integrity": "sha512-NAigyu8hfvJe7MsS18mnc4is0MZtau3QMdBklaJKK23oBQb6occO3UPM3vHmTckW8KhfyjMZaUOqdTFEYliHgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-network-error": "^1.3.0" }, "engines": { - "node": ">=10" + "node": ">=22" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -15499,6 +16539,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -15543,6 +16593,17 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "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", @@ -15583,51 +16644,22 @@ "node": ">= 6" } }, - "node_modules/playwright": { - "version": "1.62.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.62.1.tgz", - "integrity": "sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg==", + "node_modules/pkijs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.4.0.tgz", + "integrity": "sha512-emEcLuomt2j03vxD54giVB4SxTjnsqkU692xZOZXHDVoYyypEm+b3jpiTcc+Cf+myooc+/Ly0z01jqeNHVgJGw==", "dev": true, - "license": "Apache-2.0", + "license": "BSD-3-Clause", "dependencies": { - "playwright-core": "1.62.1" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=20" - }, - "optionalDependencies": { - "fsevents": "2.3.2" - } - }, - "node_modules/playwright-core": { - "version": "1.62.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.62.1.tgz", - "integrity": "sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "playwright-core": "cli.js" + "@noble/hashes": "1.4.0", + "asn1js": "^3.0.6", + "bytestreamjs": "^2.0.1", + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" }, "engines": { - "node": ">=20" - } - }, - "node_modules/playwright/node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">=16.0.0" } }, "node_modules/possible-typed-array-names": { @@ -15903,6 +16935,19 @@ "dev": true, "license": "MIT" }, + "node_modules/powershell-utils": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.2.1.tgz", + "integrity": "sha512-C+y9x90UElAddDZmV4qOx9W53B61PO7cIqWz2dQsWlwswuq4mr8NEwytdGKboYbQlGZ3awrkTeNvcZiZNHnQ8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -15960,6 +17005,34 @@ "dev": true, "license": "MIT" }, + "node_modules/proxy-addr": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz", + "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -15997,6 +17070,26 @@ ], "license": "MIT" }, + "node_modules/pvtsutils": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/pvutils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.2.0.tgz", + "integrity": "sha512-BbubeCEyTuQjVMakvJQ/Sxbc93F2pwmbsxONT/ZRrwU7Ua38d8unYTwXpTVLAKJ4BDuH9IGztCjQcd/N/39Dvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/qified": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/qified/-/qified-0.10.1.tgz", @@ -16017,6 +17110,23 @@ "dev": true, "license": "MIT" }, + "node_modules/qs": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -16038,6 +17148,36 @@ ], "license": "MIT" }, + "node_modules/range-parser": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", @@ -16113,6 +17253,13 @@ "node": ">=8" } }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -16301,38 +17448,34 @@ "node": ">=0.10.0" } }, - "node_modules/rolldown": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.7.tgz", - "integrity": "sha512-g0EtLvBjTUB7jhyV0S/TCup3v/XSVl45vUIGbOGU4QPiyjTenCe4mKuFvW9fEgYmS2Fo42AUssRmNuMziXdrig==", + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.148.0", - "@rolldown/pluginutils": "^1.0.0" - }, - "bin": { - "rolldown": "bin/cli.mjs" + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">= 18" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" }, - "optionalDependencies": { - "@rolldown/binding-android-arm-eabi": "1.2.7", - "@rolldown/binding-android-arm64": "1.2.7", - "@rolldown/binding-darwin-arm64": "1.2.7", - "@rolldown/binding-darwin-x64": "1.2.7", - "@rolldown/binding-freebsd-x64": "1.2.7", - "@rolldown/binding-linux-arm-gnueabihf": "1.2.7", - "@rolldown/binding-linux-arm64-gnu": "1.2.7", - "@rolldown/binding-linux-arm64-musl": "1.2.7", - "@rolldown/binding-linux-ppc64-gnu": "1.2.7", - "@rolldown/binding-linux-s390x-gnu": "1.2.7", - "@rolldown/binding-linux-x64-gnu": "1.2.7", - "@rolldown/binding-linux-x64-musl": "1.2.7", - "@rolldown/binding-openharmony-arm64": "1.2.7", - "@rolldown/binding-win32-arm64-msvc": "1.2.7", - "@rolldown/binding-win32-x64-msvc": "1.2.7" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/run-parallel": { @@ -16379,6 +17522,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/safe-push-apply": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", @@ -16414,6 +17578,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, "node_modules/sass": { "version": "1.104.1", "resolved": "https://registry.npmjs.org/sass/-/sass-1.104.1.tgz", @@ -16529,21 +17700,206 @@ "ajv": "^8.8.2" } }, - "node_modules/schema-utils/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/selfsigned": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-5.5.0.tgz", + "integrity": "sha512-ftnu3TW4+3eBfLRFnDEkzGxSF/10BJBkaLJuBHZX0kiPS7bRdlpZGu6YGt4KngMkdTwJE6MbjavFpqHvqVt+Ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/x509": "^1.14.2", + "pkijs": "^3.3.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-index": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.2.tgz", + "integrity": "sha512-KDj11HScOaLmrPxl70KYNW1PksP4Nb/CLL2yvC+Qd2kHMPEEpfc4Re2e4FOay+bC/+XQl/7zAcWON3JVo5v3KQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.8.0", + "mime-types": "~2.1.35", + "parseurl": "~1.3.3" + }, + "engines": { + "node": ">= 0.8.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-index/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/serve-index/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">= 0.6" + } }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/set-function-length": { @@ -16595,6 +17951,13 @@ "node": ">= 0.4" } }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -16631,6 +17994,19 @@ "node": ">=8" } }, + "node_modules/shell-quote": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.10.0.tgz", + "integrity": "sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/side-channel": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", @@ -16829,6 +18205,16 @@ "node": ">=8" } }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", @@ -17990,6 +19376,30 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/thingies": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-2.6.1.tgz", + "integrity": "sha512-cV/CMGTK3M4MlnJ/0At6ismOw/A0EEniDNScajjz/Br3c1sqE72YD01rGpPTKwd27wAxI5Pr+6+0w8yofzFRYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "^2" + } + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true, + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.17", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", @@ -18051,6 +19461,33 @@ "node": ">=8.0" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tree-dump": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz", + "integrity": "sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, "node_modules/ts-api-utils": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", @@ -18105,8 +19542,27 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, - "license": "0BSD", - "optional": true + "license": "0BSD" + }, + "node_modules/tsyringe": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.10.0.tgz", + "integrity": "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^1.9.3" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/tsyringe/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true, + "license": "0BSD" }, "node_modules/type-check": { "version": "0.4.0", @@ -18131,6 +19587,39 @@ "node": ">=4" } }, + "node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "dev": true, + "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/typed-array-buffer": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", @@ -18337,6 +19826,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/unrs-resolver": { "version": "1.12.2", "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.12.2.tgz", @@ -18438,6 +19937,16 @@ "node": ">=10.12.0" } }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/verkit": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/verkit/-/verkit-0.3.2.tgz", @@ -18451,97 +19960,6 @@ "url": "https://github.com/sponsors/sxzz" } }, - "node_modules/vite": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.2.tgz", - "integrity": "sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "lightningcss": "^1.33.0", - "picomatch": "^4.0.5", - "postcss": "^8.5.26", - "rolldown": "~1.2.4", - "tinyglobby": "^0.2.17" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.4.0 || ^0.5.0", - "esbuild": "^0.27.0 || ^0.28.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "@vitejs/devtools": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vite/node_modules/picomatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", - "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/watchpack": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.2.tgz", @@ -18648,6 +20066,90 @@ } } }, + "node_modules/webpack-dev-middleware": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-8.3.0.tgz", + "integrity": "sha512-jFNNMB29ugmkEUFW9cpWNqcWFQNwuMTMVoRpKVAiz/5nh3gA4x9x6DRPhNHGc/SLre7pEP9yrAPDAmCvdCblKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-html-community": "^0.0.8", + "memfs": "^4.68.2", + "mime-types": "^3.0.2", + "range-parser": "^1.3.0", + "schema-utils": "^4.3.3" + }, + "engines": { + "node": ">= 20.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.101.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-6.0.0.tgz", + "integrity": "sha512-q9SD4ItOGhZLeU6EGT10caDZdHjF50Pz1DtkRZZOPsfluMXOkacWKKOtSBSLVkPqKiF67eFUC0rI88U/tSFPEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/bonjour": "^3.5.13", + "@types/connect-history-api-fallback": "^1.5.4", + "@types/express": "^5.0.6", + "@types/express-serve-static-core": "^5.1.1", + "@types/serve-index": "^1.9.4", + "@types/serve-static": "^2.2.0", + "@types/ws": "^8.18.1", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.3.0", + "chokidar": "^5.0.0", + "compression": "^1.8.1", + "connect-history-api-fallback": "^2.0.0", + "express": "^5.2.1", + "graceful-fs": "^4.2.11", + "http-proxy-middleware": "^4.1.1", + "ipaddr.js": "^2.3.0", + "launch-editor": "^2.14.1", + "open": "^11.0.0", + "p-retry": "^8.0.0", + "schema-utils": "^4.3.3", + "selfsigned": "^5.5.0", + "serve-index": "^1.9.2", + "tinyglobby": "^0.2.15", + "webpack-dev-middleware": "^8.0.3", + "ws": "^8.20.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 22.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.101.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } + } + }, "node_modules/webpack-merge": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", @@ -18903,6 +20405,13 @@ "node": ">=8" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, "node_modules/write-file-atomic": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", @@ -18952,6 +20461,36 @@ } } }, + "node_modules/wsl-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-1.0.0.tgz", + "integrity": "sha512-Hl0ZOAs672vg+06kfujwRhoS6/jehvULrlFkuF2dRu6pHgA8U06h3xqNIqNNU1LTXPcedxByAR4GS6pwQK0mgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0", + "powershell-utils": "^0.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wsl-utils/node_modules/powershell-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz", + "integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index 948f812ab..37709fb19 100644 --- a/package.json +++ b/package.json @@ -95,12 +95,13 @@ "@babel/preset-react": "^8.0.1", "@babel/preset-typescript": "^8.0.1", "@happy-dom/jest-environment": "^20.14.5", - "@playwright/experimental-ct-react": "^1.62.1", + "@playwright/test": "~1.63.0", "@react-ui-org/eslint-config": "^1.1.0", "@react-ui-org/stylelint-config": "^1.0.0", "@testing-library/dom": "^10.4.2", "@testing-library/jest-dom": "^7.0.1", "@testing-library/react": "^16.3.3", + "@testing-library/user-event": "^14.6.7", "@types/jest": "^30.0.0", "@types/node": "^26.5.1", "@types/prop-types": "^15.7.15", @@ -133,6 +134,7 @@ "typescript": "npm:@typescript/typescript6@^6.0.2", "webpack": "^5.111.0", "webpack-cli": "^7.2.3", + "webpack-dev-server": "^6.0.0", "webpack-visualizer-plugin2": "^2.0.0" }, "overrides": { diff --git a/playwright-ct.config.ts b/playwright-ct.config.ts index 2a796258a..530112a7d 100644 --- a/playwright-ct.config.ts +++ b/playwright-ct.config.ts @@ -1,13 +1,16 @@ import { defineConfig, devices, -} from '@playwright/experimental-ct-react'; +} from '@playwright/test'; import { parseEnvironment } from './tests/playwright/env/parseEnvironment'; const environment = parseEnvironment(); const isCI = !!process.env.CI; +// URL of the Playwright Component Testing page served by the webpack dev server (see `tests/playwright/ct/`) +const componentTestingUrl = `http://localhost:${environment.CT_PORT}/index.html`; + /** * @see https://playwright.dev/docs/test-configuration */ @@ -42,12 +45,12 @@ module.exports = defineConfig({ updateSnapshots: 'none', // Configuration for Playwright component testing use: { - // Directory where the cache is stored - ctCacheDir: './tests/playwright/.temp/playwright-ct-cache', - // Port to use for Playwright component endpoint - ctPort: environment.CT_PORT, - // Directory where the templates are stored - ctTemplateDir: './tests/playwright/templates', + // URL of the Playwright Component Testing page the `mount` fixture navigates to + baseURL: componentTestingUrl, + // Reuse the browser context across tests in a worker (`mount()` navigates, so tests stay isolated) + reuseContext: true, + // Prevent a service worker from serving cached responses that would shadow `page.route()` mocks + serviceWorkers: 'block', // Launch options for Playwright launchOptions: { args: [ @@ -63,6 +66,12 @@ module.exports = defineConfig({ // Collect trace on all retries trace: 'on-all-retries', }, + // Webpack dev server serving the Playwright Component Testing page + webServer: { + command: `webpack serve --config tests/playwright/ct/webpack.config.js --port ${environment.CT_PORT}`, + reuseExistingServer: !isCI, + url: componentTestingUrl, + }, // Number of workers to use workers: environment.WORKERS, }); diff --git a/src/components/Alert/__tests__/Alert.spec.tsx b/src/components/Alert/__tests__/Alert.spec.tsx index 3bed2241c..a42e8935f 100644 --- a/src/components/Alert/__tests__/Alert.spec.tsx +++ b/src/components/Alert/__tests__/Alert.spec.tsx @@ -1,10 +1,8 @@ -import React from 'react'; import { expect, propTests, test, } from '../../../../tests/playwright'; -import { AlertForTest } from './Alert.story'; test.describe('Alert', () => { test.describe('visual', () => { @@ -31,11 +29,7 @@ test.describe('Alert', () => { await onBeforeTest(page); } - const component = await mount( - , - ); + const component = await mount('Alert/AlertForTest', props); if (onBeforeSnapshot) { await onBeforeSnapshot(page, component); @@ -49,11 +43,9 @@ test.describe('Alert', () => { test.describe('non-visual', () => { test('id', async ({ mount }) => { - const component = await mount( - , - ); + const component = await mount('Alert/AlertForTest', { + id: 'test-id', + }); await expect(component).toHaveAttribute('id', 'test-id'); }); @@ -61,33 +53,19 @@ test.describe('Alert', () => { test.describe('functionality', () => { test('calls onClose when close button clicked', async ({ mount }) => { - let clicked = false; - const component = await mount( - { - clicked = true; - }} - />, - ); + const component = await mount('Alert/AlertSpyForTest'); const closeButton = component.getByRole('button'); await closeButton.click(); - expect(clicked).toBeTruthy(); + await expect.poll(() => component.getSpyValue('onClose')).toContain(true); }); test('calls onClose when Enter pressed on close button', async ({ mount }) => { - let clicked = false; - const component = await mount( - { - clicked = true; - }} - />, - ); + const component = await mount('Alert/AlertSpyForTest'); const closeButton = component.getByRole('button'); await closeButton.press('Enter'); - expect(clicked).toBeTruthy(); + await expect.poll(() => component.getSpyValue('onClose')).toContain(true); }); }); }); diff --git a/src/components/Alert/__tests__/Alert.story.tsx b/src/components/Alert/__tests__/Alert.story.tsx index 108b570ef..0698b2e2c 100644 --- a/src/components/Alert/__tests__/Alert.story.tsx +++ b/src/components/Alert/__tests__/Alert.story.tsx @@ -1,6 +1,10 @@ import React from 'react'; import { Alert } from '..'; import type { AlertProps } from '..'; +import { + useSpy, + withSpy, +} from '../../../../tests/playwright/utils/spy'; import type { StoryProps } from '../../../../tests/playwright'; type AlertForTestProps = StoryProps; @@ -16,3 +20,14 @@ export const AlertForTest = ({ This is notification content. ); + +export const AlertSpyForTest = withSpy((props: AlertForTestProps) => { + const onClose = useSpy('onClose', () => true); + + return ( + + ); +}); diff --git a/src/components/Badge/__tests__/Badge.spec.tsx b/src/components/Badge/__tests__/Badge.spec.tsx index 2f351dd7d..b49bae057 100644 --- a/src/components/Badge/__tests__/Badge.spec.tsx +++ b/src/components/Badge/__tests__/Badge.spec.tsx @@ -1,11 +1,9 @@ -import React from 'react'; import { expect, mixPropTests, propTests, test, } from '../../../../tests/playwright'; -import { BadgeForTest } from './Badge.story'; import { labelPropTest } from './_propTests/labelPropTest'; import { priorityPropTest } from './_propTests/priorityPropTest'; @@ -35,11 +33,7 @@ test.describe('Badge', () => { await onBeforeTest(page); } - const component = await mount( - , - ); + const component = await mount('Badge/BadgeForTest', props); if (onBeforeSnapshot) { await onBeforeSnapshot(page, component); @@ -53,11 +47,9 @@ test.describe('Badge', () => { test.describe('non-visual', () => { test('id', async ({ mount }) => { - const component = await mount( - , - ); + const component = await mount('Badge/BadgeForTest', { + id: 'test-id', + }); await expect(component).toHaveAttribute('id', 'test-id'); }); diff --git a/src/components/Button/__tests__/Button.spec.tsx b/src/components/Button/__tests__/Button.spec.tsx index c0a843e8d..2bf79e63e 100644 --- a/src/components/Button/__tests__/Button.spec.tsx +++ b/src/components/Button/__tests__/Button.spec.tsx @@ -1,16 +1,10 @@ -import React from 'react'; import { + element, expect, mixPropTests, propTests, test, } from '../../../../tests/playwright'; -import { - ButtonForRefTest, - ButtonForTest, - ButtonInHorizontalFormLayoutForTest, - ButtonInVerticalFormLayoutForTest, -} from './Button.story'; test.describe('Button', () => { test.describe('visual', () => { @@ -48,11 +42,7 @@ test.describe('Button', () => { await onBeforeTest(page); } - const component = await mount( - , - ); + const component = await mount('Button/ButtonForTest', props); if (onBeforeSnapshot) { await onBeforeSnapshot(page, component); @@ -69,24 +59,20 @@ test.describe('Button', () => { const testId = 'testId'; const testLabel = 'testLabel'; - const component = await mount( - , - ); + const component = await mount('Button/ButtonForTest', { + id: testId, + label: testLabel, + }); await expect(component).toHaveAttribute('id', testId); await expect(component.getByText(testLabel)).toHaveAttribute('id', `${testId}__labelText`); }); test('ref', async ({ mount }) => { - const component = await mount( - , - ); + const component = await mount('Button/ButtonForRefTest', { + testRefAttrName: 'test-ref', + testRefAttrValue: 'test-ref-value', + }); await expect(component).toHaveAttribute('test-ref', 'test-ref-value'); }); @@ -94,75 +80,51 @@ test.describe('Button', () => { test.describe('functionality', () => { test('calls onClick when clicked', async ({ mount }) => { - let clicked = false; - const component = await mount( - { - clicked = true; - }} - />, - ); - await component.click(); - - expect(clicked).toBeTruthy(); + const component = await mount('Button/ButtonSpyForTest'); + await component.getByRole('button').click(); + + await expect.poll(() => component.getSpyValue('onClick')).toContain(true); }); test('calls onClick when Enter pressed', async ({ mount }) => { - let clicked = false; - const component = await mount( - { - clicked = true; - }} - />, - ); - await component.press('Enter'); - - expect(clicked).toBeTruthy(); + const component = await mount('Button/ButtonSpyForTest'); + await component.getByRole('button').press('Enter'); + + await expect.poll(() => component.getSpyValue('onClick')).toContain(true); }); test('is disabled when disabled is set', async ({ mount }) => { - let clicked = false; - const component = await mount( - { - clicked = true; - }} - />, - ); - await component.click({ force: true }); - - await expect(component).toBeDisabled(); - expect(clicked).toBeFalsy(); + const component = await mount('Button/ButtonSpyForTest', { + disabled: true, + }); + const button = component.getByRole('button'); + await button.click({ force: true }); + + await expect(button).toBeDisabled(); + await expect.poll(() => component.getSpyValue('onClick')).toEqual([]); }); test('is disabled when feedbackIcon is set', async ({ mount }) => { - let clicked = false; - const component = await mount( - Placeholder} - onClick={() => { - clicked = true; - }} - />, - ); - await component.click({ force: true }); - - await expect(component).toBeDisabled(); - expect(clicked).toBeFalsy(); + const component = await mount('Button/ButtonSpyForTest', { + feedbackIcon: element('span', { children: 'Placeholder' }), + }); + const button = component.getByRole('button'); + await button.click({ force: true }); + + await expect(button).toBeDisabled(); + await expect.poll(() => component.getSpyValue('onClick')).toEqual([]); }); }); test.describe('formLayout', () => { test.describe('visual', () => { test('vertical', async ({ mount }) => { - const component = await mount(); + const component = await mount('Button/ButtonInVerticalFormLayoutForTest'); expect(await component.screenshot()).toMatchSnapshot(); }); test('horizontal', async ({ mount }) => { - const component = await mount(); + const component = await mount('Button/ButtonInHorizontalFormLayoutForTest'); expect(await component.screenshot()).toMatchSnapshot(); }); }); diff --git a/src/components/Button/__tests__/Button.story.tsx b/src/components/Button/__tests__/Button.story.tsx index ccdca96bb..59802f13d 100644 --- a/src/components/Button/__tests__/Button.story.tsx +++ b/src/components/Button/__tests__/Button.story.tsx @@ -6,6 +6,10 @@ import { FormLayout } from '../../FormLayout'; import { TextField } from '../../TextField'; import { Button } from '..'; import type { ButtonProps } from '..'; +import { + useSpy, + withSpy, +} from '../../../../tests/playwright/utils/spy'; import type { StoryProps } from '../../../../tests/playwright'; type ButtonForTestProps = StoryProps; @@ -70,3 +74,14 @@ export const ButtonInHorizontalFormLayoutForTest = ({ /> ); + +export const ButtonSpyForTest = withSpy((props: ButtonForTestProps) => { + const onClick = useSpy('onClick', () => true); + + return ( + + ); +}); diff --git a/src/components/ButtonGroup/__tests__/ButtonGroup.spec.tsx b/src/components/ButtonGroup/__tests__/ButtonGroup.spec.tsx index fe2f3883e..9f2adb1d7 100644 --- a/src/components/ButtonGroup/__tests__/ButtonGroup.spec.tsx +++ b/src/components/ButtonGroup/__tests__/ButtonGroup.spec.tsx @@ -1,14 +1,9 @@ -import React from 'react'; import { expect, mixPropTests, propTests, test, } from '../../../../tests/playwright'; -import { - ButtonGroupForTest, - SelectedButtonGroupForTest, -} from './ButtonGroup.story'; test.describe('ButtonGroup', () => { test.describe('base', () => { @@ -36,7 +31,7 @@ test.describe('ButtonGroup', () => { await onBeforeTest(page); } - const component = await mount(); + const component = await mount('ButtonGroup/ButtonGroupForTest', props); if (onBeforeSnapshot) { await onBeforeSnapshot(page, component); @@ -67,7 +62,7 @@ test.describe('ButtonGroup', () => { await onBeforeTest(page); } - const component = await mount(); + const component = await mount('ButtonGroup/SelectedButtonGroupForTest', props); if (onBeforeSnapshot) { await onBeforeSnapshot(page, component); diff --git a/src/components/Card/__tests__/Card.spec.tsx b/src/components/Card/__tests__/Card.spec.tsx index 58eff8a7f..0dd989866 100644 --- a/src/components/Card/__tests__/Card.spec.tsx +++ b/src/components/Card/__tests__/Card.spec.tsx @@ -1,15 +1,9 @@ -import React from 'react'; import { expect, mixPropTests, propTests, test, } from '../../../../tests/playwright'; -import { - CadForTest, - CadWithScrollableForTest, - CardOnlyWithBodyForTest, -} from './Card.story'; import { densePropTest } from './_propTests/densePropTest'; test.describe('Card', () => { @@ -36,11 +30,7 @@ test.describe('Card', () => { await onBeforeTest(page); } - const component = await mount( - , - ); + const component = await mount('Card/CadForTest', props); if (onBeforeSnapshot) { await onBeforeSnapshot(page, component); @@ -73,11 +63,7 @@ test.describe('Card', () => { await onBeforeTest(page); } - const component = await mount( - , - ); + const component = await mount('Card/CardOnlyWithBodyForTest', props); if (onBeforeSnapshot) { await onBeforeSnapshot(page, component); @@ -106,11 +92,7 @@ test.describe('Card', () => { await onBeforeTest(page); } - const component = await mount( - , - ); + const component = await mount('Card/CadWithScrollableForTest', props); if (onBeforeSnapshot) { await onBeforeSnapshot(page, component); @@ -128,11 +110,9 @@ test.describe('Card', () => { test('id', async ({ mount }) => { const id = 'test-id'; - const component = await mount( - , - ); + const component = await mount('Card/CadForTest', { + id, + }); await expect(component.locator(`div[id=${id}]`)).toHaveAttribute('id', id); }); diff --git a/src/components/CheckboxField/__tests__/CheckboxField.spec.tsx b/src/components/CheckboxField/__tests__/CheckboxField.spec.tsx index 9d517ecb5..fbceb43fa 100644 --- a/src/components/CheckboxField/__tests__/CheckboxField.spec.tsx +++ b/src/components/CheckboxField/__tests__/CheckboxField.spec.tsx @@ -1,17 +1,9 @@ -import React from 'react'; import { expect, mixPropTests, propTests, test, } from '../../../../tests/playwright'; -import type { CheckboxForFormLayoutTestsProps } from './CheckboxField.story'; -import { - CheckboxFieldForRefTest, - CheckboxFieldForTest, - CheckboxForFormLayoutLabelWidthTests, - CheckboxForFormLayoutTests, -} from './CheckboxField.story'; test.describe('CheckboxField', () => { test.describe('base', () => { @@ -56,11 +48,7 @@ test.describe('CheckboxField', () => { await onBeforeTest(page); } - const component = await mount( - , - ); + const component = await mount('CheckboxField/CheckboxFieldForTest', props); if (onBeforeSnapshot) { await onBeforeSnapshot(page, component); @@ -79,14 +67,12 @@ test.describe('CheckboxField', () => { const helpText = 'checkbox-helpText'; const validationText = 'checkbox-validationText'; - const component = await mount( - , - ); + const component = await mount('CheckboxField/CheckboxFieldForTest', { + helpText, + id: idValue, + label, + validationText, + }); await expect(component.getByRole('checkbox')).toHaveAttribute('id', idValue); await expect(component).toHaveAttribute('id', `${idValue}__label`); @@ -96,12 +82,10 @@ test.describe('CheckboxField', () => { }); test('ref', async ({ mount }) => { - const component = await mount( - , - ); + const component = await mount('CheckboxField/CheckboxFieldForRefTest', { + testRefAttrName: 'test-ref', + testRefAttrValue: 'test-ref-value', + }); await expect(component.getByRole('checkbox')).toHaveAttribute('test-ref', 'test-ref-value'); }); @@ -109,24 +93,14 @@ test.describe('CheckboxField', () => { test.describe('functionality', () => { test('calls synthetic event onChange()', async ({ mount }) => { - let changeCalled = false; - - const component = await mount( - { - changeCalled = true; - }} - />, - ); - - await component.click({ force: true }); - expect(changeCalled).toBeTruthy(); + const component = await mount('CheckboxField/CheckboxFieldSpyForTest'); + + await component.getByRole('checkbox').click({ force: true }); + await expect.poll(() => component.getSpyValue('onChange')).toContain(true); }); test('check on space press when focused', async ({ mount }) => { - const component = await mount( - , - ); + const component = await mount('CheckboxField/CheckboxFieldForTest'); const input = component.getByRole('checkbox'); await input.focus(); @@ -139,7 +113,7 @@ test.describe('CheckboxField', () => { test.describe('formLayout', () => { test.describe('visual', () => { test('labelWidth:string=100px', async ({ mount }) => { - const component = await mount(); + const component = await mount('CheckboxField/CheckboxForFormLayoutLabelWidthTests'); const screenshot = await component.screenshot(); expect(screenshot).toMatchSnapshot(); @@ -161,11 +135,7 @@ test.describe('CheckboxField', () => { await onBeforeTest(page); } - const component = await mount( - , - ); + const component = await mount('CheckboxField/CheckboxForFormLayoutTests', props); if (onBeforeSnapshot) { await onBeforeSnapshot(page, component); diff --git a/src/components/CheckboxField/__tests__/CheckboxField.story.tsx b/src/components/CheckboxField/__tests__/CheckboxField.story.tsx index 89a5af054..d8d324629 100644 --- a/src/components/CheckboxField/__tests__/CheckboxField.story.tsx +++ b/src/components/CheckboxField/__tests__/CheckboxField.story.tsx @@ -7,6 +7,10 @@ import { FormLayout } from '../../FormLayout'; import { FormLayoutContext } from '../../FormLayout/FormLayoutContext'; import { CheckboxField } from '..'; import type { CheckboxFieldProps } from '..'; +import { + useSpy, + withSpy, +} from '../../../../tests/playwright/utils/spy'; import type { StoryProps } from '../../../../tests/playwright'; type CheckboxFieldForTestProps = StoryProps; @@ -80,3 +84,14 @@ export const CheckboxForFormLayoutTests = ({ ); }; + +export const CheckboxFieldSpyForTest = withSpy((props: CheckboxFieldForTestProps) => { + const onChange = useSpy('onChange', () => true); + + return ( + + ); +}); diff --git a/src/components/FileInputField/__tests__/FileInputField.spec.tsx b/src/components/FileInputField/__tests__/FileInputField.spec.tsx index 44834aeb2..27f9440df 100644 --- a/src/components/FileInputField/__tests__/FileInputField.spec.tsx +++ b/src/components/FileInputField/__tests__/FileInputField.spec.tsx @@ -1,19 +1,9 @@ -import React from 'react'; import { expect, mixPropTests, propTests, test, } from '../../../../tests/playwright'; -import type { FileInputFieldForFormLayoutTestsProps } from './FileInputField.story'; -import { - FileInputFieldForFormLayoutCustomFieldTests, - FileInputFieldForFormLayoutLabelWidthTests, - FileInputFieldForFormLayoutTests, - FileInputFieldForRefTest, - FileInputFieldForTest, - FileInputFieldWithResetButtonForTest, -} from './FileInputField.story'; import { fileSelectedPropTest } from './_propTests/fileSelectedPropTest'; test.describe('FileInputField', () => { @@ -51,11 +41,7 @@ test.describe('FileInputField', () => { await onBeforeTest(page); } - const component = await mount( - , - ); + const component = await mount('FileInputField/FileInputFieldForTest', props); if (onBeforeSnapshot) { await onBeforeSnapshot(page, component); @@ -74,14 +60,12 @@ test.describe('FileInputField', () => { const helpText = 'helpText'; const validationText = 'validationText'; - const component = await mount( - , - ); + const component = await mount('FileInputField/FileInputFieldForTest', { + helpText, + id: testId, + label: testLabel, + validationText, + }); expect(component.locator(`div[id="${testId}__root"]`)).toBeDefined(); await expect(component.getByText(testLabel)).toHaveAttribute('id', `${testId}__labelText`); @@ -91,12 +75,10 @@ test.describe('FileInputField', () => { }); test('ref', async ({ mount }) => { - const component = await mount( - , - ); + const component = await mount('FileInputField/FileInputFieldForRefTest', { + testRefAttrName: 'test-ref', + testRefAttrValue: 'test-ref-value', + }); await expect(component.locator('input[type="file"]')).toHaveAttribute('test-ref', 'test-ref-value'); }); @@ -104,15 +86,7 @@ test.describe('FileInputField', () => { test.describe('functionality', () => { test('Call onFilesChanged callback when file upload.', async ({ mount }) => { - let called = false; - - const component = await mount( - { - called = true; - }} - />, - ); + const component = await mount('FileInputField/FileInputFieldSpyForTest'); const virtualFile = { buffer: Buffer.from('This is test file.'), @@ -123,27 +97,16 @@ test.describe('FileInputField', () => { const inputField = component.locator('input[type="file"]'); await inputField.setInputFiles(virtualFile); - expect(called).toBe(true); + await expect.poll(() => component.getSpyValue('onFilesChanged')).toEqual([['testfile.txt']]); }); test('Call onFilesChanged callback when no file is selected.', async ({ mount }) => { - let called = false; - let calledWith: FileList | File[] | null = null; - - const component = await mount( - { - called = true; - calledWith = files; - }} - />, - ); + const component = await mount('FileInputField/FileInputFieldSpyForTest'); const inputField = component.locator('input[type="file"]'); await inputField.setInputFiles([]); - expect(called).toBe(true); - expect(calledWith).toStrictEqual([]); + await expect.poll(() => component.getSpyValue('onFilesChanged')).toEqual([[]]); }); test('Call onFilesChanged callback when file drag and drop into field.', async ({ @@ -151,16 +114,10 @@ test.describe('FileInputField', () => { page, }) => { const id = 'dropzoneId'; - let called = false; - const component = await mount( - { - called = true; - }} - />, - ); + const component = await mount('FileInputField/FileInputFieldSpyForTest', { + id, + }); const fileName = 'newFile.txt'; const fileContent = 'This is a test file'; @@ -187,20 +144,13 @@ test.describe('FileInputField', () => { await dropZone.dispatchEvent('dragenter', { dataTransfer }); await dropZone.dispatchEvent('drop', { dataTransfer }); - expect(called).toBe(true); + await expect.poll(() => component.getSpyValue('onFilesChanged')).toEqual([[fileName]]); }); test('Can upload multiple files.', async ({ mount }) => { - let listLength = 0; - - const component = await mount( - { - listLength = Object.keys(files).length; - }} - />, - ); + const component = await mount('FileInputField/FileInputFieldSpyForTest', { + multiple: true, + }); const virtualFile1 = { buffer: Buffer.from('This is test file.'), @@ -220,7 +170,7 @@ test.describe('FileInputField', () => { virtualFile2, ]); - expect(listLength).toBe(2); + await expect.poll(() => component.getSpyValue('onFilesChanged')).toEqual([['testfile.txt', 'testfile.txt']]); }); test('Can upload multiple files via drag and drop.', async ({ @@ -228,17 +178,11 @@ test.describe('FileInputField', () => { page, }) => { const id = 'dropzoneId'; - let numberOfCalls = 0; - - const component = await mount( - { - numberOfCalls += 1; - }} - />, - ); + + const component = await mount('FileInputField/FileInputFieldSpyForTest', { + id, + multiple: true, + }); const fileName1 = 'newFile1.txt'; const fileContent1 = 'This is a test file'; @@ -280,19 +224,11 @@ test.describe('FileInputField', () => { await dropZone.dispatchEvent('dragenter', { dataTransfer: dataTransfer2 }); await dropZone.dispatchEvent('drop', { dataTransfer: dataTransfer2 }); - expect(numberOfCalls).toBe(2); + await expect.poll(async () => (await component.getSpyValue('onFilesChanged')).length).toBe(2); }); test('Able to reset selected file.', async ({ mount }) => { - let keyLength; - - const component = await mount( - { - keyLength = Object.keys(files).length; - }} - />, - ); + const component = await mount('FileInputField/FileInputFieldWithResetButtonSpyForTest'); const virtualFile = { buffer: Buffer.from('This is test file.'), @@ -304,10 +240,10 @@ test.describe('FileInputField', () => { const resetButton = component.getByText('Reset'); await inputField.setInputFiles(virtualFile); - expect(keyLength).toBe(1); + await expect.poll(async () => (await component.getSpyValue('onFilesChanged')).at(-1)).toEqual(['testfile.txt']); await resetButton.click(); - expect(keyLength).toBe(0); + await expect.poll(async () => (await component.getSpyValue('onFilesChanged')).at(-1)).toEqual([]); }); }); }); @@ -315,7 +251,7 @@ test.describe('FileInputField', () => { test.describe('formLayout', () => { test.describe('visual', () => { test('labelWidth:string=100px', async ({ mount }) => { - const component = await mount(); + const component = await mount('FileInputField/FileInputFieldForFormLayoutLabelWidthTests'); const screenshot = await component.screenshot(); expect(screenshot).toMatchSnapshot(); @@ -337,11 +273,7 @@ test.describe('FileInputField', () => { await onBeforeTest(page); } - const component = await mount( - , - ); + const component = await mount('FileInputField/FileInputFieldForFormLayoutTests', props); if (onBeforeSnapshot) { await onBeforeSnapshot(page, component); @@ -357,7 +289,7 @@ test.describe('FileInputField', () => { test.describe('formLayoutCustomField', () => { test.describe('visual', () => { test('label:hidden', async ({ mount }) => { - const component = await mount(); + const component = await mount('FileInputField/FileInputFieldForFormLayoutCustomFieldTests'); const screenshot = await component.screenshot(); expect(screenshot).toMatchSnapshot(); diff --git a/src/components/FileInputField/__tests__/FileInputField.story.tsx b/src/components/FileInputField/__tests__/FileInputField.story.tsx index b65e6196a..82daab048 100644 --- a/src/components/FileInputField/__tests__/FileInputField.story.tsx +++ b/src/components/FileInputField/__tests__/FileInputField.story.tsx @@ -12,6 +12,10 @@ import type { import { FormLayout } from '../../FormLayout'; import { FormLayoutContext } from '../../FormLayout/FormLayoutContext'; import { FormLayoutCustomFieldContext } from '../../FormLayout/FormLayoutCustomFieldContext'; +import { + useSpy, + withSpy, +} from '../../../../tests/playwright/utils/spy'; import type { StoryProps } from '../../../../tests/playwright'; type FileInputFieldForTestProps = StoryProps; @@ -169,3 +173,28 @@ export const FileInputFieldForFormLayoutCustomFieldTests = ({ ); + +// Records the names of the selected files +const selectFileNames = (files: FileList | File[]) => Array.from(files, (file) => file.name); + +export const FileInputFieldSpyForTest = withSpy((props: FileInputFieldForTestProps) => { + const onFilesChanged = useSpy('onFilesChanged', selectFileNames); + + return ( + + ); +}); + +export const FileInputFieldWithResetButtonSpyForTest = withSpy((props: FileInputFieldForTestProps) => { + const onFilesChanged = useSpy('onFilesChanged', selectFileNames); + + return ( + + ); +}); diff --git a/src/components/FileInputField/__tests__/_propTests/fileSelectedPropTest.ts b/src/components/FileInputField/__tests__/_propTests/fileSelectedPropTest.ts index 988bc4cac..77d720e5d 100644 --- a/src/components/FileInputField/__tests__/_propTests/fileSelectedPropTest.ts +++ b/src/components/FileInputField/__tests__/_propTests/fileSelectedPropTest.ts @@ -1,5 +1,5 @@ -import type { Page } from 'playwright/test'; -import type { MountResult } from '@playwright/experimental-ct-react'; +import type { Page } from '@playwright/test'; +import type { MountResult } from '../../../../../tests/playwright'; import { propTests } from '../../../../../tests/playwright'; import type { PropTests } from '../../../../../tests/playwright/types'; diff --git a/src/components/FormLayout/__tests__/FormLayout.spec.tsx b/src/components/FormLayout/__tests__/FormLayout.spec.tsx index ceb2906d6..7050b773a 100644 --- a/src/components/FormLayout/__tests__/FormLayout.spec.tsx +++ b/src/components/FormLayout/__tests__/FormLayout.spec.tsx @@ -1,13 +1,8 @@ -import React from 'react'; import { expect, propTests, test, } from '../../../../tests/playwright'; -import { - FormLayoutForTest, - FormLayoutWithoutChildrenForTest, -} from './FormLayout.story'; import { labelWidthPropTest } from './_propTests/labelWidthPropTest'; import { fieldLayoutPropTest } from './_propTests/fieldLayoutPropTest'; @@ -38,7 +33,7 @@ test.describe('FormLayout', () => { await onBeforeTest(page); } - const component = await mount(); + const component = await mount('FormLayout/FormLayoutForTest', props); if (onBeforeSnapshot) { await onBeforeSnapshot(page, component); @@ -54,9 +49,9 @@ test.describe('FormLayout', () => { test('pass id into root component', async ({ mount }) => { const id = 'custom-id'; - const component = await mount( - , - ); + const component = await mount('FormLayout/FormLayoutForTest', { + id, + }); const root = await component.getAttribute('id'); expect(root).toBe(id); @@ -65,9 +60,7 @@ test.describe('FormLayout', () => { test.describe('functionality', () => { test('render null when no children provided', async ({ mount }) => { - const component = await mount( - , - ); + const component = await mount('FormLayout/FormLayoutWithoutChildrenForTest'); const innerHTML = await component.innerHTML(); expect(innerHTML).toBe(''); diff --git a/src/components/FormLayout/__tests__/FormLayout.spec.tsx-snapshots/FormLayout-base-visual-defaultComponentProps-object-1-chromium-linux.png b/src/components/FormLayout/__tests__/FormLayout.spec.tsx-snapshots/FormLayout-base-visual-defaultComponentProps-object-1-chromium-linux.png index f92247a8e..aae879f8f 100644 Binary files a/src/components/FormLayout/__tests__/FormLayout.spec.tsx-snapshots/FormLayout-base-visual-defaultComponentProps-object-1-chromium-linux.png and b/src/components/FormLayout/__tests__/FormLayout.spec.tsx-snapshots/FormLayout-base-visual-defaultComponentProps-object-1-chromium-linux.png differ diff --git a/src/components/FormLayout/__tests__/FormLayout.spec.tsx-snapshots/FormLayout-base-visual-fieldLayout-string-horizontal-1-chromium-linux.png b/src/components/FormLayout/__tests__/FormLayout.spec.tsx-snapshots/FormLayout-base-visual-fieldLayout-string-horizontal-1-chromium-linux.png index 9d310b885..5a7ab50bb 100644 Binary files a/src/components/FormLayout/__tests__/FormLayout.spec.tsx-snapshots/FormLayout-base-visual-fieldLayout-string-horizontal-1-chromium-linux.png and b/src/components/FormLayout/__tests__/FormLayout.spec.tsx-snapshots/FormLayout-base-visual-fieldLayout-string-horizontal-1-chromium-linux.png differ diff --git a/src/components/FormLayout/__tests__/FormLayout.spec.tsx-snapshots/FormLayout-base-visual-fieldLayout-string-vertical-1-chromium-linux.png b/src/components/FormLayout/__tests__/FormLayout.spec.tsx-snapshots/FormLayout-base-visual-fieldLayout-string-vertical-1-chromium-linux.png index f92247a8e..aae879f8f 100644 Binary files a/src/components/FormLayout/__tests__/FormLayout.spec.tsx-snapshots/FormLayout-base-visual-fieldLayout-string-vertical-1-chromium-linux.png and b/src/components/FormLayout/__tests__/FormLayout.spec.tsx-snapshots/FormLayout-base-visual-fieldLayout-string-vertical-1-chromium-linux.png differ diff --git a/src/components/FormLayout/__tests__/FormLayout.spec.tsx-snapshots/FormLayout-base-visual-labelWidth-string-100px-1-chromium-linux.png b/src/components/FormLayout/__tests__/FormLayout.spec.tsx-snapshots/FormLayout-base-visual-labelWidth-string-100px-1-chromium-linux.png index b0eaef28b..50b503464 100644 Binary files a/src/components/FormLayout/__tests__/FormLayout.spec.tsx-snapshots/FormLayout-base-visual-labelWidth-string-100px-1-chromium-linux.png and b/src/components/FormLayout/__tests__/FormLayout.spec.tsx-snapshots/FormLayout-base-visual-labelWidth-string-100px-1-chromium-linux.png differ diff --git a/src/components/FormLayout/__tests__/FormLayout.spec.tsx-snapshots/FormLayout-base-visual-labelWidth-string-auto-1-chromium-linux.png b/src/components/FormLayout/__tests__/FormLayout.spec.tsx-snapshots/FormLayout-base-visual-labelWidth-string-auto-1-chromium-linux.png index 5ac532b53..a058f093d 100644 Binary files a/src/components/FormLayout/__tests__/FormLayout.spec.tsx-snapshots/FormLayout-base-visual-labelWidth-string-auto-1-chromium-linux.png and b/src/components/FormLayout/__tests__/FormLayout.spec.tsx-snapshots/FormLayout-base-visual-labelWidth-string-auto-1-chromium-linux.png differ diff --git a/src/components/FormLayout/__tests__/FormLayout.spec.tsx-snapshots/FormLayout-base-visual-labelWidth-string-default-1-chromium-linux.png b/src/components/FormLayout/__tests__/FormLayout.spec.tsx-snapshots/FormLayout-base-visual-labelWidth-string-default-1-chromium-linux.png index 8de1c6bec..5a7ab50bb 100644 Binary files a/src/components/FormLayout/__tests__/FormLayout.spec.tsx-snapshots/FormLayout-base-visual-labelWidth-string-default-1-chromium-linux.png and b/src/components/FormLayout/__tests__/FormLayout.spec.tsx-snapshots/FormLayout-base-visual-labelWidth-string-default-1-chromium-linux.png differ diff --git a/src/components/FormLayout/__tests__/FormLayout.spec.tsx-snapshots/FormLayout-base-visual-labelWidth-string-limited-1-chromium-linux.png b/src/components/FormLayout/__tests__/FormLayout.spec.tsx-snapshots/FormLayout-base-visual-labelWidth-string-limited-1-chromium-linux.png index 81dc345a8..c6013024d 100644 Binary files a/src/components/FormLayout/__tests__/FormLayout.spec.tsx-snapshots/FormLayout-base-visual-labelWidth-string-limited-1-chromium-linux.png and b/src/components/FormLayout/__tests__/FormLayout.spec.tsx-snapshots/FormLayout-base-visual-labelWidth-string-limited-1-chromium-linux.png differ diff --git a/src/components/FormLayout/__tests__/FormLayoutCustomField.spec.tsx b/src/components/FormLayout/__tests__/FormLayoutCustomField.spec.tsx index fb07db9e2..106afb734 100644 --- a/src/components/FormLayout/__tests__/FormLayoutCustomField.spec.tsx +++ b/src/components/FormLayout/__tests__/FormLayoutCustomField.spec.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { expect, test, @@ -6,7 +5,6 @@ import { import { customFieldValidationStatesPropTest } from './_propTests/customFieldTests/customFieldValidationStatesPropTest'; import { customFieldLabelAlignmentPropTest } from './_propTests/customFieldTests/customFieldLabelAlignmentPropTest'; import { customFieldRequiredPropTest } from './_propTests/customFieldTests/customFieldRequiredPropTest'; -import { FormLayoutCustomFieldForTest } from './FormLayoutCustomField.story'; import { customFieldValidationStatesDisabledPropTest } from './_propTests/customFieldTests/customFieldValidationStatesDisabledPropTest'; test.describe('FormLayoutCustomField', () => { @@ -33,13 +31,11 @@ test.describe('FormLayoutCustomField', () => { await onBeforeTest(page); } - const component = await mount( - , - ); + const component = await mount('FormLayoutCustomField/FormLayoutCustomFieldForTest', { + ...props, + customFieldLayoutProps, + customFieldProps, + }); if (onBeforeSnapshot) { await onBeforeSnapshot(page, component); @@ -55,18 +51,16 @@ test.describe('FormLayoutCustomField', () => { test('pass id into custom field', async ({ mount }) => { const id = 'custom-id'; - const component = await mount( - , - ); + const component = await mount('FormLayoutCustomField/FormLayoutCustomFieldForTest', { + customFieldLayoutProps: { + id, + label: 'CustomLayoutFieldLabel', + }, + customFieldProps: { + label: 'CustomFieldLabel', + }, + fieldLayout: 'horizontal', + }); await expect(component.locator(`div[id=${id}]`)).not.toBeEmpty(); await expect(component.locator(`div[id=${id}__label]`)).not.toBeEmpty(); @@ -77,18 +71,16 @@ test.describe('FormLayoutCustomField', () => { const id = 'custom-id'; const labelForId = 'custom-labelForId'; - const component = await mount( - , - ); + const component = await mount('FormLayoutCustomField/FormLayoutCustomFieldForTest', { + customFieldLayoutProps: { + id, + label: 'CustomFieldLabel', + labelForId, + }, + customFieldProps: { + label: 'CustomFieldLabel', + }, + }); await expect(component.locator(`label[id=${id}__label][for=${labelForId}]`)).not.toBeEmpty(); }); @@ -96,7 +88,7 @@ test.describe('FormLayoutCustomField', () => { test.describe('functionality', () => { test('render null when no children to custom field provided', async ({ mount }) => { - const component = await mount(); + const component = await mount('FormLayoutCustomField/FormLayoutCustomFieldForTest'); const numberOfInputComponents = await component.getByRole('textbox').count(); expect(numberOfInputComponents).toBe(2); diff --git a/src/components/Grid/__tests__/Grid.spec.tsx b/src/components/Grid/__tests__/Grid.spec.tsx index e72aa7ddc..132252ba1 100644 --- a/src/components/Grid/__tests__/Grid.spec.tsx +++ b/src/components/Grid/__tests__/Grid.spec.tsx @@ -1,15 +1,8 @@ -import React from 'react'; import { expect, propTests, test, } from '../../../../tests/playwright'; -import { - GridForTest, - GridWithFixedCardsHeightForTest, - GridWithGridSpanForTest, - GridWithoutChildrenForTest, -} from './Grid.story'; import { columnPropTest } from './_propTests/columnsPropTest'; import { rowsPropTest } from './_propTests/rowsPropTest'; import { justifyItemsPropTest } from './_propTests/justifyItemsPropTest'; @@ -45,7 +38,7 @@ test.describe('Grid', () => { await onBeforeTest(page); } - const component = await mount(); + const component = await mount('Grid/GridForTest', props); if (onBeforeSnapshot) { await onBeforeSnapshot(page, component); @@ -73,7 +66,7 @@ test.describe('Grid', () => { await onBeforeTest(page); } - const component = await mount(); + const component = await mount('Grid/GridWithFixedCardsHeightForTest', props); if (onBeforeSnapshot) { await onBeforeSnapshot(page, component); @@ -102,7 +95,7 @@ test.describe('Grid', () => { await onBeforeTest(page); } - const component = await mount(); + const component = await mount('Grid/GridWithGridSpanForTest', props); if (onBeforeSnapshot) { await onBeforeSnapshot(page, component); @@ -116,32 +109,34 @@ test.describe('Grid', () => { }); test.describe('non-visual', () => { - test('pass custom id', async ({ mount }) => { + test('pass custom id', async ({ + mount, + page, + }) => { const id = 'custom-id'; - const component = await mount( -
- -
, - ); + await mount('Grid/GridForTest', { + id, + }); - await expect(component.locator(`div[id=${id}]`)).not.toBeEmpty(); + await expect(page.locator(`div[id=${id}]`)).not.toBeEmpty(); }); }); test.describe('functionality', () => { - test('have custom tag', async ({ mount }) => { - const component = await mount( -
- -
, - ); - - await expect(component.locator('ul')).not.toBeEmpty(); + test('have custom tag', async ({ + mount, + page, + }) => { + await mount('Grid/GridForTest', { + tag: 'ul', + }); + + await expect(page.locator('ul')).not.toBeEmpty(); }); test('return null when no children provided', async ({ mount }) => { - const component = await mount(); + const component = await mount('Grid/GridWithoutChildrenForTest'); await expect(component).toBeEmpty(); }); diff --git a/src/components/Grid/__tests__/_propTests/alignContentPropTest.ts b/src/components/Grid/__tests__/_propTests/alignContentPropTest.ts index ed2c12bd9..9177679aa 100644 --- a/src/components/Grid/__tests__/_propTests/alignContentPropTest.ts +++ b/src/components/Grid/__tests__/_propTests/alignContentPropTest.ts @@ -1,4 +1,4 @@ -import type { Page } from 'playwright/test'; +import type { Page } from '@playwright/test'; import { breakpoints } from '../../../../../tests/playwright/constants/breakpoints'; import type { PropTests } from '../../../../../tests/playwright/types'; diff --git a/src/components/Grid/__tests__/_propTests/columnGapPropTest.ts b/src/components/Grid/__tests__/_propTests/columnGapPropTest.ts index bfa540f84..0a865b415 100644 --- a/src/components/Grid/__tests__/_propTests/columnGapPropTest.ts +++ b/src/components/Grid/__tests__/_propTests/columnGapPropTest.ts @@ -1,4 +1,4 @@ -import type { Page } from 'playwright/test'; +import type { Page } from '@playwright/test'; import { breakpoints } from '../../../../../tests/playwright/constants/breakpoints'; import type { PropTests } from '../../../../../tests/playwright/types'; diff --git a/src/components/Grid/__tests__/_propTests/columnsPropTest.ts b/src/components/Grid/__tests__/_propTests/columnsPropTest.ts index a21c6f1d5..6a28ea505 100644 --- a/src/components/Grid/__tests__/_propTests/columnsPropTest.ts +++ b/src/components/Grid/__tests__/_propTests/columnsPropTest.ts @@ -1,4 +1,4 @@ -import type { Page } from 'playwright/test'; +import type { Page } from '@playwright/test'; import { breakpoints } from '../../../../../tests/playwright/constants/breakpoints'; import type { PropTest, diff --git a/src/components/Grid/__tests__/_propTests/justifyContentPropTest.ts b/src/components/Grid/__tests__/_propTests/justifyContentPropTest.ts index f73bae026..75636bc67 100644 --- a/src/components/Grid/__tests__/_propTests/justifyContentPropTest.ts +++ b/src/components/Grid/__tests__/_propTests/justifyContentPropTest.ts @@ -1,4 +1,4 @@ -import type { Page } from 'playwright/test'; +import type { Page } from '@playwright/test'; import { breakpoints } from '../../../../../tests/playwright/constants/breakpoints'; import type { PropTests } from '../../../../../tests/playwright/types'; diff --git a/src/components/Grid/__tests__/_propTests/justifyItemsPropTest.ts b/src/components/Grid/__tests__/_propTests/justifyItemsPropTest.ts index 947133e1f..ee0f10bd4 100644 --- a/src/components/Grid/__tests__/_propTests/justifyItemsPropTest.ts +++ b/src/components/Grid/__tests__/_propTests/justifyItemsPropTest.ts @@ -1,4 +1,4 @@ -import type { Page } from 'playwright/test'; +import type { Page } from '@playwright/test'; import { breakpoints } from '../../../../../tests/playwright/constants/breakpoints'; import type { PropTests } from '../../../../../tests/playwright/types'; diff --git a/src/components/Grid/__tests__/_propTests/rowGapPropTest.ts b/src/components/Grid/__tests__/_propTests/rowGapPropTest.ts index 2cbbd183a..6ccd9aa2c 100644 --- a/src/components/Grid/__tests__/_propTests/rowGapPropTest.ts +++ b/src/components/Grid/__tests__/_propTests/rowGapPropTest.ts @@ -1,4 +1,4 @@ -import type { Page } from 'playwright/test'; +import type { Page } from '@playwright/test'; import { breakpoints } from '../../../../../tests/playwright/constants/breakpoints'; import type { PropTests } from '../../../../../tests/playwright/types'; diff --git a/src/components/InputGroup/__tests__/InputGroup.spec.tsx b/src/components/InputGroup/__tests__/InputGroup.spec.tsx index c1afcce15..b00e9dc26 100644 --- a/src/components/InputGroup/__tests__/InputGroup.spec.tsx +++ b/src/components/InputGroup/__tests__/InputGroup.spec.tsx @@ -1,14 +1,8 @@ -import React from 'react'; import { expect, propTests, test, } from '../../../../tests/playwright'; -import { - InputGroupForTest, - InputGroupWithCustomInputPropsForTest, - InputGroupWithoutChildrenForTest, -} from './InputGroup.story'; import { childrenPropTest } from './_propTests/childrenPropTest'; import { validationTextsPropTest } from './_propTests/validationTextsPropTest'; import { validationStatePropTest } from './_propTests/validationStatePropTest'; @@ -44,8 +38,9 @@ test.describe('InputGroup', () => { const component = await mount( hasInputProps - ? - : , + ? 'InputGroup/InputGroupWithCustomInputPropsForTest' + : 'InputGroup/InputGroupForTest', + props, ); if (onBeforeSnapshot) { @@ -63,15 +58,13 @@ test.describe('InputGroup', () => { const id = 'custom-id'; const label = 'custom-label'; - const component = await mount( - , - ); + const component = await mount('InputGroup/InputGroupForTest', { + helpTexts: ['Help text.'], + id, + isLabelVisible: true, + label, + validationTexts: ['Validation text.'], + }); await expect(component).toHaveAttribute('id', id); await expect(component.getByText(label).first()).toHaveAttribute('id', `${id}__label`); @@ -84,7 +77,7 @@ test.describe('InputGroup', () => { test.describe('functionality', () => { test('return null when no children provided', async ({ mount }) => { - const component = await mount(); + const component = await mount('InputGroup/InputGroupWithoutChildrenForTest'); await expect(component).toBeEmpty(); }); diff --git a/src/components/InputGroup/__tests__/InputGroup.story.tsx b/src/components/InputGroup/__tests__/InputGroup.story.tsx index 40c3827af..7e2e456c4 100644 --- a/src/components/InputGroup/__tests__/InputGroup.story.tsx +++ b/src/components/InputGroup/__tests__/InputGroup.story.tsx @@ -7,7 +7,10 @@ import { TextField } from '../../TextField'; import type { ValidationState } from '../../../types'; import type { StoryProps } from '../../../../tests/playwright'; -type InputGroupTestProps = StoryProps; +type InputGroupTestProps = StoryProps & { + // Children rendered by the story instead of `children`, which cannot be passed as JSX to `mount()` + childrenVariant?: 'multiple' | 'single'; +}; type InputGroupWithCustomInputTestProps = InputGroupTestProps & { inputProps?: { validationState: ValidationState }; }; @@ -45,13 +48,24 @@ const defaultChildren = [ />, ]; +const childrenVariants = { + multiple: defaultChildren, + single: ( + + ), +}; + export const InputGroupForTest = ({ children, + childrenVariant, label = 'Input group label', ...props }: InputGroupTestProps) => ( - {children ?? defaultChildren} + {childrenVariant ? childrenVariants[childrenVariant] : (children ?? defaultChildren)} ); diff --git a/src/components/InputGroup/__tests__/_propTests/childrenPropTest.ts b/src/components/InputGroup/__tests__/_propTests/childrenPropTest.ts new file mode 100644 index 000000000..9def2cffe --- /dev/null +++ b/src/components/InputGroup/__tests__/_propTests/childrenPropTest.ts @@ -0,0 +1,12 @@ +import type { PropTests } from '../../../../../tests/playwright/types'; + +export const childrenPropTest: PropTests = [ + { + name: 'children:node[single]', + props: { childrenVariant: 'single' }, + }, + { + name: 'children:node[multiple]', + props: { childrenVariant: 'multiple' }, + }, +]; diff --git a/src/components/InputGroup/__tests__/_propTests/childrenPropTest.tsx b/src/components/InputGroup/__tests__/_propTests/childrenPropTest.tsx deleted file mode 100644 index 3a2277352..000000000 --- a/src/components/InputGroup/__tests__/_propTests/childrenPropTest.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import React from 'react'; -import type { PropTests } from '../../../../../tests/playwright/types'; -import { ButtonWithGlobalProps as Button } from '../../../Button/Button'; -import { SelectFieldWithGlobalProps as SelectField } from '../../../SelectField/SelectField'; -import { TextFieldWithGlobalProps as TextField } from '../../../TextField/TextField'; - -const options = [ - { - label: 'Option 1', - value: 'option1', - }, - { - label: 'Option 2', - value: 'option2', - }, - { - label: 'Option 3', - value: 'option3', - }, -]; - -export const childrenPropTest: PropTests = [ - { - name: 'children:node[single]', - props: { - children: ( - - ), - }, - }, - { - name: 'children:node[multiple]', - props: { - children: [ - , - , - + + +); + +const ToggleSpy = withSpy(() => { + const onChange = useSpy('onChange'); + const onSubmit = useSpy('onSubmit', (event: SyntheticEvent) => event.currentTarget.tagName); + + return ( + + ); +}); + +// The project configures Testing Library to use `id` as the test ID attribute, Playwright uses `data-testid` +const getSpyInput = (container: HTMLElement, name: string) => container.querySelector(`[data-testid="${name}"]`); + +describe('useSpy', () => { + it('records the first argument of every call by default', async () => { + const { container } = render(); + await userEvent.click(screen.getByText('change')); + await userEvent.click(screen.getByText('change')); + + expect(getSpyInput(container, 'onChange')).toHaveValue('[true,true]'); + expect(getSpyInput(container, 'onSubmit')).toHaveValue('[]'); + }); + + it('records the selected value of every call', async () => { + const { container } = render(); + await userEvent.click(screen.getByText('submit')); + + expect(getSpyInput(container, 'onSubmit')).toHaveValue('["FORM"]'); + }); + + it('throws when used outside of a story wrapped with withSpy', () => { + const Unwrapped = () => { + useSpy('onChange'); + + return null; + }; + const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + + expect(() => render()).toThrow('Spy "onChange" must be used in a story wrapped with `withSpy`.'); + + consoleErrorSpy.mockRestore(); + }); +}); diff --git a/tests/playwright/utils/spy/__tests__/withSpy.test.tsx b/tests/playwright/utils/spy/__tests__/withSpy.test.tsx new file mode 100644 index 000000000..bd0aa1d69 --- /dev/null +++ b/tests/playwright/utils/spy/__tests__/withSpy.test.tsx @@ -0,0 +1,77 @@ +import React from 'react'; +import { + render, + screen, +} from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { useSpy } from '../useSpy'; +import { withSpy } from '../withSpy'; + +type ToggleProps = { + onChange: (checked: boolean) => void; + onReset: () => void; +}; + +const Toggle = ({ + onChange, + onReset, +}: ToggleProps) => ( + <> + + + +); + +const ToggleSpy = withSpy((props: Partial) => { + const onChange = useSpy('onChange'); + const onReset = useSpy('onReset'); + + return ( + + ); +}); + +// The project configures Testing Library to use `id` as the test ID attribute, Playwright uses `data-testid` +const getSpyInput = (container: HTMLElement, name: string) => container.querySelector(`[data-testid="${name}"]`); + +describe('withSpy', () => { + it('renders an empty hidden input per registered spy', () => { + const { container } = render(); + + expect(getSpyInput(container, 'onChange')).toHaveValue('[]'); + expect(getSpyInput(container, 'onReset')).toHaveValue('[]'); + }); + + it('renders the story with the given props', () => { + const onChange = jest.fn(); + + render(); + screen.getByText('change'); + + expect(onChange).not.toHaveBeenCalled(); + }); + + it('lets props override the spies', async () => { + const onChange = jest.fn(); + + const { container } = render(); + await userEvent.click(screen.getByText('change')); + + expect(onChange).toHaveBeenCalledWith(true); + expect(getSpyInput(container, 'onChange')).toHaveValue('[]'); + }); +}); diff --git a/tests/playwright/utils/spy/_context/SpyContext.ts b/tests/playwright/utils/spy/_context/SpyContext.ts new file mode 100644 index 000000000..0c773121c --- /dev/null +++ b/tests/playwright/utils/spy/_context/SpyContext.ts @@ -0,0 +1,6 @@ +import React from 'react'; + +export type RegisterSpy = (name: string, calls: unknown[]) => void; + +// Provides the `registerSpy(name, calls)` function of the enclosing `withSpy` story +export const SpyContext = React.createContext(null); diff --git a/tests/playwright/utils/spy/_helpers/deserializeSpyValue.ts b/tests/playwright/utils/spy/_helpers/deserializeSpyValue.ts new file mode 100644 index 000000000..a4b872345 --- /dev/null +++ b/tests/playwright/utils/spy/_helpers/deserializeSpyValue.ts @@ -0,0 +1,4 @@ +/** + * Deserialize the values recorded by a spy from the value of its hidden input. + */ +export const deserializeSpyValue = (value: string) => JSON.parse(value) as unknown[]; diff --git a/tests/playwright/utils/spy/_helpers/serializeSpyValue.ts b/tests/playwright/utils/spy/_helpers/serializeSpyValue.ts new file mode 100644 index 000000000..99daf1209 --- /dev/null +++ b/tests/playwright/utils/spy/_helpers/serializeSpyValue.ts @@ -0,0 +1,4 @@ +/** + * Serialize the values recorded by a spy into the value of its hidden input. + */ +export const serializeSpyValue = (calls: unknown[]) => JSON.stringify(calls); diff --git a/tests/playwright/utils/spy/getSpyValue.ts b/tests/playwright/utils/spy/getSpyValue.ts new file mode 100644 index 000000000..6c98f98cb --- /dev/null +++ b/tests/playwright/utils/spy/getSpyValue.ts @@ -0,0 +1,12 @@ +import type { Locator } from '@playwright/test'; +import { deserializeSpyValue } from './_helpers/deserializeSpyValue'; + +/** + * Create a function that gets the values recorded by the spy `name` registered with `useSpy` in the story mounted + * in `root`. + * + * The values are recorded asynchronously, so assert them with `expect.poll()`. + */ +export const getSpyValue = (root: Locator) => async (name: string) => deserializeSpyValue( + await root.getByTestId(name).inputValue(), +); diff --git a/tests/playwright/utils/spy/index.ts b/tests/playwright/utils/spy/index.ts new file mode 100644 index 000000000..01507bb35 --- /dev/null +++ b/tests/playwright/utils/spy/index.ts @@ -0,0 +1,3 @@ +export { getSpyValue } from './getSpyValue'; +export { useSpy } from './useSpy'; +export { withSpy } from './withSpy'; diff --git a/tests/playwright/utils/spy/useSpy.ts b/tests/playwright/utils/spy/useSpy.ts new file mode 100644 index 000000000..d0b8f0564 --- /dev/null +++ b/tests/playwright/utils/spy/useSpy.ts @@ -0,0 +1,37 @@ +import { + useCallback, + useContext, + useEffect, + useState, +} from 'react'; +import { SpyContext } from './_context/SpyContext'; + +const firstArgument = (...args: unknown[]) => args[0]; + +/** + * Register a spy named `name` in the enclosing `withSpy` story and return the spy function. + * + * Every call of the spy appends one value to the hidden input with `data-testid` equal to `name`, stored as a JSON + * array. The value is `select(...args)`, which returns the first argument of the call by default, e.g. the input holds + * `[true,false]` after `spy(true, 'Label')` and `spy(false, 'Label')`. Pass a custom `select` to record other + * arguments, as the recorded values must be serializable to JSON. + */ +export const useSpy = ( + name: string, + select: (...args: Args) => unknown = firstArgument, +) => { + const registerSpy = useContext(SpyContext); + const [calls, setCalls] = useState([]); + + if (!registerSpy) { + throw new Error(`Spy "${name}" must be used in a story wrapped with \`withSpy\`.`); + } + + useEffect(() => { + registerSpy(name, calls); + }, [calls, name, registerSpy]); + + return useCallback((...args: Args) => { + setCalls((previousCalls) => [...previousCalls, select(...args)]); + }, [select]); +}; diff --git a/tests/playwright/utils/spy/withSpy.tsx b/tests/playwright/utils/spy/withSpy.tsx new file mode 100644 index 000000000..ddd51d31e --- /dev/null +++ b/tests/playwright/utils/spy/withSpy.tsx @@ -0,0 +1,45 @@ +import React, { + useCallback, + useState, +} from 'react'; +import type { ComponentType } from 'react'; +import { serializeSpyValue } from './_helpers/serializeSpyValue'; +import { SpyContext } from './_context/SpyContext'; + +/** + * Wrap a story so that it can register spies with `useSpy`. + * + * The recorded values of every spy are rendered into a hidden input with `data-testid` equal to the spy name, + * so that specs can assert them with `toHaveValue()`. + */ +export const withSpy = (Story: ComponentType) => { + const StoryWithSpy = (props: Props) => { + const [spies, setSpies] = useState>({}); + const registerSpy = useCallback((name: string, calls: unknown[]) => { + setSpies((previousSpies) => ({ + ...previousSpies, + [name]: calls, + })); + }, []); + + return ( + + + + + ); + }; + + StoryWithSpy.displayName = `withSpy(${Story.displayName || Story.name})`; + + return StoryWithSpy; +};