diff --git a/.oxlintrc.json b/.oxlintrc.json index 3b32721f2..9fa63199b 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -1,10 +1,7 @@ { "$schema": "./node_modules/oxlint/configuration_schema.json", "jsPlugins": [ - { - "name": "playwright", - "specifier": "eslint-plugin-playwright" - }, + "./tools/oxlint-plugin-playwright.ts", { "name": "react-hook-form", "specifier": "eslint-plugin-react-hook-form" @@ -120,7 +117,8 @@ "app/entry.server.tsx", "*.config.ts", "*.config.mjs", - "test/e2e/compact-reporter.ts" + "test/e2e/compact-reporter.ts", + "tools/oxlint-plugin-playwright.ts" ], "rules": { "import/no-default-export": "off" @@ -140,22 +138,13 @@ } }, { - // catch unawaited Playwright calls in e2e tests - "files": ["test/e2e/**/*.ts"], + "files": ["test/e2e/**/*.ts", "test/**/*.e2e.ts"], "rules": { + // catch unawaited Playwright calls "@typescript-eslint/no-floating-promises": "error", - "playwright/expect-expect": [ - "warn", - { - "assertFunctionNames": [ - "expectVisible", - "expectRowVisible", - "expectOptions", - "expectRowMenuStaysOpen" - ] - } - ], - "playwright/no-force-option": "off" + "playwright/expect-expect": "error", + "playwright/no-focused-test": "error", + "playwright/no-page-route": "error" } } ], diff --git a/package-lock.json b/package-lock.json index 974f200e0..be68e6468 100644 --- a/package-lock.json +++ b/package-lock.json @@ -79,10 +79,9 @@ "@types/uuid": "^10.0.0", "@vitejs/plugin-basic-ssl": "^2.3.0", "@vitejs/plugin-react": "^6.0.2", - "@vitest/browser-playwright": "^5.0.3", + "@vitest/browser-playwright": "^5.1.0", "commander": "^15.0.0", "dax": "^0.50.1", - "eslint-plugin-playwright": "^2.10.4", "eslint-plugin-react-hook-form": "^0.3.1", "husky": "^9.1.7", "ip-num": "^1.5.1", @@ -97,7 +96,7 @@ "typescript": "^7.0.2", "vite": "^8.3.5", "vite-node": "^6.0.0", - "vitest": "^5.0.3", + "vitest": "^5.1.0", "vitest-browser-react": "^2.3.0" }, "engines": { @@ -652,105 +651,6 @@ "console-static-text": "^0.3.5" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/config-array": { - "version": "0.23.5", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@eslint/object-schema": "^3.0.5", - "debug": "^4.3.1", - "minimatch": "^10.2.4" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/config-helpers": { - "version": "0.5.5", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@eslint/core": "^1.2.1" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/core": { - "version": "1.2.1", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/object-schema": { - "version": "3.0.5", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.7.1", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@eslint/core": "^1.2.1", - "levn": "^0.4.1" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, "node_modules/@floating-ui/core": { "version": "1.7.5", "license": "MIT", @@ -812,67 +712,6 @@ "react-dom": "^18 || ^19 || ^19.0.0-rc" } }, - "node_modules/@humanfs/core": { - "version": "0.19.2", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@humanfs/types": "^0.15.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.8", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@humanfs/core": "^0.19.2", - "@humanfs/types": "^0.15.0", - "@humanwhocodes/retry": "^0.4.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/types": { - "version": "0.15.0", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, "node_modules/@inquirer/confirm": { "version": "5.0.1", "dev": true, @@ -921,36 +760,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@inquirer/core/node_modules/ansi-styles": { - "version": "4.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@inquirer/core/node_modules/color-convert": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@inquirer/core/node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "license": "MIT" - }, "node_modules/@inquirer/core/node_modules/emoji-regex": { "version": "8.0.0", "dev": true, @@ -3933,6 +3742,8 @@ }, "node_modules/@types/chai": { "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", "dev": true, "license": "MIT", "dependencies": { @@ -3947,15 +3758,11 @@ }, "node_modules/@types/deep-eql": { "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", "dev": true, "license": "MIT" }, - "node_modules/@types/esrecurse": { - "version": "4.3.1", - "dev": true, - "license": "MIT", - "peer": true - }, "node_modules/@types/estree": { "version": "1.0.8", "dev": true, @@ -4452,46 +4259,46 @@ } }, "node_modules/@vitest/browser": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-5.0.3.tgz", - "integrity": "sha512-7KHIkVA/gTRxQV9qPa9qLK+g5INZG6iRrlwXSFG5EKsOomW3UzzW7wsdt1Yp5PufzQ43lniuONlb4lUVCC2qSg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-5.1.0.tgz", + "integrity": "sha512-2sjGVAC/Rs8Jwv+xxBgklNG2g1LGyNgcFMBw71AcfBstLz25UGv1+fVvvZ+z8IfSuNg3nipERijnFtoEIdRDEA==", "dev": true, "license": "MIT", "dependencies": { "@blazediff/core": "1.10.0", - "@vitest/mocker": "5.0.3", - "@vitest/ui": "5.0.3", - "@vitest/utils": "5.0.3", - "magic-string": "^1.2.3", + "@vitest/mocker": "5.1.0", + "@vitest/ui": "5.1.0", + "@vitest/utils": "5.1.0", + "magic-string": "^1.4.3", "pngjs": "^7.0.0", "sirv": "^3.0.2", - "tinyrainbow": "^3.1.1", - "ws": "^8.21.3" + "tinyrainbow": "^3.2.0", + "ws": "^8.22.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "5.0.3" + "vitest": "5.1.0" } }, "node_modules/@vitest/browser-playwright": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-5.0.3.tgz", - "integrity": "sha512-rYBHwIFL/ZtI6dl0rbrAatScXCrvX+TL2BX2AGOigkC1Y6u3rIWw8KMQJJMS4drSa1ksMCmZU2wpgwdE7kqu3g==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-5.1.0.tgz", + "integrity": "sha512-3PbVvX/KNeUEzUeTTXw5bQDyfJS+WqU/UJ8RNWIojHb0mE6d/IBjuLmt+i7OpJ00tjNWv0O6XBm5ler/tEf3eg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/browser": "5.0.3", - "@vitest/mocker": "5.0.3", - "tinyrainbow": "^3.1.1" + "@vitest/browser": "5.1.0", + "@vitest/mocker": "5.1.0", + "tinyrainbow": "^3.2.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "playwright": "*", - "vitest": "5.0.3" + "vitest": "5.1.0" }, "peerDependenciesMeta": { "playwright": { @@ -4500,9 +4307,9 @@ } }, "node_modules/@vitest/browser/node_modules/magic-string": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.4.3.tgz", - "integrity": "sha512-z12OxmaPGE0F4xlpGdjuAUckipLpQlTAuAmyGhNoD7ODpYUzvDfvtxUbjM/jwznyP178oju/KDdyi220wNJ0RQ==", + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.4.5.tgz", + "integrity": "sha512-oLLC2+YvQUfAa+vxoufE/yc1icb87M9iXIRqAoR/2EhE/8cb3SF/+Gi0c2By1B+5Vz2e2vQtUvHiu1+63Znh+w==", "dev": true, "license": "MIT", "dependencies": { @@ -4510,22 +4317,22 @@ } }, "node_modules/@vitest/mocker": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-5.0.3.tgz", - "integrity": "sha512-T8sWAIbkSyAjkwTcaEc3Iu0o9A27X1/kdXrizhZkGuSKScRQtRzclfAMpOTcGdXCsqxeWlpGy3XjqaW8CpLORg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-5.1.0.tgz", + "integrity": "sha512-5umB9/jlWcqVS6fsrFmv5Gdnpu6wAUio/lEOwAmvB7v+lFo/IW2B9FhAD0p19GCULTgMzLCdUmuCDCnAzWgppQ==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "0.3.31", - "@vitest/spy": "5.0.3", + "@vitest/spy": "5.1.0", "estree-walker": "^3.0.3", - "magic-string": "^1.2.3" + "magic-string": "^1.4.3" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "msw": "^2.4.9", + "msw": "^2.4.9 || ^3.0.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { @@ -4538,9 +4345,9 @@ } }, "node_modules/@vitest/mocker/node_modules/magic-string": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.4.3.tgz", - "integrity": "sha512-z12OxmaPGE0F4xlpGdjuAUckipLpQlTAuAmyGhNoD7ODpYUzvDfvtxUbjM/jwznyP178oju/KDdyi220wNJ0RQ==", + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.4.5.tgz", + "integrity": "sha512-oLLC2+YvQUfAa+vxoufE/yc1icb87M9iXIRqAoR/2EhE/8cb3SF/+Gi0c2By1B+5Vz2e2vQtUvHiu1+63Znh+w==", "dev": true, "license": "MIT", "dependencies": { @@ -4548,22 +4355,22 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-5.0.3.tgz", - "integrity": "sha512-3hr5eV5z2ff0l2Q1vaKQbB4j3M3tElw8stDokEOTqXJB3Es5VrT8gBRIMY59gGuFp8Y61sWlWFK1aNUH5dHDYw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-5.1.0.tgz", + "integrity": "sha512-IRx5mc30CrFzyMNni6SZKQ1r53kv049Mcvvk0WbWcD6PUz/bFjG4zYv0KosQAeonPpGp5WxqYBMqdSOfJfvVWw==", "dev": true, "license": "MIT", "dependencies": { - "tinyrainbow": "^3.1.1" + "tinyrainbow": "^3.2.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/spy": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-5.0.3.tgz", - "integrity": "sha512-XhFysQTB8AZ+P4gMi+Lpo99vg2AZi0qKpaB9yXQl37+CaMEAPO3iH/wGVnSyL5MPERiLezpqTVtrR6UZH5GCXg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-5.1.0.tgz", + "integrity": "sha512-2DPO5OMHWKrBngC+fCmTQqemNF0gidRhp3lxbBmRhW+M1XJrfgPpqNqWVevXtn2f2ufD5ZaR1B6B641aAlcX5A==", "dev": true, "license": "MIT", "funding": { @@ -4571,36 +4378,36 @@ } }, "node_modules/@vitest/ui": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-5.0.3.tgz", - "integrity": "sha512-9dfvqDBvSbHM6y9YVXxAypkl2z7mrWozK/JFo237qI0cj/3M/kzDKiVZqzi3TE54Ler7Jditud7OMBZCyUvRHA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-5.1.0.tgz", + "integrity": "sha512-00S3x4NRmqIBUiTVWF9/xLaA9EP9KeKv0+1sBO7e2Y4luBISlW+2ttnhlk/zjbTqEvVhJWCCo7MXzu6zNbefqQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "5.0.3", + "@vitest/utils": "5.1.0", "fflate": "^0.8.3", "flatted": "^3.4.4", "pathe": "^2.0.3", "sirv": "^3.0.2", - "tinyrainbow": "^3.1.1" + "tinyrainbow": "^3.2.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "5.0.3" + "vitest": "5.1.0" } }, "node_modules/@vitest/utils": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-5.0.3.tgz", - "integrity": "sha512-77fDTZv8eu1CzlIfgPU2yrl5MXSUaUAOvgdZEeVm3X3WIxUubIX2SforqS6176lT32HCF6uBVMYVqfHxw7o6eg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-5.1.0.tgz", + "integrity": "sha512-V+qgCwkvVw+s1t1S5TaoBHsLi5Sz/YmohCMoOu0mZ8ZQst2bKhRYARAQucWpVg8D0mv0x5MHcWXY5QdOsoiGBQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "5.0.3", + "@vitest/pretty-format": "5.1.0", "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.1.1" + "tinyrainbow": "^3.2.0" }, "funding": { "url": "https://opencollective.com/vitest" @@ -4634,49 +4441,28 @@ "node": ">= 0.6" } }, - "node_modules/acorn": { - "version": "8.16.0", + "node_modules/ansi-regex": { + "version": "5.0.1", "dev": true, "license": "MIT", - "peer": true, - "bin": { - "acorn": "bin/acorn" - }, "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "dev": true, - "license": "MIT", - "peer": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + "node": ">=8" } }, - "node_modules/ajv": { - "version": "6.15.0", + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "color-convert": "^2.0.1" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "dev": true, - "license": "MIT", "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/argparse": { @@ -4715,6 +4501,8 @@ }, "node_modules/assertion-error": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, "license": "MIT", "engines": { @@ -4735,13 +4523,11 @@ } }, "node_modules/balanced-match": { - "version": "4.0.4", - "dev": true, + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT", - "peer": true, - "engines": { - "node": "18 || 20 || >=22" - } + "peer": true }, "node_modules/baseline-browser-mapping": { "version": "2.11.28", @@ -4757,7 +4543,9 @@ } }, "node_modules/body-parser": { - "version": "1.20.8", + "version": "1.20.9", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.9.tgz", + "integrity": "sha512-FISIQxV5u91Y9gvuFq3q/tA/qTqLJn3AhRcEvyozyhJTh5OBFFtv827AdHU7Rbyct4r+Ba1/99oFa1Y3yuFsdw==", "dev": true, "license": "MIT", "dependencies": { @@ -4819,20 +4607,6 @@ "node": ">= 0.8" } }, - "node_modules/brace-expansion": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", - "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "20 || >=22" - } - }, "node_modules/braces": { "version": "3.0.3", "dev": true, @@ -4975,13 +4749,32 @@ } }, "node_modules/chai": { - "version": "6.2.2", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.3.0.tgz", + "integrity": "sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==", "dev": true, "license": "MIT", "engines": { "node": ">=18" } }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/character-entities-html4": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", @@ -5064,36 +4857,6 @@ "node": ">=12" } }, - "node_modules/cliui/node_modules/ansi-styles": { - "version": "4.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/cliui/node_modules/color-convert": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/cliui/node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "license": "MIT" - }, "node_modules/cliui/node_modules/emoji-regex": { "version": "8.0.0", "dev": true, @@ -5143,6 +4906,26 @@ "node": ">=6" } }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, "node_modules/comma-separated-tokens": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", @@ -5304,14 +5087,8 @@ } } }, - "node_modules/deep-is": { - "version": "0.1.4", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/define-data-property": { - "version": "1.1.4", + "node_modules/define-data-property": { + "version": "1.1.4", "dev": true, "license": "MIT", "dependencies": { @@ -5534,87 +5311,6 @@ "dev": true, "license": "MIT" }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "10.2.1", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.2", - "@eslint/config-array": "^0.23.5", - "@eslint/config-helpers": "^0.5.5", - "@eslint/core": "^1.2.1", - "@eslint/plugin-kit": "^0.7.1", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "ajv": "^6.14.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^9.1.2", - "eslint-visitor-keys": "^5.0.1", - "espree": "^11.2.0", - "esquery": "^1.7.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "minimatch": "^10.2.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-playwright": { - "version": "2.10.4", - "dev": true, - "license": "MIT", - "dependencies": { - "globals": "^17.3.0" - }, - "engines": { - "node": ">=16.9.0" - }, - "peerDependencies": { - "eslint": ">=8.40.0" - } - }, "node_modules/eslint-plugin-react-hook-form": { "version": "0.3.1", "dev": true, @@ -5626,86 +5322,6 @@ "node": ">=0.10.0" } }, - "node_modules/eslint-scope": { - "version": "9.1.2", - "dev": true, - "license": "BSD-2-Clause", - "peer": true, - "dependencies": { - "@types/esrecurse": "^4.3.1", - "@types/estree": "^1.0.8", - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree": { - "version": "11.2.0", - "dev": true, - "license": "BSD-2-Clause", - "peer": true, - "dependencies": { - "acorn": "^8.16.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^5.0.1" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.7.0", - "dev": true, - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "dev": true, - "license": "BSD-2-Clause", - "peer": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "dev": true, - "license": "BSD-2-Clause", - "peer": true, - "engines": { - "node": ">=4.0" - } - }, "node_modules/estree-walker": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", @@ -5716,15 +5332,6 @@ "@types/estree": "^1.0.0" } }, - "node_modules/esutils": { - "version": "2.0.3", - "dev": true, - "license": "BSD-2-Clause", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/etag": { "version": "1.8.1", "dev": true, @@ -5748,6 +5355,8 @@ }, "node_modules/expect-type": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -5826,22 +5435,10 @@ "dev": true, "license": "MIT" }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "dev": true, - "license": "MIT", - "peer": true - }, "node_modules/fast-uri": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz", - "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.9.tgz", + "integrity": "sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ==", "dev": true, "funding": [ { @@ -5862,18 +5459,6 @@ "dev": true, "license": "MIT" }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, "node_modules/filesize": { "version": "10.1.6", "license": "BSD-3-Clause", @@ -5922,22 +5507,6 @@ "dev": true, "license": "MIT" }, - "node_modules/find-up": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/find-yarn-workspace-root": { "version": "2.0.0", "dev": true, @@ -5946,19 +5515,6 @@ "micromatch": "^4.0.2" } }, - "node_modules/flat-cache": { - "version": "4.0.1", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" - } - }, "node_modules/flatted": { "version": "3.4.4", "dev": true, @@ -6117,27 +5673,10 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/glob-parent": { - "version": "6.0.2", - "dev": true, - "license": "ISC", - "peer": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/glob/node_modules/balanced-match": { - "version": "1.0.2", - "license": "MIT", - "peer": true - }, "node_modules/glob/node_modules/brace-expansion": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", - "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.8.tgz", + "integrity": "sha512-BnlDzDYQoplTsZSvPi86CNzmbcb07X8ufR8fk1zivaiEJlXJkiq1MQyDCVmlWcsWbj6aWXqHnrwwevbrm/Saig==", "license": "MIT", "peer": true, "dependencies": { @@ -6155,17 +5694,6 @@ "node": ">=10" } }, - "node_modules/globals": { - "version": "17.6.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/gopd": { "version": "1.2.0", "dev": true, @@ -6189,6 +5717,16 @@ "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/has-property-descriptors": { "version": "1.0.2", "dev": true, @@ -6387,24 +5925,6 @@ "node": ">=0.10.0" } }, - "node_modules/ignore": { - "version": "5.3.2", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.8.19" - } - }, "node_modules/inflight": { "version": "1.0.6", "license": "ISC", @@ -6440,27 +5960,6 @@ "version": "1.1.6", "license": "MIT" }, - "node_modules/is-extglob": { - "version": "2.1.1", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-network-error": { "version": "1.1.0", "license": "MIT", @@ -6552,18 +6051,6 @@ "node": ">=6" } }, - "node_modules/json-buffer": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "dev": true, - "license": "MIT", - "peer": true - }, "node_modules/json-stable-stringify": { "version": "1.1.1", "dev": true, @@ -6581,12 +6068,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "peer": true - }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -6619,15 +6100,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/keyv": { - "version": "4.5.4", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "json-buffer": "3.0.1" - } - }, "node_modules/klaw-sync": { "version": "6.0.0", "dev": true, @@ -6636,19 +6108,6 @@ "graceful-fs": "^4.1.11" } }, - "node_modules/levn": { - "version": "0.4.1", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/lightningcss": { "version": "1.32.0", "license": "MPL-2.0", @@ -6904,21 +6363,6 @@ "url": "https://opencollective.com/parcel" } }, - "node_modules/locate-path": { - "version": "6.0.0", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/lodash": { "version": "4.18.1", "license": "MIT" @@ -7149,21 +6593,6 @@ "node": ">= 0.6" } }, - "node_modules/minimatch": { - "version": "10.2.5", - "dev": true, - "license": "BlueOak-1.0.0", - "peer": true, - "dependencies": { - "brace-expansion": "^5.0.5" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/minimist": { "version": "1.2.8", "dev": true, @@ -7311,12 +6740,6 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/natural-compare": { - "version": "1.4.0", - "dev": true, - "license": "MIT", - "peer": true - }, "node_modules/negotiator": { "version": "0.6.3", "dev": true, @@ -7408,23 +6831,6 @@ "license": "MIT", "peer": true }, - "node_modules/optionator": { - "version": "0.9.4", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/outvariant": { "version": "1.4.3", "dev": true, @@ -7545,36 +6951,6 @@ "@oxlint-tsgolint/win32-x64": "0.23.0" } }, - "node_modules/p-limit": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/p-map": { "version": "7.0.8", "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.8.tgz", @@ -7638,59 +7014,6 @@ "npm": ">5" } }, - "node_modules/patch-package/node_modules/ansi-styles": { - "version": "4.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/patch-package/node_modules/chalk": { - "version": "4.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/patch-package/node_modules/color-convert": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/patch-package/node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "license": "MIT" - }, - "node_modules/patch-package/node_modules/has-flag": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/patch-package/node_modules/is-docker": { "version": "2.2.1", "dev": true, @@ -7739,26 +7062,6 @@ "node": ">=6" } }, - "node_modules/patch-package/node_modules/supports-color": { - "version": "7.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, "node_modules/path-key": { "version": "3.1.1", "dev": true, @@ -7870,15 +7173,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/prettier": { "version": "3.8.3", "license": "MIT", @@ -7988,7 +7282,7 @@ } }, "node_modules/proxy-addr": { - "version": "2.0.7", + "version": "2.0.8", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz", "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", "dev": true, @@ -8033,9 +7327,9 @@ } }, "node_modules/qs": { - "version": "6.16.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", - "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", + "version": "6.16.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.2.tgz", + "integrity": "sha512-VYdnB5S7/0IeXzBacUtwNAaM6d7Y/PgYnNC7bJCI6meqA/OgnsQbZMtVW8Dt2D1PHgbHGtYmhSoEIgvt7xZ2ZA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -8678,7 +7972,9 @@ } }, "node_modules/std-env": { - "version": "4.2.0", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.3.0.tgz", + "integrity": "sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==", "dev": true, "license": "MIT" }, @@ -8732,6 +8028,19 @@ "inline-style-parser": "0.2.7" } }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/tabbable": { "version": "6.4.0", "license": "MIT" @@ -8763,7 +8072,9 @@ } }, "node_modules/tinybench": { - "version": "6.1.4", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-6.2.3.tgz", + "integrity": "sha512-Al01C2JIBnO2CXCAPbpAl6sUiOZHeThGtHYNTa51JRw9PyWOCdr+9Ong05R4qg/Sk3+H645E8ov1h2c58ityNw==", "dev": true, "license": "MIT", "engines": { @@ -8771,7 +8082,9 @@ } }, "node_modules/tinyexec": { - "version": "1.3.0", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", "dev": true, "license": "MIT", "engines": { @@ -8828,7 +8141,9 @@ } }, "node_modules/tinyrainbow": { - "version": "3.1.1", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.2.0.tgz", + "integrity": "sha512-LgO3D9yZJjApUiuUfl9iFAwrtaX4+lok3wJIqttGoKCHlWUqHqbQpnxCf82L8FjgKsh4iGo78hqJwgL8F6To2A==", "dev": true, "license": "MIT", "engines": { @@ -8959,18 +8274,6 @@ } } }, - "node_modules/type-check": { - "version": "0.4.0", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/type-fest": { "version": "5.2.0", "dev": true, @@ -9174,15 +8477,6 @@ "uplot": "^1.6.32" } }, - "node_modules/uri-js": { - "version": "4.4.1", - "dev": true, - "license": "BSD-2-Clause", - "peer": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, "node_modules/url-parse": { "version": "1.5.10", "dev": true, @@ -9665,23 +8959,23 @@ } }, "node_modules/vitest": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-5.0.3.tgz", - "integrity": "sha512-xMw97S3rjdtj5dkVat7jCsqWBpvchs3RlpQctUqwJD0KkERk40vz2fJ77lDwW/Vzh/pk18eItYAzkodhSes3jQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-5.1.0.tgz", + "integrity": "sha512-MzmNFnrwa8v8lF1bLxw3UJ4EREwVOVb75VqJZmUDRjJ6EluK/x9th7qTbaeDk41WxE1GxWqmTXcdsfobfH5sOA==", "dev": true, "license": "MIT", "dependencies": { "@types/chai": "^5.2.2", - "@vitest/mocker": "5.0.3", - "chai": "^6.2.2", + "@vitest/mocker": "5.1.0", + "chai": "^6.3.0", "es-module-lexer": "^2.3.2", "expect-type": "^1.4.0", - "magic-string": "^1.2.3", - "obug": "^2.1.4", + "magic-string": "^1.4.3", + "obug": "^3.0.0", "picomatch": "^4.0.7", - "std-env": "^4.2.0", - "tinybench": "^6.1.4", - "tinyexec": "^1.3.0", + "std-env": "^4.3.0", + "tinybench": "^6.2.2", + "tinyexec": "^1.3.1", "tinyglobby": "^0.2.17", "why-is-node-running": "3.2.1" }, @@ -9698,12 +8992,12 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "5.0.3", - "@vitest/browser-preview": "5.0.3", + "@vitest/browser-playwright": "5.1.0", + "@vitest/browser-preview": "5.1.0", "@vitest/browser-webdriverio": "^5.0.0-beta.5 || >=5.0.0", - "@vitest/coverage-istanbul": "5.0.3", - "@vitest/coverage-v8": "5.0.3", - "@vitest/ui": "5.0.3", + "@vitest/coverage-istanbul": "5.1.0", + "@vitest/coverage-v8": "5.1.0", + "@vitest/ui": "5.1.0", "happy-dom": "*", "jsdom": "*", "vite": "^6.4.0 || ^7.0.0 || ^8.0.0" @@ -9749,6 +9043,8 @@ }, "node_modules/vitest-browser-react": { "version": "2.3.0", + "resolved": "https://registry.npmjs.org/vitest-browser-react/-/vitest-browser-react-2.3.0.tgz", + "integrity": "sha512-S5vdGHHA93j2Z6dbmIJI1wtxT3AXPTRlmC1Meo+ekG011yNnEutMAnHrwyj8qFlGhgBYA8ENGK6+o1Q+vnFz6A==", "dev": true, "license": "MIT", "funding": { @@ -9771,15 +9067,33 @@ } }, "node_modules/vitest/node_modules/magic-string": { - "version": "1.2.3", + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.4.5.tgz", + "integrity": "sha512-oLLC2+YvQUfAa+vxoufE/yc1icb87M9iXIRqAoR/2EhE/8cb3SF/+Gi0c2By1B+5Vz2e2vQtUvHiu1+63Znh+w==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" + "@jridgewell/sourcemap-codec": "^1.6.0" + } + }, + "node_modules/vitest/node_modules/obug": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/obug/-/obug-3.0.0.tgz", + "integrity": "sha512-5vvB5+W7ePv+p3uqxi+RcW1XAzLW0/hxt3/4X4Lc4qHudzOhmBiBwOY6DRob4WnanAEGvNcLjF+KNOufrUoEQw==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" } }, "node_modules/vitest/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": { @@ -9820,24 +9134,15 @@ "node": ">=20.11" } }, - "node_modules/word-wrap": { - "version": "1.2.5", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/wrappy": { "version": "1.0.2", "license": "ISC", "peer": true }, "node_modules/ws": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.22.0.tgz", - "integrity": "sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==", + "version": "8.22.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.22.2.tgz", + "integrity": "sha512-/rEZ2ejown2N5gxVTHUu9bVVC7ttu0/Tgzf5hbTOixR3Fs7mbbXgn2DbdbcKgJOmpQMtrIUWivtYuVMnp/W/yA==", "dev": true, "license": "MIT", "engines": { @@ -9936,18 +9241,6 @@ "node": ">=8" } }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/yoctocolors-cjs": { "version": "2.1.2", "dev": true, @@ -9960,7 +9253,9 @@ } }, "node_modules/zod": { - "version": "4.4.3", + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.7.tgz", + "integrity": "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index fbd1c6709..0417c4058 100644 --- a/package.json +++ b/package.json @@ -113,10 +113,9 @@ "@types/uuid": "^10.0.0", "@vitejs/plugin-basic-ssl": "^2.3.0", "@vitejs/plugin-react": "^6.0.2", - "@vitest/browser-playwright": "^5.0.3", + "@vitest/browser-playwright": "^5.1.0", "commander": "^15.0.0", "dax": "^0.50.1", - "eslint-plugin-playwright": "^2.10.4", "eslint-plugin-react-hook-form": "^0.3.1", "husky": "^9.1.7", "ip-num": "^1.5.1", @@ -131,7 +130,7 @@ "typescript": "^7.0.2", "vite": "^8.3.5", "vite-node": "^6.0.0", - "vitest": "^5.0.3", + "vitest": "^5.1.0", "vitest-browser-react": "^2.3.0" }, "msw": { diff --git a/test/e2e/disks.e2e.ts b/test/e2e/disks.e2e.ts index 1e921206e..6a54acc97 100644 --- a/test/e2e/disks.e2e.ts +++ b/test/e2e/disks.e2e.ts @@ -182,8 +182,6 @@ test.describe('Disk create', () => { }) // expects are in the afterEach - - /* eslint-disable playwright/expect-expect */ test('from blank', async ({ page }) => { await page.getByRole('radio', { name: '512' }).click() }) @@ -225,7 +223,6 @@ test.describe('Disk create', () => { await expect(source).toBeHidden() await expect(blockSize).toBeHidden() }) - /* eslint-enable playwright/expect-expect */ }) test('Distributed disk size max of 1023 GiB blocks submit', async ({ page }) => { diff --git a/test/e2e/image-upload.e2e.ts b/test/e2e/image-upload.e2e.ts index 34df27799..6ae8c82fd 100644 --- a/test/e2e/image-upload.e2e.ts +++ b/test/e2e/image-upload.e2e.ts @@ -50,7 +50,6 @@ async function fillForm(page: Page, name: string) { test.describe('Image upload', () => { test('happy path', async ({ page, browserName }) => { - // eslint-disable-next-line playwright/no-skipped-test test.skip(browserName === 'webkit', 'safari. stop this') await page.goto('/projects/mock-project/images') @@ -83,7 +82,6 @@ test.describe('Image upload', () => { }) test('with name taken', async ({ page, browserName }) => { - // eslint-disable-next-line playwright/no-skipped-test test.skip(browserName === 'webkit', 'safari. stop this') await fillForm(page, 'image-1') @@ -102,7 +100,6 @@ test.describe('Image upload', () => { }) test('block size validation', async ({ page, browserName }) => { - // eslint-disable-next-line playwright/no-skipped-test test.skip(browserName === 'webkit', 'safari. stop this') await page.goto('/projects/mock-project/images-new') @@ -157,7 +154,6 @@ test.describe('Image upload', () => { }) test('form validation', async ({ page, browserName }) => { - // eslint-disable-next-line playwright/no-skipped-test test.skip(browserName === 'webkit', 'safari. stop this') await page.goto('/projects/mock-project/images-new') @@ -225,7 +221,6 @@ test.describe('Image upload', () => { // testing the onFocusOutside fix test('cancel canceling', async ({ page, browserName }) => { - // eslint-disable-next-line playwright/no-skipped-test test.skip(browserName === 'webkit', 'safari. stop this') await fillForm(page, 'new-image') @@ -266,7 +261,6 @@ test.describe('Image upload', () => { page, browserName, }) => { - // eslint-disable-next-line playwright/no-skipped-test test.skip(browserName === 'webkit', 'safari. stop this') await fillForm(page, 'new-image') @@ -285,7 +279,6 @@ test.describe('Image upload', () => { }) test('Image upload cancel and retry', async ({ page, browserName }) => { - // eslint-disable-next-line playwright/no-skipped-test test.skip(browserName === 'webkit', 'safari. stop this') await fillForm(page, 'new-image') @@ -351,7 +344,6 @@ test.describe('Image upload', () => { for (const { imageName, stepText, message } of failureCases) { test(`failure ${imageName}`, async ({ page, browserName }) => { - // eslint-disable-next-line playwright/no-skipped-test test.skip(browserName === 'webkit', 'safari. stop this') await fillForm(page, imageName) diff --git a/test/e2e/images.e2e.ts b/test/e2e/images.e2e.ts index c60eada6e..00679495b 100644 --- a/test/e2e/images.e2e.ts +++ b/test/e2e/images.e2e.ts @@ -122,7 +122,6 @@ test('can promote an image from project', async ({ page }) => { }) test('can copy an image ID to clipboard', async ({ page, browserName }) => { - // eslint-disable-next-line playwright/no-skipped-test test.skip( browserName === 'webkit', 'navigator.clipboard.readText() is forbidden in Safari.' diff --git a/test/e2e/oxql.e2e.ts b/test/e2e/oxql.e2e.ts index 1eb62eb52..30862035b 100644 --- a/test/e2e/oxql.e2e.ts +++ b/test/e2e/oxql.e2e.ts @@ -245,7 +245,6 @@ test('results can be copied as JSON', async ({ page }) => { }) test('copied JSON actually matches the response body', async ({ page, browserName }) => { - // eslint-disable-next-line playwright/no-skipped-test test.skip( browserName === 'webkit', 'navigator.clipboard.readText() is forbidden in Safari.' diff --git a/test/e2e/theme.e2e.ts b/test/e2e/theme.e2e.ts index 8200900f3..093d90f09 100644 --- a/test/e2e/theme.e2e.ts +++ b/test/e2e/theme.e2e.ts @@ -24,6 +24,7 @@ async function seedTheme(page: Page, theme: string) { * request so renaming the entry can't silently turn this into a hydrated test. */ async function gotoBeforeHydration(page: Page, path: string) { + // oxlint-disable-next-line playwright/no-page-route -- blocks a script, not an API call await page.route('**/app/entry.client.tsx*', (route) => route.abort('blockedbyclient'), { times: 1, }) diff --git a/test/e2e/utilization.e2e.ts b/test/e2e/utilization.e2e.ts index 10dc62d68..b251276de 100644 --- a/test/e2e/utilization.e2e.ts +++ b/test/e2e/utilization.e2e.ts @@ -46,7 +46,6 @@ test.describe('System utilization', () => { }) test('can copy silo ID', async ({ page, browserName }) => { - // eslint-disable-next-line playwright/no-skipped-test test.skip( browserName === 'webkit', 'navigator.clipboard.readText() is forbidden in Safari.' diff --git a/test/e2e/utils.ts b/test/e2e/utils.ts index c0f32ed3a..0aca22ede 100644 --- a/test/e2e/utils.ts +++ b/test/e2e/utils.ts @@ -328,7 +328,6 @@ export async function addTlsCert(page: Page) { * drops real console output there and only returns React profiling entries. */ export async function expectConsoleMessage(page: Page, msg: string, type?: string) { - // eslint-disable-next-line playwright/no-conditional-in-test if (test.info().project.name === 'firefox') return const messages = await page.consoleMessages() const match = messages.find((m) => m.text().includes(msg)) @@ -338,7 +337,6 @@ export async function expectConsoleMessage(page: Page, msg: string, type?: strin /** Assert that no console message matching `msg` was logged. Skips on Firefox. */ export async function expectNoConsoleMessage(page: Page, msg: string) { - // eslint-disable-next-line playwright/no-conditional-in-test if (test.info().project.name === 'firefox') return const messages = await page.consoleMessages() const match = messages.find((m) => m.text().includes(msg)) diff --git a/tools/oxlint-plugin-playwright.spec.ts b/tools/oxlint-plugin-playwright.spec.ts new file mode 100644 index 000000000..b1310274d --- /dev/null +++ b/tools/oxlint-plugin-playwright.spec.ts @@ -0,0 +1,102 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * Copyright Oxide Computer Company + */ +import { RuleTester } from 'oxlint/plugins-dev' +import { describe, it } from 'vitest' + +import plugin from './oxlint-plugin-playwright' + +RuleTester.describe = describe +RuleTester.it = it + +const tester = new RuleTester() + +tester.run('expect-expect', plugin.rules['expect-expect'], { + valid: [ + `test('direct expect', async ({ page }) => { await expect(page).toHaveURL('/') })`, + `test('poll', async () => { await expect.poll(() => 1).toBe(1) })`, + `test('helper', async ({ page }) => { await expectRowVisible(page) })`, + `test('inside step', async ({ page }) => { + await test.step('s', async () => { await expect(page).toHaveURL('/') }) + })`, + // condition modifiers take callbacks without declaring tests + `test.slow(({ browserName }) => browserName === 'webkit')`, + `test.skip(({ browserName }) => browserName === 'webkit')`, + `test('inline modifier', async ({ page }) => { + test.skip(({ browserName }) => browserName === 'webkit') + await expect(page).toHaveURL('/') + })`, + // afterEach covers tests in its scope and nested describes, even when declared last + `test.describe('suite', () => { + test('a', async ({ page }) => { await page.click('button') }) + test.describe('nested', () => { + test('b', async ({ page }) => { await page.click('button') }) + }) + test.afterEach(async ({ page }) => { await expectToast(page, 'ok') }) + })`, + ], + invalid: [ + { + code: `test('empty', async ({ page }) => { await page.goto('/') })`, + errors: [{ messageId: 'noAssertions' }], + }, + { + code: `test.skip('skipped test body', { tag: '@slow' }, async ({ page }) => { await page.goto('/') })`, + errors: [{ messageId: 'noAssertions' }], + }, + { + // beforeEach waits for setup; it doesn't assert what the test did + code: ` + test.beforeEach(async ({ page }) => { await expect(page).toHaveURL('/') }) + test('forgot assertion', async ({ page }) => { await page.click('button') }) + `, + errors: [{ messageId: 'noAssertions' }], + }, + { + // sibling describe's afterEach doesn't leak out + code: ` + test.describe('covered', () => { + test.afterEach(async ({ page }) => { await expect(page).toHaveURL('/') }) + test('ok', async ({ page }) => { await page.click('button') }) + }) + test('uncovered', async ({ page }) => { await page.click('button') }) + `, + errors: [{ messageId: 'noAssertions' }], + }, + ], +}) + +tester.run('no-focused-test', plugin.rules['no-focused-test'], { + valid: [ + `test('ok', async () => {})`, + `test.describe('ok', () => {})`, + `other.only('not playwright', () => {})`, + ], + invalid: [ + { code: `test.only('x', async () => {})`, errors: [{ messageId: 'focused' }] }, + { code: `test.fail.only('x', async () => {})`, errors: [{ messageId: 'focused' }] }, + { code: `test.describe.only('x', () => {})`, errors: [{ messageId: 'focused' }] }, + { + code: `test.describe.serial.only('x', () => {})`, + errors: [{ messageId: 'focused' }], + }, + ], +}) + +tester.run('no-page-route', plugin.rules['no-page-route'], { + valid: [`await page.goto('/routes')`, `await page.unroute('**/*')`], + invalid: [ + { + code: `await page.route('**/v1/**', (r) => r.abort())`, + errors: [{ messageId: 'pageRoute' }], + }, + { + code: `await page.context().route('**/v1/**', (r) => r.abort())`, + errors: [{ messageId: 'pageRoute' }], + }, + ], +}) diff --git a/tools/oxlint-plugin-playwright.ts b/tools/oxlint-plugin-playwright.ts new file mode 100644 index 000000000..084d83c2f --- /dev/null +++ b/tools/oxlint-plugin-playwright.ts @@ -0,0 +1,160 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * Copyright Oxide Computer Company + */ + +// Local Playwright lint rules. expect-expect replaces eslint-plugin-playwright, +// which has eslint as a peer dep and so installed all of eslint. oxlint's +// built-in jest/expect-expect and vitest/expect-expect don't recognize +// Playwright's `test`, so they can't stand in for it. +// +// TODO: rules to add once their existing violations are fixed. They enforce +// conventions from the "Testing code" section of CLAUDE.md. Counts are +// approximate uses in test/e2e when this was written. +// - no sleep() or page.waitForTimeout() (6): fix the timing root cause instead +// - no getByTestId (8): use accessible locators like getByRole +// - no expectVisible/expectNotVisible (78): use expect().toBeVisible()/toBeHidden() +// - no legacy string selectors like page.click('role=button[name="..."]') (187): +// use getByRole and friends + +import type { RuleTester } from 'oxlint/plugins-dev' + +// oxlint only exposes its plugin types through RuleTester +type Rule = Parameters[1] +type Visitor = ReturnType> +type CallExpression = Parameters>[0] + +/** + * The dotted path of a callee made only of names, like `test.describe.only`. + * Undefined for anything else, like `this.page.route` or `expect(x).toBe`. + */ +function dottedName(node: CallExpression['callee']): string | undefined { + if (node.type === 'Identifier') return node.name + if (node.type !== 'MemberExpression' || node.computed) return undefined + if (node.property.type !== 'Identifier') return undefined + const object = dottedName(node.object) + return object && `${object}.${node.property.name}` +} + +// test.step(), test.slow(), etc. take callbacks too, but they aren't tests +const TESTS = new Set([ + 'test', + 'test.only', + 'test.skip', + 'test.fixme', + 'test.fail', + 'test.fail.only', +]) +// covers variants like test.describe.serial +const isDescribe = (name: string) => + name === 'test.describe' || name.startsWith('test.describe.') + +const expectExpect: Rule = { + meta: { type: 'problem', messages: { noAssertions: 'Test has no assertions' } }, + create(context) { + // A scope is the file or a test.describe() callback. Its afterEach hooks + // apply to every test in it, including nested describes, and can come + // after the tests, so whether an unasserted test is covered is only known + // once the scope has been fully visited. (beforeEach runs before the test + // body and often just waits for the page to load, so it doesn't count.) + type Scope = { + node?: CallExpression + afterEachAsserts: boolean + unasserted: CallExpression[] + } + const file: Scope = { afterEachAsserts: false, unasserted: [] } + const describes: Scope[] = [] + const innermost = () => describes.at(-1) ?? file + // the test or afterEach callback we're in + let body: { node: CallExpression; isAfterEach: boolean; asserted: boolean } | undefined + + return { + CallExpression(node) { + const name = dottedName(node.callee) + if (!name) return + if (body) { + // any call starting with expect counts as an assertion, which covers + // expect(), expect.poll(), and helpers like expectRowVisible() + if (name.startsWith('expect')) body.asserted = true + return + } + const fn = node.arguments.at(-1) + const hasCallback = + fn?.type === 'ArrowFunctionExpression' || fn?.type === 'FunctionExpression' + if (!hasCallback) return + if (isDescribe(name)) { + describes.push({ node, afterEachAsserts: false, unasserted: [] }) + } else if (name === 'test.afterEach') { + body = { node, isAfterEach: true, asserted: false } + } else if (TESTS.has(name) && node.arguments.length >= 2) { + // >= 2 excludes 1-arg condition modifiers like test.skip(({ browserName }) => ...) + body = { node, isAfterEach: false, asserted: false } + } + }, + 'CallExpression:exit'(node) { + const scope = innermost() + if (node === body?.node) { + if (body.isAfterEach) scope.afterEachAsserts ||= body.asserted + else if (!body.asserted) scope.unasserted.push(node) + body = undefined + } else if (node === scope.node) { + describes.pop() + // afterEach in an enclosing scope may still cover these + if (!scope.afterEachAsserts) innermost().unasserted.push(...scope.unasserted) + } + }, + 'Program:exit'() { + if (file.afterEachAsserts) return + for (const node of file.unasserted) { + context.report({ node: node.callee, messageId: 'noAssertions' }) + } + }, + } + }, +} + +const noFocusedTest: Rule = { + meta: { type: 'problem', messages: { focused: 'Remove .only so the whole suite runs' } }, + create: (context) => ({ + CallExpression(node) { + const name = dottedName(node.callee) + if (name?.startsWith('test.') && name.endsWith('.only')) { + context.report({ node: node.callee, messageId: 'focused' }) + } + }, + }), +} + +const noPageRoute: Rule = { + meta: { + type: 'problem', + messages: { + pageRoute: + "Don't intercept API calls with route(). Make the mock API handler return the error for a sentinel input instead.", + }, + }, + create: (context) => ({ + CallExpression({ callee }) { + if ( + callee.type === 'MemberExpression' && + !callee.computed && + callee.property.type === 'Identifier' && + callee.property.name === 'route' + ) { + context.report({ node: callee, messageId: 'pageRoute' }) + } + }, + }), +} + +export default { + meta: { name: 'playwright' }, + rules: { + 'expect-expect': expectExpect, + 'no-focused-test': noFocusedTest, + 'no-page-route': noPageRoute, + }, +}