diff --git a/.github/workflows/general.yml b/.github/workflows/general.yml index 597e26ad..c110aec9 100644 --- a/.github/workflows/general.yml +++ b/.github/workflows/general.yml @@ -39,20 +39,30 @@ jobs: - name: Install test dependencies run: cd tests && npm ci - - name: Run unit tests - run: npm run test:unit + - name: Run unit tests with coverage + run: npm run test:unit:coverage - name: Install Playwright Browsers run: cd tests && npx playwright install --with-deps chromium - - name: Run e2e tests - run: npm run test:e2e + - name: Run e2e tests with coverage + id: e2e + run: npm run test:e2e:coverage - - name: Upload Playwright Report + - name: Upload e2e failure artifacts + uses: actions/upload-artifact@v4 + if: ${{ !cancelled() && steps.e2e.outcome == 'failure' }} + with: + name: e2e-failure-artifacts + path: tests/.vitest-attachments/ + if-no-files-found: ignore + retention-days: 14 + + - name: Upload coverage reports uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} with: - name: playwright-report - path: tests/e2e/playwright-report/ + name: coverage-reports + path: coverage/ retention-days: 30 diff --git a/.gitignore b/.gitignore index e0afc50e..440ec3cd 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,7 @@ node_modules .vscode *.tgz .DS_Store -tests/e2e/playwright-report -tests/e2e/test-results +tests/.vitest-attachments +tests/e2e/specs/__screenshots__ +/coverage/ +/temp/ diff --git a/eslint.config.js b/eslint.config.js index d1e0489c..fc395792 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -6,7 +6,13 @@ import globals from 'globals'; export default [ // Global ignores (replaces .eslintignore) { - ignores: ['node_modules/**', 'dist/**', 'build/**', 'demo/**'] + ignores: [ + 'node_modules/**', + 'dist/**', + 'build/**', + 'demo/**', + 'coverage/**' + ] }, // Base configuration for TypeScript files diff --git a/package-lock.json b/package-lock.json index 6b93454b..8735a1f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,27 +12,28 @@ "tslib": "^2.8.1" }, "devDependencies": { + "@eslint/js": "^10.0.1", "@rollup/plugin-node-resolve": "^16.0.3", - "@rollup/plugin-terser": "^0.4.4", - "@typescript-eslint/eslint-plugin": "^8.48.0", - "@typescript-eslint/parser": "^8.48.0", + "@rollup/plugin-terser": "^1.0.0", + "@typescript-eslint/eslint-plugin": "^8.65.0", + "@typescript-eslint/parser": "^8.65.0", "chalk": "^5.6.2", - "eslint": "^9.39.1", - "globals": "^16.5.0", + "eslint": "^10.8.0", + "globals": "^17.7.0", "husky": "^9.1.7", - "lint-staged": "^16.2.7", - "prettier": "^3.7.1", - "rollup": "^4.53.3", - "rollup-plugin-license": "^3.6.0", - "rollup-plugin-sourcemaps2": "^0.5.4", + "lint-staged": "^17.2.0", + "prettier": "^3.9.6", + "rollup": "^4.62.2", + "rollup-plugin-license": "^3.7.1", + "rollup-plugin-sourcemaps2": "^0.5.8", "shelljs": "^0.10.0", "typescript": "^5.9.3" } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", "dev": true, "license": "MIT", "dependencies": { @@ -59,202 +60,129 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", - "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.7", + "@eslint/object-schema": "^3.0.5", "debug": "^4.3.1", - "minimatch": "^3.1.2" + "minimatch": "^10.2.4" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/config-helpers": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", - "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz", + "integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.17.0" + "@eslint/core": "^1.2.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/core": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", "dev": true, "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/eslintrc/node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/js": { - "version": "9.39.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz", - "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", "dev": true, "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } } }, "node_modules/@eslint/object-schema": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", - "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", "dev": true, "license": "Apache-2.0", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/plugin-kit": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", - "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", + "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.17.0", + "@eslint/core": "^1.2.1", "levn": "^0.4.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, "engines": { "node": ">=18.18.0" } }, "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanfs/core": "^0.19.1", + "@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", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -397,18 +325,18 @@ } }, "node_modules/@rollup/plugin-terser": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz", - "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-1.0.0.tgz", + "integrity": "sha512-FnCxhTBx6bMOYQrar6C8h3scPt8/JwIzw3+AJ2K++6guogH5fYaIFia+zZuhqv0eo1RN7W1Pz630SyvLbDjhtQ==", "dev": true, "license": "MIT", "dependencies": { - "serialize-javascript": "^6.0.1", + "serialize-javascript": "^7.0.3", "smob": "^1.0.0", "terser": "^5.17.4" }, "engines": { - "node": ">=14.0.0" + "node": ">=20.0.0" }, "peerDependencies": { "rollup": "^2.0.0||^3.0.0||^4.0.0" @@ -420,9 +348,9 @@ } }, "node_modules/@rollup/pluginutils": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", - "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.4.0.tgz", + "integrity": "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==", "dev": true, "license": "MIT", "dependencies": { @@ -443,9 +371,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", - "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", "cpu": [ "arm" ], @@ -457,9 +385,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", - "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", "cpu": [ "arm64" ], @@ -471,9 +399,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", - "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", "cpu": [ "arm64" ], @@ -485,9 +413,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", - "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", "cpu": [ "x64" ], @@ -499,9 +427,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", - "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", "cpu": [ "arm64" ], @@ -513,9 +441,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", - "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", "cpu": [ "x64" ], @@ -527,13 +455,16 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", - "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", "cpu": [ "arm" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -541,13 +472,16 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", - "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", "cpu": [ "arm" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -555,13 +489,16 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", - "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -569,13 +506,16 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", - "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -583,13 +523,33 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", - "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", "cpu": [ "loong64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -597,13 +557,33 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", - "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", "cpu": [ "ppc64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -611,13 +591,16 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", - "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", "cpu": [ "riscv64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -625,13 +608,16 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", - "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", "cpu": [ "riscv64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -639,13 +625,16 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", - "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", "cpu": [ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -653,13 +642,16 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", - "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -667,23 +659,40 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", - "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ "linux" ] }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", - "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", "cpu": [ "arm64" ], @@ -695,9 +704,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", - "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", "cpu": [ "arm64" ], @@ -709,9 +718,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", - "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", "cpu": [ "ia32" ], @@ -723,9 +732,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", - "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", "cpu": [ "x64" ], @@ -737,9 +746,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", - "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", "cpu": [ "x64" ], @@ -750,10 +759,17 @@ "win32" ] }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "dev": true, "license": "MIT" }, @@ -772,21 +788,20 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.48.0.tgz", - "integrity": "sha512-XxXP5tL1txl13YFtrECECQYeZjBZad4fyd3cFV4a19LkAY/bIp9fev3US4S5fDVV2JaYFiKAZ/GRTOLer+mbyQ==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.65.0.tgz", + "integrity": "sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.48.0", - "@typescript-eslint/type-utils": "8.48.0", - "@typescript-eslint/utils": "8.48.0", - "@typescript-eslint/visitor-keys": "8.48.0", - "graphemer": "^1.4.0", - "ignore": "^7.0.0", + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/type-utils": "8.65.0", + "@typescript-eslint/utils": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", + "ignore": "^7.0.5", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.1.0" + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -796,23 +811,23 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.48.0", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "@typescript-eslint/parser": "^8.65.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.48.0.tgz", - "integrity": "sha512-jCzKdm/QK0Kg4V4IK/oMlRZlY+QOcdjv89U2NgKHZk1CYTj82/RVSx1mV/0gqCVMJ/DA+Zf/S4NBWNF8GQ+eqQ==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.65.0.tgz", + "integrity": "sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.48.0", - "@typescript-eslint/types": "8.48.0", - "@typescript-eslint/typescript-estree": "8.48.0", - "@typescript-eslint/visitor-keys": "8.48.0", - "debug": "^4.3.4" + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", + "debug": "^4.4.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -822,20 +837,20 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.48.0.tgz", - "integrity": "sha512-Ne4CTZyRh1BecBf84siv42wv5vQvVmgtk8AuiEffKTUo3DrBaGYZueJSxxBZ8fjk/N3DrgChH4TOdIOwOwiqqw==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz", + "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.48.0", - "@typescript-eslint/types": "^8.48.0", - "debug": "^4.3.4" + "@typescript-eslint/tsconfig-utils": "^8.65.0", + "@typescript-eslint/types": "^8.65.0", + "debug": "^4.4.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -845,18 +860,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.48.0.tgz", - "integrity": "sha512-uGSSsbrtJrLduti0Q1Q9+BF1/iFKaxGoQwjWOIVNJv0o6omrdyR8ct37m4xIl5Zzpkp69Kkmvom7QFTtue89YQ==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.65.0.tgz", + "integrity": "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.48.0", - "@typescript-eslint/visitor-keys": "8.48.0" + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -867,9 +882,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.48.0.tgz", - "integrity": "sha512-WNebjBdFdyu10sR1M4OXTt2OkMd5KWIL+LLfeH9KhgP+jzfDV/LI3eXzwJ1s9+Yc0Kzo2fQCdY/OpdusCMmh6w==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz", + "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==", "dev": true, "license": "MIT", "engines": { @@ -880,21 +895,21 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.48.0.tgz", - "integrity": "sha512-zbeVaVqeXhhab6QNEKfK96Xyc7UQuoFWERhEnj3mLVnUWrQnv15cJNseUni7f3g557gm0e46LZ6IJ4NJVOgOpw==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.65.0.tgz", + "integrity": "sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.48.0", - "@typescript-eslint/typescript-estree": "8.48.0", - "@typescript-eslint/utils": "8.48.0", - "debug": "^4.3.4", - "ts-api-utils": "^2.1.0" + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/utils": "8.65.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -904,14 +919,14 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.48.0.tgz", - "integrity": "sha512-cQMcGQQH7kwKoVswD1xdOytxQR60MWKM1di26xSUtxehaDs/32Zpqsu5WJlXTtTTqyAVK8R7hvsUnIXRS+bjvA==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", + "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", "dev": true, "license": "MIT", "engines": { @@ -923,21 +938,21 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.48.0.tgz", - "integrity": "sha512-ljHab1CSO4rGrQIAyizUS6UGHHCiAYhbfcIZ1zVJr5nMryxlXMVWS3duFPSKvSUbFPwkXMFk1k0EMIjub4sRRQ==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz", + "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.48.0", - "@typescript-eslint/tsconfig-utils": "8.48.0", - "@typescript-eslint/types": "8.48.0", - "@typescript-eslint/visitor-keys": "8.48.0", - "debug": "^4.3.4", - "minimatch": "^9.0.4", - "semver": "^7.6.0", + "@typescript-eslint/project-service": "8.65.0", + "@typescript-eslint/tsconfig-utils": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.1.0" + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -947,20 +962,20 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/utils": { - "version": "8.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.48.0.tgz", - "integrity": "sha512-yTJO1XuGxCsSfIVt1+1UrLHtue8xz16V8apzPYI06W0HbEbEWHxHXgZaAgavIkoh+GeV6hKKd5jm0sS6OYxWXQ==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.65.0.tgz", + "integrity": "sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.48.0", - "@typescript-eslint/types": "8.48.0", - "@typescript-eslint/typescript-estree": "8.48.0" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -970,19 +985,19 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.48.0.tgz", - "integrity": "sha512-T0XJMaRPOH3+LBbAfzR2jalckP1MSG/L9eUtY0DEzUyVaXJ/t6zN0nR7co5kz0Jko/nkSYCBRkz1djvjajVTTg==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.65.0.tgz", + "integrity": "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.48.0", - "eslint-visitor-keys": "^4.2.1" + "@typescript-eslint/types": "8.65.0", + "eslint-visitor-keys": "^5.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -993,22 +1008,22 @@ } }, "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, "license": "Apache-2.0", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", "dev": true, "license": "MIT", "bin": { @@ -1029,9 +1044,9 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { @@ -1045,55 +1060,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ansi-escapes": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz", - "integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "environment": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, "node_modules/array-find-index": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", @@ -1105,20 +1071,26 @@ } }, "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } }, "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz", + "integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" } }, "node_modules/braces": { @@ -1141,16 +1113,6 @@ "dev": true, "license": "MIT" }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/chalk": { "version": "5.6.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", @@ -1164,76 +1126,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/cli-cursor": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "restore-cursor": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-truncate": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.1.1.tgz", - "integrity": "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==", - "dev": true, - "license": "MIT", - "dependencies": { - "slice-ansi": "^7.1.0", - "string-width": "^8.0.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/commander": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", - "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20" - } - }, "node_modules/commenting": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/commenting/-/commenting-1.1.0.tgz", @@ -1241,13 +1133,6 @@ "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", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1298,26 +1183,6 @@ "node": ">=0.10.0" } }, - "node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "dev": true, - "license": "MIT" - }, - "node_modules/environment": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", - "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -1332,33 +1197,33 @@ } }, "node_modules/eslint": { - "version": "9.39.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz", - "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", + "version": "10.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.8.0.tgz", + "integrity": "sha512-nuKKvN+oIBO0koN7Tm7dlkmnkc21mtt0QJLwAKzjLq14y6lRTdVG36MZHJ8eQHwdJMwZbQNMlPOYedMq/oVJvQ==", "dev": true, "license": "MIT", + "workspaces": [ + "packages/*" + ], "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.1", - "@eslint/config-helpers": "^0.4.2", - "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.1", - "@eslint/plugin-kit": "^0.4.1", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.7.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.2", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", - "ajv": "^6.12.4", - "chalk": "^4.0.0", + "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "esquery": "^1.5.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", @@ -1368,8 +1233,7 @@ "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", + "minimatch": "^10.2.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, @@ -1377,7 +1241,7 @@ "eslint": "bin/eslint.js" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://eslint.org/donate" @@ -1392,17 +1256,19 @@ } }, "node_modules/eslint-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" @@ -1421,121 +1287,64 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/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==", + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, + "license": "Apache-2.0", "engines": { - "node": ">=8" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "engines": { + "node": ">= 4" } }, - "node_modules/eslint/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/eslint/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.15.0", + "acorn": "^8.16.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" + "eslint-visitor-keys": "^5.0.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, "license": "Apache-2.0", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -1585,13 +1394,6 @@ "node": ">=0.10.0" } }, - "node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", - "dev": true, - "license": "MIT" - }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -1776,9 +1578,9 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.3.tgz", + "integrity": "sha512-/zipXxyO6rGvuNGDiULY9MvEGSkb2gaG4GGH4ygMi0ZZzyMHdUZBmntJmx5x1G2VuPytCwGN4xsJP6cw+sK+vQ==", "dev": true, "license": "ISC" }, @@ -1807,19 +1609,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-east-asian-width": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", - "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -1847,9 +1636,9 @@ } }, "node_modules/globals": { - "version": "16.5.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", - "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "version": "17.7.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.7.0.tgz", + "integrity": "sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==", "dev": true, "license": "MIT", "engines": { @@ -1859,23 +1648,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" - }, - "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/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -1925,23 +1697,6 @@ "node": ">= 4" } }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -1978,22 +1733,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-fullwidth-code-point": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", - "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.3.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -2044,19 +1783,6 @@ "dev": true, "license": "ISC" }, - "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -2103,46 +1829,27 @@ } }, "node_modules/lint-staged": { - "version": "16.2.7", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.2.7.tgz", - "integrity": "sha512-lDIj4RnYmK7/kXMya+qJsmkRFkGolciXjrsZ6PC25GdTfWOAWetR0ZbsNXRAj1EHHImRSalc+whZFg56F5DVow==", + "version": "17.2.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-17.2.0.tgz", + "integrity": "sha512-FchGnFe4i4B1C/a35SPU9bNGPEHSC1+1iV0plLjzBmKVe9klZrlRfSgK6Cw4VeHyqOXbJUXP0vON61uRftNQ0A==", "dev": true, "license": "MIT", "dependencies": { - "commander": "^14.0.2", - "listr2": "^9.0.5", - "micromatch": "^4.0.8", - "nano-spawn": "^2.0.0", - "pidtree": "^0.6.0", + "picomatch": "^4.0.5", "string-argv": "^0.3.2", - "yaml": "^2.8.1" + "tinyexec": "^1.2.4" }, "bin": { "lint-staged": "bin/lint-staged.js" }, "engines": { - "node": ">=20.17" + "node": ">=22.22.1" }, "funding": { "url": "https://opencollective.com/lint-staged" - } - }, - "node_modules/listr2": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", - "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", - "dev": true, - "license": "MIT", - "dependencies": { - "cli-truncate": "^5.0.0", - "colorette": "^2.0.20", - "eventemitter3": "^5.0.1", - "log-update": "^6.1.0", - "rfdc": "^1.4.1", - "wrap-ansi": "^9.0.0" }, - "engines": { - "node": ">=20.0.0" + "optionalDependencies": { + "yaml": "^2.9.0" } }, "node_modules/locate-path": { @@ -2162,39 +1869,12 @@ } }, "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "dev": true, "license": "MIT" }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/log-update": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", - "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-escapes": "^7.0.0", - "cli-cursor": "^5.0.0", - "slice-ansi": "^7.1.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -2237,9 +1917,9 @@ } }, "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { @@ -2259,30 +1939,17 @@ "node": ">=6" } }, - "node_modules/mimic-function": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", - "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.5" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -2305,19 +1972,6 @@ "dev": true, "license": "MIT" }, - "node_modules/nano-spawn": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-2.0.0.tgz", - "integrity": "sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/nano-spawn?sponsor=1" - } - }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -2338,22 +1992,6 @@ "node": ">=8" } }, - "node_modules/onetime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-function": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "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", @@ -2417,19 +2055,6 @@ "url": "https://github.com/sponsors/dword-design" } }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -2458,9 +2083,9 @@ "license": "MIT" }, "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, "license": "MIT", "engines": { @@ -2470,19 +2095,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pidtree": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", - "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", - "dev": true, - "license": "MIT", - "bin": { - "pidtree": "bin/pidtree.js" - }, - "engines": { - "node": ">=0.10" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -2494,9 +2106,9 @@ } }, "node_modules/prettier": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.1.tgz", - "integrity": "sha512-RWKXE4qB3u5Z6yz7omJkjWwmTfLdcbv44jUVHC5NpfXwFGzvpQM798FGv/6WNK879tc+Cn0AAyherCl1KjbyZQ==", + "version": "3.9.6", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz", + "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==", "dev": true, "license": "MIT", "bin": { @@ -2540,16 +2152,6 @@ ], "license": "MIT" }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, "node_modules/resolve": { "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", @@ -2571,33 +2173,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/restore-cursor": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", - "dev": true, - "license": "MIT", - "dependencies": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -2609,21 +2184,14 @@ "node": ">=0.10.0" } }, - "node_modules/rfdc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "dev": true, - "license": "MIT" - }, "node_modules/rollup": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", - "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.8" + "@types/estree": "1.0.9" }, "bin": { "rollup": "dist/bin/rollup" @@ -2633,46 +2201,49 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.53.3", - "@rollup/rollup-android-arm64": "4.53.3", - "@rollup/rollup-darwin-arm64": "4.53.3", - "@rollup/rollup-darwin-x64": "4.53.3", - "@rollup/rollup-freebsd-arm64": "4.53.3", - "@rollup/rollup-freebsd-x64": "4.53.3", - "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", - "@rollup/rollup-linux-arm-musleabihf": "4.53.3", - "@rollup/rollup-linux-arm64-gnu": "4.53.3", - "@rollup/rollup-linux-arm64-musl": "4.53.3", - "@rollup/rollup-linux-loong64-gnu": "4.53.3", - "@rollup/rollup-linux-ppc64-gnu": "4.53.3", - "@rollup/rollup-linux-riscv64-gnu": "4.53.3", - "@rollup/rollup-linux-riscv64-musl": "4.53.3", - "@rollup/rollup-linux-s390x-gnu": "4.53.3", - "@rollup/rollup-linux-x64-gnu": "4.53.3", - "@rollup/rollup-linux-x64-musl": "4.53.3", - "@rollup/rollup-openharmony-arm64": "4.53.3", - "@rollup/rollup-win32-arm64-msvc": "4.53.3", - "@rollup/rollup-win32-ia32-msvc": "4.53.3", - "@rollup/rollup-win32-x64-gnu": "4.53.3", - "@rollup/rollup-win32-x64-msvc": "4.53.3", + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", "fsevents": "~2.3.2" } }, "node_modules/rollup-plugin-license": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-license/-/rollup-plugin-license-3.6.0.tgz", - "integrity": "sha512-1ieLxTCaigI5xokIfszVDRoy6c/Wmlot1fDEnea7Q/WXSR8AqOjYljHDLObAx7nFxHC2mbxT3QnTSPhaic2IYw==", + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-license/-/rollup-plugin-license-3.7.1.tgz", + "integrity": "sha512-FcGXUbAmPvRSLxjVdjp/r/MUtKBlttVQd+ApUyvKfREnsoAfAZA6Ic2fE1Tz4RL0f9XqEQU9UIRNUMdtQtliDw==", "dev": true, "license": "MIT", "dependencies": { - "commenting": "~1.1.0", + "commenting": "^1.1.0", "fdir": "^6.4.3", - "lodash": "~4.17.21", - "magic-string": "~0.30.0", - "moment": "~2.30.1", - "package-name-regex": "~2.0.6", - "spdx-expression-validate": "~2.0.0", - "spdx-satisfies": "~5.0.1" + "lodash": "^4.17.21", + "magic-string": "^0.30.0", + "moment": "^2.30.1", + "package-name-regex": "^2.0.6", + "spdx-expression-validate": "^2.0.0", + "spdx-satisfies": "^5.0.1" }, "engines": { "node": ">=14.0.0" @@ -2682,16 +2253,16 @@ } }, "node_modules/rollup-plugin-sourcemaps2": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/rollup-plugin-sourcemaps2/-/rollup-plugin-sourcemaps2-0.5.4.tgz", - "integrity": "sha512-XK6ITvEsKtUFN1GQbYKoqilwh1yKxTS9BLaFlVsm0IaYUYe3eVnhBWzKP4AHbkBO2BNOheGNlf407K7wCj6Rrw==", + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/rollup-plugin-sourcemaps2/-/rollup-plugin-sourcemaps2-0.5.8.tgz", + "integrity": "sha512-c5BfiVbKAmaz+RoEhFF44Tg/X2QoLxEB8F7wS/eNWDJF/NKxi0SF7srN3Dg0RUnIcYLyCOwscket5Mr3BImMbg==", "dev": true, "license": "MIT", "dependencies": { - "@rollup/pluginutils": "5.2.0" + "@rollup/pluginutils": "5.4.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=22.13.0" }, "peerDependencies": { "@types/node": ">=18.0.0", @@ -2703,29 +2274,6 @@ } } }, - "node_modules/rollup-plugin-sourcemaps2/node_modules/@rollup/pluginutils": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.2.0.tgz", - "integrity": "sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -2750,31 +2298,10 @@ "queue-microtask": "^1.2.2" } }, - "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/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -2785,13 +2312,13 @@ } }, "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.7.tgz", + "integrity": "sha512-YAy8Od6KV+uuwUuU50np8fGB/Aues6Y0nAhA9y/hId74PlKUcme4pXcBD46NWKr1Q4osN/iseZ17YqO1XfmI8g==", "dev": true, "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" + "engines": { + "node": ">=20.0.0" } }, "node_modules/shebang-command": { @@ -2831,36 +2358,6 @@ "node": ">=18" } }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/slice-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", - "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "is-fullwidth-code-point": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, "node_modules/smob": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/smob/-/smob-1.5.0.tgz", @@ -2965,39 +2462,6 @@ "node": ">=0.6.19" } }, - "node_modules/string-width": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", - "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.3.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", @@ -3008,32 +2472,6 @@ "node": ">=6" } }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -3073,15 +2511,25 @@ "dev": true, "license": "MIT" }, + "node_modules/tinyexec": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", + "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", - "picomatch": "^4.0.3" + "picomatch": "^4.0.4" }, "engines": { "node": ">=12.0.0" @@ -3104,9 +2552,9 @@ } }, "node_modules/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "dev": true, "license": "MIT", "engines": { @@ -3185,53 +2633,21 @@ "node": ">=0.10.0" } }, - "node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/yaml": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", - "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", "dev": true, "license": "ISC", + "optional": true, "bin": { "yaml": "bin.mjs" }, "engines": { "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" } }, "node_modules/yocto-queue": { @@ -3248,4 +2664,4 @@ } } } -} \ No newline at end of file +} diff --git a/package.json b/package.json index 3cd4ec4b..92181e9e 100644 --- a/package.json +++ b/package.json @@ -28,9 +28,11 @@ "build": "node build", "test": "npm run lint && cd tests && npm test", "test:unit": "cd tests && npm run test:unit", + "test:unit:coverage": "cd tests && npm run test:unit:coverage", "test:e2e": "cd tests && npm run test:e2e", + "test:e2e:coverage": "cd tests && npm run test:e2e:coverage", "test:e2e:ui": "cd tests && npm run test:e2e:ui", - "test:e2e:debug": "cd tests && npm run test:e2e:debug", + "test:coverage": "cd tests && npm run test:coverage", "prepack": "npm run build", "checkGit": "sh ./uncommited.sh", "prepublishOnly": "npm run checkGit && npm run build && npm run test && npm run checkGit" @@ -39,19 +41,20 @@ "tslib": "^2.8.1" }, "devDependencies": { + "@eslint/js": "^10.0.1", "@rollup/plugin-node-resolve": "^16.0.3", - "@rollup/plugin-terser": "^0.4.4", - "@typescript-eslint/eslint-plugin": "^8.48.0", - "@typescript-eslint/parser": "^8.48.0", + "@rollup/plugin-terser": "^1.0.0", + "@typescript-eslint/eslint-plugin": "^8.65.0", + "@typescript-eslint/parser": "^8.65.0", "chalk": "^5.6.2", - "eslint": "^9.39.1", - "globals": "^16.5.0", + "eslint": "^10.8.0", + "globals": "^17.7.0", "husky": "^9.1.7", - "lint-staged": "^16.2.7", - "prettier": "^3.7.1", - "rollup": "^4.53.3", - "rollup-plugin-license": "^3.6.0", - "rollup-plugin-sourcemaps2": "^0.5.4", + "lint-staged": "^17.2.0", + "prettier": "^3.9.6", + "rollup": "^4.62.2", + "rollup-plugin-license": "^3.7.1", + "rollup-plugin-sourcemaps2": "^0.5.8", "shelljs": "^0.10.0", "typescript": "^5.9.3" }, @@ -69,4 +72,4 @@ "javascript", "typescript" ] -} \ No newline at end of file +} diff --git a/src/classes/adapter.ts b/src/classes/adapter.ts index cbd262ff..1aa2c849 100644 --- a/src/classes/adapter.ts +++ b/src/classes/adapter.ts @@ -52,6 +52,12 @@ const ALLOWED_METHODS_WHEN_PAUSED = ADAPTER_PROPS_STUB.filter(v => !!v.allowedWh v => v.name ); +const getDisposedResult = (immediate: boolean): AdapterMethodResult => ({ + immediate, + success: false, + details: 'Adapter was disposed' +}); + const _has = (obj: unknown, prop: string): boolean => !!obj && typeof obj === 'object' && Object.prototype.hasOwnProperty.call(obj, prop); @@ -398,16 +404,20 @@ export class Adapter implements IAdapter { } dispose(): void { + this.disposed = true; if (this.relax$) { + this.relax$.set(getDisposedResult(false)); this.relax$.dispose(); } + this.source?.[AdapterPropName.isLoading$]?.set(false); if (this.externalContext) { this.resetContext(); } - Object.getOwnPropertyNames(this).forEach(prop => { - delete (this as Record)[prop]; - }); - this.disposed = true; + Object.getOwnPropertyNames(this) + .filter(prop => prop !== 'disposed') + .forEach(prop => { + delete (this as Record)[prop]; + }); } resetContext(): void { @@ -569,7 +579,7 @@ export class Adapter implements IAdapter { reloadId: string ): Promise { const runCallback = () => - typeof callback === 'function' && reloadId === this.reloadId && callback(); + !this.disposed && typeof callback === 'function' && reloadId === this.reloadId && callback(); if (!this.isLoading) { runCallback(); } @@ -584,11 +594,7 @@ export class Adapter implements IAdapter { }); }).then(immediate => { if (this.disposed) { - return { - immediate, - success: false, - details: 'Adapter was disposed' - }; + return getDisposedResult(immediate); } const success = reloadId === this.reloadId; this.logger?.log?.(() => diff --git a/src/classes/adapter/props.ts b/src/classes/adapter/props.ts index d53383ef..f859228b 100644 --- a/src/classes/adapter/props.ts +++ b/src/classes/adapter/props.ts @@ -274,17 +274,17 @@ export const getDefaultAdapterProps = (): IAdapterProp[] => [ { type: Type.Reactive, name: Name.init$, - value: new Reactive() + value: new Reactive(false) }, { type: Type.Reactive, name: Name.isLoading$, - value: new Reactive() + value: new Reactive(false) }, { type: Type.Reactive, name: Name.loopPending$, - value: new Reactive() + value: new Reactive(false) }, { type: Type.Reactive, @@ -301,17 +301,17 @@ export const getDefaultAdapterProps = (): IAdapterProp[] => [ { type: Type.Reactive, name: Name.bof$, - value: new Reactive() + value: new Reactive(false) }, { type: Type.Reactive, name: Name.eof$, - value: new Reactive() + value: new Reactive(false) }, { type: Type.Reactive, name: Name.paused$, - value: new Reactive() + value: new Reactive(false) } ]; diff --git a/src/classes/buffer/defaultSize.ts b/src/classes/buffer/defaultSize.ts index 3b9d74fe..5fd3d980 100644 --- a/src/classes/buffer/defaultSize.ts +++ b/src/classes/buffer/defaultSize.ts @@ -85,7 +85,6 @@ export class DefaultSize { private recalculateFrequentSize(): void { const { oldItems, newItems, removed } = this.recalculation; - const oldFrequentSizeCount = this.sizeMap.get(this.frequentSize); if (newItems.length) { newItems.forEach(({ size }) => this.sizeMap.set(size, (this.sizeMap.get(size) || 0) + 1)); } @@ -105,7 +104,7 @@ export class DefaultSize { const sorted = [...this.sizeMap.entries()].sort((a, b) => b[1] - a[1]); const mostFrequentCount = sorted[0][1]; const listEqual = sorted.filter(i => i[1] === mostFrequentCount); - if (listEqual.length > 1 && listEqual.find(i => i[0] === oldFrequentSizeCount)) { + if (listEqual.length > 1 && listEqual.some(([size]) => size === this.frequentSize)) { // if there are more than 1 most frequent sizes, but the old one is present return; } diff --git a/src/classes/reactive.ts b/src/classes/reactive.ts index 75a0c6ab..1993b903 100644 --- a/src/classes/reactive.ts +++ b/src/classes/reactive.ts @@ -62,10 +62,19 @@ export class Reactive { } once(func: On): Off { - const off = this.on(v => { + let emitted = false; + let off: Off = () => null; + off = this.on(v => { + if (emitted) { + return; + } + emitted = true; off(); func(v); }); + if (emitted) { + off(); + } return off; } diff --git a/src/inputs/validation.ts b/src/inputs/validation.ts index 16d45a25..13796347 100644 --- a/src/inputs/validation.ts +++ b/src/inputs/validation.ts @@ -53,15 +53,15 @@ const onInteger = (value: unknown): ValidatedValue => { }; const onIntegerUnlimited = (value: unknown): ValidatedValue => { - let parsedValue = value; + let parsedValue: number; const errors = []; - value = getNumber(value); - if (!Number.isFinite(value)) { - parsedValue = value; + const numericValue = getNumber(value); + if (!Number.isFinite(numericValue)) { + parsedValue = numericValue; } else { - parsedValue = parseInt(String(value), 10); + parsedValue = parseInt(String(numericValue), 10); } - if (value !== parsedValue) { + if (numericValue !== parsedValue) { errors.push(ValidatorType.integerUnlimited); } return { value: parsedValue, isSet: true, isValid: !errors.length, errors }; diff --git a/src/processes/fetch.ts b/src/processes/fetch.ts index 4171e661..4b467af7 100644 --- a/src/processes/fetch.ts +++ b/src/processes/fetch.ts @@ -74,18 +74,22 @@ export default class Fetch extends BaseProcessFactory(CommonProcess.fetch) { const { index, count } = scroller.state.fetch; let immediateData, immediateError; + let immediateDataReceived = false; + let immediateErrorReceived = false; let resolve: (value: unknown) => void, reject: (value: unknown) => void; const done = (data: unknown[]) => { if (!resolve) { immediateData = data || null; + immediateDataReceived = true; return; } resolve(data); }; const fail = (error: unknown) => { if (!reject) { - immediateError = error || null; + immediateError = error; + immediateErrorReceived = true; return; } reject(error); @@ -106,12 +110,12 @@ export default class Fetch extends BaseProcessFactory(CommonProcess.fetch) { } } - if (immediateData || immediateError) { + if (immediateDataReceived || immediateErrorReceived) { // callback case or immediate observable return { - data: immediateError ? null : immediateData || [], + data: immediateErrorReceived ? null : immediateData || [], error: immediateError, - isError: !!immediateError + isError: immediateErrorReceived }; } diff --git a/src/processes/preFetch.ts b/src/processes/preFetch.ts index 446bab88..0ee5e346 100644 --- a/src/processes/preFetch.ts +++ b/src/processes/preFetch.ts @@ -131,7 +131,6 @@ export default class PreFetch extends BaseProcessFactory(CommonProcess.preFetch) } else { let index = first.indexBuffer; let position = first.position; - lastIndex = index; while (true) { lastIndex = index; const size = buffer.getSizeByIndex(index); diff --git a/tests/e2e/fixture/Adapter.ts b/tests/e2e/fixture/Adapter.ts deleted file mode 100644 index 01c7ab6f..00000000 --- a/tests/e2e/fixture/Adapter.ts +++ /dev/null @@ -1,287 +0,0 @@ -import { Page } from '@playwright/test'; - -/** - * Adapter accessor class - provides access to adapter methods in browser context - */ -export class Adapter { - constructor(private page: Page) {} - - /** - * Wait for the scroller to relax (no pending operations) - */ - async relax() { - return await this.page.evaluate(() => { - const ds = window.__vscroll__.datasource; - if (!ds.adapter) { - throw new Error( - 'Adapter not available. Set useAdapter: true in config.' - ); - } - return ds.adapter.relax(); - }); - } - - /** - * Append items to the end of the dataset - */ - async append(options) { - return await this.page.evaluate(opts => { - const ds = window.__vscroll__.datasource; - if (!ds.adapter) { - throw new Error( - 'Adapter not available. Set useAdapter: true in config.' - ); - } - return ds.adapter.append(opts); - }, options); - } - - /** - * Prepend items to the beginning of the dataset - */ - async prepend(options) { - return await this.page.evaluate(opts => { - const ds = window.__vscroll__.datasource; - if (!ds.adapter) { - throw new Error( - 'Adapter not available. Set useAdapter: true in config.' - ); - } - return ds.adapter.prepend(opts); - }, options); - } - - /** - * Reload the dataset - */ - async reload(reloadIndex) { - return await this.page.evaluate(index => { - const ds = window.__vscroll__.datasource; - if (!ds.adapter) { - throw new Error( - 'Adapter not available. Set useAdapter: true in config.' - ); - } - return ds.adapter.reload(index); - }, reloadIndex); - } - - /** - * Reset the scroller to initial state - */ - async reset(options) { - return await this.page.evaluate(opts => { - const ds = window.__vscroll__.datasource; - if (!ds.adapter) { - throw new Error( - 'Adapter not available. Set useAdapter: true in config.' - ); - } - return ds.adapter.reset(opts); - }, options); - } - - /** - * Remove items from the dataset - */ - async remove(options) { - const predicateStr = options.predicate.toString(); - return await this.page.evaluate( - ({ predicateStr, increase }) => { - const ds = window.__vscroll__.datasource; - if (!ds.adapter) { - throw new Error( - 'Adapter not available. Set useAdapter: true in config.' - ); - } - const predicate = eval(`(${predicateStr})`); - return ds.adapter.remove({ predicate, increase }); - }, - { predicateStr, increase: options.increase } - ); - } - - /** - * Insert items at a specific position - */ - async insert(options) { - return await this.page.evaluate(opts => { - const ds = window.__vscroll__.datasource; - if (!ds.adapter) { - throw new Error( - 'Adapter not available. Set useAdapter: true in config.' - ); - } - return ds.adapter.insert(opts); - }, options); - } - - /** - * Replace items in the dataset - */ - async replace(options) { - const predicateStr = options.predicate.toString(); - return await this.page.evaluate( - ({ predicateStr, items }) => { - const ds = window.__vscroll__.datasource; - if (!ds.adapter) { - throw new Error( - 'Adapter not available. Set useAdapter: true in config.' - ); - } - const predicate = eval(`(${predicateStr})`); - return ds.adapter.replace({ predicate, items }); - }, - { predicateStr, items: options.items } - ); - } - - /** - * Update items in the dataset - */ - async update(options) { - const predicateStr = options.predicate.toString(); - return await this.page.evaluate(predicateStr => { - const ds = window.__vscroll__.datasource; - if (!ds.adapter) { - throw new Error( - 'Adapter not available. Set useAdapter: true in config.' - ); - } - const predicate = eval(`(${predicateStr})`); - return ds.adapter.update({ predicate }); - }, predicateStr); - } - - /** - * Clip the dataset (remove items outside visible range) - */ - async clip(options) { - return await this.page.evaluate(opts => { - const ds = window.__vscroll__.datasource; - if (!ds.adapter) { - throw new Error( - 'Adapter not available. Set useAdapter: true in config.' - ); - } - return ds.adapter.clip(opts); - }, options); - } - - /** - * Check the dataset consistency - */ - async check() { - return await this.page.evaluate(() => { - const ds = window.__vscroll__.datasource; - if (!ds.adapter) { - throw new Error( - 'Adapter not available. Set useAdapter: true in config.' - ); - } - return ds.adapter.check(); - }); - } - - /** - * Fix scroll position - */ - async fix(options) { - return await this.page.evaluate(opts => { - const ds = window.__vscroll__.datasource; - if (!ds.adapter) { - throw new Error( - 'Adapter not available. Set useAdapter: true in config.' - ); - } - return ds.adapter.fix(opts); - }, options); - } - - /** - * Get adapter properties (as getters to match IAdapter interface) - */ - get isLoading() { - return this.page.evaluate(() => { - const ds = window.__vscroll__.datasource; - if (!ds.adapter) { - throw new Error( - 'Adapter not available. Set useAdapter: true in config.' - ); - } - return ds.adapter.isLoading; - }); - } - - get bof() { - return this.page.evaluate(() => { - const ds = window.__vscroll__.datasource; - if (!ds.adapter) { - throw new Error( - 'Adapter not available. Set useAdapter: true in config.' - ); - } - return ds.adapter.bof; - }); - } - - get eof() { - return this.page.evaluate(() => { - const ds = window.__vscroll__.datasource; - if (!ds.adapter) { - throw new Error( - 'Adapter not available. Set useAdapter: true in config.' - ); - } - return ds.adapter.eof; - }); - } - - get itemsCount() { - return this.page.evaluate(() => { - const ds = window.__vscroll__.datasource; - if (!ds.adapter) { - throw new Error( - 'Adapter not available. Set useAdapter: true in config.' - ); - } - return ds.adapter.itemsCount; - }); - } - - get firstVisible() { - return this.page.evaluate(() => { - const ds = window.__vscroll__.datasource; - if (!ds.adapter) { - throw new Error( - 'Adapter not available. Set useAdapter: true in config.' - ); - } - return ds.adapter.firstVisible; - }); - } - - get lastVisible() { - return this.page.evaluate(() => { - const ds = window.__vscroll__.datasource; - if (!ds.adapter) { - throw new Error( - 'Adapter not available. Set useAdapter: true in config.' - ); - } - return ds.adapter.lastVisible; - }); - } - - get bufferInfo() { - return this.page.evaluate(() => { - const ds = window.__vscroll__.datasource; - if (!ds.adapter) { - throw new Error( - 'Adapter not available. Set useAdapter: true in config.' - ); - } - return ds.adapter.bufferInfo; - }); - } -} diff --git a/tests/e2e/fixture/Scroller.ts b/tests/e2e/fixture/Scroller.ts deleted file mode 100644 index 7fd61cdf..00000000 --- a/tests/e2e/fixture/Scroller.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { Page } from '../types'; -import { Direction as DirectionEnum } from '../../../src/inputs/common'; - -/** - * Direction enum (inline, no import from vscroll source) - * This avoids TypeScript compilation issues when loading e2e tests - */ -export const Direction = { - forward: 'forward', - backward: 'backward' -} as const; - -export type DirectionType = (typeof Direction)[keyof typeof Direction]; - -/** - * Scroller accessor class - provides access to scroller properties in browser context - */ -export class Scroller { - constructor(private page: Page) {} - - get settings() { - const page = this.page; - return { - get startIndex(): Promise { - return page.evaluate(() => { - return window.__vscroll__.workflow.scroller.settings.startIndex; - }); - }, - get bufferSize(): Promise { - return page.evaluate(() => { - return window.__vscroll__.workflow.scroller.settings.bufferSize; - }); - }, - get padding(): Promise { - return page.evaluate(() => { - return window.__vscroll__.workflow.scroller.settings.padding; - }); - }, - get horizontal(): Promise { - return page.evaluate(() => { - return window.__vscroll__.workflow.scroller.settings.horizontal; - }); - } - }; - } - - get buffer() { - const page = this.page; - return { - get size(): Promise { - return page.evaluate(() => { - return window.__vscroll__.workflow.scroller.buffer.size; - }); - }, - get defaultSize(): Promise { - return page.evaluate(() => { - return window.__vscroll__.workflow.scroller.buffer.defaultSize; - }); - }, - getEdgeVisibleItem: async (direction: DirectionType) => { - return await page.evaluate(dir => { - const workflow = window.__vscroll__.workflow; - const item = workflow.scroller.buffer.getEdgeVisibleItem( - dir as DirectionEnum - ); - return item ? { $index: item.$index, data: item.data } : null; - }, direction); - } - }; - } - - get viewport() { - const page = this.page; - return { - getSize: async () => { - return await page.evaluate(() => { - return window.__vscroll__.workflow.scroller.viewport.getSize(); - }); - }, - getScrollableSize: async () => { - return await page.evaluate(() => { - return window.__vscroll__.workflow.scroller.viewport.getScrollableSize(); - }); - }, - get scrollPosition(): Promise { - return page.evaluate(() => { - return window.__vscroll__.workflow.scroller.viewport.scrollPosition; - }); - }, - paddings: { - [Direction.forward]: { - get size(): Promise { - return page.evaluate(() => { - return window.__vscroll__.workflow.scroller.viewport.paddings - .forward.size; - }); - } - }, - [Direction.backward]: { - get size(): Promise { - return page.evaluate(() => { - return window.__vscroll__.workflow.scroller.viewport.paddings - .backward.size; - }); - } - } - } - }; - } -} diff --git a/tests/e2e/fixture/VScrollFixture.ts b/tests/e2e/fixture/VScrollFixture.ts deleted file mode 100644 index faa594be..00000000 --- a/tests/e2e/fixture/VScrollFixture.ts +++ /dev/null @@ -1,445 +0,0 @@ -import * as path from 'path'; -import { Scroller, Direction, type DirectionType } from './Scroller.js'; -import { Adapter } from './Adapter.js'; -import { VScrollFixtureConfig, Page, Item } from '../types'; - -export { Direction, type DirectionType }; - -export class VScrollFixture { - page: Page; - private config: VScrollFixtureConfig; - readonly scroller: Scroller; - readonly adapter: Adapter; - - constructor(page: Page, config: VScrollFixtureConfig) { - this.page = page; - this.config = config; - this.scroller = new Scroller(page); - this.adapter = new Adapter(page); - } - - static async create( - page: Page, - config: VScrollFixtureConfig - ): Promise { - const fixture = new VScrollFixture(page, config); - await fixture.mount(); - return fixture; - } - - /** - * Mount vscroll in browser (POC pattern) - */ - async mount(): Promise { - const page = this.page; - - // 1. Go to blank page - await page.goto('about:blank'); - - // 2. Load vscroll UMD bundle - const vscrollPath = path.join( - process.cwd(), - '..', - 'dist', - 'bundles', - 'vscroll.umd.js' - ); - await page.addScriptTag({ path: vscrollPath }); - - // 3. Add global styles FIRST (separate evaluate call to ensure styles are processed) - const templateSettings = this.config.templateSettings || {}; - if (templateSettings.horizontal || templateSettings.itemHeight) { - await page.addStyleTag({ - content: templateSettings.horizontal - ? ` - #viewport { - white-space: nowrap; - } - #viewport div { - display: inline-block; - } - #vscroll [data-sid] { - width: ${templateSettings.itemWidth || 100}px; - overflow-x: hidden; - } - ` - : ` - #vscroll [data-sid] { - height: ${templateSettings.itemHeight}px; - overflow-y: hidden; - } - ` - }); - } - - // 4. Create viewport and vscroll element (after styles are loaded) - await page.evaluate(settings => { - // Handle windowViewport mode (use document.body as viewport) - const useWindowViewport = - settings.noViewportClass && settings.viewportHeight === 0; - - // Create header if specified - if (settings.headerHeight) { - const header = document.createElement('div'); - header.id = 'header'; - header.style.height = `${settings.headerHeight}px`; - header.style.width = '100%'; - header.style.backgroundColor = 'yellow'; - document.body.appendChild(header); - } - - const viewport = document.createElement('div'); - viewport.id = 'viewport'; - - if (useWindowViewport) { - // For windowViewport: use body as viewport, no fixed dimensions - document.body.style.margin = '0'; - document.body.style.padding = '0'; - document.body.style.overflow = settings.horizontal ? 'hidden' : 'auto'; - } else if (settings.horizontal) { - viewport.style.width = `${settings.viewportWidth || 300}px`; - viewport.style.height = '100%'; - viewport.style.overflowX = 'scroll'; // Always scroll, not auto - viewport.style.overflowY = 'hidden'; - } else { - viewport.style.height = `${settings.viewportHeight || 200}px`; - viewport.style.width = '100%'; - viewport.style.overflowY = 'scroll'; // Always scroll, not auto - viewport.style.overflowX = 'hidden'; - } - - document.body.appendChild(viewport); - - // Create vscroll element with padding elements - const vscrollEl = document.createElement('div'); - vscrollEl.id = 'vscroll'; - - const backwardPadding = document.createElement('div'); - backwardPadding.setAttribute('data-padding-backward', ''); - vscrollEl.appendChild(backwardPadding); - - const forwardPadding = document.createElement('div'); - forwardPadding.setAttribute('data-padding-forward', ''); - vscrollEl.appendChild(forwardPadding); - - viewport.appendChild(vscrollEl); - }, templateSettings); - - // 5. Initialize vscroll workflow - await this.initializeWorkflow(); - } - - /** - * Initialize vscroll workflow in browser - * Supports two patterns: - * 1. Plain object datasource (simple tests) - * 2. Datasource class with adapter (adapter tests) - */ - private async initializeWorkflow(): Promise { - const { datasource, templateFn, noAdapter } = this.config; - - // Serialize functions and config - const datasourceGetStr = datasource.get ? datasource.get.toString() : null; - const datasourceSettings = datasource.settings || {}; - // Add default devSettings for all e2e tests: - // debug enabled, colors disabled, immediateLog disabled (to store logs in logger for retrieval on test failure) - const datasourceDevSettings = { - debug: true, - logProcessRun: false, - immediateLog: true, - logColor: false, - ...(datasource.devSettings || {}) - }; - const templateFnStr = templateFn - ? templateFn.toString() - : '(item) => `
${item.$index}: ${item.data.text || ""}
`'; - - await this.page.evaluate( - ({ - datasourceGetStr, - datasourceSettings, - datasourceDevSettings, - templateFnStr, - noAdapter - }) => { - const VScroll = window.VScroll; - - // Parse configs - const datasourceGet = eval(`(${datasourceGetStr})`); - const templateFn = eval(`(${templateFnStr})`); - - // Renderer - const processItems = ( - newItems: Item[], - oldItems: Item[] - ) => { - // Remove elements not present - oldItems - .filter(item => !newItems.includes(item)) - .forEach(item => item.element.remove()); - - // Create and insert new elements - const elements = []; - const vscrollEl = document.getElementById('vscroll')!; - let beforeElement = vscrollEl.querySelector('[data-padding-forward]'); - - for (let i = newItems.length - 1; i >= 0; i--) { - if (oldItems.includes(newItems[i])) { - if (!elements.length) { - beforeElement = newItems[i].element; - continue; - } else { - break; - } - } - - // Create element - const item = newItems[i]; - const element = document.createElement('div'); - element.setAttribute('data-sid', String(item.$index)); - - if (item.invisible) { - element.style.position = 'fixed'; - element.style.top = '-99px'; - } - - element.innerHTML = templateFn(item); - - item.element = element; - elements.unshift(element); - } - - elements.forEach(elt => - beforeElement!.insertAdjacentElement('beforebegin', elt) - ); - }; - - // Create datasource (plain object or Datasource class) - let datasourceInstance; - - if (noAdapter) { - // Pattern 1: Plain object (no adapter) - datasourceInstance = { - get: datasourceGet, - settings: datasourceSettings, - devSettings: datasourceDevSettings - }; - } else { - // Pattern 2: Create Datasource class with adapter support - const Datasource = VScroll.makeDatasource(); - datasourceInstance = new Datasource({ - get: datasourceGet, - settings: datasourceSettings, - devSettings: datasourceDevSettings - }); - } - - // Expose for testing - window.__vscroll__ = { - workflow: undefined, // Will be set by createWorkflowFactory() - makeScroller: undefined, - datasource: datasourceInstance, - Direction: { forward: 'forward', backward: 'backward' } - }; - - // Factory to create workflow factory with fresh oldItems closure - const workflowFactory = () => { - let oldItems: Item[] = []; - - const workflowParams = { - consumer: { name: 'vscroll-e2e', version: '1.0.0' }, - element: document.getElementById('vscroll'), - datasource: datasourceInstance, - run: (newItems: unknown[]) => { - const items = newItems as Item[]; - if (!items.length && !oldItems.length) { - return; - } - processItems(items, oldItems); - oldItems = items; - } - }; - - return () => new VScroll.Workflow(workflowParams); - }; - - // Creates Workflow with fresh closure and updates window.__vscroll__.workflow - const makeScroller = () => - (window.__vscroll__.workflow = workflowFactory()()); - window.__vscroll__.makeScroller = makeScroller; - }, - { - datasourceGetStr, - datasourceSettings, - datasourceDevSettings, - templateFnStr, - noAdapter: !!noAdapter - } - ); - - // Run onBefore hook - // Workflow is ready but not initialized - // Datasource is instantiated, so subscriptions can be made - if (this.config.onBefore) { - await this.config.onBefore(this.page); - } - - // Create initial workflow - await this.page.evaluate(() => window.__vscroll__.makeScroller?.()); - } - - /** - * Access to workflow in browser - */ - get workflow() { - const page = this.page; - return { - get cyclesDone(): Promise { - return page.evaluate(() => window.__vscroll__.workflow.cyclesDone); - }, - get innerLoopCount(): Promise { - return page.evaluate( - () => window.__vscroll__.workflow.scroller.state.cycle.innerLoop.total - ); - } - }; - } - - /** - * Scroll to a specific position and wait for Scroller to relax. - * @param position - The position to scroll to - * @param options.noRelax - If true, no wait for relaxation - */ - async scrollTo( - position: number, - options: { noRelax?: boolean } = {} - ): Promise { - return this.page.evaluate( - async ({ position, options }) => { - const workflow = window.__vscroll__.workflow; - const viewport = workflow.scroller.viewport; - const adapter = workflow.scroller.datasource.adapter; - const cyclesDone = workflow.cyclesDone; - const positionBefore = viewport.scrollPosition; - await adapter.fix({ scrollPosition: position }); - if (options?.noRelax || viewport.scrollPosition === positionBefore) { - return; - } - if (!adapter.isLoading) { - // The new workflow cycle should run and complete - while (workflow.cyclesDone === cyclesDone) { - await new Promise(resolve => setTimeout(resolve)); - } - } - }, - { position, options } - ); - } - - /** - * Scroll to max position - */ - async scrollMax(options?: { noRelax?: boolean }): Promise { - return this.scrollTo(Infinity, options); - } - - /** - * Scroll to min position - */ - async scrollMin(options?: { noRelax?: boolean }): Promise { - return this.scrollTo(0, options); - } - - /** - * Get all rendered elements - */ - async getElements(): Promise { - return (await this.page.$$('[data-sid]')) as unknown as HTMLElement[]; - } - - /** - * Get element index - */ - async getElementIndex(element: HTMLElement): Promise { - return await this.page.evaluate(el => { - return parseInt(el.getAttribute('data-sid') || '0', 10); - }, element); - } - - /** - * Check element content by index - */ - async checkElementContentByIndex(index: number): Promise { - return await this.page.evaluate(idx => { - const elements = Array.from(document.querySelectorAll('[data-sid]')); - const element = elements.find( - el => el.getAttribute('data-sid') === String(idx) - ); - return element - ? element.textContent?.includes(String(idx)) || false - : false; - }, index); - } - - /** - * Wait for the workflow cycle to complete - * @param cycle - The cycle to wait for, if not provided, wait for the current cycle to complete - * @returns A promise that resolves when the workflow has finished current or provided cycle - */ - async relaxNext(cycle: number): Promise { - return this.page.evaluate( - ({ cycle }: { cycle: number }) => - new Promise(resolve => { - const workflow = window.__vscroll__.workflow; - const ds = workflow.scroller.datasource; - const cycleToComplete = - typeof cycle === 'number' ? cycle - 1 : workflow.cyclesDone; - - // If already loading, we can wait for the adapter is relaxed - if (ds?.adapter?.isLoading) { - return ds.adapter.relax().then(() => resolve()); - } - - // Otherwise, wait for the given cycle to complete - const waitForCycle = async () => { - if (workflow.cyclesDone > cycleToComplete && ds?.adapter) { - await ds.adapter.relax(); - return resolve(); - } else { - requestAnimationFrame(waitForCycle); - } - }; - - waitForCycle(); - }), - { cycle } - ); - } - - /** - * Dispose - */ - async dispose(): Promise { - await this.page.evaluate(() => { - window.__vscroll__?.workflow?.dispose(); - document.querySelector('#viewport')?.remove(); - }); - } - - /** - * Recreate the scroller - * Disposes the current workflow, resets the viewport, and creates a new workflow - */ - async recreateScroller(): Promise { - await this.page.evaluate(() => { - window.__vscroll__.workflow.dispose(); - - // Reset vscroll element to initial state - const vscroll = document.getElementById('vscroll') as HTMLElement; - vscroll.innerHTML = - '
'; - - window.__vscroll__.makeScroller!(); - }); - } -} diff --git a/tests/e2e/fixture/after-each-logs.ts b/tests/e2e/fixture/after-each-logs.ts deleted file mode 100644 index cd6bed1a..00000000 --- a/tests/e2e/fixture/after-each-logs.ts +++ /dev/null @@ -1,62 +0,0 @@ -import type { Page, TestInfo } from '@playwright/test'; - -/** - * afterEachLogs handler that retrieves and prints vscroll debug logs when tests fail. - * - * With immediateLog: false, logs are stored in workflow.scroller.logger - * and can be retrieved from the browser context. - * - * Usage: - * ```typescript - * import { afterEachLogs } from '../fixture/after-each-logs.js'; - * - * test.afterEach(afterEachLogs); - * ``` - */ -export async function afterEachLogs( - { page }: { page: Page }, - testInfo: TestInfo -): Promise { - // Only process if test failed - if (testInfo.status !== testInfo.expectedStatus) { - // Get logs from vscroll logger - const logs = await page - .evaluate(() => { - const vscroll = window.__vscroll__; - if (!vscroll?.workflow?.scroller?.logger) { - return []; - } - - const logger = vscroll.workflow.scroller.logger; - // Access private logs array (stored when immediateLog: false) - const logArray = logger.getLogs() || []; - - // Format logs similar to console output - return logArray.map(args => { - return args - .map(arg => { - if (typeof arg === 'object' && arg !== null) { - return JSON.stringify(arg); - } - return String(arg); - }) - .join(' '); - }); - }) - .catch(() => [] as string[]); - - if (logs.length > 0) { - const testName = testInfo.titlePath.join(' › '); - console.log('\n' + '='.repeat(80)); - console.log(`VSCROLL DEBUG LOGS (test failed): ${testName}`); - console.log('='.repeat(80)); - logs.forEach(log => console.log(log)); - console.log('='.repeat(80) + '\n'); - - await testInfo.attach('vscroll-debug-logs', { - body: logs.join('\n'), - contentType: 'text/plain' - }); - } - } -} diff --git a/tests/e2e/fixture/create-fixture.ts b/tests/e2e/fixture/create-fixture.ts deleted file mode 100644 index db1b07fa..00000000 --- a/tests/e2e/fixture/create-fixture.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { VScrollFixture } from './VScrollFixture.js'; -import { Page, IDatasource, ITestConfig } from 'types/index.js'; - -type FixtureParams = { - page: Page; - config: ITestConfig; -}; - -export const createFixture = async ({ - page, - config -}: FixtureParams): Promise => { - const { templateSettings, templateFn } = config; - - const datasource: IDatasource = { - get: config.datasourceGet, - settings: config.datasourceSettings, - devSettings: config.datasourceDevSettings - }; - - const fixture = await VScrollFixture.create(page, { - datasource, - templateSettings, - templateFn: - templateFn || - ((item: { $index: number; data: { id: number; text: string } }) => - `
${item.$index}: ${item.data.text}
`), - noAdapter: config.noAdapter, - onBefore: config.onBefore - }); - - if (!config.noRelaxOnStart) { - // Wait for initial workflow cycle to complete - await fixture.relaxNext(1); - } - - // // Debug: log actual element dimensions - // const debugInfo = await page.evaluate(() => { - // const viewport = document.getElementById('viewport')!; - // const vscroll = document.getElementById('vscroll')!; - // const items = Array.from(vscroll.querySelectorAll('[data-sid]')); - // const bwdPadding = vscroll.querySelector('[data-padding-backward]') as HTMLElement; - // const fwdPadding = vscroll.querySelector('[data-padding-forward]') as HTMLElement; - // const horizontal = (window as any).__vscroll__.workflow.scroller.settings.horizontal; - // return { - // viewportDimensions: { - // clientWidth: viewport.clientWidth, - // clientHeight: viewport.clientHeight, - // scrollWidth: viewport.scrollWidth, - // scrollHeight: viewport.scrollHeight - // }, - // vscrollDimensions: { - // clientWidth: vscroll.clientWidth, - // clientHeight: vscroll.clientHeight, - // scrollWidth: vscroll.scrollWidth, - // scrollHeight: vscroll.scrollHeight - // }, - // itemCount: items.length, - // firstItemDimensions: items[0] ? { - // width: (items[0] as HTMLElement).clientWidth, - // height: (items[0] as HTMLElement).clientHeight, - // display: getComputedStyle(items[0] as HTMLElement).display, - // whiteSpace: getComputedStyle(vscroll).whiteSpace - // } : null, - // bwdPaddingDimensions: { - // width: bwdPadding.clientWidth, - // height: bwdPadding.clientHeight, - // display: getComputedStyle(bwdPadding).display, - // computedWidth: getComputedStyle(bwdPadding).width - // }, - // fwdPaddingDimensions: { - // width: fwdPadding.clientWidth, - // height: fwdPadding.clientHeight, - // display: getComputedStyle(fwdPadding).display, - // computedWidth: getComputedStyle(fwdPadding).width - // }, - // horizontal - // }; - // }); - // console.log('[DEBUG] Initial dimensions:', JSON.stringify(debugInfo, null, 2)); - - return fixture; -}; diff --git a/tests/e2e/helpers/itemsCounter.ts b/tests/e2e/helpers/itemsCounter.ts deleted file mode 100644 index 7ef6b9bb..00000000 --- a/tests/e2e/helpers/itemsCounter.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { Direction, type DirectionType } from '../fixture/VScrollFixture.js'; - -/** - * Helper class to track item counts in forward and backward directions - */ -export class ItemsDirCounter { - count: number; - index: number; - padding: number; - paddingShift?: number; - size: number; - - constructor(count = 0, padding = 0) { - this.count = count; - this.padding = padding; - this.paddingShift = 0; - this.index = NaN; - this.size = NaN; - } -} - -/** - * Helper class for tracking expected buffer state during scroll tests - */ -export class ItemsCounter { - direction: DirectionType | null; - backward: ItemsDirCounter; - forward: ItemsDirCounter; - average: number; - - get total(): number { - return this.forward.index - this.backward.index + 1; - } - - get paddings(): number { - return this.forward.padding + this.backward.padding; - } - - constructor(direction?: DirectionType) { - this.direction = direction || null; - this.forward = new ItemsDirCounter(); - this.backward = new ItemsDirCounter(); - this.average = NaN; - } - - get(token: DirectionType): ItemsDirCounter { - return token === Direction.backward ? this.backward : this.forward; - } - - set(token: DirectionType, value: Partial): void { - if (token === Direction.backward) { - Object.assign(this.backward, value); - } else { - Object.assign(this.forward, value); - } - } -} diff --git a/tests/e2e/playwright.config.ts b/tests/e2e/playwright.config.ts deleted file mode 100644 index 79bdd5d1..00000000 --- a/tests/e2e/playwright.config.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { defineConfig, devices } from '@playwright/test'; - -export default defineConfig({ - testDir: './specs', - outputDir: './test-results', - fullyParallel: true, - forbidOnly: !!process.env.CI, - retries: process.env.CI ? 2 : 0, - workers: 1, - reporter: [ - ['list'], // Detailed console output - ['html', { outputFolder: './playwright-report', open: 'never' }] - ], - timeout: 5000, // 5 seconds per test - - use: { - baseURL: 'about:blank', - trace: 'on-first-retry', - screenshot: 'only-on-failure' - // headless: true, - // slowMo: 500, - }, - - projects: [ - { - name: 'chromium', - use: { - ...devices['Desktop Chrome'], - viewport: { width: 1024, height: 517 } - } - } - ] -}); diff --git a/tests/e2e/scaffolding/TestHost.ts b/tests/e2e/scaffolding/TestHost.ts new file mode 100644 index 00000000..7791ad14 --- /dev/null +++ b/tests/e2e/scaffolding/TestHost.ts @@ -0,0 +1,534 @@ +import { Direction, Workflow, makeDatasource } from './vscroll'; +import type { + IDatasource, + IDatasourceConstructed, + Item +} from './vscroll'; +import { getDatasource, TestDatasource } from './datasources'; +import { Expectations } from './expectations'; +import type { + DatasourceProcessor, + TemplateSettings, + TestConfig, + TestItem +} from '../types'; + +const Datasource = makeDatasource(); + +// Static, per-item styles are injected once instead of being re-applied to every +// element on every render. Only the varying bits (size, content, visibility) +// stay in render(). +let stylesInjected = false; +const ensureStyles = (): void => { + if (stylesInjected) { + return; + } + stylesInjected = true; + const style = document.createElement('style'); + style.textContent = ` + [data-vscroll-test-root] [data-sid], + [data-vscroll-test-root] [data-sid] > .item { + box-sizing: border-box; margin: 0; padding: 0; + } + [data-vscroll-test-root] [data-sid] > .item { overflow: hidden; } + [data-vscroll-test-root][data-horizontal] [data-sid], + [data-vscroll-test-root][data-horizontal] [data-sid] > .item { + display: inline-block; + } + `; + document.head.appendChild(style); +}; + +const defaultTemplateSettings: Required< + Omit +> & { dynamicSize: string | null } = { + noViewportClass: false, + viewportHeight: 120, + viewportWidth: 200, + itemHeight: 20, + itemWidth: null, + horizontal: false, + dynamicSize: null, + viewportPadding: 0, + headerHeight: 0 +}; + +export class TestHost { + readonly root: HTMLElement; + readonly viewportElement: HTMLElement; + readonly contentElement: HTMLElement; + readonly datasource: IDatasource; + workflow: Workflow; + readonly shared: Record = {}; + readonly padding: Record number }>; + readonly expect: Expectations = new Expectations(this); + + private readonly settings: TemplateSettings; + private readonly windowViewport: boolean; + private readonly setProcessor?: ( + processor: DatasourceProcessor + ) => void; + private renderedItems: Item[] = []; + private disposed = false; + + get scroller(): Workflow['scroller'] { + return this.workflow.scroller; + } + + get adapter(): IDatasourceConstructed['adapter'] { + return this.scroller.datasource.adapter; + } + + get innerLoopCount(): number { + return this.scroller.state.cycle.innerLoop.total; + } + + get horizontal(): boolean { + return this.scroller.settings.horizontal; + } + + /** The active datasource, narrowed to a concrete test datasource type. */ + source = IDatasource>(): T { + return this.datasource as T; + } + + /** + * Read firstVisible/lastVisible once so the workflow starts tracking them. + * These are "wanted" properties: vscroll only computes and emits them after + * they have been accessed, so tests that assert on them must prime the + * tracking before the first cycle runs. + */ + trackVisibleItems(): void { + void this.adapter.firstVisible; + void this.adapter.lastVisible; + } + + constructor(config: TestConfig) { + this.windowViewport = !!config.datasourceSettings?.windowViewport; + this.settings = { + ...defaultTemplateSettings, + ...(config.templateSettings || {}) + } as TemplateSettings; + + const dom = this.createDom(); + this.root = dom.root; + this.viewportElement = dom.viewport; + this.contentElement = dom.content; + + const source: IDatasource = + config.datasource?.() ?? + (getDatasource() as unknown as IDatasource); + this.setProcessor = ( + source as Partial> + ).setProcessor?.bind(source); + const datasourceParams = { + ...source, + get: source.get.bind(source), + settings: { + ...(source.settings || {}), + ...(config.datasourceSettings || {}) + }, + devSettings: { + ...(source.devSettings || {}), + ...(config.datasourceDevSettings || {}) + } + }; + + if (source.adapter) { + source.settings = datasourceParams.settings; + source.devSettings = datasourceParams.devSettings; + } + this.datasource = config.noAdapter + ? datasourceParams + : source.adapter + ? (source as IDatasourceConstructed) + : new Datasource(datasourceParams); + + try { + this.workflow = this.createWorkflow(); + this.padding = { + [Direction.forward]: { + getSize: () => this.scroller.viewport.paddings[Direction.forward].size + }, + [Direction.backward]: { + getSize: () => + this.scroller.viewport.paddings[Direction.backward].size + } + }; + } catch (error) { + this.root.remove(); + throw error; + } + } + + private createWorkflow(): Workflow { + return new Workflow({ + consumer: { name: 'vscroll-browser-tests', version: '1.0.0' }, + element: this.contentElement, + datasource: this.datasource, + run: items => this.render(items) + }); + } + + private resetContent(): void { + this.renderedItems = []; + const backwardPadding = document.createElement('div'); + backwardPadding.dataset.paddingBackward = ''; + const forwardPadding = document.createElement('div'); + forwardPadding.dataset.paddingForward = ''; + if (this.settings.horizontal) { + backwardPadding.style.display = 'inline-block'; + forwardPadding.style.display = 'inline-block'; + } + this.contentElement.replaceChildren(backwardPadding, forwardPadding); + } + + private createDom(): { + root: HTMLElement; + viewport: HTMLElement; + content: HTMLElement; + } { + ensureStyles(); + const settings = this.settings; + const root = document.createElement('section'); + root.dataset.vscrollTestRoot = ''; + if (settings.horizontal) { + root.dataset.horizontal = ''; + } + root.style.cssText = + 'display:block;font:16px/20px sans-serif;margin:0;padding:0;'; + + if (settings.headerHeight) { + const header = document.createElement('div'); + header.dataset.testHeader = ''; + header.style.height = `${settings.headerHeight}px`; + root.appendChild(header); + } + + const viewport = document.createElement('div'); + viewport.dataset.testViewport = ''; + viewport.style.boxSizing = 'content-box'; + viewport.style.display = 'block'; + viewport.style.margin = '0'; + viewport.style.padding = `${settings.viewportPadding || 0}px`; + + if (this.windowViewport) { + viewport.style.width = '100%'; + viewport.style.overflow = 'visible'; + } else if (settings.horizontal) { + viewport.style.width = `${settings.viewportWidth || 200}px`; + viewport.style.height = `${settings.viewportHeight || 100}px`; + viewport.style.overflowX = 'scroll'; + viewport.style.overflowY = 'hidden'; + viewport.style.whiteSpace = 'nowrap'; + } else { + viewport.style.width = `${settings.viewportWidth || 200}px`; + viewport.style.height = `${settings.viewportHeight || 120}px`; + viewport.style.overflowX = 'hidden'; + viewport.style.overflowY = 'auto'; + } + + const content = document.createElement('div'); + content.dataset.testContent = ''; + content.style.margin = '0'; + content.style.padding = '0'; + if (settings.horizontal) { + content.style.display = 'inline-block'; + } + + const backwardPadding = document.createElement('div'); + backwardPadding.dataset.paddingBackward = ''; + const forwardPadding = document.createElement('div'); + forwardPadding.dataset.paddingForward = ''; + if (settings.horizontal) { + backwardPadding.style.display = 'inline-block'; + forwardPadding.style.display = 'inline-block'; + } + + content.append(backwardPadding, forwardPadding); + viewport.appendChild(content); + root.appendChild(viewport); + document.body.appendChild(root); + + return { root, viewport, content }; + } + + private render(items: Item[]): void { + const itemSet = new Set(items); + this.renderedItems + .filter(item => !itemSet.has(item)) + .forEach(item => item.element?.remove()); + + const forwardPadding = this.contentElement.querySelector( + '[data-padding-forward]' + ); + if (!forwardPadding) { + throw new Error('Forward padding element is missing'); + } + + const dynamicSize = this.settings.dynamicSize as keyof Data | null; + const horizontal = !!this.settings.horizontal; + const configuredSize = horizontal + ? this.settings.itemWidth + : this.settings.itemHeight; + + for (const item of items) { + const element = (item.element ??= document.createElement('div')); + element.dataset.sid = String(item.$index); + element.style.position = item.invisible ? 'fixed' : ''; + element.style.left = item.invisible ? '-99999px' : ''; + + let templateElement = element.firstElementChild as HTMLElement | null; + if (!templateElement) { + templateElement = document.createElement('div'); + templateElement.className = 'item'; + element.appendChild(templateElement); + } + + const itemSize = dynamicSize + ? Number(item.data[dynamicSize]) + : Number(configuredSize); + if (Number.isFinite(itemSize)) { + templateElement.style[horizontal ? 'width' : 'height'] = `${itemSize}px`; + } + + templateElement.innerHTML = `${item.$index}: ${item.data.text}`; + this.contentElement.insertBefore(element, forwardPadding); + } + + this.renderedItems = [...items]; + } + + getElements(): HTMLElement[] { + return Array.from(this.contentElement.querySelectorAll('[data-sid]')); + } + + getElement(index: number): HTMLElement | null { + return this.contentElement.querySelector(`[data-sid="${index}"]`); + } + + getElementIndex(element: HTMLElement): number { + return Number(element.dataset.sid); + } + + getElementText(index: number): string | null { + return this.getElement(index)?.innerText.trim() || null; + } + + checkElementContentByIndex(index: number): boolean { + return this.checkElementContent(index, index); + } + + checkElementContent(index: number, id: number): boolean { + return this.getElementText(index) === `${index}: item #${id}`; + } + + checkElementId(element: HTMLElement, index: number): boolean { + return element.dataset.sid === String(index); + } + + getViewportSize(): number { + return this.scroller.viewport.getSize(); + } + + getScrollableSize(): number { + return this.scroller.viewport.getScrollableSize(); + } + + getScrollPosition(): number { + return this.scroller.viewport.scrollPosition; + } + + scrollTo(position: number): void { + this.adapter.fix({ scrollPosition: position }); + } + + scrollMin(): void { + this.scrollTo(0); + } + + scrollMax(): void { + this.scrollTo(Infinity); + } + + waitNextCycle(): Promise { + return new Promise(resolve => this.workflow.cyclesDone$.once(resolve)); + } + + waitForCycles(target: number): Promise { + if (this.workflow.cyclesDone >= target) { + return Promise.resolve(); + } + return new Promise(resolve => { + const off = this.workflow.cyclesDone$.on(cyclesDone => { + if (cyclesDone >= target) { + off(); + resolve(); + } + }); + }); + } + + waitForAdapterInit(): Promise { + return new Promise(resolve => { + const off = this.adapter.init$.on(initialized => { + if (initialized) { + off(); + resolve(); + } + }); + }); + } + + async recreate(): Promise { + const initialized = this.waitForAdapterInit(); + this.workflow.dispose(); + this.resetContent(); + this.workflow = this.createWorkflow(); + await initialized; + await this.adapter.relax(); + } + + captureInnerLoops( + count: number, + snapshot: (loop: number) => T + ): Promise { + if (count <= 0) { + return Promise.resolve([]); + } + + return new Promise((resolve, reject) => { + const snapshots: T[] = []; + const innerLoop = this.scroller.state.cycle.innerLoop; + const off = innerLoop.busy.on(pending => { + if (pending) { + return; + } + try { + snapshots.push(snapshot(innerLoop.total)); + if (snapshots.length === count) { + off(); + resolve(snapshots); + } + } catch (error) { + off(); + reject(error); + } + }); + }); + } + + async relaxNext(): Promise { + await this.waitNextCycle(); + await this.adapter.relax(); + } + + private async runAndRelax( + action: () => void | Promise + ): Promise { + const cycleDone = this.waitNextCycle(); + await action(); + await cycleDone; + await this.adapter.relax(); + } + + scrollToRelax(position: number): Promise { + return this.runAndRelax(() => + this.adapter.fix({ scrollPosition: position }) + ); + } + + scrollMinRelax(): Promise { + return this.scrollToRelax(0); + } + + scrollMaxRelax(): Promise { + return this.scrollToRelax(Infinity); + } + + async scrollMinMaxRelax(): Promise { + if (this.getScrollPosition() === 0) { + await this.scrollMaxRelax(); + await this.scrollMinRelax(); + } else { + await this.scrollMinRelax(); + await this.scrollMaxRelax(); + } + } + + /** Scroll toward an edge, settling each step, until its BOF/EOF flag is set. */ + async reachEdge(direction: Direction, maxAttempts = 50): Promise { + const forward = direction === Direction.forward; + const reached = (): boolean => + forward ? this.scroller.buffer.eof.get() : this.scroller.buffer.bof.get(); + + for (let attempt = 0; attempt < maxAttempts; attempt++) { + await this.adapter.relax(); + if (reached()) { + return; + } + await (forward ? this.scrollMaxRelax() : this.scrollMinRelax()); + } + + throw new Error(`Unable to reach ${forward ? 'EOF' : 'BOF'}`); + } + + async scrollToIndexRelax(index: number, maxAttempts = 20): Promise { + for (let attempt = 0; attempt < maxAttempts; attempt++) { + const { firstIndex, lastIndex } = this.adapter.bufferInfo; + const buffered = index >= firstIndex && index <= lastIndex; + const edgeIndex = index > lastIndex ? lastIndex : firstIndex; + const position = this.getScrollPosition(); + + this.adapter.fix({ + scrollToItem: item => item.$index === (buffered ? index : edgeIndex) + }); + + if (position !== this.getScrollPosition()) { + await this.relaxNext(); + } + if (buffered) { + return; + } + } + + throw new Error(`Unable to scroll to index ${index}`); + } + + setDatasourceProcessor(processor: DatasourceProcessor): void { + if (!this.setProcessor) { + throw new Error('This test datasource does not support processing'); + } + this.setProcessor(processor); + } + + setItemProcessor( + itemUpdater: (item: { $index: number; data: Data }) => unknown + ): void { + this.setDatasourceProcessor(items => items.forEach(itemUpdater)); + } + + delay(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); + } + + waitForPaint(): Promise { + return new Promise(resolve => requestAnimationFrame(() => resolve())); + } + + dispose(): void { + if (this.disposed) { + return; + } + this.disposed = true; + this.workflow.dispose(); + const disposable = this.datasource as IDatasource & { + dispose?: () => void; + }; + disposable.dispose?.(); + this.root.remove(); + if (this.windowViewport) { + window.scrollTo(0, 0); + } + } +} diff --git a/tests/e2e/scaffolding/data.ts b/tests/e2e/scaffolding/data.ts new file mode 100644 index 00000000..4dc7ee6a --- /dev/null +++ b/tests/e2e/scaffolding/data.ts @@ -0,0 +1,25 @@ +import type { DatasourceGet } from './vscroll'; +import type { TestItem } from '../types'; + +export const makeItem = (id: number): TestItem => ({ id, text: `item #${id}` }); + +export const makeItems = (firstId: number, count: number): TestItem[] => + Array.from({ length: count }, (_, offset) => makeItem(firstId + offset)); + +// A datasource `get` that appends ' *' to each item's text. Reset specs use it +// to prove the workflow adopts a brand-new data-producing function. +export const starGet = (( + index: number, + count: number, + success: (items: unknown[]) => void +): void => { + success(makeItems(index, count).map(item => ({ ...item, text: `${item.text} *` }))); +}) as DatasourceGet; + +const minSize = 1; +const maxSize = 100; +const initialSize = 20; + +// Deterministic per-index size profile used by dynamic-size scenarios. +export const getDynamicSize = (index: number): number => + Math.max(minSize, Math.min(maxSize, initialSize + index)); diff --git a/tests/e2e/scaffolding/datasources.ts b/tests/e2e/scaffolding/datasources.ts new file mode 100644 index 00000000..f6cdac8d --- /dev/null +++ b/tests/e2e/scaffolding/datasources.ts @@ -0,0 +1,218 @@ +import { makeDatasource } from './vscroll'; +import type { BufferUpdater, DatasourceGet } from './vscroll'; +import { makeItem } from './data'; +import type { DatasourceProcessor, IndexedItem, TestItem } from '../types'; + +// A datasource that also lets a test inject a per-fetch processor, used to +// mutate item data/sizes between the fetch and the workflow (e.g. dynamic size). +export interface TestDatasource { + get: DatasourceGet; + setProcessor(processor: DatasourceProcessor): void; +} + +export interface DatasourceOptions { + min?: number; // inclusive lower bound; omit for unbounded + max?: number; // inclusive upper bound; omit for unbounded + delay?: number; // async delay in ms; omit/0 for synchronous delivery + size?: number; // per-item `size` field; omit to leave it out + mode?: 'callback' | 'promise' | 'observable'; // transport, default callback +} + +interface MutableState { + min: number; + max: number; + data: TestItem[]; + requests: Array<{ index: number; count: number }>; + processor?: DatasourceProcessor; +} + +const Datasource = makeDatasource(); + +export class MutableDatasource extends Datasource { + private readonly state: MutableState; + + constructor(min: number, max: number, firstId = min) { + const state = { min, max, data: [], requests: [] } as MutableState; + const read = (index: number, count: number): TestItem[] => { + state.requests.push({ index, count }); + const items: IndexedItem[] = []; + for (let current = index; current < index + count; current++) { + const data = state.data[current - state.min]; + if (data) { + items.push({ $index: current, data }); + } + } + state.processor?.(items, index, count, state.min, state.max); + return items.map(({ data }) => data); + }; + super({ get: (index, count, success) => success(read(index, count)) }); + this.state = state; + this.reset(min, max, firstId); + } + + reset(min: number, max: number, firstId = min): void { + this.state.min = min; + this.state.max = max; + this.state.data = Array.from({ length: max - min + 1 }, (_, offset) => + makeItem(firstId + offset) + ); + } + + setProcessor(processor: DatasourceProcessor): void { + this.state.processor = processor; + } + + insert( + items: TestItem[], + index: number, + before: boolean, + decrease = false + ): void { + const offset = index - this.state.min; + if (offset >= 0 && offset < this.state.data.length) { + this.state.data.splice(offset + (before ? 0 : 1), 0, ...items); + } + this.state.min -= decrease ? items.length : 0; + this.state.max += decrease ? 0 : items.length; + } + + remove(indexes: number[], increase = false): void { + const indexSet = new Set(indexes); + const min = this.state.min; + let removed = 0; + this.state.data = this.state.data.filter((_item, offset) => { + const remove = indexSet.has(min + offset); + removed += Number(remove); + return !remove; + }); + this.state.min += increase ? removed : 0; + this.state.max -= increase ? 0 : removed; + } + + replace(indexes: number[], items: TestItem[], fixRight = false): void { + const sorted = [...indexes].sort((a, b) => a - b); + const first = sorted[0]; + const last = sorted[sorted.length - 1]; + const removed = last - first + 1; + const delta = items.length - removed; + + this.state.data.splice(first - this.state.min, removed, ...items); + this.state.min -= fixRight ? delta : 0; + this.state.max += fixRight ? 0 : delta; + } + + update(predicate: BufferUpdater, fixRight = false): void { + const min = this.state.min; + const data = this.state.data.flatMap((item, offset) => { + const result = predicate({ uid: 0, $index: min + offset, data: item }); + return result === true + ? [item] + : Array.isArray(result) + ? (result as TestItem[]) + : []; + }); + const delta = data.length - this.state.data.length; + + this.state.data = data; + this.state.min -= fixRight ? delta : 0; + this.state.max += fixRight ? 0 : delta; + } + + setSizes(getSize: (index: number) => number): void { + this.state.data.forEach( + (item, offset) => (item.size = getSize(this.state.min + offset)) + ); + } + + setSize(index: number, size: number): void { + const item = this.state.data[index - this.state.min]; + if (item) { + item.size = size; + } + } + + clearRequests(): void { + this.state.requests.length = 0; + } + + get requests(): ReadonlyArray<{ index: number; count: number }> { + return this.state.requests; + } +} + +export const getDatasource = ( + options: DatasourceOptions = {} +): TestDatasource => { + const { min, max, delay = 0, size, mode = 'callback' } = options; + let processor: DatasourceProcessor | undefined; + + const read = (index: number, count: number): TestItem[] => { + const items: IndexedItem[] = []; + for (let i = index; i < index + count; i++) { + if ((min === undefined || i >= min) && (max === undefined || i <= max)) { + items.push({ + $index: i, + data: { ...makeItem(i), ...(size === undefined ? {} : { size }) } + }); + } + } + processor?.(items, index, count, min, max); + return items.map(({ data }) => data); + }; + + const get = ( + index: number, + count: number, + success?: (data: TestItem[]) => void + ): unknown => { + if (mode === 'promise') { + return new Promise(resolve => + delay + ? setTimeout(() => resolve(read(index, count)), delay) + : resolve(read(index, count)) + ); + } + if (mode === 'observable') { + return { + subscribe( + next: (data: TestItem[]) => void, + _error: (error: unknown) => void, + complete: () => void + ) { + let active = true; + const emit = () => { + if (!active) { + return; + } + next(read(index, count)); + complete(); + }; + const timer = delay ? setTimeout(emit, delay) : undefined; + if (!delay) { + emit(); + } + return { + unsubscribe: () => { + active = false; + if (timer) { + clearTimeout(timer); + } + } + }; + } + }; + } + if (delay) { + setTimeout(() => success?.(read(index, count)), delay); + } else { + success?.(read(index, count)); + } + }; + + return { + get: get as DatasourceGet, + setProcessor: processorFn => { + processor = processorFn; + } + }; +}; diff --git a/tests/e2e/scaffolding/expectations.ts b/tests/e2e/scaffolding/expectations.ts new file mode 100644 index 00000000..706d3f0b --- /dev/null +++ b/tests/e2e/scaffolding/expectations.ts @@ -0,0 +1,157 @@ +import { Direction } from './vscroll'; +import { TestHost } from './TestHost'; +import type { TestItem } from '../types'; + +const range = (from: number, to: number): number[] => + Array.from({ length: to - from + 1 }, (_, i) => from + i); + +/** Assertions over a TestHost's state, reached via `misc.expect`. */ +export class Expectations { + constructor(private readonly host: TestHost) {} + + /** DOM padding elements render exactly the model's padding sizes. */ + paddingsReflectModel(): void { + const { host } = this; + const domSize = (direction: Direction): number => { + const element = host.contentElement.querySelector( + `[data-padding-${direction}]` + ) as HTMLElement | null; + const rect = element?.getBoundingClientRect(); + return rect ? (host.horizontal ? rect.width : rect.height) : NaN; + }; + const { paddings } = host.scroller.viewport; + expect(domSize(Direction.backward)).toBe(paddings.backward.size); + expect(domSize(Direction.forward)).toBe(paddings.forward.size); + } + + /** DOM and Buffer contain the same contiguous indexed items. */ + domIndexesMatchBuffer(): void { + const { host } = this; + const domIndexes = host.getElements().map(el => host.getElementIndex(el)); + const bufferIndexes = host.scroller.buffer.items.map(item => item.$index); + + expect(domIndexes).toEqual(bufferIndexes); + this.paddingsReflectModel(); + + if (!bufferIndexes.length) { + return; + } + const { firstIndex, lastIndex } = host.adapter.bufferInfo; + expect(bufferIndexes).toEqual(range(firstIndex, lastIndex)); + } + + /** Also verifies the standard test datasource content for every item. */ + domMatchesBuffer(): void { + const { host } = this; + this.domIndexesMatchBuffer(); + host + .getElements() + .map(el => host.getElementIndex(el)) + .forEach(index => + expect(host.checkElementContentByIndex(index)).toBe(true) + ); + } + + /** Bounded-side paddings for uniform-size data. */ + uniformPaddings(): void { + const { host } = this; + if (!host.scroller.buffer.items.length) { + return; + } + const { firstIndex, lastIndex, absMinIndex, absMaxIndex } = + host.adapter.bufferInfo; + const size = host.scroller.buffer.defaultSize; + + if (Number.isFinite(absMinIndex)) { + expect(host.padding[Direction.backward].getSize()).toBe( + (firstIndex - absMinIndex) * size + ); + } + if (Number.isFinite(absMaxIndex)) { + expect(host.padding[Direction.forward].getSize()).toBe( + (absMaxIndex - lastIndex) * size + ); + } + } + + consistent(): void { + this.domMatchesBuffer(); + this.uniformPaddings(); + } + + startVisible(startIndex: number): void { + const { host } = this; + expect(host.adapter.firstVisible.$index).toBe(startIndex); + expect(host.checkElementContentByIndex(startIndex)).toBe(true); + } + + /** Exact Buffer edges without predicting fetch and clip decisions. */ + bufferRange([firstIndex, lastIndex]: [number, number]): void { + const info = this.host.adapter.bufferInfo; + expect(info.firstIndex).toBe(firstIndex); + expect(info.lastIndex).toBe(lastIndex); + } + + noForwardGap(): void { + const { host } = this; + const children = host.contentElement.children; + const lastItem = children[children.length - 2] as HTMLElement; + const viewport = host.viewportElement.getBoundingClientRect(); + const item = lastItem.getBoundingClientRect(); + const gap = host.horizontal + ? Math.max(0, viewport.right - item.right) + : Math.max(0, viewport.bottom - item.bottom); + + expect(gap).toBe(0); + expect(host.padding[Direction.forward].getSize()).toBe(0); + } + + /** Rendered items cover both viewport edges. */ + viewportFilled(): void { + const { host } = this; + this.domMatchesBuffer(); + + const elements = host.getElements(); + expect(elements.length).toBeGreaterThan(0); + + const first = elements[0].getBoundingClientRect(); + const last = elements[elements.length - 1].getBoundingClientRect(); + const viewport = host.viewportElement.getBoundingClientRect(); + + if (host.horizontal) { + expect(first.left).toBeLessThanOrEqual(viewport.left + 1); + expect(last.right).toBeGreaterThanOrEqual(viewport.right - 1); + } else { + expect(first.top).toBeLessThanOrEqual(viewport.top + 1); + expect(last.bottom).toBeGreaterThanOrEqual(viewport.bottom - 1); + } + } + + /** The visible window stays within the buffered index range. */ + visibleWithinBuffer(): void { + const { adapter } = this.host; + const { firstIndex, lastIndex } = adapter.bufferInfo; + expect(adapter.firstVisible.$index).toBeGreaterThanOrEqual(firstIndex); + expect(adapter.lastVisible.$index).toBeLessThanOrEqual(lastIndex); + } + + uniqueItems( + items: ReadonlyArray<{ uid: number }> = this.host.scroller.buffer.items + ): void { + expect(new Set(items.map(({ uid }) => uid)).size).toBe(items.length); + } + + itemsIdentity( + before: ReadonlyArray<{ uid: number; data: { id: number } }>, + after: ReadonlyArray<{ + uid: number; + data: { id: number }; + }> = this.host.scroller.buffer.items + ): void { + after.forEach(item => { + const previous = before.find(({ data }) => data.id === item.data.id); + expect(previous?.uid).toBe(item.uid); + }); + this.uniqueItems(after); + } +} \ No newline at end of file diff --git a/tests/e2e/scaffolding/index.ts b/tests/e2e/scaffolding/index.ts new file mode 100644 index 00000000..6529301a --- /dev/null +++ b/tests/e2e/scaffolding/index.ts @@ -0,0 +1,8 @@ +export * from './vscroll'; +export * from './data'; +export * from './datasources'; +export * from './TestHost'; +export { TestHost as Misc } from './TestHost'; +export * from './expectations'; +export * from './runner'; +export * from '../types'; diff --git a/tests/e2e/scaffolding/runner.ts b/tests/e2e/scaffolding/runner.ts new file mode 100644 index 00000000..b3f1e29f --- /dev/null +++ b/tests/e2e/scaffolding/runner.ts @@ -0,0 +1,108 @@ +import { describe, test } from 'vitest'; +import { TestHost } from './TestHost'; +import type { TestConfig, TestItem } from '../types'; + +export type OperationConfig< + Operation extends PropertyKey, + Custom = void, + Data = TestItem +> = { + [key in Operation]: TestConfig; +}; + +type TestBody = void | Promise | (() => void | Promise); + +export type ItFunc = ( + misc: TestHost +) => TestBody; + +export type ItFuncConfig< + Custom = void, + Data extends TestItem = TestItem +> = (config: TestConfig) => ItFunc; + +export interface MakeTestConfig< + Custom = void, + Data extends TestItem = TestItem +> { + title: string; + meta?: string; + config: TestConfig; + it: ItFunc; + before?: (misc: TestHost) => void | Promise; + after?: (misc: TestHost) => void | Promise; +} + +const metaTitle = ( + data: MakeTestConfig +): string => { + const result: string[] = []; + const template = data.config.templateSettings; + const settings = data.config.datasourceSettings; + + if (template?.viewportHeight) { + result.push(`vp height = ${template.viewportHeight}`); + } + if (template?.viewportWidth) { + result.push(`vp width = ${template.viewportWidth}`); + } + if (settings?.padding) { + result.push(`padding = ${settings.padding}`); + } + if (settings?.itemSize) { + result.push(`itemSize = ${settings.itemSize}`); + } + if (settings?.startIndex) { + result.push(`start = ${settings.startIndex}`); + } + if (settings?.bufferSize) { + result.push(`buffer = ${settings.bufferSize}`); + } + if (settings?.horizontal) { + result.push('HORIZONTAL'); + } + if (settings?.windowViewport) { + result.push('ENTIRE WINDOW'); + } + if (data.meta) { + result.push(data.meta); + } + + return `[${result.join(', ') || 'default params'}]`; +}; + +const runBody = async (body: TestBody): Promise => { + await (typeof body === 'function' ? body() : body); +}; + +export const makeTest = ( + data: MakeTestConfig +): void => { + describe(metaTitle(data), () => { + test( + data.title, + async () => { + const misc = new TestHost( + data.config as TestConfig + ); + try { + await data.before?.(misc); + await runBody(data.it(misc)); + await data.after?.(misc); + // Enforce the core invariant on every test: once the body settles, the + // rendered DOM must mirror the buffer (same contiguous indexes, paddings + // reflecting the model). This is a general property of a healthy + // scroller, not a per-test assertion, so it runs here for all tests. + // Opt out via `skipInvariantAutoCheck` when a test intentionally ends + // in a non-settled state (disposed, paused, halted, mid-burst, error). + if (!data.config.skipInvariantAutoCheck) { + misc.expect.domIndexesMatchBuffer(); + } + } finally { + misc.dispose(); + } + }, + data.config.timeout || 2000 + ); + }); +}; diff --git a/tests/e2e/scaffolding/vscroll.ts b/tests/e2e/scaffolding/vscroll.ts new file mode 100644 index 00000000..6c3f67c5 --- /dev/null +++ b/tests/e2e/scaffolding/vscroll.ts @@ -0,0 +1,25 @@ +export { + AdapterPropName, + Direction, + SizeStrategy, + Workflow, + makeDatasource +} from '../../../src/index'; + +export { Settings as ParsedSettings } from '../../../src/classes/settings'; +export { getDefaultAdapterProps } from '../../../src/classes/adapter/props'; + +export type { + AdapterClipOptions, + AdapterMethodResult, + BufferUpdater, + DatasourceGet, + DevSettings, + IDatasource, + IDatasourceConstructed, + IDatasourceOptional, + IAdapterProp, + Item, + ItemAdapter, + Settings +} from '../../../src/interfaces/index'; diff --git a/tests/e2e/specs/adapter-promises.spec.ts b/tests/e2e/specs/adapter-promises.spec.ts new file mode 100644 index 00000000..ba39ae1d --- /dev/null +++ b/tests/e2e/specs/adapter-promises.spec.ts @@ -0,0 +1,294 @@ +import { + AdapterPropName as Adapter, + getDatasource, + makeItem, + makeTest, + Misc, + AdapterMethodResult, + TestConfig +} from '../scaffolding'; + +type Resolution = 'async cycle' | 'sync cycle' | 'immediate' | 'error'; + +interface MethodScenario { + method: Adapter; + invoke: (misc: Misc) => Promise; + bufferSize?: number; + prepare?: (misc: Misc) => void; +} + +interface ConcurrentScenario { + min: number; + max: number; + startIndex: number; + count: number; + horizontal?: boolean; + interrupt?: 'reload' | 'reset'; +} + +const itemSize = 20; + +const delayedScenarios: MethodScenario[] = [ + { + method: Adapter.reset, + invoke: misc => misc.adapter.reset() + }, + { + method: Adapter.reload, + invoke: misc => misc.adapter.reload() + }, + { + method: Adapter.append, + invoke: misc => misc.adapter.append({ items: [makeItem(100.1)] }) + }, + { + method: Adapter.prepend, + invoke: misc => misc.adapter.prepend({ items: [makeItem(100.1)] }) + }, + { + method: Adapter.check, + invoke: misc => misc.adapter.check(), + prepare: misc => + misc.adapter.fix({ + updater: ({ $index }) => { + const element = misc.getElement($index); + if (element) { + element.style.height = '5px'; + } + } + }) + }, + { + method: Adapter.remove, + invoke: misc => + misc.adapter.remove({ predicate: ({ $index }) => $index > 1 }) + }, + { + method: Adapter.insert, + invoke: misc => + misc.adapter.insert({ + after: ({ $index }) => $index === 5, + items: [makeItem(5.1)] + }) + }, + { + method: Adapter.update, + invoke: misc => + misc.adapter.update({ predicate: ({ $index }) => $index !== 1 }) + } +]; + +const immediateScenarios: MethodScenario[] = [ + { + method: Adapter.append, + invoke: misc => misc.adapter.append({ items: [makeItem(100.1)], eof: true }) + }, + { + method: Adapter.prepend, + invoke: misc => + misc.adapter.prepend({ items: [makeItem(100.1)], bof: true }) + }, + { + method: Adapter.check, + invoke: misc => misc.adapter.check() + }, + { + method: Adapter.remove, + invoke: misc => + misc.adapter.remove({ predicate: ({ $index }) => $index > 100 }) + }, + { + method: Adapter.insert, + invoke: misc => + misc.adapter.insert({ + after: ({ $index }) => $index === 55, + items: [makeItem(55.1)] + }) + }, + { + method: Adapter.fix, + invoke: misc => misc.adapter.fix({ minIndex: -99 }) + }, + { + method: Adapter.update, + invoke: misc => misc.adapter.update({ predicate: _item => true }) + } +]; + +const synchronousScenarios: MethodScenario[] = [1, 40].map(bufferSize => ({ + method: Adapter.clip, + invoke: misc => misc.adapter.clip(), + bufferSize +})); + +const errorScenarios: MethodScenario[] = [ + { + method: Adapter.reset, + invoke: misc => misc.adapter.reset({ get: 'error' } as never) + }, + { + method: Adapter.append, + invoke: misc => misc.adapter.append({ items: 'error' } as never) + }, + { + method: Adapter.remove, + invoke: misc => misc.adapter.remove('error' as never) + }, + { + method: Adapter.insert, + invoke: misc => misc.adapter.insert('error' as never) + }, + { + method: Adapter.fix, + invoke: misc => misc.adapter.fix({ minIndex: 'error' } as never) + } +]; + +const concurrentScenarios: ConcurrentScenario[] = [ + { min: 1, max: 100, startIndex: 100, count: 25 }, + { + min: 51, + max: 200, + startIndex: 200, + count: 30, + horizontal: true + }, + { min: 1, max: 100, startIndex: 100, count: 99, interrupt: 'reload' }, + { + min: 51, + max: 200, + startIndex: 200, + count: 99, + horizontal: true, + interrupt: 'reset' + } +]; + +const createMethodConfig = (scenario: MethodScenario): TestConfig => ({ + datasource: () => getDatasource({ delay: 25, mode: 'observable' }), + datasourceSettings: { + itemSize, + startIndex: 1, + bufferSize: scenario.bufferSize ?? 10, + padding: 0.5 + }, + templateSettings: { viewportHeight: itemSize * 10, itemHeight: itemSize }, + timeout: 4000 +}); + +const createConcurrentConfig = ( + scenario: ConcurrentScenario +): TestConfig => ({ + datasource: () => getDatasource({ min: scenario.min, max: scenario.max }), + datasourceSettings: { + startIndex: scenario.startIndex, + ...(scenario.horizontal ? { horizontal: true } : {}) + }, + templateSettings: scenario.horizontal + ? { viewportWidth: 450, itemWidth: 100, horizontal: true } + : { itemHeight: itemSize }, + timeout: 4000 +}); + +const registerMethodScenario = + (resolution: Resolution) => + (scenario: MethodScenario): void => { + const startsCycle = resolution.endsWith('cycle'); + const completesSynchronously = resolution === 'sync cycle'; + const error = resolution === 'error'; + + makeTest({ + config: createMethodConfig(scenario), + title: `should resolve ${scenario.method} with the expected result`, + meta: resolution, + it: misc => async () => { + await misc.relaxNext(); + expect(misc.workflow.cyclesDone).toBe(1); + scenario.prepare?.(misc); + + const resultPromise = scenario.invoke(misc); + expect(misc.workflow.cyclesDone).toBe(completesSynchronously ? 2 : 1); + + const result = await resultPromise; + expect(misc.workflow.cyclesDone).toBe(startsCycle ? 2 : 1); + expect(result.immediate).toBe(!startsCycle); + expect(result.success).toBe(!error); + + if (error) { + expect(result.details).toBeTruthy(); + expect(misc.workflow.errors).toHaveLength(1); + expect(misc.workflow.errors[0].process).toBe( + `adapter.${scenario.method}` + ); + } else { + expect(result.details).toBeNull(); + expect(misc.workflow.errors).toHaveLength(0); + } + } + }); + }; + +const appendAndScroll = async ( + misc: Misc, + index: number +): Promise => { + const ready = await misc.adapter.relax(); + if (!ready.success) { + return ready; + } + const appended = await misc.adapter.append(makeItem(index)); + if (!appended.success) { + return appended; + } + return misc.adapter.fix({ scrollPosition: Infinity }); +}; + +const registerConcurrentScenario = (scenario: ConcurrentScenario): void => + makeTest({ + config: createConcurrentConfig(scenario), + title: scenario.interrupt + ? `should settle all promises after ${scenario.interrupt}` + : 'should run concurrent nested sequences in order', + it: misc => async () => { + const initialInstance = misc.scroller.settings.instanceIndex; + await misc.relaxNext(); + const initialPosition = misc.getScrollPosition(); + const initialSize = misc.scroller.buffer.defaultSize; + const tasks = Array.from({ length: scenario.count }, (_, index) => + appendAndScroll(misc, scenario.startIndex + index + 1) + ); + + if (scenario.interrupt) { + await misc.adapter[scenario.interrupt](); + } + const results = await Promise.all(tasks); + await misc.adapter.relax(); + + expect(results).toHaveLength(scenario.count); + if (scenario.interrupt) { + expect(misc.workflow.cyclesDone).toBe(2); + expect(misc.scroller.settings.instanceIndex).toBe( + initialInstance + (scenario.interrupt === 'reset' ? 1 : 0) + ); + } else { + expect(results.every(result => result.success)).toBe(true); + expect(misc.getScrollPosition()).toBe( + initialPosition + initialSize * scenario.count + ); + expect(misc.workflow.cyclesDone).toBe(scenario.count + 1); + expect(misc.scroller.settings.instanceIndex).toBe(initialInstance); + } + } + }); + +describe('Adapter Promises Spec', () => { + describe('Promisified methods', () => { + delayedScenarios.forEach(registerMethodScenario('async cycle')); + synchronousScenarios.forEach(registerMethodScenario('sync cycle')); + immediateScenarios.forEach(registerMethodScenario('immediate')); + errorScenarios.forEach(registerMethodScenario('error')); + }); + + describe('Concurrent sequences', () => + concurrentScenarios.forEach(registerConcurrentScenario)); +}); diff --git a/tests/e2e/specs/adapter.append-prepend.spec.ts b/tests/e2e/specs/adapter.append-prepend.spec.ts new file mode 100644 index 00000000..ce021b4d --- /dev/null +++ b/tests/e2e/specs/adapter.append-prepend.spec.ts @@ -0,0 +1,363 @@ +import { + AdapterPropName as Adapter, + Direction, + getDatasource, + makeItems, + makeTest, + Misc, + MutableDatasource, + TestConfig, + TestItem +} from '../scaffolding'; + +type Operation = Adapter.append | Adapter.prepend; + +interface VirtualOutcome { + absMinIndex: number; + absMaxIndex: number; + bufferShift: number; + edgeIndex: number; + edgeId: number; + sourceFirstId: number; +} + +interface EmptyGolden { + firstIndex: number; + lastIndex: number; + firstId: number; + scrollPosition?: number; +} + +const min = 1; +const max = 100; +const itemSize = 20; +const bufferSize = 10; +const padding = 0.5; +const amount = 3; +const emptyAmount = 25; +const operations: Operation[] = [Adapter.append, Adapter.prepend]; + +// Pure geometry derived from the config above (not scroller heuristics): the +// scrollable size is simply the item count times the item size. +const datasetScrollable = (max - min + 1) * itemSize; // full dataset, px +const virtualAddedScrollable = datasetScrollable + amount * itemSize; // + N virtual +const emptyIndexedScrollable = emptyAmount * itemSize; // indexed-empty ds once filled + +const getVirtualOutcome = ( + operation: Operation, + opposite: boolean +): VirtualOutcome => { + if (operation === Adapter.append) { + // decrease (opposite) shifts existing indexes down instead of growing max + const absMinIndex = opposite ? min - amount : min; + const absMaxIndex = opposite ? max : max + amount; + return { + absMinIndex, + absMaxIndex, + bufferShift: opposite ? -amount : 0, + edgeIndex: absMaxIndex, + edgeId: max + amount, + sourceFirstId: min + }; + } + // prepend; increase (opposite) shifts existing indexes up instead of growing min + const absMinIndex = opposite ? min : min - amount; + const absMaxIndex = opposite ? max + amount : max; + return { + absMinIndex, + absMaxIndex, + bufferShift: opposite ? amount : 0, + edgeIndex: absMinIndex, + edgeId: min - amount, + sourceFirstId: min - amount + }; +}; + +const indexedEmptyGoldens: Record = { + [Adapter.append]: [ + { firstIndex: 76, lastIndex: 100, firstId: 77, scrollPosition: 0 }, + { firstIndex: 52, lastIndex: 76, firstId: 77, scrollPosition: 300 } + ], + [Adapter.prepend]: [ + { firstIndex: 1, lastIndex: 25, firstId: 1, scrollPosition: 300 }, + { firstIndex: 25, lastIndex: 49, firstId: 1, scrollPosition: 0 } + ] +}; + +const unboundedEmptyGoldens: Record = { + [Adapter.append]: [ + { firstIndex: 1, lastIndex: 25, firstId: 1 }, + { firstIndex: -23, lastIndex: 1, firstId: 1 } + ], + [Adapter.prepend]: [ + { firstIndex: -23, lastIndex: 1, firstId: -23 }, + { firstIndex: 1, lastIndex: 25, firstId: -23 } + ] +}; + +const getDirection = (operation: Operation): Direction => + operation === Adapter.append ? Direction.forward : Direction.backward; + +const getOppositeDirection = (operation: Operation): Direction => + operation === Adapter.append ? Direction.backward : Direction.forward; + +const addItems = ( + misc: Misc, + operation: Operation, + items: TestItem[], + opposite: boolean, + boundary?: boolean, + virtualize = false +) => + operation === Adapter.append + ? misc.adapter.append({ + items, + decrease: opposite, + ...(virtualize ? { virtualize: true } : { eof: !!boundary }) + }) + : misc.adapter.prepend({ + items, + increase: opposite, + ...(virtualize ? { virtualize: true } : { bof: !!boundary }) + }); + +const createBasicConfig = (operation: Operation): TestConfig => ({ + datasource: () => getDatasource({ min, max }), + datasourceSettings: { + startIndex: + operation === Adapter.append ? max - bufferSize + 1 - amount : min, + minIndex: operation === Adapter.append ? min : min + amount, + maxIndex: operation === Adapter.append ? max - amount : max, + bufferSize, + padding + }, + templateSettings: { viewportHeight: 200, itemHeight: itemSize } +}); + +const createVirtualConfig = ( + operation: Operation, + atOperationEdge: boolean +): TestConfig => ({ + datasource: () => new MutableDatasource(min, max), + datasourceSettings: { + startIndex: atOperationEdge + ? operation === Adapter.append + ? max + : min + : operation === Adapter.append + ? min + : max, + minIndex: min, + maxIndex: max, + bufferSize, + padding + }, + templateSettings: { viewportHeight: 200, itemHeight: itemSize } +}); + +const createEmptyConfig = ( + operation: Operation, + indexed: boolean +): TestConfig => ({ + datasource: () => getDatasource({ min: 1, max: 0 }), + datasourceSettings: indexed + ? { + startIndex: + operation === Adapter.append + ? max - emptyAmount + 1 + : min + emptyAmount - 1, + ...(operation === Adapter.append + ? { maxIndex: max - emptyAmount + 1 } + : { minIndex: min + emptyAmount - 1 }), + bufferSize: emptyAmount, + padding + } + : {}, + templateSettings: { viewportHeight: 200, itemHeight: itemSize } +}); + +const registerImmediate = (operation: Operation, opposite: boolean): void => + makeTest({ + config: createBasicConfig(operation), + title: `should ${operation} immediately${opposite ? ' (opposite)' : ''}`, + it: misc => async () => { + await misc.relaxNext(); + const direction = getDirection(operation); + const items = + operation === Adapter.append + ? makeItems(max - amount + 1, amount) + : makeItems(min, amount).reverse(); + const initialScrollableSize = (max - amount) * itemSize; + + expect(misc.getScrollableSize()).toBe(initialScrollableSize); + expect(misc.padding[direction].getSize()).toBe(0); + + const result = addItems(misc, operation, items, opposite); + expect(misc.getScrollableSize()).toBe(initialScrollableSize); + expect((await result).success).toBe(true); + await misc.adapter.relax(); + + const shift = operation === Adapter.append ? -amount : amount; + const expectedShift = opposite ? shift : 0; + const { bufferInfo } = misc.adapter; + expect(misc.getScrollableSize()).toBe(max * itemSize); + expect(misc.padding[direction].getSize()).toBe(0); + expect(bufferInfo.absMinIndex).toBe(min + expectedShift); + expect(bufferInfo.absMaxIndex).toBe(max + expectedShift); + + const edgeIndex = + operation === Adapter.append + ? max + expectedShift + : min + expectedShift; + expect( + operation === Adapter.append ? bufferInfo.maxIndex : bufferInfo.minIndex + ).toBe(edgeIndex); + expect( + misc.checkElementContent( + edgeIndex, + operation === Adapter.append ? max : min + ) + ).toBe(true); + misc.expect.domIndexesMatchBuffer(); + } + }); + +const registerVirtual = ( + operation: Operation, + opposite: boolean, + atOperationEdge: boolean +): void => + makeTest({ + config: createVirtualConfig(operation, atOperationEdge), + title: + `should ${operation} virtually after scroll` + + (atOperationEdge ? ' (at EOF/BOF)' : '') + + (opposite ? ' (opposite)' : ''), + it: misc => async () => { + await misc.relaxNext(); + const append = operation === Adapter.append; + const direction = getDirection(operation); + const oppositeDirection = getOppositeDirection(operation); + const outcome = getVirtualOutcome(operation, opposite); + const { viewport, buffer } = misc.scroller; + const beforeItems = [...buffer.items]; + const firstVisibleId = misc.adapter.firstVisible.data.id; + const lastVisibleId = misc.adapter.lastVisible.data.id; + const directionPadding = misc.padding[direction].getSize(); + const oppositePadding = misc.padding[oppositeDirection].getSize(); + const bufferedEdge = append ? buffer.lastIndex : buffer.firstIndex; + const items = makeItems(append ? 101 : -2, amount); + + expect(misc.getScrollableSize()).toBe(datasetScrollable); + expect(atOperationEdge ? directionPadding : oppositePadding).toBe(0); + + misc.source().reset( + outcome.absMinIndex, + outcome.absMaxIndex, + outcome.sourceFirstId + ); + const result = await addItems( + misc, + operation, + items, + opposite, + true, + atOperationEdge + ); + expect(result.success).toBe(true); + + expect(append ? buffer.lastIndex : buffer.firstIndex).toBe( + bufferedEdge + outcome.bufferShift + ); + misc.expect.itemsIdentity(beforeItems, buffer.items); + expect(misc.adapter.firstVisible.data.id).toBe(firstVisibleId); + expect(misc.adapter.lastVisible.data.id).toBe(lastVisibleId); + expect(viewport.getScrollableSize()).toBe(virtualAddedScrollable); + expect(misc.padding[direction].getSize()).toBe( + directionPadding + amount * itemSize + ); + expect(misc.padding[oppositeDirection].getSize()).toBe(oppositePadding); + expect(buffer.absMinIndex).toBe(outcome.absMinIndex); + expect(buffer.absMaxIndex).toBe(outcome.absMaxIndex); + + if (append) { + await misc.scrollMaxRelax(); + } else { + await misc.scrollMinRelax(); + } + + expect(viewport.getScrollableSize()).toBe(virtualAddedScrollable); + expect(misc.padding[direction].getSize()).toBe(0); + expect(buffer.absMinIndex).toBe(outcome.absMinIndex); + expect(buffer.absMaxIndex).toBe(outcome.absMaxIndex); + expect(append ? buffer.lastIndex : buffer.firstIndex).toBe( + outcome.edgeIndex + ); + expect(misc.checkElementContent(outcome.edgeIndex, outcome.edgeId)).toBe( + true + ); + misc.expect.domIndexesMatchBuffer(); + } + }); + +const registerEmpty = ( + operation: Operation, + opposite: boolean, + indexed: boolean, + boundary = false +): void => + makeTest({ + config: createEmptyConfig(operation, indexed), + title: + `should ${operation}${boundary ? ' virtually' : ''} to empty datasource` + + (indexed ? '' : ' without indexes') + + (opposite ? ' (opposite)' : ''), + it: misc => async () => { + await misc.relaxNext(); + const golden = (indexed ? indexedEmptyGoldens : unboundedEmptyGoldens)[ + operation + ][Number(opposite)]; + const items = makeItems(golden.firstId, emptyAmount); + if (operation === Adapter.prepend) { + items.reverse(); + } + + expect(misc.getScrollableSize()).toBe(200); // empty ds: scrollable == viewport height + expect(misc.adapter.itemsCount).toBe(0); + const result = await addItems(misc, operation, items, opposite, boundary); + expect(result.success).toBe(true); + await misc.adapter.relax(); + + expect(misc.adapter.itemsCount).toBe(emptyAmount); + expect(misc.adapter.bufferInfo.firstIndex).toBe(golden.firstIndex); + expect(misc.adapter.bufferInfo.lastIndex).toBe(golden.lastIndex); + expect(misc.checkElementContent(golden.firstIndex, golden.firstId)).toBe( + true + ); + expect( + misc.checkElementContent( + golden.lastIndex, + golden.firstId + emptyAmount - 1 + ) + ).toBe(true); + if (indexed) { + expect(misc.getScrollableSize()).toBe(emptyIndexedScrollable); + expect(misc.getScrollPosition()).toBe(golden.scrollPosition); + } + misc.expect.domIndexesMatchBuffer(); + } + }); + +describe('Adapter Append-Prepend Spec', () => + operations.forEach(operation => + [false, true].forEach(opposite => { + registerImmediate(operation, opposite); + [false, true].forEach(atEdge => + registerVirtual(operation, opposite, atEdge) + ); + [false, true].forEach(boundary => + registerEmpty(operation, opposite, true, boundary) + ); + registerEmpty(operation, opposite, false); + }) + )); diff --git a/tests/e2e/specs/adapter.check.spec.ts b/tests/e2e/specs/adapter.check.spec.ts new file mode 100644 index 00000000..933230ef --- /dev/null +++ b/tests/e2e/specs/adapter.check.spec.ts @@ -0,0 +1,249 @@ +import { + getDatasource, + makeTest, + Misc, + SizeStrategy, + TestConfig +} from '../scaffolding'; + +interface Scenario { + min: number; + max: number; + size: number; + itemSize: number; + padding: number; + bufferSize: number; + viewportSize: number; + horizontal?: boolean; + // Harvested empirically: settled scrollable px after the size change is + // absorbed. It depends on vscroll's own measurement, so it is captured, not + // derived (deriving it would re-implement the scroller). + expectedScrollable: number; +} + +const minIndex = -99; +const maxIndex = 100; + +const scenarios: Scenario[] = [ + { + min: 1, + max: 10, + size: 10, + itemSize: 100, + padding: 0.5, + bufferSize: 10, + viewportSize: 600, + expectedScrollable: 14810 + }, + { + min: 4, + max: 5, + size: 100, + itemSize: 20, + padding: 0.88, + bufferSize: 10, + viewportSize: 300, + expectedScrollable: 4788 + }, + { + min: 2, + max: 11, + size: 200, + itemSize: 20, + padding: 0.5, + bufferSize: 10, + viewportSize: 600, + expectedScrollable: 10000 + }, + { + min: -2, + max: 2, + size: 20, + itemSize: 100, + padding: 0.5, + bufferSize: 10, + viewportSize: 600, + expectedScrollable: 17390 + }, + { + min: 1, + max: 2, + size: 20, + itemSize: 100, + padding: 0.33, + bufferSize: 5, + viewportSize: 450, + horizontal: true, + expectedScrollable: 18000 + } +]; + +const createConfig = (scenario: Scenario): TestConfig => ({ + datasource: () => getDatasource({ min: minIndex, max: maxIndex }), + datasourceSettings: { + startIndex: 1, + minIndex, + maxIndex, + itemSize: scenario.itemSize, + padding: scenario.padding, + bufferSize: scenario.bufferSize, + sizeStrategy: SizeStrategy.Average, + ...(scenario.horizontal ? { horizontal: true } : {}) + }, + templateSettings: scenario.horizontal + ? { + viewportWidth: scenario.viewportSize, + itemWidth: scenario.itemSize, + horizontal: true, + dynamicSize: 'size' + } + : { + viewportHeight: scenario.viewportSize, + dynamicSize: 'size' + }, + timeout: 9000 +}); + +const updateElementSize = (misc: Misc, index: number, size: number): void => { + const element = misc.getElement(index); + if (element) { + element.style[misc.horizontal ? 'width' : 'height'] = `${size}px`; + } +}; + +const updateSizes = (misc: Misc, scenario: Scenario): void => { + for (let index = scenario.min; index <= scenario.max; index++) { + updateElementSize(misc, index, scenario.size); + } + misc.setItemProcessor(({ data }) => { + data.size = + data.id >= scenario.min && data.id <= scenario.max + ? scenario.size + : scenario.itemSize; + }); +}; + +const getFirstFullyVisibleIndex = (misc: Misc): number => { + const edge = misc.getScrollPosition(); + const element = misc + .getElements() + .find(item => item[misc.horizontal ? 'offsetLeft' : 'offsetTop'] >= edge); + return element ? misc.getElementIndex(element) : NaN; +}; + +const expectCheckState = (misc: Misc, expected: boolean): void => { + expect(misc.scroller.state.fetch.simulate).toBe(expected); + expect(misc.scroller.state.fetch.isCheck).toBe(expected); +}; + +const expectSurvivorIdentity = ( + misc: Misc, + before: Misc['scroller']['buffer']['items'], + after: Misc['scroller']['buffer']['items'] +): void => { + const uidByIndex = new Map(before.map(item => [item.$index, item.uid])); + after.forEach(item => { + const uid = uidByIndex.get(item.$index); + if (uid !== undefined) { + expect(item.uid).toBe(uid); + } + }); + misc.expect.uniqueItems(after); +}; + +const registerCheckScenario = (scenario: Scenario): void => + makeTest({ + config: createConfig(scenario), + title: 'should check changed sizes', + before: misc => + misc.setItemProcessor(({ data }) => { + data.size = scenario.itemSize; + }), + it: misc => async () => { + await misc.relaxNext(); + const beforeItems = [...misc.scroller.buffer.items]; + updateSizes(misc, scenario); + const firstVisibleIndex = getFirstFullyVisibleIndex(misc); + + await misc.adapter.check(); + + expectSurvivorIdentity(misc, beforeItems, misc.scroller.buffer.items); + expect(misc.adapter.firstVisible.$index).toBe(firstVisibleIndex); + expect(misc.getScrollableSize()).toBe(scenario.expectedScrollable); + misc.expect.domMatchesBuffer(); + } + }); + +const registerFetchAfterCheck = (scenario: Scenario): void => + makeTest({ + config: createConfig(scenario), + title: 'should fetch after check', + before: misc => + misc.setItemProcessor(({ data }) => { + data.size = scenario.itemSize; + }), + it: misc => async () => { + await misc.relaxNext(); + updateElementSize(misc, misc.scroller.settings.startIndex, 50); + + const result = misc.adapter.check(); + expectCheckState(misc, true); + await result; + expectCheckState(misc, false); + misc.expect.uniqueItems(); + + await misc.scrollMaxRelax(); + misc.expect.domMatchesBuffer(); + } + }); + +const registerDoubleCheck = ( + scenario: Scenario, + operation: 'append' | 'prepend' +): void => + makeTest({ + config: createConfig(scenario), + title: `should check after check and ${operation}`, + before: misc => + misc.setItemProcessor(({ data }) => { + data.size = scenario.itemSize; + }), + it: misc => async () => { + await misc.relaxNext(); + const startIndex = misc.scroller.settings.startIndex; + updateElementSize(misc, startIndex, 50); + + let result = misc.adapter.check(); + expectCheckState(misc, true); + await result; + expectCheckState(misc, false); + misc.expect.uniqueItems(); + + const item = { + id: operation === 'prepend' ? minIndex - 1 : maxIndex + 1, + text: 'new item', + size: scenario.itemSize + }; + if (operation === 'prepend') { + await misc.adapter.prepend({ items: [item] }); + } else { + await misc.adapter.append({ items: [item] }); + } + misc.expect.uniqueItems(); + + updateElementSize(misc, startIndex, scenario.itemSize); + result = misc.adapter.check(); + expectCheckState(misc, true); + await result; + expectCheckState(misc, false); + misc.expect.uniqueItems(); + } + }); + +describe('Adapter Check Size Spec', () => { + scenarios.forEach(registerCheckScenario); + registerFetchAfterCheck(scenarios[1]); + registerFetchAfterCheck(scenarios[4]); + registerDoubleCheck(scenarios[1], 'prepend'); + registerDoubleCheck(scenarios[4], 'append'); +}); diff --git a/tests/e2e/specs/adapter.clip.spec.ts b/tests/e2e/specs/adapter.clip.spec.ts new file mode 100644 index 00000000..e5740b8b --- /dev/null +++ b/tests/e2e/specs/adapter.clip.spec.ts @@ -0,0 +1,229 @@ +import { + getDatasource, + makeTest, + Misc, + AdapterClipOptions, + TestConfig +} from '../scaffolding'; + +interface BaseScenario { + startIndex: number; + bufferSize: number; + padding: number; + itemSize: number; + viewportSize: number; + horizontal?: boolean; +} + +interface ClipSnapshot { + range: [number, number]; + backwardPadding: number; + forwardPadding: number; + itemsCount: number; + firstVisible: number; + scrollableSize: number; + defaultSize: number; +} + +interface ClipScenario { + base: BaseScenario; + options?: AdapterClipOptions; + expected: ClipSnapshot; +} + +const newItemsCount = 50; + +const baseScenarios: BaseScenario[] = [ + { + startIndex: 1, + bufferSize: 5, + padding: 0.2, + itemSize: 20, + viewportSize: 100 + }, + { + startIndex: -158, + bufferSize: 11, + padding: 0.68, + itemSize: 20, + viewportSize: 77 + }, + { + startIndex: 1, + bufferSize: 5, + padding: 1, + itemSize: 100, + viewportSize: 450, + horizontal: true + }, + { + startIndex: -274, + bufferSize: 3, + padding: 1.22, + itemSize: 75, + viewportSize: 320, + horizontal: true + } +]; + +const snapshot = ( + range: [number, number], + backwardPadding: number, + forwardPadding: number, + firstVisible: number, + scrollableSize: number, + defaultSize: number +): ClipSnapshot => ({ + range, + backwardPadding, + forwardPadding, + itemsCount: range[1] - range[0] + 1, + firstVisible, + scrollableSize, + defaultSize +}); + +// Each `expected` is the settled post-clip state captured EMPIRICALLY (buffer +// edges, paddings, scrollable size). Clip is a vscroll heuristic, so these are +// harvested rather than recomputed. +const clipScenarios: ClipScenario[] = [ + { + base: baseScenarios[0], + expected: snapshot([0, 6], 80, 1000, 1, 1220, 20) + }, + { + base: baseScenarios[1], + expected: snapshot([-161, -152], 160, 1080, -158, 1440, 20) + }, + { + base: baseScenarios[2], + expected: snapshot([-4, 9], 0, 5000, 1, 6400, 100) + }, + { + base: baseScenarios[3], + expected: snapshot([-280, -265], 0, 3750, -274, 4950, 75) + }, + { + base: baseScenarios[0], + options: { forwardOnly: true }, + expected: snapshot([-4, 6], 0, 1000, 1, 1220, 20) + }, + { + base: baseScenarios[1], + options: { backwardOnly: true }, + expected: snapshot([-161, -98], 160, 0, -158, 1440, 20) + }, + { + base: baseScenarios[2], + options: { forwardOnly: true }, + expected: snapshot([-4, 9], 0, 5000, 1, 6400, 100) + }, + { + base: baseScenarios[3], + options: { backwardOnly: true }, + expected: snapshot([-280, -215], 0, 0, -274, 4950, 75) + }, + { + base: baseScenarios[0], + options: { forwardOnly: true, backwardOnly: true }, + expected: snapshot([0, 6], 80, 1000, 1, 1220, 20) + } +]; + +const createConfig = ( + scenario: BaseScenario, + infinite = false +): TestConfig => ({ + datasource: () => getDatasource(), + datasourceSettings: { + startIndex: scenario.startIndex, + bufferSize: scenario.bufferSize, + padding: scenario.padding, + itemSize: scenario.itemSize, + infinite, + ...(scenario.horizontal ? { horizontal: true } : {}) + }, + templateSettings: scenario.horizontal + ? { + viewportWidth: scenario.viewportSize, + itemWidth: scenario.itemSize, + horizontal: true + } + : { + viewportHeight: scenario.viewportSize, + itemHeight: scenario.itemSize + } +}); + +const takeSnapshot = (misc: Misc): ClipSnapshot => ({ + range: [ + misc.adapter.bufferInfo.firstIndex, + misc.adapter.bufferInfo.lastIndex + ], + backwardPadding: misc.padding.backward.getSize(), + forwardPadding: misc.padding.forward.getSize(), + itemsCount: misc.adapter.itemsCount, + firstVisible: misc.adapter.firstVisible.$index, + scrollableSize: misc.getScrollableSize(), + defaultSize: misc.scroller.buffer.defaultSize +}); + +const registerClipScenario = (scenario: ClipScenario): void => + makeTest({ + config: createConfig(scenario.base), + title: 'should clip after appending many items', + meta: scenario.options + ? Object.keys(scenario.options).join(' + ') + : 'both directions', + it: misc => async () => { + await misc.relaxNext(); + const { buffer } = misc.scroller; + const firstIndex = buffer.firstIndex; + const indexToAppend = buffer.lastIndex + 1; + const items = Array.from({ length: newItemsCount }, (_, offset) => ({ + id: indexToAppend - offset, + text: `!item #${indexToAppend - offset}` + })); + + await misc.adapter.append({ items }); + expect(buffer.firstIndex).toBe(firstIndex); + expect(buffer.lastIndex).toBe(indexToAppend + newItemsCount - 1); + expect(misc.padding.backward.getSize()).toBe(0); + + await misc.adapter.clip(scenario.options); + + misc.expect.uniqueItems(buffer.items); + expect(takeSnapshot(misc)).toEqual(scenario.expected); + misc.expect.domIndexesMatchBuffer(); + } + }); + +const registerInfiniteScenario = (scenario: BaseScenario): void => + makeTest({ + config: createConfig(scenario, true), + title: 'should clip after scroll in infinite mode', + it: misc => async () => { + await misc.relaxNext(); + const initialCount = misc.adapter.itemsCount; + + await misc.scrollMaxRelax(); + const beforeItems = [...misc.scroller.buffer.items]; + const expandedCount = misc.adapter.itemsCount; + expect(expandedCount).toBeGreaterThan(initialCount); + + await misc.adapter.clip(); + expect(misc.adapter.itemsCount).toBeLessThan(expandedCount); + misc.expect.itemsIdentity(beforeItems, misc.scroller.buffer.items); + + const clipCount = misc.scroller.state.clip.callCount; + await misc.scrollMaxRelax(); + expect(misc.scroller.state.clip.callCount).toBe(clipCount); + misc.expect.domIndexesMatchBuffer(); + } + }); + +describe('Adapter Clip Spec', () => { + clipScenarios.forEach(registerClipScenario); + registerInfiniteScenario(baseScenarios[0]); + registerInfiniteScenario(baseScenarios[3]); +}); diff --git a/tests/e2e/specs/adapter.insert.spec.ts b/tests/e2e/specs/adapter.insert.spec.ts new file mode 100644 index 00000000..5f0b8181 --- /dev/null +++ b/tests/e2e/specs/adapter.insert.spec.ts @@ -0,0 +1,563 @@ +import { + getDatasource, + makeItems, + makeTest, + Misc, + MutableDatasource, + TestConfig, + TestItem +} from '../scaffolding'; + +interface InsertScenario { + before: boolean; + index: number; + amount: number; + startIndex: number; + decrease?: boolean; + useIndexApi?: boolean; +} + +interface VirtualScenario extends InsertScenario { + expected: { min: number; max: number; ids: number[] }; +} + +interface EmptyScenario extends Omit { + expected: { + min: number; + max: number; + start: number; + firstVisible?: number; + lastVisible?: number; + }; +} + +interface StateSnapshot { + absMin: number; + absMax: number; + bufferSize: number; + scrollableSize: number; + indexes: number[]; + ids: number[]; +} + +const min = 1; +const max = 100; +const middle = 50; +const itemSize = 20; + +const coreScenarios: InsertScenario[] = [ + { before: false, index: middle, amount: 3, startIndex: middle }, + { before: true, index: middle, amount: 3, startIndex: middle }, + { before: false, index: max, amount: 3, startIndex: max }, + { before: true, index: min, amount: 3, startIndex: min } +]; + +const indexScenarios = [coreScenarios[0], coreScenarios[3]].map(scenario => ({ + ...scenario, + useIndexApi: true +})); + +const staticScenarios = [ + ...coreScenarios, + ...indexScenarios, + ...[...coreScenarios, ...indexScenarios].map(scenario => ({ + ...scenario, + decrease: true + })) +]; + +const outsideScenarios: InsertScenario[] = [ + { before: false, index: 1, amount: 3, startIndex: middle }, + { before: false, index: max - 1, amount: 3, startIndex: middle }, + { before: true, index: 1, amount: 3, startIndex: middle }, + { before: true, index: max - 1, amount: 3, startIndex: middle } +]; + +const dynamicBase = [ + ...coreScenarios, + { ...coreScenarios[0], decrease: true }, + { ...coreScenarios[1], decrease: true } +]; +const dynamicScenarios = [ + ...dynamicBase, + ...dynamicBase.map(scenario => ({ ...scenario, useIndexApi: true })) +]; + +const virtualScenarios: VirtualScenario[] = [ + { + before: false, + index: -3, + amount: 2, + startIndex: 0, + expected: { + min: -4, + max: 7, + ids: [-4, -3, 101, 102, -2, -1, 0, 1, 2, 3, 4, 5] + } + }, + { + before: false, + index: -3, + amount: 2, + startIndex: 0, + decrease: true, + expected: { + min: -6, + max: 5, + ids: [-4, -3, 101, 102, -2, -1, 0, 1, 2, 3, 4, 5] + } + }, + { + before: true, + index: -3, + amount: 2, + startIndex: 0, + expected: { + min: -4, + max: 7, + ids: [-4, 101, 102, -3, -2, -1, 0, 1, 2, 3, 4, 5] + } + }, + { + before: true, + index: -3, + amount: 2, + startIndex: 0, + decrease: true, + expected: { + min: -6, + max: 5, + ids: [-4, 101, 102, -3, -2, -1, 0, 1, 2, 3, 4, 5] + } + }, + { + before: false, + index: 4, + amount: 2, + startIndex: 0, + expected: { + min: -4, + max: 7, + ids: [-4, -3, -2, -1, 0, 1, 2, 3, 4, 101, 102, 5] + } + }, + { + before: false, + index: 4, + amount: 2, + startIndex: 0, + decrease: true, + expected: { + min: -6, + max: 5, + ids: [-4, -3, -2, -1, 0, 1, 2, 3, 4, 101, 102, 5] + } + }, + { + before: true, + index: 4, + amount: 2, + startIndex: 0, + expected: { + min: -4, + max: 7, + ids: [-4, -3, -2, -1, 0, 1, 2, 3, 101, 102, 4, 5] + } + }, + { + before: true, + index: 4, + amount: 2, + startIndex: 0, + decrease: true, + expected: { + min: -6, + max: 5, + ids: [-4, -3, -2, -1, 0, 1, 2, 3, 101, 102, 4, 5] + } + }, + { + before: true, + index: -4, + amount: 20, + startIndex: 0, + expected: { + min: -4, + max: 25, + ids: [ + ...Array.from({ length: 20 }, (_, index) => 101 + index), + -4, + -3, + -2, + -1, + 0, + 1, + 2, + 3, + 4, + 5 + ] + } + }, + { + before: false, + index: 5, + amount: 20, + startIndex: 0, + decrease: true, + expected: { + min: -24, + max: 5, + ids: [ + -4, + -3, + -2, + -1, + 0, + 1, + 2, + 3, + 4, + 5, + ...Array.from({ length: 20 }, (_, index) => 101 + index) + ] + } + } +]; + +const emptyScenarios: EmptyScenario[] = [ + { + before: false, + index: 0, + amount: 5, + expected: { min: 1, max: 5, start: 1, firstVisible: 1 } + }, + { + before: true, + index: 2, + amount: 5, + expected: { min: 1, max: 5, start: 1, firstVisible: 1 } + }, + { + before: false, + index: 5, + amount: 5, + decrease: true, + expected: { min: 1, max: 5, start: 1, firstVisible: 1 } + }, + { + before: true, + index: 6, + amount: 5, + decrease: true, + expected: { min: 1, max: 5, start: 1, firstVisible: 1 } + }, + { + before: false, + index: -10, + amount: 20, + expected: { min: -9, max: 10, start: 1, firstVisible: 1 } + }, + { + before: false, + index: 100, + amount: 10, + expected: { min: 101, max: 110, start: 101, firstVisible: 101 } + }, + { + before: false, + index: -100, + amount: 10, + expected: { min: -99, max: -90, start: -90, lastVisible: -90 } + } +]; + +const createConfig = (scenario: InsertScenario): TestConfig => ({ + datasource: () => new MutableDatasource(min, max), + datasourceSettings: { + startIndex: scenario.startIndex, + minIndex: min, + maxIndex: max, + bufferSize: 10, + padding: 0.5, + itemSize + }, + templateSettings: { viewportHeight: 200, itemHeight: itemSize }, + timeout: 4000 +}); + +const createVirtualConfig = (): TestConfig => ({ + datasource: () => new MutableDatasource(-4, 5), + datasourceSettings: { + minIndex: -4, + maxIndex: 5, + startIndex: 0, + padding: 0.1, + bufferSize: 1, + itemSize + }, + templateSettings: { viewportHeight: 60, itemHeight: itemSize }, + timeout: 4000 +}); + +const emptyConfig: TestConfig = { + datasource: () => getDatasource({ min: 1, max: 0 }), + datasourceSettings: { + minIndex: -15, + maxIndex: 15, + startIndex: 1, + padding: 0.1, + bufferSize: 7, + itemSize + }, + templateSettings: { viewportHeight: 100, itemHeight: itemSize } +}; + +const takeSnapshot = (misc: Misc): StateSnapshot => ({ + absMin: misc.scroller.buffer.absMinIndex, + absMax: misc.scroller.buffer.absMaxIndex, + bufferSize: misc.scroller.buffer.size, + scrollableSize: misc.getScrollableSize(), + indexes: misc.scroller.buffer.items.map(item => item.$index), + ids: misc.scroller.buffer.items.map(item => item.data.id) +}); + +const insertViaAdapter = ( + misc: Misc, + scenario: Pick< + InsertScenario, + 'before' | 'index' | 'decrease' | 'useIndexApi' + >, + items: TestItem[] +) => + misc.adapter.insert({ + items, + decrease: !!scenario.decrease, + ...(scenario.useIndexApi + ? scenario.before + ? { beforeIndex: scenario.index } + : { afterIndex: scenario.index } + : scenario.before + ? { before: ({ $index }) => $index === scenario.index } + : { after: ({ $index }) => $index === scenario.index }) + }); + +const expectStaticResult = ( + before: StateSnapshot, + misc: Misc, + scenario: InsertScenario, + inserted: boolean +): void => { + const after = takeSnapshot(misc); + if (!inserted) { + expect(after).toEqual(before); + return; + } + + const items = makeItems(max + 1, scenario.amount); + const expectedIds = [...before.ids]; + const anchor = expectedIds.indexOf(scenario.index); + expectedIds.splice( + anchor + (scenario.before ? 0 : 1), + 0, + ...items.map(item => item.id) + ); + const firstIndex = + before.indexes[0] - (scenario.decrease ? scenario.amount : 0); + + expect(after.absMin).toBe( + before.absMin - (scenario.decrease ? scenario.amount : 0) + ); + expect(after.absMax).toBe( + before.absMax + (scenario.decrease ? 0 : scenario.amount) + ); + expect(after.bufferSize).toBe(before.bufferSize + scenario.amount); + expect(after.scrollableSize).toBe( + before.scrollableSize + scenario.amount * itemSize + ); + expect(after.indexes).toEqual( + Array.from({ length: after.bufferSize }, (_, offset) => firstIndex + offset) + ); + expect(after.ids).toEqual(expectedIds); +}; + +const runBufferedInsert = async ( + misc: Misc, + scenario: InsertScenario, + shouldInsert: boolean +): Promise => { + await misc.relaxNext(); + const items = makeItems(max + 1, scenario.amount); + const datasource = misc.source(); + datasource.insert( + items, + scenario.index, + scenario.before, + !!scenario.decrease + ); + const before = takeSnapshot(misc); + + const resultPromise = insertViaAdapter(misc, scenario, items); + if (shouldInsert) { + expect(misc.adapter.isLoading).toBe(true); + } + const result = await resultPromise; + + expect(result.success).toBe(true); + expect(result.immediate).toBe(!shouldInsert); + expect(misc.adapter.isLoading).toBe(false); + expectStaticResult(before, misc, scenario, shouldInsert); + misc.expect.uniqueItems(); + misc.expect.domIndexesMatchBuffer(); + return before; +}; + +const registerStatic = ( + scenario: InsertScenario, + shouldInsert: boolean +): void => + makeTest({ + config: createConfig(scenario), + title: shouldInsert ? 'should insert into the buffer' : 'should not insert', + meta: + `${scenario.before ? 'before' : 'after'} ${scenario.index}` + + (scenario.decrease ? ', decrease' : '') + + (scenario.useIndexApi ? ', index API' : ''), + it: misc => async () => { + await runBufferedInsert(misc, scenario, shouldInsert); + } + }); + +const registerDynamic = (scenario: InsertScenario): void => + makeTest({ + config: createConfig(scenario), + title: 'should persist insertion after scrolling', + meta: + `${scenario.before ? 'before' : 'after'} ${scenario.index}` + + (scenario.decrease ? ', decrease' : '') + + (scenario.useIndexApi ? ', index API' : ''), + it: misc => async () => { + await runBufferedInsert(misc, scenario, true); + const datasource = misc.source(); + + if (misc.getScrollPosition() !== 0) { + await misc.scrollMinRelax(); + } + await misc.scrollMaxRelax(); + datasource.clearRequests(); + await misc.scrollMinRelax(); + + expect(datasource.requests[0]?.index).toBe( + min - (scenario.decrease ? scenario.amount : 0) + ); + expect(misc.adapter.bufferInfo.absMinIndex).toBe( + min - (scenario.decrease ? scenario.amount : 0) + ); + expect(misc.adapter.bufferInfo.absMaxIndex).toBe( + max + (scenario.decrease ? 0 : scenario.amount) + ); + misc.expect.domIndexesMatchBuffer(); + } + }); + +const registerVirtual = (scenario: VirtualScenario): void => + makeTest({ + config: createVirtualConfig(), + title: 'should insert virtually and preserve it at both edges', + meta: + `${scenario.before ? 'before' : 'after'} ${scenario.index}` + + (scenario.decrease ? ', decrease' : ''), + it: misc => async () => { + await misc.relaxNext(); + const items = makeItems(max + 1, scenario.amount); + const beforeItems = [...misc.scroller.buffer.items]; + const firstVisibleId = misc.adapter.firstVisible.data.id; + misc.source().insert( + items, + scenario.index, + scenario.before, + !!scenario.decrease + ); + + await insertViaAdapter(misc, { ...scenario, useIndexApi: true }, items); + + misc.expect.itemsIdentity(beforeItems, misc.scroller.buffer.items); + expect(misc.adapter.firstVisible.data.id).toBe(firstVisibleId); + expect(misc.getScrollableSize()).toBe( + (scenario.expected.max - scenario.expected.min + 1) * itemSize + ); + + await misc.scrollToIndexRelax(scenario.expected.min, 25); + expect(misc.scroller.buffer.absMinIndex).toBe(scenario.expected.min); + expect(misc.scroller.buffer.minIndex).toBe(scenario.expected.min); + misc.scroller.buffer.items.forEach((item, index) => + expect(item.data.id).toBe(scenario.expected.ids[index]) + ); + + await misc.scrollToIndexRelax(scenario.expected.max); + expect(misc.scroller.buffer.absMaxIndex).toBe(scenario.expected.max); + expect(misc.scroller.buffer.maxIndex).toBe(scenario.expected.max); + [...misc.scroller.buffer.items] + .reverse() + .forEach((item, index) => + expect(item.data.id).toBe( + scenario.expected.ids[scenario.expected.ids.length - index - 1] + ) + ); + misc.expect.domIndexesMatchBuffer(); + } + }); + +const registerEmpty = (scenario: EmptyScenario): void => + makeTest({ + config: emptyConfig, + title: 'should insert into an empty datasource', + meta: + `${scenario.before ? 'before' : 'after'} ${scenario.index}` + + (scenario.decrease ? ', decrease' : ''), + it: misc => async () => { + await misc.relaxNext(); + expect(misc.scroller.buffer.size).toBe(0); + const items = makeItems(max + 1, scenario.amount); + + await insertViaAdapter(misc, { ...scenario, useIndexApi: true }, items); + + const { buffer } = misc.scroller; + const expected = scenario.expected; + expect(misc.getScrollableSize()).toBe( + Math.max((expected.max - expected.min + 1) * itemSize, 100) + ); + if (expected.firstVisible !== undefined) { + expect(misc.adapter.firstVisible.$index).toBe(expected.firstVisible); + } + if (expected.lastVisible !== undefined) { + expect(misc.adapter.lastVisible.$index).toBe(expected.lastVisible); + } + expect(buffer.startIndex).toBe(expected.start); + expect(buffer.absMinIndex).toBe(expected.min); + expect(buffer.minIndex).toBe(expected.min); + expect(buffer.firstIndex).toBe(expected.min); + expect(buffer.absMaxIndex).toBe(expected.max); + expect(buffer.maxIndex).toBe(expected.max); + expect(buffer.lastIndex).toBe(expected.max); + expect(buffer.items.map(item => item.data.id)).toEqual( + items.map(item => item.id) + ); + misc.expect.domIndexesMatchBuffer(); + } + }); + +describe('Adapter Insert Spec', () => { + describe('Buffer', () => { + staticScenarios.forEach(scenario => registerStatic(scenario, true)); + outsideScenarios.forEach(scenario => registerStatic(scenario, false)); + }); + + describe('Persistence after scroll', () => + dynamicScenarios.forEach(registerDynamic)); + + describe('Virtual insertion', () => + virtualScenarios.forEach(registerVirtual)); + + describe('Empty datasource', () => emptyScenarios.forEach(registerEmpty)); +}); diff --git a/tests/e2e/specs/adapter.pause.spec.ts b/tests/e2e/specs/adapter.pause.spec.ts new file mode 100644 index 00000000..ca5f41a8 --- /dev/null +++ b/tests/e2e/specs/adapter.pause.spec.ts @@ -0,0 +1,125 @@ +import { + AdapterMethodResult, + AdapterPropName as Adapter, + makeTest, + Misc +} from '../scaffolding'; + +interface BlockedCall { + method: Adapter; + invoke: (misc: Misc) => Promise; +} + +const pausedResult: AdapterMethodResult = { + success: true, + immediate: true, + details: 'Scroller is paused' +}; + +const blockedCalls: BlockedCall[] = [ + { method: Adapter.reload, invoke: misc => misc.adapter.reload() }, + { method: Adapter.clip, invoke: misc => misc.adapter.clip() }, + { + method: Adapter.fix, + invoke: misc => misc.adapter.fix({ scrollPosition: 0 }) + }, + { method: Adapter.pause, invoke: misc => misc.adapter.pause() } +]; + +const attemptScroll = async (misc: Misc, position: number): Promise => { + const cyclesDone = misc.workflow.cyclesDone; + misc.viewportElement.scrollTop = position; + misc.viewportElement.dispatchEvent(new Event('scroll')); + await misc.waitForPaint(); + + expect(misc.workflow.cyclesDone).toBe(cyclesDone); + expect(misc.adapter.isLoading).toBe(false); +}; + +const expectBlocked = async ( + misc: Misc, + scenario: BlockedCall +): Promise => { + const cyclesDone = misc.workflow.cyclesDone; + const result = await scenario.invoke(misc); + + expect(result, `${scenario.method} should be blocked`).toEqual(pausedResult); + expect(misc.workflow.cyclesDone).toBe(cyclesDone); + expect(misc.adapter.isLoading).toBe(false); +}; + +describe('Adapter Pause/Resume Spec', () => { + makeTest({ + title: 'should pause & resume', + config: {}, + it: misc => async () => { + const { adapter, workflow } = misc; + let reactivePaused = adapter.paused; + const offPaused = adapter.paused$.on(value => (reactivePaused = value)); + + await misc.relaxNext(); + const lastVisibleIndex = adapter.lastVisible.$index; + const maxIndex = adapter.bufferInfo.maxIndex; + + expect(workflow.cyclesDone).toBe(1); + expect(adapter.paused).toBe(false); + expect(reactivePaused).toBe(false); + + const pauseResult = await adapter.pause(); + expect(pauseResult).toMatchObject({ success: true, immediate: true }); + expect(adapter.paused).toBe(true); + expect(reactivePaused).toBe(true); + + const relaxResult = await adapter.relax(); + expect(relaxResult).toMatchObject({ success: true, immediate: true }); + + await attemptScroll(misc, 0); + await attemptScroll(misc, 9999); + for (const scenario of blockedCalls) { + await expectBlocked(misc, scenario); + } + + expect(workflow.cyclesDone).toBe(1); + expect(adapter.lastVisible.$index).toBe(lastVisibleIndex); + expect(adapter.bufferInfo.maxIndex).toBe(maxIndex); + + const resumeResult = await adapter.resume(); + expect(resumeResult).toMatchObject({ success: true, immediate: false }); + expect(adapter.paused).toBe(false); + expect(reactivePaused).toBe(false); + expect(workflow.cyclesDone).toBe(2); + expect(adapter.lastVisible.$index).toBe(maxIndex); + expect(adapter.bufferInfo.maxIndex).toBeGreaterThan(maxIndex); + + await misc.scrollMaxRelax(); + expect(workflow.cyclesDone).toBe(3); + await adapter.reload(); + expect(workflow.cyclesDone).toBe(4); + + offPaused(); + } + }); + + makeTest({ + title: 'should allow reset while paused', + config: {}, + it: misc => async () => { + await misc.relaxNext(); + expect(misc.workflow.cyclesDone).toBe(1); + + await misc.adapter.pause(); + await expectBlocked(misc, blockedCalls[0]); + expect(misc.workflow.cyclesDone).toBe(1); + expect(misc.adapter.paused).toBe(true); + + await misc.adapter.reset(); + expect(misc.workflow.cyclesDone).toBe(2); + expect(misc.adapter.paused).toBe(false); + + await misc.scrollMaxRelax(); + expect(misc.workflow.cyclesDone).toBe(3); + await misc.adapter.reload(); + expect(misc.workflow.cyclesDone).toBe(4); + } + }); +}); diff --git a/tests/e2e/specs/adapter.prepend.spec.ts b/tests/e2e/specs/adapter.prepend.spec.ts new file mode 100644 index 00000000..e77a7f49 --- /dev/null +++ b/tests/e2e/specs/adapter.prepend.spec.ts @@ -0,0 +1,165 @@ +import { + makeItem, + makeItems, + makeTest, + Misc, + MutableDatasource, + TestConfig +} from '../scaffolding'; + +const min = -40; +const max = 40; +const virtualAmount = 10; + +// `prependOne` / `prependMany` run against the default unbounded datasource +// (these configs set no `datasource`); only the virtual cases below build a +// bounded MutableDatasource. +const configs: TestConfig[] = [ + { + datasourceSettings: { + startIndex: 100, + bufferSize: 4, + padding: 0.22, + itemSize: 20 + }, + templateSettings: { viewportHeight: 71, itemHeight: 20 } + }, + { + datasourceSettings: { + startIndex: 1, + bufferSize: 5, + padding: 0.2, + itemSize: 20 + }, + templateSettings: { viewportHeight: 100 } + }, + { + datasourceSettings: { + startIndex: -15, + bufferSize: 12, + padding: 0.98, + itemSize: 20 + }, + templateSettings: { viewportHeight: 66, itemHeight: 20 } + }, + { + datasourceSettings: { + startIndex: 1, + bufferSize: 5, + padding: 1, + horizontal: true, + itemSize: 100 + }, + templateSettings: { viewportWidth: 450, itemWidth: 100, horizontal: true } + }, + { + datasourceSettings: { + startIndex: -74, + bufferSize: 4, + padding: 0.72, + horizontal: true, + itemSize: 75 + }, + templateSettings: { viewportWidth: 300, itemWidth: 75, horizontal: true } + } +]; + +const manyConfigs = [configs[1], configs[4]]; + +const virtualConfigs = [configs[0], configs[3]].map(config => ({ + ...config, + datasource: () => new MutableDatasource(min, max), + datasourceSettings: { + ...config.datasourceSettings, + minIndex: min, + maxIndex: max, + startIndex: 10 + }, + timeout: 4000 +})); + +const prependOne = (misc: Misc) => async () => { + await misc.relaxNext(); + const index = misc.adapter.bufferInfo.firstIndex - 1; + + await misc.adapter.prepend({ items: [makeItem(index)] }); + + expect(misc.padding.backward.getSize()).toBe(0); + expect(misc.adapter.bufferInfo.firstIndex).toBe(index); + expect(misc.checkElementContentByIndex(index)).toBe(true); +}; + +const prependMany = (misc: Misc) => async () => { + await misc.relaxNext(); + const amount = 50; + const lastIndex = misc.adapter.bufferInfo.firstIndex - 1; + const firstIndex = lastIndex - amount + 1; + const items = makeItems(firstIndex, amount).reverse(); + + await misc.adapter.prepend({ items }); + + expect(misc.padding.backward.getSize()).toBe(0); + expect(misc.adapter.bufferInfo.firstIndex).toBe(firstIndex); + expect(misc.checkElementContentByIndex(firstIndex)).toBe(true); +}; + +const registerVirtual = (config: TestConfig, increase: boolean): void => + makeTest({ + config, + title: `should prepend virtually${increase ? ' (increase)' : ''}`, + it: misc => async () => { + await misc.relaxNext(); + const itemSize = misc.scroller.settings.itemSize; + const newMin = increase ? min : min - virtualAmount; + const newMax = increase ? max + virtualAmount : max; + const firstId = min - virtualAmount; + const items = makeItems(firstId, virtualAmount); + const { buffer } = misc.scroller; + const before = { + firstIndex: buffer.firstIndex, + lastIndex: buffer.lastIndex, + backwardPadding: misc.padding.backward.getSize(), + forwardPadding: misc.padding.forward.getSize() + }; + + misc.source().reset(newMin, newMax, firstId); + await misc.adapter.prepend({ items, bof: true, increase }); + + expect(misc.getScrollableSize()).toBe((newMax - newMin + 1) * itemSize); + expect(misc.padding.backward.getSize()).toBe( + before.backwardPadding + virtualAmount * itemSize + ); + expect(misc.padding.forward.getSize()).toBe(before.forwardPadding); + expect(buffer.absMinIndex).toBe(newMin); + expect(buffer.absMaxIndex).toBe(newMax); + expect(buffer.firstIndex).toBe( + before.firstIndex + (increase ? virtualAmount : 0) + ); + expect(buffer.lastIndex).toBe( + before.lastIndex + (increase ? virtualAmount : 0) + ); + + await misc.scrollToIndexRelax(newMax, 30); + await misc.scrollToIndexRelax(newMin, 30); + + expect(misc.getScrollableSize()).toBe((newMax - newMin + 1) * itemSize); + expect(buffer.absMinIndex).toBe(newMin); + expect(buffer.absMaxIndex).toBe(newMax); + expect(buffer.firstIndex).toBe(newMin); + expect(misc.checkElementContent(newMin, firstId)).toBe(true); + } + }); + +describe('Adapter Prepend Spec', () => { + configs.forEach(config => + makeTest({ config, title: 'should prepend', it: prependOne }) + ); + + manyConfigs.forEach(config => + makeTest({ config, title: 'should prepend many', it: prependMany }) + ); + + [false, true].forEach(increase => + virtualConfigs.forEach(config => registerVirtual(config, increase)) + ); +}); diff --git a/tests/e2e/specs/adapter.reload.spec.ts b/tests/e2e/specs/adapter.reload.spec.ts new file mode 100644 index 00000000..5a64d26d --- /dev/null +++ b/tests/e2e/specs/adapter.reload.spec.ts @@ -0,0 +1,365 @@ +import { getDatasource, makeTest, Misc, TestConfig } from '../scaffolding'; + +interface ReloadExpectation { + index: number | null; + interruptions: number; + reloads: number; +} + +interface FirstVisibleScenario extends ReloadExpectation { + threshold: number; +} + +const baseConfigs: TestConfig[] = [ + { + datasource: () => getDatasource({ mode: 'promise' }), + datasourceSettings: { startIndex: 100, bufferSize: 5, padding: 0.2 }, + templateSettings: { viewportHeight: 100 } + }, + { + datasource: () => getDatasource({ mode: 'promise' }), + datasourceSettings: { startIndex: -50, bufferSize: 4, padding: 0.49 }, + templateSettings: { viewportHeight: 70 } + }, + { + datasource: () => getDatasource({ mode: 'promise' }), + datasourceSettings: { + startIndex: -33, + bufferSize: 5, + padding: 0.3, + horizontal: true + }, + templateSettings: { viewportWidth: 300, itemWidth: 100, horizontal: true } + } +]; + +const indexedReloads = [-10, 1255, 2]; +const scrolledReloads = [ + { index: -20, scrolls: 3 }, + { index: null, scrolls: 5 }, + { index: 4, scrolls: 2 } +]; +const beforeLoadReloads = [-30, null, 12]; +const asyncFetchReloads = [null, 1025, -40]; +const afterAdjustReloads = [10, 365, -14]; +const onRenderReloads = [500, -25, null]; +const syncFetchReloads = [1, 2, 3]; +const firstVisibleReloads: FirstVisibleScenario[] = [ + { index: 1, threshold: 50, interruptions: 1, reloads: 1 }, + { index: -50, threshold: -60, interruptions: 1, reloads: 1 }, + { index: -99, threshold: -55, interruptions: 1, reloads: 1 } +]; + +const largeDoubleReloadConfig: TestConfig = { + datasource: () => getDatasource({ mode: 'promise' }), + datasourceSettings: { + startIndex: 1, + itemSize: 100, + bufferSize: 10, + padding: 0.1 + }, + templateSettings: { viewportHeight: 600 } +}; + +const reload = (misc: Misc, index: number | null) => + index === null ? misc.adapter.reload() : misc.adapter.reload(index); + +const expectedStartIndex = (config: TestConfig, index: number | null): number => + index ?? (config.datasourceSettings?.startIndex as number); + +const expectReloaded = ( + misc: Misc, + config: TestConfig, + expected: ReloadExpectation +): void => { + const startIndex = expectedStartIndex(config, expected.index); + const bufferSize = config.datasourceSettings?.bufferSize as number; + const firstIndex = startIndex - bufferSize; + const nextIndex = firstIndex + bufferSize + 1; + const itemsPerViewport = Math.ceil( + misc.getViewportSize() / misc.scroller.buffer.defaultSize + ); + + expect(misc.scroller.buffer.getFirstVisibleItem()?.$index).toBe(firstIndex); + expect(misc.checkElementContentByIndex(firstIndex)).toBe(true); + expect(misc.checkElementContentByIndex(nextIndex)).toBe(true); + expect(misc.adapter.firstVisible.uid).toBeDefined(); + expect(misc.adapter.lastVisible.uid).toBeDefined(); + misc.expect.uniqueItems(); + expect(misc.adapter.firstVisible.$index).toBe(startIndex); + expect(misc.adapter.lastVisible.$index).toBe( + startIndex + itemsPerViewport - 1 + ); + expect(misc.workflow.interruptionCount).toBe(expected.interruptions); + expect(misc.scroller.adapter.reloadCount).toBe(expected.reloads); +}; + +const registerSettledReload = ( + config: TestConfig, + title: string, + index: number | null, + scrolls = 0 +): void => + makeTest({ + config, + title, + meta: index === null ? 'default index' : `index = ${index}`, + it: misc => async () => { + misc.trackVisibleItems(); + await misc.relaxNext(); + for (let step = 0; step < scrolls; step++) { + await misc.scrollMaxRelax(); + } + + await reload(misc, index); + + expectReloaded(misc, config, { + index, + interruptions: 0, + reloads: 1 + }); + } + }); + +const registerBeforeLoad = (config: TestConfig, index: number | null): void => + makeTest({ + config, + title: 'should reload between initial inner loops', + meta: index === null ? 'default index' : `index = ${index}`, + it: misc => async () => { + misc.trackVisibleItems(); + await misc.captureInnerLoops(1, () => null); + const finished = misc.waitForCycles(2); + + await reload(misc, index); + await finished; + await misc.adapter.relax(); + + expectReloaded(misc, config, { + index, + interruptions: 1, + reloads: 1 + }); + } + }); + +const registerAsyncFetch = ( + baseConfig: TestConfig, + index: number | null +): void => { + const config: TestConfig = { + ...baseConfig, + datasource: () => getDatasource({ delay: 150 }), + timeout: 5000 + }; + + makeTest({ + config, + title: 'should reload during a pending datasource request', + meta: index === null ? 'default index' : `index = ${index}`, + it: misc => async () => { + misc.trackVisibleItems(); + await misc.captureInnerLoops(1, () => null); + await misc.delay(75); + const finished = misc.waitForCycles(2); + + await reload(misc, index); + await finished; + await misc.adapter.relax(); + + expectReloaded(misc, config, { + index, + interruptions: 1, + reloads: 1 + }); + } + }); +}; + +const registerBeforeInit = (baseConfig: TestConfig): void => { + const config: TestConfig = { + ...baseConfig, + datasourceDevSettings: { initDelay: 50 } + }; + + makeTest({ + config, + title: 'should ignore reload before initialization', + it: misc => async () => { + misc.trackVisibleItems(); + const result = await misc.adapter.reload(); + expect(result).toEqual({ + immediate: true, + success: true, + details: 'Adapter is not initialized' + }); + + await misc.waitForAdapterInit(); + await misc.adapter.relax(); + + expect(misc.scroller.state.fetch.cancel).toBeNull(); + expectReloaded(misc, config, { + index: null, + interruptions: 0, + reloads: 0 + }); + } + }); +}; + +const registerDoubleReload = ( + config: TestConfig, + index: number | null, + timing: 'after adjust' | 'during render' +): void => + makeTest({ + config, + title: 'should interrupt the first reload with the second', + meta: `${timing}, ${index === null ? 'default index' : `index = ${index}`}`, + it: misc => async () => { + misc.trackVisibleItems(); + await misc.relaxNext(); + const finished = misc.waitForCycles(3); + let secondReload: ReturnType; + + if (timing === 'after adjust') { + let off = () => {}; + off = misc.scroller.state.cycle.innerLoop.busy.on(pending => { + if (!pending) { + off(); + secondReload = reload(misc, index); + } + }); + } else { + setTimeout(() => (secondReload = reload(misc, index))); + } + + const firstReload = misc.adapter.reload(10); + await finished; + await Promise.all([firstReload, secondReload!]); + + expect(misc.workflow.cyclesDone).toBe(3); + expectReloaded(misc, config, { + index, + interruptions: 1, + reloads: 2 + }); + } + }); + +const registerSyncFetch = (baseConfig: TestConfig, index: number): void => { + const config: TestConfig = { + ...baseConfig, + datasource: () => getDatasource({ delay: 1 }), + timeout: 4000 + }; + + makeTest({ + config, + title: 'should reload from inside datasource.get', + meta: `index = ${index}`, + it: misc => async () => { + misc.trackVisibleItems(); + let reloadResult: ReturnType | undefined; + misc.setDatasourceProcessor(() => { + reloadResult ??= misc.adapter.reload(index); + }); + + await misc.waitForCycles(2); + await reloadResult; + + expect(misc.workflow.cyclesDone).toBe(2); + expectReloaded(misc, config, { + index, + interruptions: 1, + reloads: 1 + }); + } + }); +}; + +const registerFirstVisibleReload = ( + config: TestConfig, + scenario: FirstVisibleScenario +): void => + makeTest({ + config, + title: 'should reload from a firstVisible change', + meta: `threshold = ${scenario.threshold}`, + it: misc => async () => { + misc.trackVisibleItems(); + let reloadResult: ReturnType | undefined; + let off = () => {}; + off = misc.adapter.firstVisible$.on(({ $index }) => { + if ( + reloadResult || + !Number.isFinite($index) || + $index > scenario.threshold + ) { + return; + } + off(); + reloadResult = reload(misc, scenario.index); + }); + + await misc.relaxNext(); + while (!reloadResult) { + await misc.scrollMinRelax(); + } + await reloadResult; + + expectReloaded(misc, config, scenario); + } + }); + +describe('Adapter Reload Spec', () => { + describe('Settled reload', () => { + baseConfigs.forEach(config => + registerSettledReload( + config, + 'should reload at the initial position', + null + ) + ); + baseConfigs.forEach((config, index) => + registerSettledReload( + config, + 'should reload at the requested position', + indexedReloads[index] + ) + ); + baseConfigs.forEach((config, index) => + registerSettledReload( + config, + 'should reload after scrolling', + scrolledReloads[index].index, + scrolledReloads[index].scrolls + ) + ); + }); + + describe('Interruption', () => { + baseConfigs.forEach((config, index) => + registerBeforeLoad(config, beforeLoadReloads[index]) + ); + baseConfigs.forEach((config, index) => + registerAsyncFetch(config, asyncFetchReloads[index]) + ); + baseConfigs.forEach((config, index) => + registerDoubleReload(config, afterAdjustReloads[index], 'after adjust') + ); + registerDoubleReload(largeDoubleReloadConfig, 999, 'after adjust'); + baseConfigs.forEach((config, index) => + registerDoubleReload(config, onRenderReloads[index], 'during render') + ); + baseConfigs.forEach((config, index) => + registerSyncFetch(config, syncFetchReloads[index]) + ); + baseConfigs.forEach((config, index) => + registerFirstVisibleReload(config, firstVisibleReloads[index]) + ); + }); + + describe('Before initialization', () => + baseConfigs.forEach(registerBeforeInit)); +}); diff --git a/tests/e2e/specs/adapter.remove.spec.ts b/tests/e2e/specs/adapter.remove.spec.ts new file mode 100644 index 00000000..85902c85 --- /dev/null +++ b/tests/e2e/specs/adapter.remove.spec.ts @@ -0,0 +1,633 @@ +import { + getDynamicSize, + makeTest, + Misc, + MutableDatasource, + SizeStrategy, + TestConfig +} from '../scaffolding'; + +interface RemovalScenario { + config: TestConfig; + indexes: number[]; + increase?: boolean; + useIndexes?: boolean; + byId?: boolean; +} + +interface DynamicScenario { + startIndex: number; + indexToRemove: number; + strategy: SizeStrategy; + increase: boolean; + reloadIndex?: number; +} + +interface FlushScenario { + startIndex: number; + maxIndex: number; + fixRight: boolean; + expectedRange: [number, number]; + meta: string; +} + +interface MixedScenario { + startIndex: number; + indexes: number[]; + increase: boolean; + expected: { min: number; max: number }; + meta: string; +} + +const min = -99; +const max = 100; +const itemSize = 20; +const oversizedItemSize = 100; + +const createConfig = ( + datasourceSettings: NonNullable, + sourceMin = min, + sourceMax = max, + templateSettings: TestConfig['templateSettings'] = { viewportHeight: 100 } +): TestConfig => ({ + datasource: () => new MutableDatasource(sourceMin, sourceMax), + datasourceSettings, + templateSettings, + timeout: 4000 +}); + +const bufferConfigs = [ + createConfig({ + startIndex: 1, + bufferSize: 5, + padding: 0.2, + itemSize + }), + createConfig({ + startIndex: 55, + bufferSize: 8, + padding: 1, + itemSize + }), + createConfig({ + startIndex: 10, + bufferSize: 5, + padding: 0.2, + itemSize + }) +]; + +const baseRemovals = [ + [3, 4, 5], + [54, 55, 56, 57, 58], + [7, 8, 9] +]; + +const interruptedRemovals = [ + [2, 3, 5, 6], + [54, 56, 57, 58] +]; + +const bufferScenarios: RemovalScenario[] = [ + ...bufferConfigs.flatMap((config, index) => [ + { config, indexes: baseRemovals[index] }, + { config, indexes: baseRemovals[index], byId: true } + ]), + ...interruptedRemovals.map((indexes, index) => ({ + config: bufferConfigs[index], + indexes + })), + ...bufferConfigs.map((config, index) => ({ + config, + indexes: baseRemovals[index], + increase: true + })), + { + config: bufferConfigs[0], + indexes: baseRemovals[0], + useIndexes: true + }, + { + config: bufferConfigs[1], + indexes: interruptedRemovals[1], + useIndexes: true + }, + { + config: bufferConfigs[2], + indexes: baseRemovals[2], + increase: true, + useIndexes: true + } +]; + +const virtualConfig = createConfig({ + startIndex: 1, + minIndex: min, + maxIndex: max, + bufferSize: 5, + padding: 0.2, + itemSize +}); + +const virtualScenarios: RemovalScenario[] = [ + { + config: virtualConfig, + indexes: [51, 52, 53, 54, 55], + useIndexes: true + }, + { + config: virtualConfig, + indexes: [-51, -52, -53, -54, -55], + useIndexes: true + }, + { + config: virtualConfig, + indexes: [-51, -52, -53, -54, -55, 51, 52, 53, 54, 55], + useIndexes: true + } +]; + +const dynamicScenarios: DynamicScenario[] = [ + { + startIndex: 10, + indexToRemove: 11, + strategy: SizeStrategy.Average, + increase: false + }, + { + startIndex: 11, + indexToRemove: 9, + strategy: SizeStrategy.Frequent, + increase: false + }, + { + startIndex: 10, + indexToRemove: 11, + strategy: SizeStrategy.Average, + increase: true + }, + { + startIndex: 11, + indexToRemove: 9, + strategy: SizeStrategy.Frequent, + increase: true + }, + { + startIndex: 20, + indexToRemove: 20, + strategy: SizeStrategy.Average, + increase: false, + reloadIndex: 2 + }, + { + startIndex: 1, + indexToRemove: 1, + strategy: SizeStrategy.Frequent, + increase: false, + reloadIndex: 15 + }, + { + startIndex: 20, + indexToRemove: 20, + strategy: SizeStrategy.Average, + increase: true, + reloadIndex: 2 + }, + { + startIndex: 1, + indexToRemove: 1, + strategy: SizeStrategy.Frequent, + increase: true, + reloadIndex: 15 + } +]; + +const flushScenarios: FlushScenario[] = [ + { + startIndex: 1, + maxIndex: 20, + fixRight: false, + expectedRange: [1, 8], + meta: 'BOF' + }, + { + startIndex: 1, + maxIndex: 15, + fixRight: true, + expectedRange: [9, 15], + meta: 'BOF, 15 items' + }, + { + startIndex: 1, + maxIndex: 30, + fixRight: true, + expectedRange: [9, 16], + meta: 'BOF, 30 items' + }, + { + startIndex: 20, + maxIndex: 20, + fixRight: false, + expectedRange: [5, 12], + meta: 'EOF' + }, + { + startIndex: 15, + maxIndex: 15, + fixRight: true, + expectedRange: [9, 15], + meta: 'EOF, 15 items' + }, + { + startIndex: 15, + maxIndex: 25, + fixRight: true, + expectedRange: [18, 25], + meta: 'EOF, 25 items' + } +]; + +const mixedScenarios: MixedScenario[] = [ + { + startIndex: 1, + indexes: [1, 20], + increase: false, + expected: { min: 1, max: 18 }, + meta: 'forward, edge items' + }, + { + startIndex: 1, + indexes: [1, 20], + increase: true, + expected: { min: 3, max: 20 }, + meta: 'forward, edge items, increase' + }, + { + startIndex: 20, + indexes: [1, 20], + increase: false, + expected: { min: 1, max: 18 }, + meta: 'backward, edge items' + }, + { + startIndex: 20, + indexes: [1, 20], + increase: true, + expected: { min: 3, max: 20 }, + meta: 'backward, edge items, increase' + }, + { + startIndex: 1, + indexes: [2, 3, 17, 18], + increase: false, + expected: { min: 1, max: 16 }, + meta: 'forward, inner items' + }, + { + startIndex: 1, + indexes: [2, 3, 17, 18], + increase: true, + expected: { min: 5, max: 20 }, + meta: 'forward, inner items, increase' + }, + { + startIndex: 20, + indexes: [2, 3, 17, 18], + increase: false, + expected: { min: 1, max: 16 }, + meta: 'backward, inner items' + }, + { + startIndex: 20, + indexes: [2, 3, 17, 18], + increase: true, + expected: { min: 5, max: 20 }, + meta: 'backward, inner items, increase' + } +]; + +const remove = async ( + misc: Misc, + scenario: Pick< + RemovalScenario, + 'indexes' | 'increase' | 'useIndexes' | 'byId' + > +) => { + const { indexes, increase, useIndexes, byId } = scenario; + misc.source().remove(indexes, increase); + return useIndexes + ? misc.adapter.remove({ indexes, increase }) + : misc.adapter.remove({ + predicate: item => indexes.includes(byId ? item.data.id : item.$index), + increase + }); +}; + +const registerBufferRemoval = (scenario: RemovalScenario): void => + makeTest({ + config: scenario.config, + title: 'should remove buffered items', + meta: + (scenario.byId + ? 'by id' + : scenario.useIndexes + ? 'indexes API' + : 'by index') + (scenario.increase ? ', increase' : ''), + it: misc => async () => { + await misc.relaxNext(); + const { buffer } = misc.scroller; + const before = { + size: buffer.size, + minIndex: buffer.minIndex, + maxIndex: buffer.maxIndex, + absMinIndex: buffer.absMinIndex, + absMaxIndex: buffer.absMaxIndex, + scrollableSize: misc.getScrollableSize() + }; + const firstLoop = misc.captureInnerLoops(1, () => ({ + size: buffer.size, + minIndex: buffer.minIndex, + maxIndex: buffer.maxIndex, + absMinIndex: buffer.absMinIndex, + absMaxIndex: buffer.absMaxIndex, + scrollableSize: misc.getScrollableSize() + })); + + await remove(misc, scenario); + const [after] = await firstLoop; + const count = scenario.indexes.length; + const increase = !!scenario.increase; + + expect(after).toEqual({ + size: before.size - count, + minIndex: before.minIndex + (increase ? count : 0), + maxIndex: before.maxIndex - (increase ? 0 : count), + absMinIndex: before.absMinIndex + (increase ? count : 0), + absMaxIndex: before.absMaxIndex - (increase ? 0 : count), + scrollableSize: before.scrollableSize - count * itemSize + }); + + buffer.items.forEach(item => { + const shift = scenario.indexes.reduce( + (total, removedIndex) => + total + + (increase + ? item.data.id < removedIndex + ? -1 + : 0 + : item.data.id > removedIndex + ? 1 + : 0), + 0 + ); + expect(item.data.id).toBe(item.$index + shift); + }); + } + }); + +const registerVirtualRemoval = (scenario: RemovalScenario): void => + makeTest({ + config: scenario.config, + title: 'should remove fixed-size items outside the buffer', + meta: scenario.indexes.every(index => index < 0) + ? 'backward' + : scenario.indexes.every(index => index > 0) + ? 'forward' + : 'both directions', + it: misc => async () => { + await misc.relaxNext(); + const { + $index: firstVisibleIndex, + uid: firstVisibleUid, + data: { id: firstVisibleId } + } = misc.adapter.firstVisible; + const expectedSize = (max - min + 1 - scenario.indexes.length) * itemSize; + const removedBefore = scenario.indexes.filter( + index => index < firstVisibleIndex + ).length; + + await remove(misc, scenario); + + expect(misc.getScrollableSize()).toBe(expectedSize); + expect(misc.adapter.firstVisible.$index).toBe( + firstVisibleIndex - removedBefore + ); + expect(misc.adapter.firstVisible.uid).toBe(firstVisibleUid); + expect(misc.adapter.firstVisible.data.id).toBe(firstVisibleId); + misc.expect.uniqueItems(); + + const firstRemoved = Math.min(...scenario.indexes); + const previous = firstRemoved - 1; + const sortedIndexes = [...scenario.indexes].sort((a, b) => a - b); + const gap = sortedIndexes.findIndex( + (index, offset) => index !== firstRemoved + offset + ); + const adjacentRemoved = gap < 0 ? sortedIndexes.length : gap; + await misc.scrollToRelax((previous - min) * itemSize); + expect(misc.adapter.firstVisible.$index).toBe(previous); + expect(misc.checkElementContentByIndex(previous)).toBe(true); + expect( + misc.checkElementContent(firstRemoved, firstRemoved + adjacentRemoved) + ).toBe(true); + + await misc.scrollMaxRelax(); + expect(misc.adapter.lastVisible.$index).toBe( + max - scenario.indexes.length + ); + expect(misc.checkElementContent(max - scenario.indexes.length, max)).toBe( + true + ); + expect(misc.getScrollableSize()).toBe(expectedSize); + } + }); + +const registerDynamicRemoval = (scenario: DynamicScenario): void => { + const source = () => { + const datasource = new MutableDatasource(1, 20); + datasource.setSizes(getDynamicSize); + datasource.setSize(scenario.indexToRemove, oversizedItemSize); + return datasource; + }; + const config: TestConfig = { + datasource: source, + datasourceSettings: { + startIndex: scenario.startIndex, + minIndex: 1, + maxIndex: 20, + bufferSize: 1, + padding: 0.5, + sizeStrategy: scenario.strategy + }, + datasourceDevSettings: + scenario.reloadIndex === undefined ? {} : { cacheOnReload: true }, + templateSettings: { viewportHeight: 100, dynamicSize: 'size' }, + timeout: 4000 + }; + + makeTest({ + config, + title: 'should remove a dynamic-size item', + meta: + `${scenario.strategy}, ${scenario.reloadIndex === undefined ? 'buffered' : 'virtual'}` + + (scenario.increase ? ', increase' : ''), + it: misc => async () => { + await misc.relaxNext(); + if (scenario.reloadIndex !== undefined) { + await misc.adapter.reload(scenario.reloadIndex); + } + + const firstVisibleIndex = misc.adapter.firstVisible.$index; + const firstVisibleId = misc.adapter.firstVisible.data.id; + const defaultSize = misc.scroller.buffer.defaultSize; + misc + .source() + .remove([scenario.indexToRemove], scenario.increase); + await misc.adapter.remove({ + indexes: [scenario.indexToRemove], + increase: scenario.increase + }); + + const shift = scenario.increase + ? Number(scenario.indexToRemove > firstVisibleIndex) + : -Number(scenario.indexToRemove < firstVisibleIndex); + expect(misc.adapter.firstVisible.$index).toBe(firstVisibleIndex + shift); + expect(misc.adapter.firstVisible.data.id).toBe(firstVisibleId); + if (scenario.strategy === SizeStrategy.Average) { + expect(misc.scroller.buffer.defaultSize).not.toBe(defaultSize); + } + + await misc.scrollMinRelax(); + await misc.scrollToIndexRelax(scenario.increase ? 20 : 19, 30); + const remainingSize = Array.from( + { length: 20 }, + (_, offset) => offset + 1 + ) + .filter(index => index !== scenario.indexToRemove) + .reduce((total, index) => total + getDynamicSize(index), 0); + expect(misc.getScrollableSize()).toBe(remainingSize); + } + }); +}; + +const registerFlush = (scenario: FlushScenario): void => { + const config = createConfig( + { + startIndex: scenario.startIndex, + minIndex: 1, + maxIndex: scenario.maxIndex, + bufferSize: 1, + padding: 0.5 + }, + 1, + scenario.maxIndex + ); + + makeTest({ + config, + title: 'should continue after removing the entire buffer', + meta: scenario.meta + (scenario.fixRight ? ', increase' : ''), + it: misc => async () => { + await misc.relaxNext(); + const indexes = misc.scroller.buffer.items.map(item => item.$index); + + misc.source().remove(indexes, scenario.fixRight); + await misc.adapter.remove({ + predicate: _item => true, + increase: scenario.fixRight + }); + + expect(misc.workflow.cyclesDone).toBe(2); + expect(misc.scroller.state.cycle.innerLoop.count).toBeGreaterThan(1); + misc.expect.bufferRange(scenario.expectedRange); + } + }); +}; + +const registerMixedRemoval = (scenario: MixedScenario): void => { + const config = createConfig( + { + startIndex: scenario.startIndex, + minIndex: 1, + maxIndex: 20, + bufferSize: 1, + padding: 0.3 + }, + 1, + 20 + ); + + makeTest({ + config, + title: 'should remove buffered and virtual items together', + meta: scenario.meta, + it: misc => async () => { + await misc.relaxNext(); + misc + .source() + .remove(scenario.indexes, scenario.increase); + await misc.adapter.remove({ + indexes: scenario.indexes, + increase: scenario.increase + }); + + expect(misc.scroller.buffer.absMinIndex).toBe(scenario.expected.min); + expect(misc.scroller.buffer.absMaxIndex).toBe(scenario.expected.max); + } + }); +}; + +describe('Adapter Remove Spec', () => { + describe('Buffer', () => bufferScenarios.forEach(registerBufferRemoval)); + + describe('No-op and invalid input', () => { + [ + { predicate: ({ $index }: { $index: number }) => $index > 999 }, + { indexes: [999] } + ].forEach(options => + makeTest({ + config: bufferConfigs[0], + title: 'should not run a cycle when nothing is removed', + meta: 'predicate' in options ? 'predicate' : 'indexes', + it: misc => async () => { + await misc.relaxNext(); + const innerLoops = misc.innerLoopCount; + + await misc.adapter.remove(options); + + expect(misc.workflow.cyclesDone).toBe(1); + expect(misc.innerLoopCount).toBe(innerLoops); + expect(misc.workflow.errors).toHaveLength(0); + } + }) + ); + + [null, () => null, (_item: unknown, _extra: unknown) => null].forEach( + predicate => + makeTest({ + config: bufferConfigs[0], + title: 'should reject an invalid predicate', + it: misc => async () => { + await misc.relaxNext(); + const innerLoops = misc.innerLoopCount; + + const result = await misc.adapter.remove({ + predicate: predicate as never + }); + + expect(result.success).toBe(false); + expect(misc.workflow.cyclesDone).toBe(1); + expect(misc.innerLoopCount).toBe(innerLoops); + expect(misc.workflow.errors).toHaveLength(1); + expect(misc.workflow.errors[0].process).toContain('remove'); + } + }) + ); + }); + + describe('Virtual', () => virtualScenarios.forEach(registerVirtualRemoval)); + describe('Dynamic size', () => + dynamicScenarios.forEach(registerDynamicRemoval)); + describe('Flush', () => flushScenarios.forEach(registerFlush)); + describe('Buffer and virtual', () => + mixedScenarios.forEach(registerMixedRemoval)); +}); diff --git a/tests/e2e/specs/adapter.replace.spec.ts b/tests/e2e/specs/adapter.replace.spec.ts new file mode 100644 index 00000000..fcb25a64 --- /dev/null +++ b/tests/e2e/specs/adapter.replace.spec.ts @@ -0,0 +1,145 @@ +import { + makeTest, + MutableDatasource, + TestConfig, + TestItem +} from '../scaffolding'; + +type Position = 'first' | 'middle' | 'last'; + +interface ReplaceScenario { + position: Position; + indexes: number[]; + amount: number; + fixRight?: boolean; +} + +interface ScenarioGroup { + title: string; + scenarios: ReplaceScenario[]; +} + +const min = 1; +const max = 100; +const itemSize = 20; + +const oneToOne: ReplaceScenario[] = [ + { position: 'middle', indexes: [2], amount: 1 }, + { position: 'first', indexes: [1], amount: 1 }, + { position: 'last', indexes: [100], amount: 1 } +]; + +const manyToOne: ReplaceScenario[] = [ + { position: 'middle', indexes: [2, 3, 4], amount: 1 }, + { position: 'first', indexes: [1, 2, 3], amount: 1 }, + { position: 'last', indexes: [98, 99, 100], amount: 1 } +]; + +const manyToMany = [2, 3, 4].flatMap(amount => + manyToOne.map(scenario => ({ ...scenario, amount })) +); + +const groups: ScenarioGroup[] = [ + { title: 'one-to-one', scenarios: oneToOne }, + { title: 'many-to-one', scenarios: manyToOne }, + { + title: 'many-to-one, fixRight', + scenarios: manyToOne.map(scenario => ({ ...scenario, fixRight: true })) + }, + { title: 'many-to-many', scenarios: manyToMany }, + { + title: 'many-to-many, fixRight', + scenarios: [manyToMany[0], manyToMany[5], manyToMany[8]].map(scenario => ({ + ...scenario, + fixRight: true + })) + } +]; + +const createConfig = (scenario: ReplaceScenario): TestConfig => ({ + datasource: () => new MutableDatasource(min, max), + datasourceSettings: { + startIndex: scenario.position === 'last' ? max : min, + minIndex: min, + maxIndex: max, + itemSize + }, + templateSettings: { viewportHeight: 120, itemHeight: itemSize } +}); + +const makeReplacement = (index: number): TestItem => ({ + id: index, + text: `item #${index} *` +}); + +const registerScenario = (scenario: ReplaceScenario, group: string): void => + makeTest({ + config: createConfig(scenario), + title: `should replace ${scenario.position} ${scenario.indexes.length} to ${scenario.amount}`, + meta: group, + it: misc => async () => { + await misc.relaxNext(); + const removed = scenario.indexes.length; + const delta = scenario.amount - removed; + const firstIndex = scenario.indexes[0] - (scenario.fixRight ? delta : 0); + const lastIndex = firstIndex + scenario.amount - 1; + const absMinIndex = min - (scenario.fixRight ? delta : 0); + const items = Array.from({ length: scenario.amount }, (_, offset) => + makeReplacement(firstIndex + offset) + ); + const scrollableSize = misc.getScrollableSize(); + + misc + .source() + .replace(scenario.indexes, items, scenario.fixRight); + await misc.adapter.replace({ + predicate: ({ $index }) => scenario.indexes.includes($index), + items, + fixRight: scenario.fixRight + }); + + await misc.scrollMinMaxRelax(); + const position = + scenario.position === 'last' + ? misc.getScrollableSize() - misc.getViewportSize() + : (firstIndex - absMinIndex) * itemSize; + if (misc.getScrollPosition() !== position) { + await misc.scrollToRelax(position); + } + + if (scenario.position === 'last') { + expect(misc.adapter.lastVisible.$index).toBe(lastIndex); + } else { + expect(misc.adapter.firstVisible.$index).toBe(firstIndex); + } + expect(misc.getElementText(firstIndex)).toBe( + `${firstIndex}: ${items[0].text}` + ); + expect(misc.getElementText(lastIndex)).toBe( + `${lastIndex}: ${items[items.length - 1].text}` + ); + + // The neighbor is the untouched original item just outside the replaced + // range (before it for 'last', after it otherwise). Its id is its original + // datasource id: unchanged on the pinned side, but shifted by `delta` on the + // side that absorbs the size change (min when fixRight, max otherwise). + const neighborIndex = + scenario.position === 'last' ? firstIndex - 1 : lastIndex + 1; + const neighborId = + scenario.position === 'last' + ? firstIndex - (scenario.fixRight ? 1 - delta : 1) + : lastIndex + (scenario.fixRight ? 1 : 1 - delta); + expect(misc.checkElementContent(neighborIndex, neighborId)).toBe(true); + expect(misc.getScrollableSize()).toBe(scrollableSize + delta * itemSize); + misc.expect.uniqueItems(); + } + }); + +describe('Adapter Replace Spec', () => + groups.forEach(group => + describe(group.title, () => + group.scenarios.forEach(scenario => + registerScenario(scenario, group.title) + ) + ) + )); diff --git a/tests/e2e/specs/adapter.reset-persistence.spec.ts b/tests/e2e/specs/adapter.reset-persistence.spec.ts new file mode 100644 index 00000000..5b4f12c6 --- /dev/null +++ b/tests/e2e/specs/adapter.reset-persistence.spec.ts @@ -0,0 +1,248 @@ +import { + AdapterPropName as Adapter, + getDefaultAdapterProps, + getDatasource, + makeDatasource, + makeTest, + Misc, + starGet, + DatasourceGet, + IDatasourceOptional, + TestConfig +} from '../scaffolding'; + +interface ResetVariant { + isNew: boolean; + options: IDatasourceOptional; +} + +type PermanentProp = Adapter.id | Adapter.version; + +const Datasource = makeDatasource(); +const adapterProps = getDefaultAdapterProps(); + +const mainConfig: TestConfig = { + datasource: () => getDatasource({ mode: 'promise' }), + datasourceSettings: { startIndex: 1 }, + datasourceDevSettings: { initDelay: 10 }, + templateSettings: { viewportHeight: 100 } +}; + +const bofConfig: TestConfig = { + datasource: () => getDatasource({ min: 1, max: 100 }), + datasourceSettings: { startIndex: 1, bufferSize: 25 }, + datasourceDevSettings: { initDelay: 10 }, + templateSettings: { viewportHeight: 100 } +}; + +const workflowConfig: TestConfig = { + datasource: () => getDatasource({ delay: 150 }), + datasourceSettings: { startIndex: 1 }, + templateSettings: { viewportHeight: 100 }, + timeout: 5000 +}; + +const variants: ResetVariant[] = [ + { + isNew: true, + options: { get: starGet, settings: { startIndex: 100 } } + }, + { + isNew: false, + options: { get: starGet, settings: { startIndex: 100 } } + } +]; + +const bofVariants: ResetVariant[] = [ + { isNew: true, options: { settings: { startIndex: 1 } } }, + { isNew: false, options: { settings: { startIndex: 1 } } } +]; + +const reset = (misc: Misc, variant: ResetVariant) => { + if (!variant.isNew) { + return misc.adapter.reset(variant.options); + } + + const datasource = new Datasource({ + get: + variant.options.get ?? + (misc.datasource.get as unknown as DatasourceGet), + settings: variant.options.settings, + devSettings: variant.options.devSettings + }); + return misc.adapter.reset(datasource).finally(() => datasource.dispose()); +}; + +const expectAdaptersLinked = ( + misc: Misc, + publicAdapter = misc.adapter +): void => { + expect(misc.datasource.adapter).toBe(publicAdapter); + expect(misc.scroller.datasource.adapter).toBe(publicAdapter); + expect(misc.adapter).toBe(publicAdapter); +}; + +const adapters = (misc: Misc) => [ + misc.adapter, + misc.scroller.datasource.adapter, + misc.scroller.adapter +]; + +const registerPermanentProp = ( + token: PermanentProp, + variant: ResetVariant +): void => + makeTest({ + config: mainConfig, + title: `should persist ${token}`, + meta: variant.isNew ? 'new datasource' : 'partial datasource', + it: misc => async () => { + const publicAdapter = misc.adapter; + const value = misc.scroller.adapter[token]; + const definition = adapterProps.find(prop => prop.name === token); + + expect(value).toBeTruthy(); + expect(definition?.permanent).toBe(true); + expect(definition?.value).toBeFalsy(); + expectAdaptersLinked(misc, publicAdapter); + + await misc.relaxNext(); + expect(misc.scroller.adapter[token]).toBe(value); + await reset(misc, variant); + + expectAdaptersLinked(misc, publicAdapter); + expect(misc.scroller.adapter[token]).toBe(value); + } + }); + +const registerItemsCount = (variant: ResetVariant): void => + makeTest({ + config: mainConfig, + title: 'should keep itemsCount connected to the active buffer', + meta: variant.isNew ? 'new datasource' : 'partial datasource', + it: misc => async () => { + const publicAdapter = misc.adapter; + const definition = adapterProps.find( + prop => prop.name === Adapter.itemsCount + ); + + expect(publicAdapter.itemsCount).toBe(definition?.value); + await misc.relaxNext(); + expect(publicAdapter.itemsCount).toBe( + misc.scroller.buffer.getVisibleItemsCount() + ); + + await reset(misc, variant); + + expectAdaptersLinked(misc, publicAdapter); + expect(publicAdapter.itemsCount).toBe( + misc.scroller.buffer.getVisibleItemsCount() + ); + } + }); + +const registerIsLoading = (variant: ResetVariant): void => + makeTest({ + config: mainConfig, + title: 'should preserve isLoading$ subscriptions', + meta: variant.isNew ? 'new datasource' : 'partial datasource', + it: misc => async () => { + const targets = adapters(misc); + const emissions = targets.map(() => []); + const offs = targets.map((adapter, index) => + adapter.isLoading$.on(value => { + emissions[index].push(value); + expect(value).toBe(adapter.isLoading); + }) + ); + + await misc.relaxNext(); + await reset(misc, variant); + offs.forEach(off => off()); + + // isLoading toggles once for the initial load and once for the reset load + emissions.forEach(values => + expect(values).toEqual([true, false, true, false]) + ); + } + }); + +const registerFirstVisible = (variant: ResetVariant): void => + makeTest({ + config: mainConfig, + title: 'should preserve firstVisible$ subscriptions', + meta: variant.isNew ? 'new datasource' : 'partial datasource', + it: misc => async () => { + const targets = adapters(misc); + const emissions = targets.map>(() => []); + const offs = targets.map((adapter, index) => + adapter.firstVisible$.on(value => { + emissions[index].push(value.$index); + expect(value.$index).toBe(adapter.firstVisible.$index); + }) + ); + + await misc.relaxNext(); + await reset(misc, variant); + offs.forEach(off => off()); + + // undefined before init, 1 after init (startIndex 1), 100 after reset + emissions.forEach(values => expect(values).toEqual([undefined, 1, 100])); + } + }); + +const registerBof = (variant: ResetVariant): void => + makeTest({ + config: bofConfig, + title: 'should preserve bof$ subscriptions', + meta: variant.isNew ? 'new datasource' : 'partial datasource', + it: misc => async () => { + const targets = adapters(misc); + const emissions = targets.map(() => []); + const offs = targets.map((adapter, index) => + adapter.bof$.on(value => { + emissions[index].push(value); + expect(value).toBe(adapter.bof); + }) + ); + + await misc.relaxNext(); + await misc.scrollMaxRelax(); + await reset(misc, variant); + offs.forEach(off => off()); + + // bof at start, false after scrollMax, true again after reset to startIndex 1 + emissions.forEach(values => expect(values).toEqual([true, false, true])); + } + }); + +describe('Adapter Reset Persistence Spec', () => { + variants.forEach(variant => { + registerPermanentProp(Adapter.id, variant); + registerPermanentProp(Adapter.version, variant); + registerItemsCount(variant); + registerIsLoading(variant); + registerFirstVisible(variant); + }); + + bofVariants.forEach(registerBof); + + makeTest({ + config: workflowConfig, + title: 'should keep workflow calls working after reset', + it: misc => async () => { + await misc.relaxNext(); + expect(misc.workflow.cyclesDone).toBe(1); + + const thirdCycle = misc.waitForCycles(3); + const resetResult = misc.adapter.reset(); + const reloadResult = misc.adapter.reload(); + await thirdCycle; + await Promise.all([resetResult, reloadResult]); + expect(misc.workflow.cyclesDone).toBe(3); + + await misc.adapter.reload(); + expect(misc.workflow.cyclesDone).toBe(4); + } + }); +}); diff --git a/tests/e2e/specs/adapter.reset.spec.ts b/tests/e2e/specs/adapter.reset.spec.ts new file mode 100644 index 00000000..d3abc675 --- /dev/null +++ b/tests/e2e/specs/adapter.reset.spec.ts @@ -0,0 +1,219 @@ +import { + Direction, + getDatasource, + makeTest, + Misc, + starGet, + IDatasourceOptional, + TestConfig +} from '../scaffolding'; + +interface ResetSnapshot { + instanceIndex: number; + firstVisible: number; + firstVisibleUid: number; + lastVisible: number; + firstVisibleText: string; + interruptions: number; +} + +interface ResetScenario { + config: TestConfig; + options?: IDatasourceOptional; + scrolls?: number; + direction?: Direction; + interruption?: boolean; + starData?: boolean; +} + +const baseConfigs: TestConfig[] = [ + { + datasource: () => getDatasource({ mode: 'promise' }), + datasourceSettings: { startIndex: 1, bufferSize: 5, padding: 0.25 }, + templateSettings: { viewportHeight: 100 } + }, + { + datasource: () => getDatasource({ mode: 'promise' }), + datasourceSettings: { startIndex: -123, bufferSize: 6, padding: 0.62 }, + templateSettings: { viewportHeight: 160 } + }, + { + datasource: () => getDatasource({ mode: 'promise' }), + datasourceSettings: { + startIndex: -33, + bufferSize: 5, + padding: 0.3, + horizontal: true + }, + templateSettings: { viewportWidth: 300, itemWidth: 100, horizontal: true } + } +]; + +const settingsScenarios: ResetScenario[] = [baseConfigs[0], baseConfigs[2]].map( + config => ({ + config, + options: { + settings: { + ...config.datasourceSettings, + startIndex: 999 + } as IDatasourceOptional['settings'] + } + }) +); + +const scrollScenarios: ResetScenario[] = settingsScenarios.flatMap(scenario => + [Direction.forward, Direction.backward].map(direction => ({ + ...scenario, + scrolls: 4, + direction + })) +); + +const newGetScenarios: ResetScenario[] = [baseConfigs[0], baseConfigs[2]].map( + config => ({ config, options: { get: starGet }, starData: true }) +); + +const invalidOptions: unknown[] = [ + { settings: 'bad' }, + { devSettings: 'bad' }, + { get: 'bad' }, + { get: (_index: number) => 'bad' }, + { + get: (_index: number, _count: number) => null, + settings: {}, + devSettings: 'bad' + } +]; + +const takeSnapshot = (misc: Misc): ResetSnapshot => ({ + instanceIndex: misc.scroller.settings.instanceIndex, + firstVisible: misc.adapter.firstVisible.$index, + firstVisibleUid: misc.adapter.firstVisible.uid, + lastVisible: misc.adapter.lastVisible.$index, + firstVisibleText: misc.adapter.firstVisible.data.text, + interruptions: misc.workflow.interruptionCount +}); + +const expectReset = ( + misc: Misc, + before: ResetSnapshot, + scenario: ResetScenario +): void => { + const configuredStart = scenario.options?.settings?.startIndex; + const firstVisible = + configuredStart === undefined ? before.firstVisible : configuredStart; + const after = takeSnapshot(misc); + + expect(after.instanceIndex).toBe(before.instanceIndex + 1); + expect(after.firstVisible).toBe(firstVisible); + expect(after.firstVisibleUid).not.toBe(before.firstVisibleUid); + expect(after.lastVisible).toBe( + firstVisible + before.lastVisible - before.firstVisible + ); + expect(after.firstVisibleText).toBe( + `item #${firstVisible}${scenario.starData ? ' *' : ''}` + ); + expect(after.interruptions).toBe( + before.interruptions + Number(!!scenario.interruption) + ); + misc.expect.uniqueItems(); +}; + +const reset = (misc: Misc, options?: IDatasourceOptional) => + options ? misc.adapter.reset(options) : misc.adapter.reset(); + +const registerReset = (scenario: ResetScenario, title: string): void => + makeTest({ + config: scenario.config, + title, + meta: + scenario.options?.settings?.startIndex !== undefined + ? `start = ${scenario.options.settings.startIndex}` + : scenario.starData + ? 'new get' + : scenario.interruption + ? 'interrupted' + : 'same datasource', + it: misc => async () => { + misc.trackVisibleItems(); + await misc.relaxNext(); + for (let step = 0; step < (scenario.scrolls ?? 0); step++) { + await (scenario.direction === Direction.backward + ? misc.scrollMinRelax() + : misc.scrollMaxRelax()); + } + const before = takeSnapshot(misc); + + if (scenario.interruption) { + // Start a reload, then fire reset on the next macrotask so it lands + // while the reload is still pending and interrupts it. Both settle + // within the two cycles we wait for. + const finished = misc.waitForCycles(misc.workflow.cyclesDone + 2); + const reloadResult = misc.adapter.reload(); + let resetResult: ReturnType; + setTimeout(() => (resetResult = reset(misc, scenario.options))); + await finished; + await Promise.all([reloadResult, resetResult!]); + } else { + await reset(misc, scenario.options); + } + + expectReset(misc, before, scenario); + } + }); + +describe('Adapter Reset Spec', () => { + describe('Without parameters', () => + baseConfigs.forEach(config => + registerReset({ config }, 'should reset at the current position') + )); + + describe('Invalid parameters', () => + invalidOptions.forEach((options, index) => + makeTest({ + config: baseConfigs[0], + title: 'should reject invalid reset parameters', + meta: `case ${index + 1}`, + it: misc => async () => { + misc.trackVisibleItems(); + await misc.relaxNext(); + const before = takeSnapshot(misc); + + const result = await misc.adapter.reset( + options as IDatasourceOptional + ); + + expect(result.success).toBe(false); + expect(result.immediate).toBe(true); + expect(misc.workflow.cyclesDone).toBe(1); + expect(takeSnapshot(misc)).toEqual(before); + expect( + misc.workflow.errors.some(error => error.process.endsWith('reset')) + ).toBe(true); + } + }) + )); + + describe('Interruption', () => + [baseConfigs[0], baseConfigs[2]].forEach(config => + registerReset( + { config, interruption: true }, + 'should interrupt a pending reload' + ) + )); + + describe('New settings', () => + settingsScenarios.forEach(scenario => + registerReset(scenario, 'should reset at the new position') + )); + + describe('New settings after scrolling', () => + scrollScenarios.forEach(scenario => + registerReset(scenario, 'should reset after scrolling') + )); + + describe('New get', () => + newGetScenarios.forEach(scenario => + registerReset(scenario, 'should reset with new data') + )); +}); diff --git a/tests/e2e/specs/adapter.update.spec.ts b/tests/e2e/specs/adapter.update.spec.ts new file mode 100644 index 00000000..2714582a --- /dev/null +++ b/tests/e2e/specs/adapter.update.spec.ts @@ -0,0 +1,408 @@ +import { + BufferUpdater, + makeTest, + Misc, + MutableDatasource, + TestConfig, + TestItem +} from '../scaffolding'; + +interface ExpectedRow { + index: number; + text: string; +} + +interface IdentityExpectation { + stable: string[]; + fresh: string[]; +} + +interface UpdateOutcome { + rows: ExpectedRow[]; + identity: IdentityExpectation; + firstVisible: number; +} + +interface UpdateScenario { + title: string; + startIndex?: number; + predicate: BufferUpdater; + left: UpdateOutcome; + right: UpdateOutcome; + /** Settled Average-strategy value captured from the implementation. */ + expectedDefaultSize?: number; +} + +const min = -29; +const max = 30; +const itemSize = 20; +const viewportSize = 100; +const itemsPerPage = viewportSize / itemSize; + +const make = (text: string, size: number): TestItem => ({ id: 0, text, size }); +const row = (index: number, text: string): ExpectedRow => ({ index, text }); +const outcome = ( + rows: ExpectedRow[], + stable: string[], + fresh: string[], + firstVisible: number +): UpdateOutcome => ({ + rows, + identity: { stable, fresh }, + firstVisible +}); + +const scenarios: UpdateScenario[] = [ + { + title: 'no-op', + predicate: _item => true, + left: outcome( + [row(0, 'item #0'), row(1, 'item #1'), row(2, 'item #2')], + ['item #0', 'item #1', 'item #2'], + [], + 1 + ), + right: outcome( + [row(0, 'item #0'), row(1, 'item #1'), row(2, 'item #2')], + ['item #0', 'item #1', 'item #2'], + [], + 1 + ) + }, + { + title: 'replace one-to-one', + predicate: ({ $index }) => ($index === 1 ? [make('xxx', 125)] : true), + left: outcome( + [row(0, 'item #0'), row(1, 'xxx'), row(2, 'item #2')], + ['item #0', 'item #2'], + ['xxx'], + 1 + ), + right: outcome( + [row(0, 'item #0'), row(1, 'xxx'), row(2, 'item #2')], + ['item #0', 'item #2'], + ['xxx'], + 1 + ), + expectedDefaultSize: 28 + }, + { + title: 'replace one-to-three', + predicate: ({ $index }) => + $index === 3 ? [make('xxx', 1), make('yyy', 1), make('zzz', 125)] : true, + left: outcome( + [ + row(2, 'item #2'), + row(3, 'xxx'), + row(4, 'yyy'), + row(5, 'zzz'), + row(6, 'item #4') + ], + ['item #2', 'item #4'], + ['xxx', 'yyy', 'zzz'], + 1 + ), + right: outcome( + [ + row(0, 'item #2'), + row(1, 'xxx'), + row(2, 'yyy'), + row(3, 'zzz'), + row(4, 'item #4') + ], + ['item #2', 'item #4'], + ['xxx', 'yyy', 'zzz'], + -1 + ), + expectedDefaultSize: 24 + }, + { + title: 'insert two around one original item', + startIndex: 10, + predicate: ({ $index, data }) => + $index === 10 ? [make('xxx', itemSize), data, make('yyy', 125)] : true, + left: outcome( + [ + row(9, 'item #9'), + row(10, 'xxx'), + row(11, 'item #10'), + row(12, 'yyy'), + row(13, 'item #11') + ], + ['item #9', 'item #10', 'item #11'], + ['xxx', 'yyy'], + 11 + ), + right: outcome( + [ + row(7, 'item #9'), + row(8, 'xxx'), + row(9, 'item #10'), + row(10, 'yyy'), + row(11, 'item #11') + ], + ['item #9', 'item #10', 'item #11'], + ['xxx', 'yyy'], + 9 + ), + expectedDefaultSize: 27 + }, + { + title: 'prepend', + startIndex: min, + predicate: ({ $index, data }) => + $index === min ? [make('xxx', 125), data] : true, + left: outcome( + [row(min, 'xxx'), row(min + 1, `item #${min}`)], + [`item #${min}`], + ['xxx'], + min + 1 + ), + right: outcome( + [row(min - 1, 'xxx'), row(min, `item #${min}`)], + [`item #${min}`], + ['xxx'], + min + ), + expectedDefaultSize: 32 + }, + { + title: 'append', + startIndex: max, + predicate: ({ $index, data }) => + $index === max ? [data, make('xxx', 125)] : true, + left: outcome( + [row(max, `item #${max}`), row(max + 1, 'xxx')], + [`item #${max}`], + ['xxx'], + max - itemsPerPage + 1 + ), + right: outcome( + [row(max - 1, `item #${max}`), row(max, 'xxx')], + [`item #${max}`], + ['xxx'], + max - itemsPerPage + ), + expectedDefaultSize: 32 + }, + { + title: 'remove two', + predicate: ({ $index }) => !($index === 1 || $index === 3), + left: outcome( + [row(0, 'item #0'), row(1, 'item #2'), row(2, 'item #4')], + ['item #0', 'item #2', 'item #4'], + [], + 1 + ), + right: outcome( + [row(2, 'item #0'), row(3, 'item #2'), row(4, 'item #4')], + ['item #0', 'item #2', 'item #4'], + [], + 3 + ) + }, + { + title: 'remove left edge', + startIndex: min, + predicate: ({ $index }) => $index !== min, + left: outcome( + [row(min, `item #${min + 1}`), row(min + 1, `item #${min + 2}`)], + [`item #${min + 1}`, `item #${min + 2}`], + [], + min + ), + right: outcome( + [row(min + 1, `item #${min + 1}`), row(min + 2, `item #${min + 2}`)], + [`item #${min + 1}`, `item #${min + 2}`], + [], + min + 1 + ) + }, + { + title: 'remove right edge', + startIndex: max, + predicate: ({ $index }) => $index !== max, + left: outcome( + [row(max - 2, `item #${max - 2}`), row(max - 1, `item #${max - 1}`)], + [`item #${max - 2}`, `item #${max - 1}`], + [], + max - itemsPerPage + ), + right: outcome( + [row(max - 1, `item #${max - 2}`), row(max, `item #${max - 1}`)], + [`item #${max - 2}`, `item #${max - 1}`], + [], + max - itemsPerPage + 1 + ) + }, + { + title: 'perform complex update', + predicate: ({ $index, data }) => { + switch ($index) { + case 1: + return [make('a', 2), data]; + case 2: + return []; + case 3: + return [make('b', 2), make('c', 2)]; + case 4: + return []; + case 5: + return [data, make('d', 2)]; + default: + return true; + } + }, + left: outcome( + [ + row(1, 'a'), + row(2, 'item #1'), + row(3, 'b'), + row(4, 'c'), + row(5, 'item #5'), + row(6, 'd') + ], + ['item #1', 'item #5'], + ['a', 'b', 'c', 'd'], + 2 + ), + right: outcome( + [ + row(0, 'a'), + row(1, 'item #1'), + row(2, 'b'), + row(3, 'c'), + row(4, 'item #5'), + row(5, 'd') + ], + ['item #1', 'item #5'], + ['a', 'b', 'c', 'd'], + 1 + ), + expectedDefaultSize: 16 + } +]; + +const createSource = (): MutableDatasource => { + const source = new MutableDatasource(min, max); + source.setSizes(() => itemSize); + return source; +}; + +const createConfig = (scenario: UpdateScenario): TestConfig => ({ + datasource: createSource, + datasourceSettings: { + startIndex: scenario.startIndex ?? 1, + minIndex: min, + maxIndex: max, + itemSize + }, + templateSettings: { + viewportHeight: viewportSize, + itemHeight: itemSize, + dynamicSize: 'size' + }, + timeout: 4000 +}); + +const expectRows = (misc: Misc, rows: ExpectedRow[]): void => { + const items = misc.scroller.buffer.items; + const start = items.findIndex(item => item.$index === rows[0].index); + expect(start).toBeGreaterThanOrEqual(0); + + rows.forEach((expected, offset) => { + const item = items[start + offset]; + expect(item.$index).toBe(expected.index); + expect(item.data.text).toBe(expected.text); + expect(item.invisible).toBe(false); + expect(misc.getElementText(expected.index)).toBe( + `${expected.index}: ${expected.text}` + ); + }); +}; + +const expectIdentity = ( + misc: Misc, + before: Misc['scroller']['buffer']['items'], + expected: IdentityExpectation +): void => { + const after = misc.scroller.buffer.items; + misc.expect.uniqueItems(after); + + expected.stable.forEach(text => { + const oldItem = before.find(item => item.data.text === text); + const newItem = after.find(item => item.data.text === text); + expect(oldItem).toBeDefined(); + expect(newItem?.uid).toBe(oldItem?.uid); + }); + expected.fresh.forEach(text => { + expect(before.some(item => item.data.text === text)).toBe(false); + expect(after.some(item => item.data.text === text)).toBe(true); + }); +}; + +const registerScenario = (scenario: UpdateScenario, fixRight: boolean): void => + makeTest({ + config: createConfig(scenario), + title: `should ${scenario.title}`, + meta: `fixRight = ${fixRight}`, + it: misc => async () => { + await misc.relaxNext(); + const expected = fixRight ? scenario.right : scenario.left; + const beforeItems = [...misc.scroller.buffer.items]; + + misc.source().update(scenario.predicate, fixRight); + await misc.adapter.update({ predicate: scenario.predicate, fixRight }); + + expect(misc.adapter.firstVisible.$index).toBe(expected.firstVisible); + expectRows(misc, expected.rows); + expectIdentity(misc, beforeItems, expected.identity); + if (scenario.expectedDefaultSize !== undefined) { + expect(misc.scroller.buffer.defaultSize).toBe( + scenario.expectedDefaultSize + ); + } + + await misc.scrollMinMaxRelax(); + await misc.scrollToIndexRelax(expected.rows[0].index); + expectRows(misc, expected.rows); + } + }); + +const registerCleanup = (fixRight: boolean): void => + makeTest({ + config: createConfig(scenarios[0]), + title: 'should refill after removing the entire buffer', + meta: `fixRight = ${fixRight}`, + it: misc => async () => { + await misc.relaxNext(); + const { firstIndex, lastIndex } = misc.scroller.buffer; + const removed = lastIndex - firstIndex + 1; + const predicate: BufferUpdater = ({ $index }) => + !($index >= firstIndex && $index <= lastIndex); + + misc.source().update(predicate, fixRight); + await misc.adapter.update({ predicate, fixRight }); + + expect(misc.adapter.firstVisible.$index).toBe( + fixRight ? lastIndex + 1 : firstIndex + ); + await misc.scrollMinRelax(); + expect(misc.scroller.buffer.firstIndex).toBe( + min + (fixRight ? removed : 0) + ); + await misc.scrollMaxRelax(); + expect(misc.scroller.buffer.lastIndex).toBe( + max - (fixRight ? 0 : removed) + ); + } + }); + +describe('Adapter Update Spec', () => { + describe('Updates', () => + [false, true].forEach(fixRight => + scenarios.forEach(scenario => registerScenario(scenario, fixRight)) + )); + + describe('After cleanup', () => [false, true].forEach(registerCleanup)); +}); diff --git a/tests/e2e/specs/bug.spec.ts b/tests/e2e/specs/bug.spec.ts new file mode 100644 index 00000000..6fec2dfe --- /dev/null +++ b/tests/e2e/specs/bug.spec.ts @@ -0,0 +1,376 @@ +import { + getDatasource, + makeDatasource, + makeTest, + TestHost, + DatasourceProcessor, + ItemAdapter, + TestConfig, + TestItem +} from '../scaffolding'; + +const Datasource = makeDatasource(); +const removedIndexes = [1, 2, 3, 4, 5]; +const replacementMin = 2; +const replacementMax = 5; +const clippedIndexes: number[] = []; + +const shiftDatasourceAfterReplacement: DatasourceProcessor = items => + items.forEach(({ data }) => { + if (data.id < replacementMin) { + return; + } + data.id += replacementMax - replacementMin + 1; + data.text = `item #${data.id}`; + }); + +const removeDatasourceBeginning: DatasourceProcessor = items => { + for (let index = items.length - 1; index >= 0; index--) { + if (removedIndexes.includes(items[index].data.id)) { + items.splice(index, 1); + } + } +}; + +const emptyScrollableConfig: TestConfig = { + datasource: () => getDatasource({ min: 1, max: 0 }), + templateSettings: { viewportPadding: 200 } +}; + +const bothEdgesConfig: TestConfig = { + datasource: () => getDatasource({ min: 1, max: 100 }), + datasourceSettings: { padding: 5 }, + templateSettings: { viewportHeight: 300, itemHeight: 15 } +}; + +const reloadConfig: TestConfig = { + datasource: () => getDatasource({ delay: 150 }), + datasourceSettings: { bufferSize: 15 }, + timeout: 4000 +}; + +const inverseConfig: TestConfig = { + datasource: () => getDatasource({ min: 1, max: 10 }), + datasourceSettings: { + startIndex: -1, + bufferSize: 7, + itemSize: 20, + inverse: true + }, + templateSettings: { viewportHeight: 300, dynamicSize: 'size' } +}; + +const replacementConfig: TestConfig = { + datasource: () => getDatasource({ min: -99, max: 100 }), + datasourceSettings: { startIndex: replacementMin - 1 } +}; + +const clipConfig: TestConfig = { + datasource: () => getDatasource(), + datasourceSettings: { bufferSize: 50 } +}; + +const beforeClipConfig: TestConfig = { + datasource: () => getDatasource(), + datasourceSettings: { + onBeforeClip: items => + items.forEach(({ $index }) => clippedIndexes.push($index)) + } +}; + +const infiniteConfig: TestConfig = { + datasource: () => getDatasource({ delay: 25 }), + datasourceSettings: { bufferSize: 50, infinite: true }, + timeout: 4000 +}; + +const removeConfig: TestConfig = { + datasource: () => getDatasource({ min: 1, max: 100 }), + datasourceSettings: { + startIndex: 1, + minIndex: 1, + maxIndex: 100, + bufferSize: 5 + } +}; + +const largePaddingConfig: TestConfig = { + datasource: () => getDatasource(), + datasourceSettings: { + startIndex: 1, + minIndex: 1, + maxIndex: 123456, + bufferSize: 5 + } +}; + +const negativeCutConfig: TestConfig = { + datasource: () => getDatasource({ min: -10, max: 100 }), + datasourceSettings: { startIndex: 1, bufferSize: 5 }, + templateSettings: { viewportHeight: 250, itemHeight: 22 } +}; + +describe('Bug Spec', () => { + makeTest({ + config: emptyScrollableConfig, + title: 'should stop empty datasource cycles on the first inner loop', + it: misc => async () => { + await misc.relaxNext(); + await misc.scrollMaxRelax(); + await misc.scrollMinRelax(); + await misc.scrollMaxRelax(); + + expect(misc.workflow.cyclesDone).toBe(4); + expect(misc.innerLoopCount).toBe(4); + expect(misc.scroller.buffer.size).toBe(0); + } + }); + + makeTest({ + config: bothEdgesConfig, + title: 'should update firstVisible and lastVisible at both edges', + it: misc => async () => { + let firstUpdates = 0; + let lastUpdates = 0; + let checkedFirst = 0; + let checkedLast = 0; + const offFirst = misc.adapter.firstVisible$.on(() => firstUpdates++); + const offLast = misc.adapter.lastVisible$.on(() => lastUpdates++); + const check = () => { + expect(misc.adapter.bof).toBe(true); + expect(misc.adapter.eof).toBe(true); + expect(firstUpdates).toBeGreaterThan(checkedFirst); + expect(lastUpdates).toBeGreaterThan(checkedLast); + checkedFirst = firstUpdates; + checkedLast = lastUpdates; + misc.expect.visibleWithinBuffer(); + }; + + try { + await misc.relaxNext(); + check(); + await misc.scrollMaxRelax(); + check(); + await misc.scrollMinRelax(); + check(); + } finally { + offFirst(); + offLast(); + } + } + }); + + makeTest({ + config: reloadConfig, + title: 'should recover from repeated reload interruptions', + it: misc => async () => { + if (!misc.adapter.init) { + await misc.waitForAdapterInit(); + } + + const reloads: Array> = []; + const reloadTwice = () => { + reloads.push(misc.adapter.reload(), misc.adapter.reload()); + }; + + reloadTwice(); + await misc.delay(25); + reloadTwice(); + await misc.delay(25); + reloadTwice(); + + const results = await Promise.all(reloads); + await misc.adapter.relax(); + + expect(results).toHaveLength(6); + expect(results.every(result => result.success)).toBe(true); + expect(misc.workflow.isInitialized).toBe(true); + misc.expect.domMatchesBuffer(); + + const cyclesDone = misc.workflow.cyclesDone; + await misc.adapter.reload(); + expect(misc.workflow.cyclesDone).toBeGreaterThan(cyclesDone); + } + }); + + makeTest({ + config: inverseConfig, + title: 'should not extend forward padding for an oversized inverse item', + before: misc => + misc.setItemProcessor(({ $index, data }) => { + data.size = $index === 4 ? 120 : 20; + }), + it: misc => async () => { + await misc.relaxNext(); + + expect(misc.padding.backward.getSize()).toBe(0); + expect(misc.padding.forward.getSize()).toBe(0); + misc.expect.domIndexesMatchBuffer(); + } + }); + + test('should deliver firstVisible to an early subscriber', async () => { + const source = getDatasource({ min: 1, max: 100 }); + const datasource = new Datasource({ + get: source.get.bind(source), + settings: { startIndex: 1 } + }); + let firstVisible: ItemAdapter | undefined; + const off = datasource.adapter.firstVisible$.on(value => { + firstVisible = value; + }); + const host = new TestHost({ datasource: () => datasource }); + + try { + await host.relaxNext(); + expect(firstVisible).toBe(host.adapter.firstVisible); + expect(firstVisible?.$index).toBe(1); + } finally { + off(); + host.dispose(); + } + }); + + makeTest({ + config: replacementConfig, + title: 'should keep firstVisible while replacing via remove and insert', + it: misc => async () => { + await misc.relaxNext(); + expect(misc.adapter.firstVisible.$index).toBe(replacementMin - 1); + + misc.setDatasourceProcessor(shiftDatasourceAfterReplacement); + await misc.adapter.remove({ + predicate: ({ $index }) => + $index >= replacementMin && $index <= replacementMax + }); + await misc.adapter.insert({ + items: [{ id: replacementMax + 1, text: `item #${replacementMax} *` }], + after: ({ $index }) => $index === replacementMin - 1 + }); + + expect(misc.adapter.firstVisible.$index).toBe(replacementMin - 1); + misc.scroller.buffer.items.forEach(item => { + const expected = + item.$index === replacementMin + ? `${replacementMax} *` + : String( + item.$index > replacementMin + ? item.$index + replacementMax - replacementMin + : item.$index + ); + expect(item.data.text).toBe(`item #${expected}`); + }); + misc.expect.domIndexesMatchBuffer(); + } + }); + + makeTest({ + config: clipConfig, + title: 'should clip only once', + it: misc => async () => { + const { clip } = misc.scroller.state; + await misc.relaxNext(); + expect(clip.callCount).toBe(0); + + await misc.adapter.clip(); + expect(clip.callCount).toBe(1); + + await misc.adapter.clip(); + expect(clip.callCount).toBe(1); + } + }); + + makeTest({ + config: beforeClipConfig, + title: 'should pass exactly removed items to onBeforeClip', + before: () => { + clippedIndexes.length = 0; + }, + it: misc => async () => { + await misc.relaxNext(); + const before = misc.scroller.buffer.items.map(item => item.$index); + + await misc.scrollMaxRelax(); + + const after = new Set( + misc.scroller.buffer.items.map(item => item.$index) + ); + const removed = before.filter(index => !after.has(index)).sort(); + + expect(removed.length).toBeGreaterThan(0); + expect([...clippedIndexes].sort()).toEqual(removed); + } + }); + + makeTest({ + config: infiniteConfig, + title: 'should finish a scroll cycle in infinite mode', + it: misc => async () => { + await misc.relaxNext(); + const cyclesDone = misc.workflow.cyclesDone; + + await misc.scrollMinRelax(); + + expect(misc.workflow.cyclesDone).toBe(cyclesDone + 1); + expect(misc.scroller.state.cycle.busy.get()).toBe(false); + misc.expect.domMatchesBuffer(); + } + }); + + makeTest({ + config: removeConfig, + title: 'should shift startIndex after removal with increase', + it: misc => async () => { + await misc.relaxNext(); + const { buffer } = misc.scroller; + const startIndex = buffer.startIndex; + const stableBufferSize = buffer.size; + + await misc.adapter.remove({ + predicate: ({ $index }) => removedIndexes.includes($index), + increase: true + }); + misc.setDatasourceProcessor(removeDatasourceBeginning); + + expect(buffer.startIndex).toBe(startIndex + removedIndexes.length); + await misc.scrollMaxRelax(); + await misc.scrollMinRelax(); + + misc.expect.domMatchesBuffer(); + expect(buffer.size).toBeLessThanOrEqual(stableBufferSize); + } + }); + + makeTest({ + config: largePaddingConfig, + title: 'should not jump when padding is larger than one million pixels', + it: misc => async () => { + await misc.relaxNext(); + await misc.scrollMaxRelax(); + + expect(misc.adapter.eof).toBe(true); + expect(misc.getScrollPosition()).toBe( + misc.getScrollableSize() - misc.getViewportSize() + ); + misc.expect.consistent(); + } + }); + + makeTest({ + config: negativeCutConfig, + title: 'should not leave a gap after cutting negative indexes', + it: misc => async () => { + await misc.relaxNext(); + await misc.scrollMaxRelax(); + await misc.scrollMinRelax(); + + expect(misc.adapter.bof).toBe(true); + expect(misc.adapter.bufferInfo.minIndex).toBe(-10); + expect(misc.adapter.bufferInfo.firstIndex).toBe(-10); + expect(misc.adapter.bufferInfo.absMinIndex).toBe(-10); + expect(misc.adapter.firstVisible.$index).toBe(-5); + expect(misc.checkElementContentByIndex(-5)).toBe(true); + misc.expect.domMatchesBuffer(); + } + }); +}); diff --git a/tests/e2e/specs/common.spec.ts b/tests/e2e/specs/common.spec.ts new file mode 100644 index 00000000..823db9f6 --- /dev/null +++ b/tests/e2e/specs/common.spec.ts @@ -0,0 +1,208 @@ +import { + getDatasource, + makeTest, + ParsedSettings, + TestHost, + Settings, + TestConfig +} from '../scaffolding'; + +const initDelay = 50; +const baseConfig: TestConfig = { + datasource: () => getDatasource({ min: 1, max: 100 }), + datasourceSettings: { + startIndex: 1, + minIndex: 1, + maxIndex: 100, + itemSize: 20, + bufferSize: 5, + padding: 0.5 + }, + templateSettings: { viewportHeight: 200, itemHeight: 20 } +}; + +const parseSettings = (settings?: unknown): ParsedSettings => + new ParsedSettings(settings as Settings | undefined, undefined, 1); + +const takeCoreSettings = (settings: ParsedSettings) => ({ + startIndex: settings.startIndex, + bufferSize: settings.bufferSize, + padding: settings.padding, + itemSize: settings.itemSize, + infinite: settings.infinite, + horizontal: settings.horizontal +}); + +describe('Common Spec', () => { + describe('Settings', () => { + test('uses defaults for missing or malformed input', () => { + [undefined, null, 'invalid', 42].forEach(input => + expect(takeCoreSettings(parseSettings(input))).toEqual({ + startIndex: 1, + bufferSize: 5, + padding: 0.5, + itemSize: NaN, + infinite: false, + horizontal: false + }) + ); + }); + + test('applies valid overrides', () => { + expect( + takeCoreSettings( + parseSettings({ startIndex: 99, bufferSize: 11, infinite: true }) + ) + ).toEqual({ + startIndex: 99, + bufferSize: 11, + padding: 0.5, + itemSize: NaN, + infinite: true, + horizontal: false + }); + }); + + test('normalizes invalid and below-limit values', () => { + const cases: Array<{ + key: keyof Settings; + input: unknown; + expected: unknown; + }> = [ + { key: 'startIndex', input: false, expected: 1 }, + { key: 'bufferSize', input: { invalid: true }, expected: 5 }, + { key: 'bufferSize', input: 5.5, expected: 5 }, + { key: 'bufferSize', input: -1, expected: 1 }, + { key: 'padding', input: 'invalid', expected: 0.5 }, + { key: 'padding', input: -0.1, expected: 0.01 }, + { key: 'itemSize', input: -5, expected: 1 }, + { key: 'itemSize', input: 1.5, expected: NaN }, + { key: 'infinite', input: 'invalid', expected: false }, + { key: 'horizontal', input: null, expected: false } + ]; + + cases.forEach(({ key, input, expected }) => + expect({ key, value: parseSettings({ [key]: input })[key] }).toEqual({ + key, + value: expected + }) + ); + }); + + test('accepts an HTML element', () => { + const element = document.createElement('div'); + const settings = parseSettings({ viewportElement: element }); + + expect(settings.viewportElement).toBe(element); + expect(settings.viewport).toBe(element); + }); + + test('rejects an element-like object', () => { + const settings = parseSettings({ viewportElement: { nodeType: 1 } }); + + expect(settings.viewportElement).toBeNull(); + expect(settings.viewport).toBeNull(); + }); + }); + + describe('Delayed initialization', () => { + makeTest({ + config: { + ...baseConfig, + datasourceDevSettings: { initDelay }, + timeout: 1000, + skipInvariantAutoCheck: true // test disposes the host in-body + }, + title: 'should initialize after the configured delay', + it: misc => async () => { + expect(misc.workflow.isInitialized).toBe(false); + expect(misc.adapter.init).toBe(false); + + await misc.waitForAdapterInit(); + await misc.adapter.relax(); + + expect(misc.workflow.isInitialized).toBe(true); + expect(misc.adapter.init).toBe(true); + misc.expect.domMatchesBuffer(); + + const workflow = misc.workflow; + const adapter = misc.adapter; + misc.dispose(); + + expect(workflow.isInitialized).toBe(false); + expect(workflow.disposed).toBe(true); + expect(adapter.init).toBe(false); + } + }); + + makeTest({ + config: { + ...baseConfig, + datasourceDevSettings: { initDelay }, + timeout: 1000, + skipInvariantAutoCheck: true // test disposes the host in-body + }, + title: 'should remain disposed when destroyed before initialization', + it: misc => async () => { + const workflow = misc.workflow; + const adapter = misc.adapter; + + expect(workflow.isInitialized).toBe(false); + expect(adapter.init).toBe(false); + + misc.dispose(); + await misc.delay(initDelay * 2); + + expect(workflow.isInitialized).toBe(false); + expect(workflow.disposed).toBe(true); + expect(adapter.init).toBe(false); + } + }); + }); + + makeTest({ + config: baseConfig, + title: 'should keep simultaneous workflows independent', + it: first => async () => { + const second = new TestHost(baseConfig); + + try { + await Promise.all([first.relaxNext(), second.relaxNext()]); + first.expect.domMatchesBuffer(); + second.expect.domMatchesBuffer(); + expect(first.adapter.id).not.toBe(second.adapter.id); + expect(first.adapter.version).toBe(second.adapter.version); + + const firstCycles = first.workflow.cyclesDone; + const secondState = { + cycles: second.workflow.cyclesDone, + position: second.getScrollPosition(), + bufferInfo: { ...second.adapter.bufferInfo } + }; + const firstLoading: boolean[] = []; + const secondLoading: boolean[] = []; + const offFirst = first.adapter.isLoading$.on(value => + firstLoading.push(value) + ); + const offSecond = second.adapter.isLoading$.on(value => + secondLoading.push(value) + ); + + await first.scrollToRelax(400); + offFirst(); + offSecond(); + + expect(firstLoading).toEqual([true, false]); + expect(secondLoading).toEqual([]); + expect(first.workflow.cyclesDone).toBeGreaterThan(firstCycles); + expect({ + cycles: second.workflow.cyclesDone, + position: second.getScrollPosition(), + bufferInfo: second.adapter.bufferInfo + }).toEqual(secondState); + } finally { + second.dispose(); + } + } + }); +}); diff --git a/tests/e2e/specs/datasource.spec.ts b/tests/e2e/specs/datasource.spec.ts new file mode 100644 index 00000000..77677cc3 --- /dev/null +++ b/tests/e2e/specs/datasource.spec.ts @@ -0,0 +1,113 @@ +import { + getDatasource, + IDatasource, + makeDatasource, + makeTest, + Misc, + Settings, + TestConfig, + TestItem +} from '../scaffolding'; + +const createItems = (index: number, count: number): TestItem[] => + Array.from({ length: count }, (_, offset) => { + const current = index + offset; + return { id: current, text: `item #${current}` }; + }); + +class ClassDatasource implements IDatasource { + settings: Settings = {}; + + get(index: number, count: number, success: (data: TestItem[]) => void): void { + success(createItems(index, count)); + } + + reset(): void { } +} + +const expectFetched = (misc: Misc) => async () => { + await misc.relaxNext(); + expect(misc.scroller.state.fetch.callCount).toBeGreaterThan(0); + expect(misc.scroller.buffer.size).toBeGreaterThan(0); + misc.expect.domMatchesBuffer(); +}; + +const expectEmpty = (misc: Misc) => async () => { + await misc.relaxNext(); + const { buffer, state } = misc.scroller; + expect(misc.innerLoopCount).toBe(1); + expect(state.fetch.callCount).toBe(0); + expect(buffer.size).toBe(0); + expect(buffer.bof.get()).toBe(true); + expect(buffer.eof.get()).toBe(true); +}; + +const scopes = ['infinite', 'limited'] as const; +const modes = ['observable', 'promise', 'callback'] as const; +const Datasource = makeDatasource(); + +describe('Datasource Class', () => { + makeTest({ + config: { datasource: () => new ClassDatasource() }, + title: 'should run Workflow with a class-based datasource', + it: expectFetched + }); + + makeTest({ + config: { + datasource: () => + new Datasource({ + get: (index, count, success) => success(createItems(index, count)), + settings: { startIndex: 1 }, + devSettings: { throttle: 1 } + }), + datasourceSettings: { startIndex: 42 }, + datasourceDevSettings: { throttle: 17 } + }, + title: 'should apply settings to a constructed datasource', + it: misc => async () => { + await misc.relaxNext(); + expect(misc.scroller.settings.startIndex).toBe(42); + expect(misc.scroller.settings.throttle).toBe(17); + } + }); +}); + +describe('Datasource Get', () => { + ([ + { suite: 'immediate', delay: 0 }, + { suite: 'non-immediate', delay: 1 } + ]).forEach(timing => + describe(timing.suite, () => { + scopes.forEach(scope => + modes.forEach(mode => { + const config: TestConfig = { + datasource: () => + getDatasource({ + ...(scope === 'limited' ? { min: 1, max: 100 } : {}), + mode, + delay: timing.delay + }) + }; + makeTest({ + config, + title: `should run Workflow with ${scope} ${mode}-based datasource`, + it: expectFetched + }); + }) + ); + }) + ); + + describe('empty', () => { + for (const mode of ['callback', 'observable'] as const) { + makeTest({ + config: { + datasource: () => getDatasource({ min: 1, max: 0, mode }) + }, + title: `should stop without fetching (${mode})`, + it: expectEmpty + }); + } + }); +}); diff --git a/tests/e2e/specs/direction-priority.spec.ts b/tests/e2e/specs/direction-priority.spec.ts new file mode 100644 index 00000000..4d006925 --- /dev/null +++ b/tests/e2e/specs/direction-priority.spec.ts @@ -0,0 +1,126 @@ +import { + Direction, + getDatasource, + makeTest, + Misc, + TestConfig +} from '../scaffolding'; + +interface Scenario { + title: string; + priority: Direction; + getSize: (index: number) => number; + scrollPosition?: number; + expected: { + firstIndex?: number; + lastIndex?: number; + atMax?: boolean; + }; +} + +interface TestCase { + scenario: Scenario; + config: TestConfig; +} + +const minIndex = 0; +const maxIndex = 99; +const itemSize = 20; +const baseDsSettings = { startIndex: maxIndex, minIndex, maxIndex }; + +const scenarios: Scenario[] = [ + { + title: + 'should stay at the bottom edge when odd items are big (fwd on init)', + priority: Direction.forward, + getSize: index => (index % 2 === 0 ? 50 : 100), + expected: { lastIndex: maxIndex, atMax: true } + }, + { + title: + 'should stay at the bottom edge when even items are big (fwd on init)', + priority: Direction.forward, + getSize: index => (index % 2 !== 0 ? 50 : 100), + expected: { lastIndex: maxIndex, atMax: true } + }, + { + title: + 'should stay at the bottom edge when odd items are big (bwd on init)', + priority: Direction.backward, + getSize: index => (index % 2 === 0 ? 50 : 100), + expected: { lastIndex: maxIndex, atMax: true } + }, + { + title: + 'should not stay at the bottom edge when even items are big (bwd on init)', + priority: Direction.backward, + getSize: index => (index % 2 !== 0 ? 50 : 100), + expected: { lastIndex: maxIndex - 1, atMax: false } + }, + { + title: 'should not shift position (bwd on scroll)', + priority: Direction.backward, + getSize: index => (index >= 80 ? itemSize : 100), + scrollPosition: 75 * itemSize, + expected: { firstIndex: 75 } + }, + { + title: 'should shift position (fwd on scroll)', + priority: Direction.forward, + getSize: index => (index >= 80 ? itemSize : 100), + scrollPosition: 75 * itemSize, + expected: { firstIndex: 79 } + } +]; + +const cases: TestCase[] = scenarios.map(scenario => ({ + scenario, + config: { + datasource: () => getDatasource({ min: minIndex, max: maxIndex }), + datasourceSettings: { + ...baseDsSettings, + ...(scenario.scrollPosition === undefined ? {} : { itemSize }) + }, + datasourceDevSettings: { directionPriority: scenario.priority }, + templateSettings: { viewportHeight: 200, dynamicSize: 'size' } + } +})); + +const expectResult = (misc: Misc, scenario: Scenario): void => { + const { firstIndex, lastIndex, atMax } = scenario.expected; + + if (firstIndex !== undefined) { + expect(misc.adapter.firstVisible.$index).toBe(firstIndex); + } + if (lastIndex !== undefined) { + expect(misc.adapter.lastVisible.$index).toBe(lastIndex); + } + if (atMax !== undefined) { + const maxPosition = misc.getScrollableSize() - misc.getViewportSize(); + if (atMax) { + expect(misc.getScrollPosition()).toBe(maxPosition); + } else { + expect(misc.getScrollPosition()).not.toBe(maxPosition); + } + } +}; + +describe('Direction Priority Spec', () => + cases.forEach(({ scenario, config }) => + makeTest({ + config, + title: scenario.title, + before: misc => + misc.setItemProcessor(({ $index, data }) => { + data.size = scenario.getSize($index); + }), + it: misc => async () => { + await misc.relaxNext(); + if (scenario.scrollPosition !== undefined) { + await misc.scrollToRelax(scenario.scrollPosition); + } + misc.expect.domMatchesBuffer(); + expectResult(misc, scenario); + } + }) + )); diff --git a/tests/e2e/specs/dynamic-height-reload.spec.ts b/tests/e2e/specs/dynamic-height-reload.spec.ts new file mode 100644 index 00000000..e1f9d37c --- /dev/null +++ b/tests/e2e/specs/dynamic-height-reload.spec.ts @@ -0,0 +1,49 @@ +import { + getDatasource, + getDynamicSize, + makeTest, + SizeStrategy, + TestConfig +} from '../scaffolding'; + +const reloadIndexes = [ + -99, -98, -90, -75, -35, -20, -10, -5, -2, -1, 0, 1, 2, 5, 10, 20, 35, 50, 75, + 90 +]; + +const baseConfig: TestConfig = { + datasource: () => getDatasource({ min: -99, max: 100 }), + datasourceSettings: { + startIndex: 1, + padding: 0.5, + bufferSize: 10, + minIndex: -99, + maxIndex: 100, + itemSize: 20, + sizeStrategy: SizeStrategy.Average + }, + templateSettings: { viewportHeight: 600, dynamicSize: 'size' }, + timeout: 4000 +}; + +describe('Dynamic Size Reload Spec', () => { + reloadIndexes.forEach(reloadIndex => + makeTest({ + config: baseConfig, + title: 'should reload properly', + meta: `reloadIndex: ${reloadIndex}`, + before: misc => + misc.setItemProcessor(({ $index, data }) => { + data.size = getDynamicSize($index); + }), + it: misc => async () => { + await misc.relaxNext(); + await misc.adapter.reload(reloadIndex); + await misc.adapter.relax(); + + misc.expect.domMatchesBuffer(); + expect(misc.adapter.firstVisible.$index).toBe(reloadIndex); + } + }) + ); +}); diff --git a/tests/e2e/specs/dynamic-height-scroll.spec.ts b/tests/e2e/specs/dynamic-height-scroll.spec.ts new file mode 100644 index 00000000..b775344b --- /dev/null +++ b/tests/e2e/specs/dynamic-height-scroll.spec.ts @@ -0,0 +1,115 @@ +import { + getDatasource, + getDynamicSize, + makeTest, + Misc, + SizeStrategy, + TestConfig, + TestItem +} from '../scaffolding'; + +const takeLayoutSnapshot = (misc: Misc) => { + const { buffer, viewport } = misc.scroller; + return { + viewportSize: viewport.getSize(), + scrollableSize: viewport.getScrollableSize(), + backwardPadding: viewport.paddings.backward.size, + forwardPadding: viewport.paddings.forward.size, + bufferSize: buffer.size, + firstIndex: buffer.firstIndex, + lastIndex: buffer.lastIndex, + defaultSize: buffer.defaultSize + }; +}; + +const createItems = (start: number, count: number): TestItem[] => + Array.from({ length: count }, (_, offset) => { + const index = start + offset; + return { + id: index, + text: `item #${index}`, + size: getDynamicSize(index) + }; + }); + +const registerFetchCase = (strategy: SizeStrategy): void => { + const config: TestConfig = { + datasource: () => getDatasource({ min: 1, max: 20 }), + datasourceSettings: { + startIndex: 1, + padding: 0.5, + bufferSize: 5, + minIndex: 1, + maxIndex: 20, + itemSize: 20, + sizeStrategy: strategy + }, + templateSettings: { viewportHeight: 100, dynamicSize: 'size' } + }; + + makeTest({ + config, + title: `should fetch properly (${strategy})`, + before: misc => + misc.setItemProcessor(({ $index, data }) => { + data.size = $index === 1 ? 200 : 20; + }), + it: misc => async () => { + await misc.relaxNext(); + const before = takeLayoutSnapshot(misc); + + await misc.scrollToRelax(100); + + expect(takeLayoutSnapshot(misc)).toEqual(before); + misc.expect.domMatchesBuffer(); + } + }); +}; + +const registerAppendCase = (strategy: SizeStrategy): void => { + const maxIndex = 100; + const appendCount = 50; + const config: TestConfig = { + datasource: () => getDatasource({ min: -99, max: maxIndex + appendCount }), + datasourceSettings: { + startIndex: 50, + minIndex: -99, + maxIndex, + padding: 0.5, + bufferSize: 5, + itemSize: 20, + sizeStrategy: strategy + }, + templateSettings: { viewportHeight: 200, dynamicSize: 'size' } + }; + + makeTest({ + config, + title: `should append properly (${strategy})`, + before: misc => + misc.setItemProcessor(({ $index, data }) => { + data.size = getDynamicSize($index); + }), + it: misc => async () => { + await misc.relaxNext(); + await misc.adapter.append({ + items: createItems(maxIndex + 1, appendCount), + eof: true + }); + + await misc.scrollMaxRelax(); + const innerLoopCount = misc.innerLoopCount; + await misc.scrollMaxRelax(); + + expect(misc.innerLoopCount).toBe(innerLoopCount + 1); + misc.expect.domMatchesBuffer(); + } + }); +}; + +describe('Dynamic Size Scroll Spec', () => { + for (const strategy of [SizeStrategy.Average, SizeStrategy.Frequent]) { + registerFetchCase(strategy); + registerAppendCase(strategy); + } +}); diff --git a/tests/e2e/specs/dynamic-height-update.spec.ts b/tests/e2e/specs/dynamic-height-update.spec.ts new file mode 100644 index 00000000..54b7e53b --- /dev/null +++ b/tests/e2e/specs/dynamic-height-update.spec.ts @@ -0,0 +1,122 @@ +import { + getDatasource, + makeTest, + Misc, + SizeStrategy, + TestConfig +} from '../scaffolding'; + +interface Scenario { + title: string; + limits: { min: number; max: number }; + getSize: (index: number) => number; + action: (misc: Misc) => Promise; + defaultSize: { initial: number; final: number }; +} + +const itemSize = 20; +const firstFifteen = Array.from({ length: 15 }, (_, index) => index + 1); + +const scenarios: Scenario[] = [ + { + title: 'should remove one big item via Adapter.update', + limits: { min: 1, max: 50 }, + getSize: index => (index === 10 ? 100 : itemSize), + action: misc => + misc.adapter.update({ predicate: ({ $index }) => $index !== 10 }), + defaultSize: { initial: 25, final: itemSize } + }, + { + title: 'should remove one big item via Adapter.remove in-buffer', + limits: { min: 1, max: 50 }, + getSize: index => (index === 10 ? 100 : itemSize), + action: misc => + misc.adapter.remove({ predicate: ({ $index }) => $index === 10 }), + defaultSize: { initial: 25, final: itemSize } + }, + { + title: 'should remove some items via Adapter.remove virtually', + limits: { min: 1, max: 35 }, + getSize: index => index + itemSize, + action: async misc => { + await misc.scrollToIndexRelax(35); + await misc.adapter.remove({ indexes: firstFifteen }); + }, + defaultSize: { initial: 28, final: 46 } + }, + { + title: + 'should remove some items via Adapter.remove virtually (interrupted)', + limits: { min: 1, max: 35 }, + getSize: index => index + itemSize, + action: async misc => { + await misc.scrollToIndexRelax(35); + await misc.adapter.remove({ + indexes: [1, 2, 3, 4, 5, 11, 12, 13, 14, 15] + }); + }, + defaultSize: { initial: 28, final: 42 } + }, + { + title: + 'should remove one big item in-buffer and another one virtually via Adapter.remove', + limits: { min: 1, max: 35 }, + getSize: index => (index === 1 || index === 35 ? 100 : itemSize), + action: async misc => { + await misc.scrollToIndexRelax(35); + await misc.adapter.remove({ indexes: [1, 35] }); + }, + defaultSize: { initial: 25, final: itemSize } + }, + { + title: + 'should remove one big item in-buffer and another one virtually via Adapter.remove (inverted)', + limits: { min: 1, max: 35 }, + getSize: index => (index === 3 || index === 33 ? 100 : itemSize), + action: async misc => { + await misc.scrollToIndexRelax(35); + await misc.scrollMinRelax(); + await misc.adapter.remove({ indexes: [3, 33] }); + }, + defaultSize: { initial: 25, final: itemSize } + } +]; + +const baseSettings: NonNullable = { + startIndex: 1, + padding: 0.5, + bufferSize: 5, + itemSize, + sizeStrategy: SizeStrategy.Average +}; + +describe('Dynamic Size Update Spec', () => { + scenarios.forEach(scenario => + makeTest({ + config: { + datasource: () => getDatasource(scenario.limits), + datasourceSettings: baseSettings, + templateSettings: { viewportHeight: 200, dynamicSize: 'size' }, + timeout: 4000 + }, + title: scenario.title, + before: misc => + misc.setItemProcessor(({ $index, data }) => { + data.size = scenario.getSize($index); + }), + it: misc => async () => { + await misc.relaxNext(); + expect(misc.scroller.buffer.defaultSize).toBe( + scenario.defaultSize.initial + ); + + await scenario.action(misc); + + expect(misc.scroller.buffer.defaultSize).toBe( + scenario.defaultSize.final + ); + misc.expect.domIndexesMatchBuffer(); + } + }) + ); +}); diff --git a/tests/e2e/specs/dynamic-size.average.spec.ts b/tests/e2e/specs/dynamic-size.average.spec.ts new file mode 100644 index 00000000..f8415fb1 --- /dev/null +++ b/tests/e2e/specs/dynamic-size.average.spec.ts @@ -0,0 +1,285 @@ +import { + Direction, + getDatasource, + getDynamicSize, + makeTest, + Misc, + SizeStrategy, + TestConfig +} from '../scaffolding'; + +/** + * Settled layout captured EMPIRICALLY for a load scenario. `defaultSize` and the + * paddings come from the Average size strategy's own measurements, so they are + * harvested rather than recomputed (recomputing would re-implement the strategy). + */ +interface LayoutSnapshot { + range: [number, number]; + defaultSize: number; + backwardPadding: number; + forwardPadding: number; + innerLoopCount: number; +} + +interface LoadScenario { + settings: NonNullable & { + startIndex: number; + }; + template?: TestConfig['templateSettings']; + expected: LayoutSnapshot; + firstForwardPadding?: number; +} + +const sourceLimits = { min: -50, max: 99 }; +const baseTemplate = { viewportHeight: 100, dynamicSize: 'size' }; + +const initialScenarios: LoadScenario[] = [ + { + settings: { startIndex: 0, padding: 0.5, bufferSize: 5 }, + template: { viewportHeight: 100 }, + expected: { + range: [-5, 9], + defaultSize: 22, + backwardPadding: 0, + forwardPadding: 0, + innerLoopCount: 4 + } + }, + { + settings: { + startIndex: 0, + padding: 0.5, + bufferSize: 5, + minIndex: -20, + maxIndex: 10 + }, + template: { viewportHeight: 100 }, + expected: { + range: [-5, 9], + defaultSize: 22, + backwardPadding: 330, + forwardPadding: 22, + innerLoopCount: 4 + } + }, + { + settings: { startIndex: -5, padding: 1.2, bufferSize: 1 }, + template: { viewportHeight: 250 }, + expected: { + range: [-50, 31], + defaultSize: 17, + backwardPadding: 0, + forwardPadding: 0, + innerLoopCount: 8 + } + }, + { + settings: { + startIndex: 0, + padding: 0.25, + bufferSize: 10, + windowViewport: true + }, + template: { noViewportClass: true, viewportHeight: 0 }, + expected: { + range: [-30, 38], + defaultSize: 25, + backwardPadding: 0, + forwardPadding: 0, + innerLoopCount: 6 + } + }, + { + settings: { + startIndex: 20, + padding: 0.75, + bufferSize: 15, + horizontal: true + }, + template: { viewportWidth: 300, horizontal: true }, + expected: { + range: [5, 34], + defaultSize: 40, + backwardPadding: 0, + forwardPadding: 0, + innerLoopCount: 3 + } + } +]; + +const lackScenarios: LoadScenario[] = [ + { + settings: { + startIndex: -25, + padding: 1.2, + bufferSize: 1, + minIndex: -50 + }, + template: { viewportHeight: 250 }, + expected: { + range: [-50, 99], + defaultSize: 47, + backwardPadding: 0, + forwardPadding: 0, + innerLoopCount: 8 + }, + firstForwardPadding: 249 + }, + { + settings: { + startIndex: 11, + padding: 0.7, + bufferSize: 7, + minIndex: 1 + }, + template: { viewportHeight: 350 }, + expected: { + range: [1, 28], + defaultSize: 35, + backwardPadding: 0, + forwardPadding: 0, + innerLoopCount: 5 + }, + firstForwardPadding: 112 + }, + { + settings: { + startIndex: 1, + padding: 0.15, + bufferSize: 9, + minIndex: -10, + windowViewport: true + }, + template: { noViewportClass: true, viewportHeight: 0 }, + expected: { + range: [-8, 36], + defaultSize: 34, + backwardPadding: 68, + forwardPadding: 0, + innerLoopCount: 4 + }, + firstForwardPadding: 543 + }, + { + settings: { + startIndex: -10, + padding: 0.75, + bufferSize: 4, + minIndex: -20, + horizontal: true + }, + template: { viewportWidth: 300, horizontal: true }, + expected: { + range: [-20, 33], + defaultSize: 27, + backwardPadding: 0, + forwardPadding: 0, + innerLoopCount: 5 + }, + firstForwardPadding: 254 + } +]; + +const scrollScenarios = initialScenarios.filter( + (_scenario, index) => index !== 2 && index !== 3 +); + +const createConfig = (scenario: LoadScenario): TestConfig => ({ + datasource: () => getDatasource(sourceLimits), + datasourceSettings: { + sizeStrategy: SizeStrategy.Average, + ...scenario.settings + }, + templateSettings: { ...baseTemplate, ...scenario.template }, + timeout: 4000 +}); + +const takeLayoutSnapshot = (misc: Misc): LayoutSnapshot => ({ + range: [ + misc.adapter.bufferInfo.firstIndex, + misc.adapter.bufferInfo.lastIndex + ], + defaultSize: misc.scroller.buffer.defaultSize, + backwardPadding: misc.padding.backward.getSize(), + forwardPadding: misc.padding.forward.getSize(), + innerLoopCount: misc.innerLoopCount +}); + +const setDynamicSizes = (misc: Misc): void => + misc.setItemProcessor(({ $index, data }) => { + data.size = getDynamicSize($index); + }); + +const registerLoadScenario = (scenario: LoadScenario, index: number): void => + makeTest({ + config: createConfig(scenario), + title: + scenario.firstForwardPadding !== undefined + ? `should load with fwd padding shift (${index})` + : `should fill the viewport with paddings (${index})`, + before: setDynamicSizes, + it: misc => async () => { + const cycleDone = misc.waitNextCycle(); + const loops = await misc.captureInnerLoops( + scenario.expected.innerLoopCount, + () => ({ + forwardPadding: misc.padding.forward.getSize(), + indexes: misc.scroller.buffer.items.map(item => item.$index), + firstIndex: misc.adapter.bufferInfo.firstIndex, + lastIndex: misc.adapter.bufferInfo.lastIndex + }) + ); + await cycleDone; + await misc.adapter.relax(); + + // structural (captured, not predicted): the buffer is a strictly + // contiguous range at every inner loop of the progressive fill + loops.forEach(({ indexes, firstIndex, lastIndex }) => + expect(indexes).toEqual( + Array.from( + { length: lastIndex - firstIndex + 1 }, + (_, offset) => firstIndex + offset + ) + ) + ); + + misc.expect.domMatchesBuffer(); + misc.expect.startVisible(scenario.settings.startIndex); + + expect(takeLayoutSnapshot(misc)).toEqual(scenario.expected); + if (scenario.firstForwardPadding !== undefined) { + expect(loops[0].forwardPadding).toBe(scenario.firstForwardPadding); + } + } + }); + +describe('Dynamic Size Spec for Average strategy', () => { + describe('Initial load', () => + initialScenarios.forEach((scenario, index) => + registerLoadScenario(scenario, index) + )); + + describe('Lack of items on 1st fetch', () => + lackScenarios.forEach((scenario, index) => + registerLoadScenario(scenario, index) + )); + + describe('After scroll', () => + scrollScenarios.forEach((scenario, index) => + makeTest({ + config: createConfig(scenario), + title: `should fill the viewport with paddings (${index})`, + before: setDynamicSizes, + it: misc => async () => { + await misc.relaxNext(); + await misc.reachEdge(Direction.forward); + expect(misc.scroller.buffer.eof.get()).toBe(true); + + await misc.reachEdge(Direction.backward); + + expect(misc.scroller.buffer.bof.get()).toBe(true); + misc.expect.domMatchesBuffer(); + } + }) + )); +}); diff --git a/tests/e2e/specs/dynamic-size.frequent.spec.ts b/tests/e2e/specs/dynamic-size.frequent.spec.ts new file mode 100644 index 00000000..50e095dc --- /dev/null +++ b/tests/e2e/specs/dynamic-size.frequent.spec.ts @@ -0,0 +1,181 @@ +import { + getDatasource, + makeTest, + Misc, + SizeStrategy, + TestConfig +} from '../scaffolding'; + +type Action = + | { type: 'scroll'; positions: Array<'min' | 'max' | number> } + | { type: 'check'; indexes: number[]; size: number }; + +interface Scenario { + title: string; + getSize: (index: number) => number; + initialDefault: number; + finalDefault?: number; + action?: Action; +} + +const settings = { + padding: 0.5, + bufferSize: 5, + startIndex: 1, + minIndex: -99, + maxIndex: 100 +}; + +const frequentScenarios: Scenario[] = [ + { + title: 'set default on load (0)', + getSize: () => 20, + initialDefault: 20 + }, + { + title: 'set default on load (1)', + getSize: index => (index < 0 ? -index : index === 0 ? 1 : index), + initialDefault: 1 + }, + { + title: 'change default on scroll min', + getSize: index => (index < 0 ? 20 : 30), + action: { type: 'scroll', positions: ['min'] }, + initialDefault: 30, + finalDefault: 20 + }, + { + title: 'not change default on scroll max', + getSize: index => (index < 0 ? 20 : 30), + action: { type: 'scroll', positions: ['max'] }, + initialDefault: 30 + }, + { + title: 'not change default on scroll max & min', + getSize: index => (index < 0 ? 20 : 30), + action: { type: 'scroll', positions: ['max', 'min'] }, + initialDefault: 30 + }, + { + title: 'change default on scroll max & min & 100', + getSize: index => (index < 0 ? 20 : 30), + action: { type: 'scroll', positions: ['max', 'min', 100] }, + initialDefault: 30, + finalDefault: 20 + }, + { + title: 'change default on Adapter.check', + getSize: index => + index >= -2 && index <= 2 ? 2 : index < 0 ? -index : index, + action: { type: 'check', indexes: [5, 6, 7, 8, 9, 10], size: 1 }, + initialDefault: 2, + finalDefault: 1 + }, + { + title: 'not change default on Adapter.check', + getSize: index => + index >= -2 && index <= 2 ? 2 : index < 0 ? -index : index, + action: { type: 'check', indexes: [5, 6, 7, 8, 9], size: 1 }, + initialDefault: 2, + finalDefault: 2 + } +]; + +const constantScenarios: Scenario[] = frequentScenarios + .filter((_scenario, index) => index % 2 === 0) + .map((scenario, index) => ({ + ...scenario, + title: `not change (${index + 1})`, + initialDefault: 22, + finalDefault: 22 + })); + +const runAction = async (misc: Misc, action: Action): Promise => { + if (action.type === 'scroll') { + for (const position of action.positions) { + if (position === 'min') { + await misc.scrollMinRelax(); + } else if (position === 'max') { + await misc.scrollMaxRelax(); + } else { + await misc.scrollToRelax(position); + } + } + return; + } + + action.indexes.forEach(index => { + const element = misc.getElement(index); + if (!element) { + throw new Error(`Item ${index} is not rendered`); + } + element.style.height = `${action.size}px`; + }); + await misc.adapter.check(); +}; + +const expectHealthyState = (misc: Misc): void => { + misc.expect.domMatchesBuffer(); + expect(misc.padding.backward.getSize()).toBeGreaterThanOrEqual(0); + expect(misc.padding.forward.getSize()).toBeGreaterThanOrEqual(0); + expect(misc.getScrollableSize()).toBeGreaterThanOrEqual( + misc.getViewportSize() + ); + misc.expect.visibleWithinBuffer(); +}; + +const registerScenario = ( + strategy: SizeStrategy, + scenario: Scenario, + configuredSize?: number +): void => { + const config: TestConfig = { + datasource: () => getDatasource({ min: -99, max: 100 }), + datasourceSettings: { + ...settings, + sizeStrategy: strategy, + ...(configuredSize === undefined ? {} : { itemSize: configuredSize }) + }, + templateSettings: { viewportHeight: 200, dynamicSize: 'size' } + }; + + makeTest({ + config, + title: `should ${scenario.title}`, + before: misc => + misc.setItemProcessor(({ $index, data }) => { + data.size = scenario.getSize($index); + }), + it: misc => async () => { + await misc.relaxNext(); + expect(misc.scroller.buffer.defaultSize).toBe(scenario.initialDefault); + expectHealthyState(misc); + + if (scenario.action) { + await runAction(misc, scenario.action); + expect(misc.scroller.buffer.defaultSize).toBe( + scenario.finalDefault ?? scenario.initialDefault + ); + expectHealthyState(misc); + } + } + }); +}; + +describe('Dynamic Size Spec for Frequent and Constant strategies', () => { + describe('SizeStrategy.Frequent', () => + frequentScenarios.forEach(scenario => + registerScenario(SizeStrategy.Frequent, scenario) + )); + + describe('SizeStrategy.Constant', () => { + constantScenarios.forEach(scenario => + registerScenario(SizeStrategy.Constant, scenario, 22) + ); + registerScenario(SizeStrategy.Constant, { + title: 'not change when itemSize is not set', + getSize: () => 10, + initialDefault: 10 + }); + }); +}); diff --git a/tests/e2e/specs/dynamic-size.zero.spec.ts b/tests/e2e/specs/dynamic-size.zero.spec.ts index 9fb4efd8..e01fc1d6 100644 --- a/tests/e2e/specs/dynamic-size.zero.spec.ts +++ b/tests/e2e/specs/dynamic-size.zero.spec.ts @@ -1,175 +1,73 @@ -import { test, expect } from '@playwright/test'; -import { Direction } from '../fixture/VScrollFixture.js'; -import { afterEachLogs } from '../fixture/after-each-logs.js'; -import { createFixture } from '../fixture/create-fixture.js'; -import { ITestConfig } from 'types/index.js'; -import { SizeStrategy } from '../../../src/index.js'; +import { getDatasource, makeTest, SizeStrategy } from '../scaffolding'; -test.afterEach(afterEachLogs); - -// // Capture console logs for comparison -// test.beforeEach(async ({ page }) => { -// page.on('console', msg => { -// console.log('[BROWSER]', msg.text()); -// }); -// }); - -type WithAsyncSize = Window & { __vscrollAsyncSize?: number }; - -const dynamicTemplate = (item: { - $index: number; - data: { id: number; text: string; size?: number }; -}): string => { - const size = item.data.size ?? 0; - return `
${item.$index}: ${item.data.text}
`; -}; - -const zeroSizeDatasourceGet: ITestConfig['datasourceGet'] = ( - index, - count, - success -) => { - const data = []; - for (let i = index; i < index + count; i++) { - if (i >= 1 && i <= 100) { - data.push({ id: i, text: `item #${i}`, size: 0 }); - } - } - setTimeout(() => success(data), 0); -}; - -// Async resize datasource: its item size is controlled via a flag on window -const asyncResizeDatasourceGet: ITestConfig['datasourceGet'] = ( - index, - count, - success -) => { - const data = []; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const size = ((window as any).__vscrollAsyncSize as number | undefined) ?? 0; - for (let i = index; i < index + count; i++) { - if (i >= 1 && i <= 100) { - data.push({ id: i, text: `item #${i}`, size }); - } - } - setTimeout(() => success(data), 0); -}; - -const secondPackZeroSizeDatasourceGet: ITestConfig['datasourceGet'] = ( - index, - count, - success -) => { - const data = []; - for (let i = index; i < index + count; i++) { - if (i >= 1 && i <= 100) { - const size = i >= 6 ? 0 : 20; - data.push({ id: i, text: `item #${i}`, size }); - } - } - setTimeout(() => success(data), 0); -}; - -const baseSettings = { - startIndex: 1, - bufferSize: 5, - minIndex: 1, - padding: 0.5, - sizeStrategy: SizeStrategy.Average -} as const; - -const zeroSizeConfig: ITestConfig = { - datasourceGet: zeroSizeDatasourceGet, - datasourceSettings: { ...baseSettings }, - templateSettings: { viewportHeight: 200 }, - templateFn: dynamicTemplate -}; - -const secondPackConfig: ITestConfig = { - datasourceGet: secondPackZeroSizeDatasourceGet, - datasourceSettings: { ...baseSettings }, - templateSettings: { viewportHeight: 200 }, - templateFn: dynamicTemplate -}; - -const asyncResizeConfig: ITestConfig = { - datasourceGet: asyncResizeDatasourceGet, +const baseConfig = { datasourceSettings: { - startIndex: 1, bufferSize: 5, - padding: 0.5, + minIndex: 1, sizeStrategy: SizeStrategy.Average }, - templateSettings: { viewportHeight: 200 }, - templateFn: dynamicTemplate + templateSettings: { dynamicSize: 'size', viewportHeight: 200 } }; -test.describe('Dynamic Zero Size Spec', () => { - test('Items with zero size: should stop the Workflow after the first loop', async ({ - page - }) => { - const fixture = await createFixture({ page, config: zeroSizeConfig }); - - const cyclesDone = await fixture.workflow.cyclesDone; - const innerLoopCount = await fixture.workflow.innerLoopCount; - expect(cyclesDone).toBe(1); - expect(innerLoopCount).toBe(1); - - await fixture.dispose(); - }); - - test('Items with zero size started from 2 pack: should stop the Workflow after the second loop', async ({ - page - }) => { - const fixture = await createFixture({ page, config: secondPackConfig }); - - const cyclesDone = await fixture.workflow.cyclesDone; - const innerLoopCount = await fixture.workflow.innerLoopCount; - expect(cyclesDone).toBe(1); - expect(innerLoopCount).toBe(2); - - await fixture.dispose(); - }); - - test('Items get non-zero size asynchronously: should continue the Workflow after re-size and check', async ({ - page - }) => { - const fixture = await createFixture({ page, config: asyncResizeConfig }); - const viewport = fixture.scroller.viewport; - - const scrollableSizeBefore = await viewport.getScrollableSize(); - const forwardPaddingBefore = - await viewport.paddings[Direction.forward].size; - expect(scrollableSizeBefore).toEqual(forwardPaddingBefore); - expect(scrollableSizeBefore).toBe(200); - - // Switch datasource to produce size=20 for all future items - // and make currently rendered items non-zero-sized via adapter.fix updater - await page.evaluate(() => { - const w = window as WithAsyncSize; - w.__vscrollAsyncSize = 20; - const adapter = w.__vscroll__.datasource.adapter; - return adapter.fix({ - updater: ({ element, data }) => { - (data as { size?: number }).size = 20; - const el = element as HTMLElement; - const child = (el.firstElementChild as HTMLElement) || el; - child.style.height = '20px'; - } - }); - }); - - await fixture.adapter.check(); - - const cyclesDone = await fixture.workflow.cyclesDone; - const scrollableSizeAfter = await viewport.getScrollableSize(); - const forwardPaddingAfter = await viewport.paddings[Direction.forward].size; - - expect(cyclesDone).toBe(2); - expect(forwardPaddingAfter).toBe(0); - expect(scrollableSizeAfter).toBeGreaterThan(0); - expect(scrollableSizeAfter).toBe(300); - - await fixture.dispose(); - }); +describe('Dynamic Zero Size Spec', () => { + describe('Items with zero size', () => + makeTest({ + config: { + ...baseConfig, + datasource: () => getDatasource({ min: 1, max: 100, size: 0 }) + }, + title: 'should stop the Workflow after the first loop', + it: misc => async () => { + await misc.relaxNext(); + expect(misc.innerLoopCount).toEqual(1); + } + })); + + describe('Items with zero size started from 2 pack', () => + makeTest({ + config: { + ...baseConfig, + datasource: () => getDatasource({ min: 1, max: 100 }) + }, + title: 'should stop the Workflow after the second loop', + it: misc => async () => { + misc.setItemProcessor( + ({ $index, data }) => (data.size = $index >= 6 ? 0 : 20) + ); + await misc.relaxNext(); + expect(misc.innerLoopCount).toEqual(2); + } + })); + + describe('Items get non-zero size asynchronously', () => + makeTest({ + config: { + ...baseConfig, + datasource: () => getDatasource({ min: 1, max: 100, size: 0 }) + }, + title: 'should continue the Workflow after re-size and check', + it: misc => async () => { + const { + scroller: { viewport }, + adapter + } = misc; + await misc.relaxNext(); + + expect(viewport.getScrollableSize()).toEqual( + viewport.paddings.forward.size + ); + misc.setItemProcessor(({ data }) => (data.size = 20)); + adapter.fix({ + updater: ({ element, data }) => { + data.size = 20; + ((element as HTMLElement).children[0] as HTMLElement).style.height = + '20px'; + } + }); + await adapter.check(); + + expect(viewport.getScrollableSize()).toBeGreaterThan(0); + expect(viewport.paddings.forward.size).toEqual(0); + } + })); }); diff --git a/tests/e2e/specs/eof.spec.ts b/tests/e2e/specs/eof.spec.ts index 016df814..6204bb2b 100644 --- a/tests/e2e/specs/eof.spec.ts +++ b/tests/e2e/specs/eof.spec.ts @@ -1,366 +1,256 @@ -import { test, expect, Page } from '@playwright/test'; import { - VScrollFixture, Direction, - type DirectionType -} from '../fixture/VScrollFixture.js'; -import { afterEachLogs } from '../fixture/after-each-logs.js'; -import { createFixture } from '../fixture/create-fixture.js'; -import { ITestConfig } from 'types/index.js'; - -test.afterEach(afterEachLogs); - -const min = 1; -const max = 100; -const scrollCount = 10; - -type IConfig = ITestConfig; - -// Limited datasource: items [1..100] -const datasourceGetLimited = (index, count, success) => { - const data = []; - for (let i = index; i < index + count; i++) { - if (i >= 1 && i <= 100) { - data.push({ id: i, text: `item #${i}` }); - } - } - setTimeout(() => success(data), 25); -}; - -// Empty datasource: no items at all -const datasourceGetEmpty = (_index, _count, success) => { - setTimeout(() => success([]), 25); -}; - -type BofEofState = { - bufferBof: boolean; - bufferEof: boolean; - adapterBof: boolean; - adapterEof: boolean; - sharedBof?: boolean; - sharedEof?: boolean; - bofCount?: number; - eofCount?: number; -}; - -type Counter = { - count: number; - value: boolean; - off?: () => void; -}; - -type BofEofCountersContainer = { - bof: Counter; - eof: Counter; -}; - -type WithCounters = Window & { __bofEofCounters?: BofEofCountersContainer }; - -/** Setup bof/eof tracking container in browser context */ -const setupBofEofTracking = async (page: Page) => - page.evaluate(() => { - const w = window as WithCounters; - const adapter = w.__vscroll__.datasource.adapter; - const bof: Counter = { - count: 0, - value: adapter.bof - }; - const eof: Counter = { - count: 0, - value: adapter.eof - }; - - w.__bofEofCounters = { bof, eof }; - - adapter.bof$.on((value: boolean) => { - bof.count++; - bof.value = value; - }); - adapter.eof$.on((value: boolean) => { - eof.count++; - eof.value = value; - }); - }); - -// Base configs for BOF and EOF cases -const bofConfig: IConfig = { - datasourceGet: datasourceGetLimited, - datasourceSettings: { - startIndex: min, - bufferSize: 10, - padding: 0.5 + getDatasource, + makeTest, + Misc, + MakeTestConfig, + OperationConfig, + TestConfig +} from '../scaffolding'; + +enum Operation { + eof = 'eof', + bof = 'bof' +} +const min = 1, + max = 100, + scrollCount = 10; + +const config: OperationConfig = { + [Operation.bof]: { + datasource: () => getDatasource({ min, max, delay: 1 }), + datasourceSettings: { + startIndex: min, + bufferSize: 10, + padding: 0.5 + }, + templateSettings: { viewportHeight: 200 } }, - templateSettings: { viewportHeight: 200, itemHeight: 20 }, - onBefore: setupBofEofTracking -}; - -const eofConfig: IConfig = { - datasourceGet: datasourceGetLimited, - datasourceSettings: { - startIndex: max - 10 + 1, - bufferSize: 10, - padding: 0.5 - }, - templateSettings: { viewportHeight: 200, itemHeight: 20 }, - onBefore: setupBofEofTracking + [Operation.eof]: { + datasource: () => getDatasource({ min, max, delay: 1 }), + datasourceSettings: { + startIndex: max - 10 + 1, + bufferSize: 10, + padding: 0.5 + }, + templateSettings: { viewportHeight: 200 } + } }; -// Config where datasource always returns empty array -const emptyConfig: IConfig = { - datasourceGet: datasourceGetEmpty, +const emptyConfig: TestConfig = { + ...config[Operation.bof], + datasource: () => getDatasource({ min: 1, max: 0 }), datasourceSettings: { - startIndex: min, - bufferSize: 10, - padding: 0.5 - }, - templateSettings: { viewportHeight: 200, itemHeight: 20 }, - onBefore: setupBofEofTracking + ...config[Operation.bof].datasourceSettings + } }; -// Config with explicit min/max indexes to track bof/eof reactive counters -const multipleScrollsConfig: IConfig = { - datasourceGet: datasourceGetLimited, +const observableCountConfig: TestConfig = { + ...config[Operation.bof], datasourceSettings: { - startIndex: min, - bufferSize: 10, - padding: 0.5, + ...config[Operation.bof].datasourceSettings, minIndex: min, maxIndex: max - }, - templateSettings: { viewportHeight: 200, itemHeight: 20 }, - onBefore: setupBofEofTracking + } }; -const getBofEofState = async (fixture: VScrollFixture): Promise => - fixture.page.evaluate(() => { - const w = window as WithCounters; - const workflow = w.__vscroll__.workflow; - const buffer = workflow.scroller.buffer; - const adapter = w.__vscroll__.datasource.adapter; - const counters = w.__bofEofCounters; - - return { - bufferBof: buffer.bof.get(), - bufferEof: buffer.eof.get(), - adapterBof: adapter.bof, - adapterEof: adapter.eof, - sharedBof: counters.bof?.value, - sharedEof: counters.eof?.value, - bofCount: counters.bof?.count || 0, - eofCount: counters.eof?.count || 0 - }; - }); - -/** - * Expect that BOF/EOF limit is reached for the given direction. - */ -const expectLimit = async ( - fixture: VScrollFixture, - config: IConfig, - direction: DirectionType, - noscroll = false -) => { - const forward = direction === Direction.forward; +interface Subscription { + unsubscribe: () => void; +} - const elements = await fixture.getElements(); - const bufferSize = config.datasourceSettings.bufferSize as number; - - expect(elements.length).toBeGreaterThan(bufferSize); - - const forwardPadding = - await fixture.scroller.viewport.paddings[Direction.forward].size; - const backwardPadding = - await fixture.scroller.viewport.paddings[Direction.backward].size; - - if (forward) { - expect(forwardPadding).toBe(0); - if (!noscroll) { - expect(backwardPadding).toBeGreaterThan(0); +interface BEContainer { + count: number; + value: boolean; + subscription: Subscription; +} + +interface BofEofContainer { + eof: BEContainer; + bof: BEContainer; +} + +const initializeBofEofContainer = (misc: Misc) => { + const { adapter, shared } = misc; + const bof: BEContainer = { + count: 0, + value: adapter.bof, + subscription: { + unsubscribe: adapter.bof$.on(value => { + bof.count++; + bof.value = value; + }) } - } else { - expect(backwardPadding).toBe(0); - if (!noscroll) { - expect(forwardPadding).toBeGreaterThan(0); + }; + const eof: BEContainer = { + count: 0, + value: adapter.eof, + subscription: { + unsubscribe: adapter.eof$.on(value => { + eof.count++; + eof.value = value; + }) } - } - - const targetElement = forward - ? (elements[elements.length - 1] as HTMLElement) - : (elements[0] as HTMLElement); - const targetIndex = await fixture.getElementIndex(targetElement); - const expectedIndex = forward ? max : min; - - expect(targetIndex).toBe(expectedIndex); - - const state = await getBofEofState(fixture); + }; + shared.bofEofContainer = { bof, eof } as BofEofContainer; +}; - // bof/eof flags in buffer, adapter and shared container must be in sync - expect(state.bufferBof).toBe(!forward); - expect(state.adapterBof).toBe(state.bufferBof); - if (typeof state.sharedBof !== 'undefined') { - expect(state.sharedBof).toBe(state.bufferBof); - } +const disposeBofEofContainer = (misc: Misc) => { + const { bof, eof } = misc.shared.bofEofContainer as BofEofContainer; + eof.subscription.unsubscribe(); + bof.subscription.unsubscribe(); +}; - expect(state.bufferEof).toBe(forward); - expect(state.adapterEof).toBe(state.bufferEof); - if (typeof state.sharedEof !== 'undefined') { - expect(state.sharedEof).toBe(state.bufferEof); +const expectLimit = (misc: Misc, direction: Direction, noscroll = false) => { + const _forward = direction === Direction.forward; + misc.expect.viewportFilled(); + const elements = misc.getElements(); + expect( + misc.padding[_forward ? Direction.forward : Direction.backward].getSize() + ).toEqual(0); + if (!noscroll) { + expect( + misc.padding[_forward ? Direction.backward : Direction.forward].getSize() + ).toBeGreaterThan(0); } + expect( + misc.checkElementId( + elements[_forward ? elements.length - 1 : 0], + _forward ? max : min + ) + ).toEqual(true); + + const { + adapter, + scroller: { + buffer: { eof, bof } + }, + shared + } = misc; + expect(bof.get()).toEqual(!_forward); + expect(bof.get()).toEqual(adapter.bof); + expect(bof.get()).toEqual( + (shared.bofEofContainer as BofEofContainer).bof.value + ); + expect(eof.get()).toEqual(adapter.eof); + expect(eof.get()).toEqual(_forward); + expect(eof.get()).toEqual( + (shared.bofEofContainer as BofEofContainer).eof.value + ); }; -const makeLimitTest = ( - title: string, - config: IConfig, - testFunc: (fixture: VScrollFixture, config: IConfig) => Promise -) => { - test(title, async ({ page }) => { - const fixture = await createFixture({ page, config }); - await testFunc(fixture, config); - await fixture.dispose(); +const _makeTest = (data: MakeTestConfig) => + makeTest({ + ...data, + before: (misc: Misc) => initializeBofEofContainer(misc), + after: (misc: Misc) => disposeBofEofContainer(misc) }); -}; -const runLimitSuite = (kind: 'bof' | 'eof') => { - const isEOF = kind === 'eof'; - const config = isEOF ? eofConfig : bofConfig; - const suiteTitle = isEOF ? 'End of file' : 'Begin of file'; - const limitDirection = isEOF ? Direction.forward : Direction.backward; - const oppositeDirection = isEOF ? Direction.backward : Direction.forward; - - test.describe(suiteTitle, () => { - makeLimitTest( - `should get ${kind} on init`, - config, - async (fixture, cfg) => { - // Initial workflow has already completed in createFixture - await expectLimit(fixture, cfg, limitDirection, true); +const runLimitSuite = (operation = Operation.bof) => { + const isEOF = operation === Operation.eof; + + describe((isEOF ? 'End' : 'Begin') + ' of file', () => { + const _operation = isEOF ? Operation.bof : Operation.eof; + const direction = isEOF ? Direction.forward : Direction.backward; + const directionOpposite = isEOF ? Direction.backward : Direction.forward; + const doScroll = (misc: Misc) => + isEOF ? misc.scrollMinRelax() : misc.scrollMaxRelax(); + const doScrollOpposite = (misc: Misc) => + isEOF ? misc.scrollMaxRelax() : misc.scrollMinRelax(); + + _makeTest({ + config: config[operation], + title: `should get ${operation} on init`, + it: misc => async () => { + await misc.relaxNext(); + expectLimit(misc, direction, true); } - ); - - makeLimitTest( - `should reset ${kind} after scroll`, - config, - async fixture => { - const initial = await getBofEofState(fixture); - if (isEOF) { - expect(initial.bufferEof).toBe(true); - expect(initial.adapterEof).toBe(true); - expect(initial.sharedEof).toBe(true); - expect(initial.bufferBof).toBe(false); - } else { - expect(initial.bufferBof).toBe(true); - expect(initial.bufferEof).toBe(false); - expect(initial.adapterEof).toBe(false); - expect(initial.sharedEof).toBe(false); - } - - // Scroll away from the current limit - if (isEOF) { - await fixture.scrollMin(); - } else { - await fixture.scrollMax(); - } + }); - const after = await getBofEofState(fixture); - expect(after.bufferBof).toBe(false); - expect(after.adapterBof).toBe(false); - expect(after.sharedBof).toBe(false); - expect(after.bufferEof).toBe(false); - expect(after.adapterEof).toBe(false); - expect(after.sharedEof).toBe(false); + _makeTest({ + config: config[operation], + title: `should reset ${operation} after scroll`, + it: misc => async () => { + const { + scroller: { buffer }, + adapter + } = misc; + const bofEofContainer = misc.shared.bofEofContainer as BofEofContainer; + + await misc.relaxNext(); + expect(buffer[operation].get()).toEqual(true); + expect(adapter[operation]).toEqual(true); + expect(bofEofContainer[operation].value).toEqual(true); + + await doScroll(misc); + expect(buffer[operation].get()).toEqual(false); + expect(adapter[operation]).toEqual(false); + expect(bofEofContainer[operation].value).toEqual(false); + expect(buffer[_operation].get()).toEqual(false); + expect(adapter[_operation]).toEqual(false); + expect(bofEofContainer[_operation].value).toEqual(false); } - ); - - makeLimitTest( - `should stop when ${kind} is reached again`, - config, - async (fixture, cfg) => { - // Move to the opposite limit and then back - if (isEOF) { - await fixture.scrollMin(); - await fixture.scrollMax(); - } else { - await fixture.scrollMax(); - await fixture.scrollMin(); - } + }); - await expectLimit(fixture, cfg, limitDirection); + _makeTest({ + config: config[operation], + title: `should stop when ${operation} is reached again`, + it: misc => async () => { + await misc.relaxNext(); + await doScroll(misc); + await doScrollOpposite(misc); + expectLimit(misc, direction); } - ); + }); - makeLimitTest( - `should reach ${isEOF ? 'bof' : 'eof'} after some scrolls`, - config, - async (fixture, cfg) => { - // From current limit, repeated scrolls must eventually reach the opposite limit - for (let i = 0; i < scrollCount; i++) { - if (isEOF) { - await fixture.scrollMin(); - } else { - await fixture.scrollMax(); - } + _makeTest({ + config: config[operation], + title: `should reach ${_operation} after some scrolls`, + it: misc => async () => { + await misc.relaxNext(); + while (misc.workflow.cyclesDone < scrollCount) { + await doScroll(misc); } - - await expectLimit(fixture, cfg, oppositeDirection); + expectLimit(misc, directionOpposite); } - ); + }); }); }; -test.describe('EOF/BOF Spec', () => { - runLimitSuite('bof'); - runLimitSuite('eof'); - - test.describe('Empty datasource', () => { - test('should reach both bof and eof during the first workflow cycle', async ({ - page - }) => { - const fixture = await createFixture({ page, config: emptyConfig }); - - const state = await getBofEofState(fixture); - expect(state.bufferBof).toBe(true); - expect(state.bufferEof).toBe(true); - expect(state.adapterBof).toBe(true); - expect(state.adapterEof).toBe(true); - expect(state.sharedBof).toBe(true); - expect(state.sharedEof).toBe(true); - expect(state.bofCount).toBe(1); - expect(state.eofCount).toBe(1); - - await fixture.dispose(); - }); +describe('EOF/BOF Spec', () => { + runLimitSuite(Operation.bof); + runLimitSuite(Operation.eof); + + _makeTest({ + config: emptyConfig, + title: 'should reach both bof and eof during the first WF cycle', + it: misc => async () => { + await misc.relaxNext(); + const { bof, eof } = misc.shared.bofEofContainer as BofEofContainer; + expect(bof.count).toEqual(1); + expect(eof.count).toEqual(1); + expect(bof.value).toEqual(true); + expect(eof.value).toEqual(true); + } }); - test.describe('Multiple scrolls', () => { - test('should reach bof/eof multiple times', async ({ page }) => { - const fixture = await createFixture({ - page, - config: multipleScrollsConfig - }); - - // Perform a sequence of scrolls: - // max, min, max, min, ... to toggle bof/eof repeatedly + _makeTest({ + config: observableCountConfig, + title: 'should reach bof/eof multiple times', + it: misc => async () => { const COUNT = 10; - for (let i = 1; i <= COUNT; i++) { - const cyclesDone = await fixture.workflow.cyclesDone; - if (cyclesDone === 1) { - await fixture.scrollMax(); - } else if (cyclesDone > 1 && cyclesDone < COUNT) { - if (cyclesDone % 2 === 0) { - await fixture.scrollMin(); - } else { - await fixture.scrollMax(); - } + await misc.relaxNext(); + + while (misc.workflow.cyclesDone < COUNT) { + if (misc.workflow.cyclesDone % 2 === 0) { + await misc.scrollMinRelax(); } else { - break; + await misc.scrollMaxRelax(); } } - const state = await getBofEofState(fixture); - expect(state.bofCount || 0).toEqual(COUNT); - expect(state.eofCount || 0).toEqual(COUNT - 1); - - await fixture.dispose(); - }); + const { bof, eof } = misc.shared.bofEofContainer as BofEofContainer; + expect(bof.count).toEqual(COUNT); + expect(eof.count).toEqual(COUNT - 1); + } }); }); diff --git a/tests/e2e/specs/initial-load.spec.ts b/tests/e2e/specs/initial-load.spec.ts index a3504d3c..e66bc713 100644 --- a/tests/e2e/specs/initial-load.spec.ts +++ b/tests/e2e/specs/initial-load.spec.ts @@ -1,61 +1,45 @@ -import { test, expect } from '@playwright/test'; -import { VScrollFixture, Direction } from '../fixture/VScrollFixture.js'; -import { afterEachLogs } from '../fixture/after-each-logs.js'; -import { createFixture } from '../fixture/create-fixture.js'; -import { ITestConfig } from 'types/index.js'; -import { ItemsCounter } from '../helpers/itemsCounter.js'; +import { makeTest, Misc, TestConfig } from '../scaffolding'; -test.afterEach(afterEachLogs); +type InitialLoadConfig = TestConfig & { + datasourceSettings: NonNullable; + templateSettings: NonNullable; +}; -// Basic unlimited datasource for all tests -const unlimitedDatasource = (index, count, success) => { - const data = []; - for (let i = index; i < index + count; i++) { - data.push({ id: i, text: `item #${i}` }); - } - setTimeout(() => success(data), 25); +type GoldenConfig = InitialLoadConfig & { + golden: [number, number]; }; -const fixedItemSizeConfigList: ITestConfig[] = [ +type Scenario = ( + config: Config +) => (misc: Misc) => () => Promise; + +const fixedItemSizeConfigList: GoldenConfig[] = [ { - datasourceGet: unlimitedDatasource, - datasourceSettings: { - startIndex: 1, - padding: 2, - itemSize: 15 - }, - templateSettings: { viewportHeight: 20, itemHeight: 15 } + datasourceSettings: { startIndex: 1, padding: 2, itemSize: 15 }, + templateSettings: { viewportHeight: 20, itemHeight: 15 }, + golden: [-4, 5] }, { - datasourceGet: unlimitedDatasource, - datasourceSettings: { - startIndex: 1, - padding: 0.5, - itemSize: 20 - }, - templateSettings: { viewportHeight: 120, itemHeight: 20 } + datasourceSettings: { startIndex: 1, padding: 0.5, itemSize: 20 }, + templateSettings: { viewportHeight: 120, itemHeight: 20 }, + golden: [-4, 9] }, { - datasourceGet: unlimitedDatasource, - datasourceSettings: { - startIndex: -99, - padding: 0.3, - itemSize: 25 - }, - templateSettings: { viewportHeight: 200, itemHeight: 25 } + datasourceSettings: { startIndex: -99, padding: 0.3, itemSize: 25 }, + templateSettings: { viewportHeight: 200, itemHeight: 25 }, + golden: [-104, -89] }, { - datasourceGet: unlimitedDatasource, datasourceSettings: { startIndex: -77, padding: 0.62, itemSize: 100, horizontal: true }, - templateSettings: { viewportWidth: 450, itemWidth: 100, horizontal: true } + templateSettings: { viewportWidth: 450, itemWidth: 100, horizontal: true }, + golden: [-82, -70] }, { - datasourceGet: unlimitedDatasource, datasourceSettings: { startIndex: 1, padding: 0.5, @@ -66,23 +50,23 @@ const fixedItemSizeConfigList: ITestConfig[] = [ noViewportClass: true, viewportHeight: 0, itemHeight: 20 - } + }, + golden: [-19, 58] } ]; -const fixedItemSizeAndBigBufferSizeConfigList: ITestConfig[] = [ +const fixedItemSizeAndBigBufferSizeConfigList: GoldenConfig[] = [ { - datasourceGet: unlimitedDatasource, datasourceSettings: { startIndex: 100, padding: 0.1, itemSize: 20, bufferSize: 20 }, - templateSettings: { viewportHeight: 100, itemHeight: 20 } + templateSettings: { viewportHeight: 100, itemHeight: 20 }, + golden: [80, 119] }, { - datasourceGet: unlimitedDatasource, datasourceSettings: { startIndex: -50, padding: 0.1, @@ -90,75 +74,70 @@ const fixedItemSizeAndBigBufferSizeConfigList: ITestConfig[] = [ bufferSize: 10, horizontal: true }, - templateSettings: { viewportWidth: 200, itemWidth: 100, horizontal: true } + templateSettings: { viewportWidth: 200, itemWidth: 100, horizontal: true }, + golden: [-60, -41] } ]; -const tunedItemSizeConfigList: ITestConfig[] = [ +const tunedItemSizeConfigList: GoldenConfig[] = [ { - datasourceGet: unlimitedDatasource, datasourceSettings: { startIndex: 1, bufferSize: 1, padding: 0.5, itemSize: 40 }, - noRelaxOnStart: true, - templateSettings: { viewportHeight: 100, itemHeight: 20 } + templateSettings: { viewportHeight: 100, itemHeight: 20 }, + golden: [-2, 8] }, { - datasourceGet: unlimitedDatasource, datasourceSettings: { startIndex: -50, bufferSize: 2, padding: 0.5, itemSize: 30 }, - noRelaxOnStart: true, - templateSettings: { viewportHeight: 120, itemHeight: 20 } + templateSettings: { viewportHeight: 120, itemHeight: 20 }, + golden: [-53, -42] }, { - datasourceGet: unlimitedDatasource, datasourceSettings: { startIndex: -77, padding: 0.82, itemSize: 200, horizontal: true }, - noRelaxOnStart: true, - templateSettings: { viewportWidth: 450, itemWidth: 100, horizontal: true } + templateSettings: { viewportWidth: 450, itemWidth: 100, horizontal: true }, + golden: [-82, -68] }, { - datasourceGet: unlimitedDatasource, datasourceSettings: { startIndex: -47, padding: 0.3, itemSize: 60, windowViewport: true }, - noRelaxOnStart: true, templateSettings: { noViewportClass: true, viewportHeight: 0, itemHeight: 40 - } + }, + golden: [-53, -23] } ]; -const tunedItemSizeAndBigBufferSizeConfigList: ITestConfig[] = [ +const tunedItemSizeAndBigBufferSizeConfigList: GoldenConfig[] = [ { - datasourceGet: unlimitedDatasource, datasourceSettings: { startIndex: -50, bufferSize: 7, padding: 0.5, itemSize: 30 }, - noRelaxOnStart: true, - templateSettings: { viewportHeight: 120, itemHeight: 20 } + templateSettings: { viewportHeight: 120, itemHeight: 20 }, + golden: [-57, -37] }, { - datasourceGet: unlimitedDatasource, datasourceSettings: { startIndex: 50, padding: 0.33, @@ -166,89 +145,45 @@ const tunedItemSizeAndBigBufferSizeConfigList: ITestConfig[] = [ bufferSize: 20, windowViewport: true }, - noRelaxOnStart: true, templateSettings: { noViewportClass: true, viewportHeight: 0, itemHeight: 20 - } + }, + golden: [30, 101] } ]; -const noItemSizeConfigList: ITestConfig[] = tunedItemSizeConfigList.map( - ({ - datasourceGet, - datasourceSettings: { itemSize: _, ...restDatasourceSettings }, - ...config - }) => ({ - ...config, - datasourceGet, - datasourceSettings: { ...restDatasourceSettings } - }) -); +const withoutItemSize = (config: GoldenConfig): GoldenConfig => { + const datasourceSettings = { ...config.datasourceSettings }; + delete datasourceSettings.itemSize; + return { ...config, datasourceSettings }; +}; -const noItemSizeAndBigBufferConfigList: ITestConfig[] = - tunedItemSizeAndBigBufferSizeConfigList.map( - ({ - datasourceGet, - datasourceSettings: { itemSize: _, ...restDatasourceSettings }, - ...config - }) => ({ - ...config, - datasourceGet, - datasourceSettings: { ...restDatasourceSettings } - }) - ); +const noItemSizeConfigList = tunedItemSizeConfigList.map(withoutItemSize); +const noItemSizeAndBigBufferConfigList = + tunedItemSizeAndBigBufferSizeConfigList.map(withoutItemSize); -const lackOfItemsOnFirstFetchConfigList: ITestConfig[] = [ +const lackOfItemsOnFirstFetchConfigList: InitialLoadConfig[] = [ { - datasourceGet: (index, count, success) => { - const data = []; - for (let i = index; i < index + count; i++) { - if (i >= 1) { - data.push({ id: i, text: `item #${i}` }); - } - } - setTimeout(() => success(data), 25); - }, datasourceSettings: { startIndex: 100, padding: 0.5, bufferSize: 10, minIndex: 1 }, - noRelaxOnStart: true, templateSettings: { viewportHeight: 300, itemHeight: 20 } }, { - datasourceGet: (index, count, success) => { - const data = []; - for (let i = index; i < index + count; i++) { - if (i >= -75) { - data.push({ id: i, text: `item #${i}` }); - } - } - setTimeout(() => success(data), 25); - }, datasourceSettings: { startIndex: -70, padding: 0.5, bufferSize: 2, minIndex: -75 }, - noRelaxOnStart: true, templateSettings: { viewportHeight: 200, itemHeight: 20 } }, { - datasourceGet: (index, count, success) => { - const data = []; - for (let i = index; i < index + count; i++) { - if (i >= -9) { - data.push({ id: i, text: `item #${i}` }); - } - } - setTimeout(() => success(data), 25); - }, datasourceSettings: { startIndex: 1, padding: 0.1, @@ -256,7 +191,6 @@ const lackOfItemsOnFirstFetchConfigList: ITestConfig[] = [ minIndex: -9, windowViewport: true }, - noRelaxOnStart: true, templateSettings: { noViewportClass: true, viewportHeight: 0, @@ -264,15 +198,6 @@ const lackOfItemsOnFirstFetchConfigList: ITestConfig[] = [ } }, { - datasourceGet: (index, count, success) => { - const data = []; - for (let i = index; i < index + count; i++) { - if (i >= -120) { - data.push({ id: i, text: `item #${i}` }); - } - } - setTimeout(() => success(data), 25); - }, datasourceSettings: { startIndex: -99, padding: 0.3, @@ -280,413 +205,130 @@ const lackOfItemsOnFirstFetchConfigList: ITestConfig[] = [ minIndex: -120, horizontal: true }, - noRelaxOnStart: true, templateSettings: { horizontal: true, viewportWidth: 300, itemWidth: 40 } } ]; -const getSetItemSizeCounter = async ( - config: ITestConfig, - fixture: VScrollFixture, - itemSize: number -): Promise => { - const bufferSize = await fixture.scroller.settings.bufferSize; - const startIndex = config.datasourceSettings.startIndex as number; - const padding = config.datasourceSettings.padding as number; - const viewportSize = await fixture.scroller.viewport.getSize(); - - const backwardLimit = viewportSize * padding; - const forwardLimit = viewportSize + backwardLimit; - const itemsCounter = new ItemsCounter(); - - itemsCounter.backward.count = Math.ceil(backwardLimit / itemSize); - itemsCounter.forward.count = Math.ceil(forwardLimit / itemSize); - - // when bufferSize is big enough - const bwdDiff = bufferSize - itemsCounter.backward.count; - if (bwdDiff > 0) { - itemsCounter.backward.count += bwdDiff; - } - const fwdDiff = bufferSize - itemsCounter.forward.count; - if (fwdDiff > 0) { - itemsCounter.forward.count += fwdDiff; - } - - itemsCounter.backward.index = startIndex - itemsCounter.backward.count; - itemsCounter.forward.index = startIndex + itemsCounter.forward.count - 1; - return itemsCounter; -}; - -const getNotSetItemSizeCounter = async ( - config: ITestConfig, - fixture: VScrollFixture, - itemSize: number, - previous?: ItemsCounter -): Promise => { - const bufferSize = await fixture.scroller.settings.bufferSize; - const startIndex = config.datasourceSettings.startIndex as number; - const padding = config.datasourceSettings.padding as number; - const viewportSize = await fixture.scroller.viewport.getSize(); - const backwardLimit = viewportSize * padding; - const forwardLimit = viewportSize + backwardLimit; - const itemsCounter = new ItemsCounter(); - const countB = previous ? previous.backward.count : 0; - const countF = previous ? previous.forward.count : 0; - let bwd, fwd; - - // 1) fetch only in forward direction if this is the first fetch - // 2) fetch bufferSize items if Settings.itemSize value hasn't been set up - itemsCounter.backward.count = previous - ? itemSize - ? Math.ceil(backwardLimit / itemSize) - : bufferSize - : 0; - itemsCounter.forward.count = itemSize - ? Math.ceil(forwardLimit / itemSize) - : bufferSize; - if (previous) { - itemsCounter.backward.count = Math.max(itemsCounter.backward.count, countB); - itemsCounter.forward.count = Math.max(itemsCounter.forward.count, countF); - } - - // when bufferSize is big enough - bwd = itemsCounter.backward.count - (previous ? countB : 0); - fwd = itemsCounter.forward.count - (previous ? countF : 0); - const bwdDiff = bwd > 0 ? bufferSize - bwd : 0; - const fwdDiff = fwd > 0 ? bufferSize - fwd : 0; - if (bwdDiff > 0 && bwd > fwd) { - itemsCounter.backward.count += bwdDiff; - itemsCounter.forward.count = previous ? countF : itemsCounter.forward.count; - } - if (fwdDiff > 0 && fwd >= bwd) { - itemsCounter.forward.count += fwdDiff; - itemsCounter.backward.count = previous - ? countB - : itemsCounter.backward.count; - } - - if (previous) { - bwd = itemsCounter.backward.count - countB; - fwd = itemsCounter.forward.count - countF; - if (bwd > 0 && bwd > fwd) { - itemsCounter.backward.count = countB + bwd; - itemsCounter.forward.count = - fwd > 0 ? countF : itemsCounter.forward.count; - } - if (fwd > 0 && fwd >= bwd) { - itemsCounter.forward.count = countF + fwd; - itemsCounter.backward.count = - bwd > 0 ? countB : itemsCounter.backward.count; - } - } - - itemsCounter.backward.index = startIndex - itemsCounter.backward.count; - itemsCounter.forward.index = startIndex + itemsCounter.forward.count - 1; - - const defaultSize = await fixture.scroller.buffer.defaultSize; - itemsCounter.backward.padding = 0; - itemsCounter.forward.padding = Math.max( - 0, - viewportSize - itemsCounter.forward.count * defaultSize - ); - - return itemsCounter; -}; - -const testItemsCounter = async ( - startIndex: number, - fixture: VScrollFixture, - itemsCounter: ItemsCounter -) => { - const elements = await fixture.getElements(); - const firstIndex = await fixture.getElementIndex(elements[0] as HTMLElement); - const lastIndex = await fixture.getElementIndex( - elements[elements.length - 1] as HTMLElement - ); - - expect(firstIndex).toEqual(itemsCounter.backward.index); - expect(lastIndex).toEqual(itemsCounter.forward.index); - expect( - await fixture.scroller.viewport.paddings[Direction.backward].size - ).toEqual(itemsCounter.backward.padding); - expect( - await fixture.scroller.viewport.paddings[Direction.forward].size - ).toEqual(itemsCounter.forward.padding); - - const bufferInfo = await fixture.adapter.bufferInfo; - expect(bufferInfo.firstIndex).toEqual(itemsCounter.backward.index); - expect(bufferInfo.lastIndex).toEqual(itemsCounter.forward.index); - - const firstVisible = await fixture.adapter.firstVisible; - expect(firstVisible.$index).toEqual(startIndex); -}; - -const testSetItemSizeCase = async ( - fixture: VScrollFixture, - config: ITestConfig -) => { - // Wait for the workflow to complete - await fixture.adapter.relax(); - - const cyclesDone = await fixture.workflow.cyclesDone; - const fetchCount = await fixture.page.evaluate( - () => window.__vscroll__.workflow.scroller.state.fetch.callCount - ); - const innerLoopCount = await fixture.workflow.innerLoopCount; - const clipCount = await fixture.page.evaluate( - () => window.__vscroll__.workflow.scroller.state.clip.callCount - ); - - expect(cyclesDone).toEqual(1); - expect(fetchCount).toEqual(2); - expect(innerLoopCount).toEqual(3); - expect(clipCount).toEqual(0); - expect( - await fixture.scroller.viewport.paddings[Direction.backward].size - ).toEqual(0); - expect( - await fixture.scroller.viewport.paddings[Direction.forward].size - ).toEqual(0); - - const startIndex = config.datasourceSettings.startIndex as number; - const tplSettings = config.templateSettings || {}; - const horizontal = config.datasourceSettings.horizontal; - const itemSize = tplSettings[ - horizontal ? 'itemWidth' : 'itemHeight' - ] as number; - const itemsCounter = await getSetItemSizeCounter(config, fixture, itemSize); - await testItemsCounter(startIndex, fixture, itemsCounter); -}; - -type LoopData = { - loopCount: number; - viewportSize: number; - defaultSize: number; - bufferSize: number; - firstIndex: number; - lastIndex: number; - bwdPaddingSize: number; - fwdPaddingSize: number; - elementsLength: number; - firstElementIndex: number; - lastElementIndex: number; - bufferItemsLength: number; - bufferFirstIndex: number; - bufferLastIndex: number; - firstVisibleIndex: number; -}; - -const testNotSetItemSizeCase = async ( - fixture: VScrollFixture, - config: ITestConfig -) => { - const startIndex = config.datasourceSettings.startIndex as number; - const initialItemSize = config.datasourceSettings.itemSize as number; - const tplSettings = config.templateSettings || {}; - const horizontal = config.datasourceSettings.horizontal; - const itemSize = tplSettings[ - horizontal ? 'itemWidth' : 'itemHeight' - ] as number; - - // Capture data from all loops in browser context - const loopData = await fixture.page.evaluate(() => { - return new Promise<{ loops: LoopData[] }>(resolve => { - const workflow = window.__vscroll__.workflow; - const innerLoop = workflow.scroller.state.cycle.innerLoop; - const loops: LoopData[] = []; - - innerLoop.busy.on(loopPending => { - if (loopPending) { - return; - } - - const loopCount = innerLoop.total; - - // On loops 1, 2, 3: memorize complete state - if (loopCount >= 1 && loopCount <= 3) { - const buffer = workflow.scroller.buffer; - loops.push({ - loopCount: loopCount, - viewportSize: workflow.scroller.viewport.getScrollableSize(), - defaultSize: buffer.defaultSize, - bufferSize: buffer.size, - firstIndex: buffer.firstIndex, - lastIndex: buffer.lastIndex, - bwdPaddingSize: workflow.scroller.viewport.paddings.backward.size, - fwdPaddingSize: workflow.scroller.viewport.paddings.forward.size, - elementsLength: buffer.items.length, - firstElementIndex: buffer.items[0].$index, - lastElementIndex: buffer.items[buffer.items.length - 1].$index, - bufferItemsLength: buffer.items.length, - bufferFirstIndex: workflow.scroller.adapter.bufferInfo.firstIndex, - bufferLastIndex: workflow.scroller.adapter.bufferInfo.lastIndex, - firstVisibleIndex: workflow.scroller.adapter.firstVisible.$index - }); - } - - // On loop 4: resolve with all collected data - if (loopCount === 4) { - resolve({ loops }); - return; - } - }); +const testFixedItemSize: Scenario = + config => misc => async () => { + await misc.relaxNext(); + + const { fetch, clip } = misc.scroller.state; + expect(misc.workflow.cyclesDone).toEqual(1); + expect(fetch.callCount).toEqual(2); + expect(misc.innerLoopCount).toEqual(3); + expect(clip.callCount).toEqual(0); + + misc.expect.consistent(); + misc.expect.bufferRange(config.golden); + misc.expect.startVisible(config.datasourceSettings.startIndex as number); + expect(misc.padding.backward.getSize()).toEqual(0); + expect(misc.padding.forward.getSize()).toEqual(0); + }; + +const testMeasuredItemSize: Scenario = + config => misc => async () => { + const cycleDone = misc.waitNextCycle(); + const innerLoops = await misc.captureInnerLoops(3, () => ({ + indexes: misc.scroller.buffer.items.map(item => item.$index), + firstIndex: misc.adapter.bufferInfo.firstIndex, + lastIndex: misc.adapter.bufferInfo.lastIndex + })); + await cycleDone; + await misc.adapter.relax(); + + expect(misc.workflow.cyclesDone).toEqual(1); + expect(misc.scroller.state.fetch.callCount).toEqual(3); + expect(misc.scroller.state.clip.callCount).toEqual(0); + + innerLoops.forEach(({ indexes, firstIndex, lastIndex }) => { + const contiguous = Array.from( + { length: lastIndex - firstIndex + 1 }, + (_, offset) => firstIndex + offset + ); + expect(indexes).toEqual(contiguous); }); - }); - - // Wait for scroller to stop completely - await fixture.adapter.relax(); - // Check final state in node context - const cyclesDone = await fixture.workflow.cyclesDone; - const { fetchCount, clipCount } = await fixture.page.evaluate(() => ({ - fetchCount: window.__vscroll__.workflow.scroller.state.fetch.callCount, - clipCount: window.__vscroll__.workflow.scroller.state.clip.callCount + misc.expect.consistent(); + misc.expect.bufferRange(config.golden); + misc.expect.startVisible(config.datasourceSettings.startIndex as number); + expect(misc.padding.backward.getSize()).toEqual(0); + expect(misc.padding.forward.getSize()).toEqual(0); + }; + +const testFirstFetchGap: Scenario = config => misc => async () => { + const cycleDone = misc.waitNextCycle(); + const [firstLoop] = await misc.captureInnerLoops(1, () => ({ + scrollPosition: misc.getScrollPosition(), + backwardPadding: misc.padding.backward.getSize(), + firstVisibleIndex: misc.adapter.firstVisible.$index })); - expect(cyclesDone).toEqual(1); - expect(fetchCount).toEqual(3); - expect(clipCount).toEqual(0); - // Validate each loop's itemsCounter against captured snapshots - let previousCounter: ItemsCounter | undefined; - - for (const loop of loopData.loops) { - const sizeToUse = loop.loopCount === 1 ? initialItemSize : itemSize; - const itemsCounter = await getNotSetItemSizeCounter( - config, - fixture, - sizeToUse, - previousCounter - ); - - // Replicate all checks from original testItemsCounter using captured data - expect(loop.bwdPaddingSize).toEqual(itemsCounter.backward.padding); - expect(loop.fwdPaddingSize).toEqual(itemsCounter.forward.padding); - expect(loop.elementsLength).toEqual(itemsCounter.total); - expect(loop.bufferItemsLength).toEqual(itemsCounter.total); - expect(loop.firstElementIndex).toEqual(itemsCounter.backward.index); - expect(loop.lastElementIndex).toEqual(itemsCounter.forward.index); - expect(loop.bufferFirstIndex).toEqual(itemsCounter.backward.index); - expect(loop.bufferLastIndex).toEqual(itemsCounter.forward.index); - expect(loop.firstVisibleIndex).toEqual(startIndex); - - previousCounter = itemsCounter; - } -}; - -const testLackOfItemsOnFirstFetchCase = async ( - fixture: VScrollFixture, - config: ITestConfig -) => { const startIndex = config.datasourceSettings.startIndex as number; + expect(firstLoop.scrollPosition).toBe(firstLoop.backwardPadding); + expect(firstLoop.firstVisibleIndex).toEqual(startIndex); - // Capture state after the first inner loop completes - const result = await fixture.page.evaluate(() => { - return new Promise<{ - scrollPosition: number; - bwdPaddingSize: number; - firstVisibleIndex: number; - }>(resolve => { - const workflow = window.__vscroll__.workflow; - const innerLoop = workflow.scroller.state.cycle.innerLoop; - let resolved = false; - - innerLoop.busy.on((loopPending: boolean) => { - if (!loopPending && !resolved) { - // First inner loop just completed - resolved = true; - const viewport = workflow.scroller.viewport; - resolve({ - scrollPosition: viewport.scrollPosition, - bwdPaddingSize: viewport.paddings.backward.size, - firstVisibleIndex: workflow.scroller.adapter.firstVisible.$index - }); - } - }); - }); - }); - - // Start workflow and wait for it to complete - await fixture.adapter.relax(); - - // Verify state after first inner loop - expect(result.scrollPosition).toBe(result.bwdPaddingSize); - expect(result.firstVisibleIndex).toEqual(startIndex); + await cycleDone; + await misc.adapter.relax(); - // Verify final state - const finalFirstVisibleIndex = (await fixture.adapter.firstVisible).$index; - expect(finalFirstVisibleIndex).toEqual(startIndex); + misc.expect.consistent(); + misc.expect.startVisible(startIndex); }; -const makeTest = ( +const registerCases = ( + configs: Config[], title: string, - config: ITestConfig, - testFunc: (fixture: VScrollFixture, config: ITestConfig) => Promise -) => { - test(title, async ({ page }) => { - const fixture = await createFixture({ page, config }); - await testFunc(fixture, config); - await fixture.dispose(); - }); -}; - -test.describe('Initial Load Spec', () => { - test.describe('Fixed itemSize', () => { - fixedItemSizeConfigList.forEach((config, i) => - makeTest( - `should make 2 fetches to satisfy padding limits (config ${i})`, - config, - testSetItemSizeCase - ) + scenario: Scenario +) => + configs.forEach(config => makeTest({ config, title, it: scenario(config) })); + +describe('Initial Load Spec', () => { + describe('Fixed itemSize', () => { + registerCases( + fixedItemSizeConfigList, + 'should make 2 fetches to satisfy padding limits', + testFixedItemSize ); - fixedItemSizeAndBigBufferSizeConfigList.forEach((config, i) => - makeTest( - `should make 2 fetches to overflow padding limits (bufferSize is big enough) (config ${i})`, - config, - testSetItemSizeCase - ) + registerCases( + fixedItemSizeAndBigBufferSizeConfigList, + 'should make 2 fetches to overflow padding limits (bufferSize is big enough)', + testFixedItemSize ); }); - test.describe('Tuned itemSize', () => { - tunedItemSizeConfigList.forEach((config, i) => - makeTest( - `should make 3 fetches to satisfy padding limits (config ${i})`, - config, - testNotSetItemSizeCase - ) + describe('Tuned itemSize', () => { + registerCases( + tunedItemSizeConfigList, + 'should make 3 fetches to satisfy padding limits', + testMeasuredItemSize ); - tunedItemSizeAndBigBufferSizeConfigList.forEach((config, i) => - makeTest( - `should make 3 fetches to overflow padding limits (bufferSize is big enough) (config ${i})`, - config, - testNotSetItemSizeCase - ) + registerCases( + tunedItemSizeAndBigBufferSizeConfigList, + 'should make 3 fetches to overflow padding limits (bufferSize is big enough)', + testMeasuredItemSize ); }); - test.describe('No itemSize', () => { - noItemSizeConfigList.forEach((config, i) => - makeTest( - `should make 3 fetches to satisfy padding limits (config ${i})`, - config, - testNotSetItemSizeCase - ) + describe('No itemSize', () => { + registerCases( + noItemSizeConfigList, + 'should make 3 fetches to satisfy padding limits', + testMeasuredItemSize ); - noItemSizeAndBigBufferConfigList.forEach((config, i) => - makeTest( - `should make 3 fetches to overflow padding limits (bufferSize is big enough) (config ${i})`, - config, - testNotSetItemSizeCase - ) + registerCases( + noItemSizeAndBigBufferConfigList, + 'should make 3 fetches to overflow padding limits (bufferSize is big enough)', + testMeasuredItemSize ); }); - test.describe('Lack of items after the 1st fetch', () => - lackOfItemsOnFirstFetchConfigList.forEach((config, i) => - makeTest( - `should stretch the forward padding element (${i})`, + describe('Lack of items after the 1st fetch', () => { + lackOfItemsOnFirstFetchConfigList.forEach((config, index) => + makeTest({ config, - testLackOfItemsOnFirstFetchCase - ) - )); + title: `should stretch the forward padding element (${index})`, + it: testFirstFetchGap(config) + }) + ); + }); }); diff --git a/tests/e2e/specs/min-max-indexes.spec.ts b/tests/e2e/specs/min-max-indexes.spec.ts index fba6fc1a..ce47715a 100644 --- a/tests/e2e/specs/min-max-indexes.spec.ts +++ b/tests/e2e/specs/min-max-indexes.spec.ts @@ -1,22 +1,25 @@ -import { test, expect } from '@playwright/test'; -import { VScrollFixture, Direction } from '../fixture/VScrollFixture.js'; -import { afterEachLogs } from '../fixture/after-each-logs.js'; -import { createFixture } from '../fixture/create-fixture.js'; -import { ITestConfig } from 'types/index.js'; +import { makeTest, Misc, TestConfig } from '../scaffolding'; -test.afterEach(afterEachLogs); +type Config = TestConfig & { + datasourceSettings: NonNullable; + templateSettings: NonNullable; +}; + +interface ExpectedRange { + bufferRange: [number, number]; +} + +interface ExpectedLoad extends ExpectedRange { + loops: number; +} + +interface GoldenCase { + config: Config; + expected: Expected; +} -const configList: ITestConfig[] = [ +const configs: Config[] = [ { - datasourceGet: (index, count, success) => { - const data = []; - for (let i = index; i < index + count; i++) { - if (i >= -49 && i <= 100) { - data.push({ id: i, text: `item #${i}` }); - } - } - setTimeout(() => success(data), 25); - }, datasourceSettings: { startIndex: 1, padding: 0.5, @@ -27,15 +30,6 @@ const configList: ITestConfig[] = [ templateSettings: { viewportHeight: 200, itemHeight: 20 } }, { - datasourceGet: (index, count, success) => { - const data = []; - for (let i = index; i < index + count; i++) { - if (i >= -69 && i <= 1300) { - data.push({ id: i, text: `item #${i}` }); - } - } - setTimeout(() => success(data), 25); - }, datasourceSettings: { startIndex: 600, padding: 1.2, @@ -46,15 +40,6 @@ const configList: ITestConfig[] = [ templateSettings: { viewportHeight: 100, itemHeight: 40 } }, { - datasourceGet: (index, count, success) => { - const data = []; - for (let i = index; i < index + count; i++) { - if (i >= 169 && i <= 230) { - data.push({ id: i, text: `item #${i}` }); - } - } - setTimeout(() => success(data), 25); - }, datasourceSettings: { startIndex: 174, padding: 0.7, @@ -65,15 +50,6 @@ const configList: ITestConfig[] = [ templateSettings: { viewportHeight: 50, itemHeight: 25 } }, { - datasourceGet: (index, count, success) => { - const data = []; - for (let i = index; i < index + count; i++) { - if (i >= 20 && i <= 230) { - data.push({ id: i, text: `item #${i}` }); - } - } - setTimeout(() => success(data), 25); - }, datasourceSettings: { startIndex: 33, padding: 0.62, @@ -85,15 +61,6 @@ const configList: ITestConfig[] = [ templateSettings: { viewportWidth: 450, itemWidth: 100, horizontal: true } }, { - datasourceGet: (index, count, success) => { - const data = []; - for (let i = index; i < index + count; i++) { - if (i >= -40 && i <= 159) { - data.push({ id: i, text: `item #${i}` }); - } - } - setTimeout(() => success(data), 25); - }, datasourceSettings: { startIndex: 1, padding: 0.25, @@ -110,340 +77,210 @@ const configList: ITestConfig[] = [ } ]; -const noItemSizeConfigList: ITestConfig[] = configList.map( - ({ - datasourceGet, - datasourceSettings: { itemSize: _, ...restDatasourceSettings }, - ...config - }) => ({ - ...config, - datasourceGet, - datasourceSettings: { ...restDatasourceSettings } - }) -); - -const commonConfig = configList[0]; // [-49, ..., 100] +const withoutSetting = ( + config: Config, + setting: 'itemSize' | 'minIndex' | 'maxIndex' +): Config => { + const datasourceSettings = { ...config.datasourceSettings }; + delete datasourceSettings[setting]; + return { ...config, datasourceSettings }; +}; -const startIndexAroundMinIndexConfigList: ITestConfig[] = [ - { - ...commonConfig, - datasourceSettings: { - ...commonConfig.datasourceSettings, - startIndex: -9999 - } - }, - { - ...commonConfig, - datasourceSettings: { ...commonConfig.datasourceSettings, startIndex: -50 } - }, - { - ...commonConfig, - datasourceSettings: { ...commonConfig.datasourceSettings, startIndex: -49 } - }, - { - ...commonConfig, - datasourceSettings: { ...commonConfig.datasourceSettings, startIndex: -48 } - } +const commonExpected: ExpectedLoad[] = [ + { bufferRange: [-4, 15], loops: 3 }, + { bufferRange: [595, 605], loops: 3 }, + { bufferRange: [169, 178], loops: 3 }, + { bufferRange: [28, 40], loops: 3 }, + { bufferRange: [-9, 48], loops: 3 } +]; +const noItemSizeExpected: ExpectedLoad[] = [ + { bufferRange: [-4, 15], loops: 4 }, + { bufferRange: [595, 609], loops: 4 }, + { bufferRange: [169, 178], loops: 3 }, + { bufferRange: [28, 42], loops: 4 }, + { bufferRange: [-9, 48], loops: 4 } +]; +const aroundMinExpected: ExpectedRange[] = [ + { bufferRange: [-49, -35] }, + { bufferRange: [-49, -35] }, + { bufferRange: [-49, -35] }, + { bufferRange: [-49, -34] } +]; +const aroundMaxExpected: ExpectedRange[] = [ + { bufferRange: [86, 100] }, + { bufferRange: [86, 100] }, + { bufferRange: [86, 100] }, + { bufferRange: [86, 100] } ]; -const startIndexAroundMaxIndexConfigList: ITestConfig[] = [ - { - ...commonConfig, - datasourceSettings: { ...commonConfig.datasourceSettings, startIndex: 99 } - }, - { - ...commonConfig, - datasourceSettings: { ...commonConfig.datasourceSettings, startIndex: 100 } - }, - { - ...commonConfig, - datasourceSettings: { ...commonConfig.datasourceSettings, startIndex: 101 } - }, - { - ...commonConfig, - datasourceSettings: { ...commonConfig.datasourceSettings, startIndex: 999 } +const zip = ( + configs: Config[], + expected: Expected[] +): GoldenCase[] => { + if (configs.length !== expected.length) { + throw new Error( + `golden mismatch: ${configs.length} configs vs ${expected.length} expected` + ); } -]; + return configs.map((config, index) => ({ + config, + expected: expected[index] + })); +}; -const noMaxIndexConfigList: ITestConfig[] = configList.map( - ({ - datasourceGet, - datasourceSettings: { maxIndex: _, ...datasourceSettings }, - ...config - }) => ({ ...config, datasourceGet, datasourceSettings }) +const commonCases = zip(configs, commonExpected); +const noMaxCases = zip( + configs.map(config => withoutSetting(config, 'maxIndex')), + commonExpected ); - -const noMinIndexConfigList: ITestConfig[] = configList.map( - ({ - datasourceGet, - datasourceSettings: { minIndex: _, ...datasourceSettings }, - ...config - }) => ({ ...config, datasourceGet, datasourceSettings }) +const noMinCases = zip( + configs.map(config => withoutSetting(config, 'minIndex')), + commonExpected +); +const noItemSizeCases = zip( + configs.map(config => withoutSetting(config, 'itemSize')), + noItemSizeExpected ); -const forwardGapConfigList: ITestConfig[] = - startIndexAroundMaxIndexConfigList.map( - ({ - datasourceGet, - datasourceSettings: { minIndex: _, ...datasourceSettings }, - ...config - }) => ({ ...config, datasourceGet, datasourceSettings }) - ); - -const checkMinMaxIndexes = async (fixture: VScrollFixture) => { - const elements = await fixture.getElements(); - const bufferInfo = await fixture.adapter.bufferInfo; - const { firstIndex, lastIndex, minIndex, maxIndex } = bufferInfo; - const _minIndex = await fixture.getElementIndex(elements[0]); - const _maxIndex = await fixture.getElementIndex( - elements[elements.length - 1] - ); - - expect(firstIndex).toEqual(minIndex); - expect(lastIndex).toEqual(maxIndex); - expect(minIndex).toEqual(_minIndex); - expect(maxIndex).toEqual(_maxIndex); -}; - -const getParams = ({ - datasourceSettings: settings -}: ITestConfig): { - maxIndex: number; - minIndex: number; - itemSize: number; - startIndex: number; - padding: number; -} => ({ - maxIndex: settings.maxIndex as number, - minIndex: settings.minIndex as number, - itemSize: settings.itemSize as number, - startIndex: settings.startIndex as number, - padding: settings.padding as number -}); - -const testCommonCase = async (fixture: VScrollFixture, config: ITestConfig) => { - await fixture.adapter.relax(); - - const bufferSize = await fixture.scroller.settings.bufferSize; - const { - maxIndex, - minIndex, - itemSize: _itemSize, - startIndex, - padding - } = getParams(config); - const viewportSize = await fixture.scroller.viewport.getSize(); - const viewportSizeDelta = viewportSize * padding; - const itemSize = _itemSize || (await fixture.scroller.buffer.defaultSize); - const hasMinIndex = minIndex !== undefined; - const hasMaxIndex = maxIndex !== undefined; - let innerLoopCount = 3; - - const _negativeItemsAmount = Math.ceil(viewportSizeDelta / itemSize); - const negativeItemsAmount = Math.max(_negativeItemsAmount, bufferSize); - const _positiveItemsAmount = Math.ceil( - (viewportSize + viewportSizeDelta) / itemSize - ); - let positiveItemsAmount = Math.max(_positiveItemsAmount, bufferSize); +const commonConfig = configs[0]; +const aroundMinCases = zip( + [-9999, -50, -49, -48].map(startIndex => ({ + ...commonConfig, + datasourceSettings: { ...commonConfig.datasourceSettings, startIndex } + })), + aroundMinExpected +); +const aroundMaxConfigs = [99, 100, 101, 999].map(startIndex => ({ + ...commonConfig, + datasourceSettings: { ...commonConfig.datasourceSettings, startIndex } +})); +const aroundMaxCases = zip(aroundMaxConfigs, aroundMaxExpected); +const forwardGapConfigs = aroundMaxConfigs.map(config => + withoutSetting(config, 'minIndex') +); - if (!_itemSize) { - // if Settings.itemSize is not set, then there could be 1 more fetch - const positiveDiff = _positiveItemsAmount - bufferSize; - if (positiveDiff > 0) { - innerLoopCount = 4; - // if the additional fetch size is less than bufferSize - if (positiveDiff < bufferSize) { - positiveItemsAmount = 2 * bufferSize; - } +const getItemSize = (misc: Misc, config: Config): number => + config.datasourceSettings.itemSize ?? misc.scroller.buffer.defaultSize; + +const testCommonCase = + ({ config, expected }: GoldenCase) => + (misc: Misc) => + async () => { + await misc.relaxNext(); + + const { minIndex, maxIndex, startIndex } = config.datasourceSettings; + const size = getItemSize(misc, config); + + misc.expect.consistent(); + misc.expect.bufferRange(expected.bufferRange); + expect(misc.innerLoopCount).toEqual(expected.loops); + misc.expect.startVisible(startIndex as number); + + expect(misc.adapter.bufferInfo.absMinIndex).toEqual(minIndex ?? -Infinity); + expect(misc.adapter.bufferInfo.absMaxIndex).toEqual(maxIndex ?? Infinity); + + const { firstIndex, lastIndex } = misc.adapter.bufferInfo; + if (minIndex !== undefined && maxIndex !== undefined) { + expect(misc.getScrollableSize()).toEqual( + (maxIndex - minIndex + 1) * size + ); + } else if (minIndex !== undefined) { + expect(misc.getScrollableSize()).toEqual( + (lastIndex - minIndex + 1) * size + ); + } else if (maxIndex !== undefined) { + expect(misc.getScrollableSize()).toEqual( + (maxIndex - firstIndex + 1) * size + ); } - } - - const bufferInfo = await fixture.adapter.bufferInfo; - - if (hasMinIndex) { - const negativeSize = (startIndex - minIndex) * itemSize; - const negativeItemsSize = negativeItemsAmount * itemSize; - const bwdPaddingSize = negativeSize - negativeItemsSize; - const _bwdPaddingSize = - await fixture.scroller.viewport.paddings[Direction.backward].size; - expect(_bwdPaddingSize).toEqual(bwdPaddingSize); - expect(bufferInfo.absMinIndex).toEqual(minIndex); - } else { - expect(bufferInfo.absMinIndex).toEqual(-Infinity); - } - - if (hasMaxIndex) { - const positiveSize = (maxIndex - startIndex + 1) * itemSize; - const positiveItemsSize = positiveItemsAmount * itemSize; - const fwdPaddingSize = positiveSize - positiveItemsSize; - const _fwdPaddingSize = - await fixture.scroller.viewport.paddings[Direction.forward].size; - expect(_fwdPaddingSize).toEqual(fwdPaddingSize); - expect(bufferInfo.absMaxIndex).toEqual(maxIndex); - } else { - expect(bufferInfo.absMaxIndex).toEqual(Infinity); - } - - let totalSize = 0; - if (hasMinIndex && hasMaxIndex) { - totalSize = (maxIndex - minIndex + 1) * itemSize; - } else if (hasMinIndex) { - const knownSize = (startIndex - minIndex) * itemSize; - totalSize = knownSize + positiveItemsAmount * itemSize; - } else if (hasMaxIndex) { - const knownSize = (maxIndex - startIndex + 1) * itemSize; - totalSize = knownSize + negativeItemsAmount * itemSize; - } - - const _totalSize = await fixture.scroller.viewport.getScrollableSize(); - const _innerLoopCount = await fixture.workflow.innerLoopCount; - expect(_totalSize).toEqual(totalSize); - expect(_innerLoopCount).toEqual(innerLoopCount); - await checkMinMaxIndexes(fixture); -}; - -const testStartIndexEdgeCase = async ( - fixture: VScrollFixture, - config: ITestConfig -) => { - await fixture.adapter.relax(); - - const { maxIndex, minIndex, itemSize, startIndex, padding } = - getParams(config); - const bufferInfo = await fixture.adapter.bufferInfo; - const viewportSize = await fixture.scroller.viewport.getSize(); - const totalSize = (maxIndex - minIndex + 1) * itemSize; - const viewportSizeDelta = viewportSize * padding; - let _startIndex = Math.max(minIndex, startIndex); // startIndex < minIndex - _startIndex = Math.min(maxIndex, _startIndex); // startIndex > maxIndex - - // visible part of the viewport must be filled - const viewportItemsAmount = Math.ceil(viewportSize / itemSize); - const diff = _startIndex + viewportItemsAmount - maxIndex - 1; - if (diff > 0) { - _startIndex -= diff; - } - - const negativeSize = (_startIndex - minIndex) * itemSize; - const negativeItemsAmount = Math.ceil(viewportSizeDelta / itemSize); - const negativeItemsSize = negativeItemsAmount * itemSize; - const bwdPaddingSize = Math.max(0, negativeSize - negativeItemsSize); - - const positiveSize = (maxIndex - _startIndex + 1) * itemSize; - const positiveItemsAmount = Math.ceil( - (viewportSize + viewportSizeDelta) / itemSize - ); - const positiveItemsSize = positiveItemsAmount * itemSize; - const fwdPaddingSize = Math.max(0, positiveSize - positiveItemsSize); - - const _totalSize = await fixture.scroller.viewport.getScrollableSize(); - const _bwdPaddingSize = - await fixture.scroller.viewport.paddings[Direction.backward].size; - const _fwdPaddingSize = - await fixture.scroller.viewport.paddings[Direction.forward].size; - expect(_totalSize).toEqual(totalSize); - expect(_bwdPaddingSize).toEqual(bwdPaddingSize); - expect(_fwdPaddingSize).toEqual(fwdPaddingSize); - - expect(bufferInfo.absMinIndex).toEqual(minIndex); - expect(bufferInfo.absMaxIndex).toEqual(maxIndex); - await checkMinMaxIndexes(fixture); -}; - -const testForwardGapCase = async (fixture: VScrollFixture) => { - await fixture.adapter.relax(); - - const gapSize = await fixture.page.evaluate(() => { - const viewportChildren = - window.__vscroll__.workflow.scroller.routines.element.children; - const lastChild = viewportChildren[viewportChildren.length - 2]; - const lastChildBottom = lastChild.getBoundingClientRect().bottom; - const viewport = window.__vscroll__.workflow.scroller.viewport; - const viewportSize = viewport.getSize(); - let gap = viewportSize - lastChildBottom; - gap = Math.max(0, gap); - return gap; - }); - - expect(gapSize).toEqual(0); - expect( - await fixture.scroller.viewport.paddings[Direction.forward].size - ).toEqual(0); + }; + +const testStartIndexEdge = + ({ config, expected }: GoldenCase) => + (misc: Misc) => + async () => { + await misc.relaxNext(); + + const minIndex = config.datasourceSettings.minIndex as number; + const maxIndex = config.datasourceSettings.maxIndex as number; + const size = getItemSize(misc, config); + + misc.expect.consistent(); + misc.expect.bufferRange(expected.bufferRange); + expect(misc.getScrollableSize()).toEqual((maxIndex - minIndex + 1) * size); + expect(misc.adapter.bufferInfo.absMinIndex).toEqual(minIndex); + expect(misc.adapter.bufferInfo.absMaxIndex).toEqual(maxIndex); + }; + +const testForwardGap = (misc: Misc) => async () => { + await misc.relaxNext(); + misc.expect.consistent(); + misc.expect.noForwardGap(); }; -const makeTest = ( +const registerGolden = ( title: string, - config: ITestConfig, - testFunc: (fixture: VScrollFixture, config: ITestConfig) => Promise -) => { - test(title, async ({ page }) => { - const fixture = await createFixture({ page, config }); - await testFunc(fixture, config); - await fixture.dispose(); - }); + testTitle: string, + cases: GoldenCase[], + scenario: ( + testCase: GoldenCase + ) => (misc: Misc) => () => Promise +): void => { + describe(title, () => + cases.forEach((testCase, index) => + makeTest({ + config: testCase.config, + title: `${testTitle} (config ${index})`, + it: scenario(testCase) + }) + ) + ); }; -test.describe('Min/max Indexes Spec', () => { - test.describe('Common cases', () => - configList.forEach((config, index) => - makeTest( - `should fill the viewport and the paddings (config ${index})`, - config, - testCommonCase - ) - )); - - test.describe('No maxIndex cases', () => - noMaxIndexConfigList.forEach((config, index) => - makeTest( - `should fill the viewport and backward padding (config ${index})`, - config, - testCommonCase - ) - )); - - test.describe('No minIndex cases', () => - noMinIndexConfigList.forEach((config, index) => - makeTest( - `should fill the viewport and forward padding (config ${index})`, - config, - testCommonCase - ) - )); - - test.describe('No itemSize cases', () => - noItemSizeConfigList.forEach((config, index) => - makeTest( - `should fill the viewport and the paddings (config ${index})`, - config, - testCommonCase - ) - )); - - test.describe('startIndex around minIndex', () => - startIndexAroundMinIndexConfigList.forEach((config, index) => - makeTest( - `should reset backward padding (config ${index})`, - config, - testStartIndexEdgeCase - ) - )); - - test.describe('startIndex around maxIndex', () => - startIndexAroundMaxIndexConfigList.forEach((config, index) => - makeTest( - `should reset forward padding (config ${index})`, - config, - testStartIndexEdgeCase - ) - )); +describe('Min/max Indexes Spec', () => { + registerGolden( + 'Common cases', + 'should fill the viewport and the paddings', + commonCases, + testCommonCase + ); + registerGolden( + 'No maxIndex cases', + 'should fill the viewport and backward padding', + noMaxCases, + testCommonCase + ); + registerGolden( + 'No minIndex cases', + 'should fill the viewport and forward padding', + noMinCases, + testCommonCase + ); + registerGolden( + 'No itemSize cases', + 'should fill the viewport and the paddings', + noItemSizeCases, + testCommonCase + ); + registerGolden( + 'startIndex around minIndex', + 'should reset backward padding', + aroundMinCases, + testStartIndexEdge + ); + registerGolden( + 'startIndex around maxIndex', + 'should reset forward padding', + aroundMaxCases, + testStartIndexEdge + ); - test.describe('startIndex around maxIndex and no minIndex', () => - forwardGapConfigList.forEach((config, index) => - makeTest( - `should fill forward padding gap (config ${index})`, + describe('startIndex around maxIndex and no minIndex', () => + forwardGapConfigs.forEach((config, index) => + makeTest({ config, - testForwardGapCase - ) + title: `should fill forward padding gap (config ${index})`, + it: testForwardGap + }) )); }); diff --git a/tests/e2e/specs/recreation.spec.ts b/tests/e2e/specs/recreation.spec.ts index 4752833d..99bdff36 100644 --- a/tests/e2e/specs/recreation.spec.ts +++ b/tests/e2e/specs/recreation.spec.ts @@ -1,182 +1,104 @@ -import { test, expect, Page } from '@playwright/test'; -import { afterEachLogs } from '../fixture/after-each-logs.js'; -import { createFixture } from '../fixture/create-fixture.js'; -import { ITestConfig } from 'types/index.js'; - -test.afterEach(afterEachLogs); - -const datasourceGet = ( - index: number, - count: number, - success: (data: unknown[]) => void -): void => { - const data = []; - for (let i = index; i < index + count; i++) { - data.push({ id: i, text: `item #${i}` }); - } - setTimeout(() => success(data), 0); -}; - -const baseConfig: ITestConfig = { - datasourceGet, +import { + makeTest, + Misc, + TestConfig, + Workflow +} from '../scaffolding'; + +const baseConfig: TestConfig = { datasourceSettings: { startIndex: 1, bufferSize: 5, padding: 0.5 }, templateSettings: { viewportHeight: 200, itemHeight: 20 } }; -type RecreationBox = { - adapterInitCount: number; - wfInitOnFirstMake: boolean; -}; -type WithRecreationResult = Window & { __recreationBox?: RecreationBox }; - -const onBefore = async (page: Page) => - page.evaluate(() => { - const w = window as WithRecreationResult; - const { datasource } = w.__vscroll__; +const getWorkflowState = (workflow: Workflow) => ({ + initialized: workflow.isInitialized, + internalAdapterInitialized: workflow.scroller?.adapter?.init, + disposed: workflow.disposed +}); - // Subscribe to init$ BEFORE creating workflow - const box: RecreationBox = { - adapterInitCount: 0, - wfInitOnFirstMake: false - }; - w.__recreationBox = box; +const getInstanceState = (misc: Misc) => ({ + initialized: misc.workflow.isInitialized, + scrollerId: misc.scroller.settings.instanceIndex, + internalAdapterId: misc.scroller.adapter.id, + adapterId: misc.adapter.id, + elementCount: misc.getElements().length +}); - datasource.adapter.init$.on(value => { - if (!value) { - return; - } - if (++box.adapterInitCount === 1) { - // First init - capture initial workflow state - box.wfInitOnFirstMake = w.__vscroll__.workflow.isInitialized; +describe('Recreation Spec', () => { + describe('Destroying (plain DS)', () => { + makeTest({ + config: { ...baseConfig, noAdapter: true, skipInvariantAutoCheck: true }, + title: 'should not reset Datasource on destroy', + it: misc => async () => { + await misc.relaxNext(); + const workflow = misc.workflow; + const before = getWorkflowState(workflow); + + misc.dispose(); + const after = getWorkflowState(workflow); + + expect(before.initialized).toBe(true); + expect(before.internalAdapterInitialized).toBe(true); + expect(before.disposed).toBe(false); + expect(after.initialized).toBe(false); + expect(after.internalAdapterInitialized).toBe(undefined); + expect(after.disposed).toBe(true); } }); }); -test.describe('Recreation Spec', () => { - test.describe('Destroying (plain DS)', () => { - test('should not reset Datasource on destroy', async ({ page }) => { - const fixture = await createFixture({ - page, - config: { ...baseConfig, noAdapter: true } - }); - - const getWorkflowData = () => - page.evaluate(() => ({ - workflowInit: window.__vscroll__.workflow.isInitialized, - internalAdapterInit: - window.__vscroll__.workflow?.scroller?.adapter?.init, - disposed: window.__vscroll__.workflow.disposed - })); - - // Store workflow data before cleanup - const before = await getWorkflowData(); - - // Destroy the scroller - await fixture.dispose(); - - // Wait a bit to ensure no async errors - await page.waitForTimeout(25); - - // Store workflow data after cleanup - const after = await getWorkflowData(); - - expect(before.workflowInit).toBe(true); - expect(after.workflowInit).toBe(false); - expect(before.internalAdapterInit).toBe(true); - expect(after.internalAdapterInit).toBe(undefined); - expect(before.disposed).toBe(false); - expect(after.disposed).toBe(true); - }); - - // Note: Test 1.2 ('should not reset Datasource on destroy via ngIf') is deferred. - // It requires viewport removal detection feature (Routines.onViewportRemoved with MutationObserver) - // to automatically call workflow.dispose() when viewport element is removed from DOM. - }); - - test.describe('Recreation via ngIf (instance DS)', () => { - test('should switch Adapter.init trice', async ({ page }) => { - const fixture = await createFixture({ - page, - config: { ...baseConfig, noRelaxOnStart: true, onBefore } - }); - - const result = await page.evaluate(async () => { - const w = window as WithRecreationResult; - const { datasource, makeScroller } = w.__vscroll__; - // 3 recreation cycles: 1 from onBefore and 2 from this loop - for (let i = 0; i < 2; i++) { - // Recreate scroller workflow, and wait for adapter is initialized - w.__vscroll__.workflow.dispose(); - makeScroller!(); - await new Promise(r => datasource.adapter.init$.once(r)); - } - return w.__recreationBox; - }); - - await fixture.adapter.relax(); - - // Workflow should not be initialized immediately after creation - expect(result.wfInitOnFirstMake).toBe(false); - - // Adapter should be initialized 3 times - expect(result.adapterInitCount).toBe(3); - - await fixture.dispose(); + describe('Recreation (instance DS)', () => { + makeTest({ + config: baseConfig, + title: 'should switch Adapter.init three times', + it: misc => async () => { + let initCount = 0; + let initializedDuringFirstAdapterInit = true; + const off = misc.adapter.init$.on(initialized => { + if (initialized && ++initCount === 1) { + initializedDuringFirstAdapterInit = misc.workflow.isInitialized; + } + }); + + await misc.relaxNext(); + await misc.recreate(); + await misc.recreate(); + off(); + + expect(initializedDuringFirstAdapterInit).toBe(false); + expect(initCount).toBe(3); + } }); - test('should re-render the viewport', async ({ page }) => { - const fixture = await createFixture({ page, config: baseConfig }); - - // Helper to capture state - const getState = () => - page.evaluate(() => ({ - workflowInit: window.__vscroll__.workflow.isInitialized, - scrollerId: - window.__vscroll__.workflow.scroller.settings.instanceIndex, - internalAdapterId: window.__vscroll__.workflow.scroller.adapter.id, - adapterId: window.__vscroll__.datasource.adapter.id - })); - - // Capture initial state - const before = await getState(); - const beforeElements = await fixture.getElements(); - - // Dispose and recreate - await fixture.recreateScroller(); - await fixture.adapter.relax(); - - // Capture state after recreation - const after = await getState(); - const afterElements = await fixture.getElements(); - - // Verify expectations - expect(after.workflowInit).toBe(true); - expect(after.scrollerId).toBe(before.scrollerId + 1); // New scroller instance - expect(after.internalAdapterId).toBe(before.adapterId); // Scroller uses same adapter - expect(after.adapterId).toBe(before.adapterId); // Same external adapter - expect(afterElements.length).toBe(beforeElements.length); // Same number of elements - - await fixture.dispose(); + makeTest({ + config: baseConfig, + title: 'should re-render the viewport', + it: misc => async () => { + await misc.relaxNext(); + const before = getInstanceState(misc); + + await misc.recreate(); + const after = getInstanceState(misc); + + expect(after.initialized).toBe(true); + expect(after.scrollerId).toBe(before.scrollerId + 1); + expect(after.internalAdapterId).toBe(before.adapterId); + expect(after.adapterId).toBe(before.adapterId); + expect(after.elementCount).toBe(before.elementCount); + } }); - test('should scroll and take firstVisible', async ({ page }) => { - const fixture = await createFixture({ page, config: baseConfig }); - - await fixture.recreateScroller(); - await page.waitForFunction( - () => window.__vscroll__.datasource.adapter.init - ); - await fixture.adapter.relax(); + makeTest({ + config: baseConfig, + title: 'should scroll and take firstVisible', + it: misc => async () => { + await misc.relaxNext(); + await misc.recreate(); + expect(misc.adapter.firstVisible.$index).toBe(1); - const firstVisible = await fixture.adapter.firstVisible; - expect(firstVisible.$index).toBe(1); - - await fixture.scrollTo(200); - - const firstVisibleAfter = await fixture.adapter.firstVisible; - expect(firstVisibleAfter.$index).toBeGreaterThan(1); - - await fixture.dispose(); + await misc.scrollToRelax(200); + expect(misc.adapter.firstVisible.$index).toBeGreaterThan(1); + } }); }); }); diff --git a/tests/e2e/specs/scroll-basic.spec.ts b/tests/e2e/specs/scroll-basic.spec.ts index 35002d5e..6ac1139c 100644 --- a/tests/e2e/specs/scroll-basic.spec.ts +++ b/tests/e2e/specs/scroll-basic.spec.ts @@ -1,55 +1,90 @@ -import { test, expect } from '@playwright/test'; import { - VScrollFixture, Direction, - type DirectionType -} from '../fixture/VScrollFixture.js'; -import { afterEachLogs } from '../fixture/after-each-logs.js'; -import { createFixture } from '../fixture/create-fixture.js'; -import { ItemsCounter } from '../helpers/itemsCounter.js'; -import { ITestConfig } from 'types/index.js'; - -test.afterEach(afterEachLogs); - -type IConfig = ITestConfig<{ - direction: DirectionType; + getDatasource, + makeTest, + Misc, + TestConfig +} from '../scaffolding'; + +interface ScrollPlan { + direction: Direction; count: number; bouncing?: boolean; mass?: boolean; -}>; +} + +type Config = TestConfig & { + custom: ScrollPlan; + datasourceSettings: NonNullable & { + startIndex: number; + bufferSize: number; + padding: number; + itemSize: number; + }; + templateSettings: NonNullable; +}; -const configList: IConfig[] = [ +type BaseConfig = Omit; + +/** + * Settled state captured EMPIRICALLY for one (scenario, baseConfig) pair once + * the scroll sequence completes. These values encode vscroll's own fetch/clip/ + * padding decisions, so they are hardcoded rather than recomputed here — + * recomputing would duplicate the scroller logic under test (the "oracle" we + * deliberately avoid). + * + * - `range`: buffer edge indexes [firstIndex, lastIndex] left in the buffer. + * - `oppositePadding`: px size of the padding opposite the final scroll + * direction (the scrolled-to side is always 0, asserted separately). + * + * To regenerate after an intentional algorithm change, log + * `misc.adapter.bufferInfo` / `misc.padding[dir].getSize()` inside `testScroll`. + */ +interface Golden { + range: [number, number]; + oppositePadding: number; +} + +interface Scenario { + suite: string; + plan: (index: number) => ScrollPlan; + title: (plan: ScrollPlan, index: number) => string; + golden: Golden[]; +} + +const baseConfigs: BaseConfig[] = [ { + datasource: () => getDatasource({ delay: 25 }), datasourceSettings: { startIndex: 100, bufferSize: 4, padding: 0.22, itemSize: 20 }, - templateSettings: { viewportHeight: 71, itemHeight: 20 }, - custom: { direction: Direction.forward, count: 1 } + templateSettings: { viewportHeight: 71, itemHeight: 20 } }, { + datasource: () => getDatasource({ delay: 25 }), datasourceSettings: { startIndex: 1, bufferSize: 5, padding: 0.2, itemSize: 20 }, - templateSettings: { viewportHeight: 100, itemHeight: 20 }, - custom: { direction: Direction.forward, count: 1 } + templateSettings: { viewportHeight: 100, itemHeight: 20 } }, { + datasource: () => getDatasource({ delay: 25 }), datasourceSettings: { startIndex: -15, bufferSize: 12, padding: 0.98, itemSize: 20 }, - templateSettings: { viewportHeight: 66, itemHeight: 20 }, - custom: { direction: Direction.forward, count: 1 } + templateSettings: { viewportHeight: 66, itemHeight: 20 } }, { + datasource: () => getDatasource({ delay: 25 }), datasourceSettings: { startIndex: 1, bufferSize: 5, @@ -57,10 +92,14 @@ const configList: IConfig[] = [ horizontal: true, itemSize: 100 }, - templateSettings: { viewportWidth: 450, itemWidth: 100, horizontal: true }, - custom: { direction: Direction.forward, count: 1 } + templateSettings: { + viewportWidth: 450, + itemWidth: 100, + horizontal: true + } }, { + datasource: () => getDatasource({ delay: 25 }), datasourceSettings: { startIndex: -74, bufferSize: 4, @@ -68,327 +107,189 @@ const configList: IConfig[] = [ horizontal: true, itemSize: 75 }, - templateSettings: { viewportWidth: 300, itemWidth: 75, horizontal: true }, - custom: { direction: Direction.forward, count: 1 } + templateSettings: { + viewportWidth: 300, + itemWidth: 75, + horizontal: true + } } ]; -// Helper functions (ported from original) -const treatIndex = (index: number) => (index <= 3 ? index : 3 * 2 - index); +const treatIndex = (index: number): number => (index <= 3 ? index : 6 - index); -const doScrollMax = async (config: IConfig, fixture: VScrollFixture) => { - if (config.custom.direction === Direction.forward) { - await fixture.scrollMax(); - } else { - await fixture.scrollMin(); - } -}; +const invert = (direction: Direction): Direction => + direction === Direction.forward ? Direction.backward : Direction.forward; -const invertDirection = (config: IConfig) => { - const _forward = config.custom.direction === Direction.forward; - config.custom.direction = _forward ? Direction.backward : Direction.forward; -}; +const scrollToEdge = (misc: Misc, direction: Direction): Promise => + direction === Direction.forward + ? misc.scrollMaxRelax() + : misc.scrollMinRelax(); -const getInitialItemsCounter = async ( - fixture: VScrollFixture, - config: IConfig -): Promise => { - const edgeItem = await fixture.scroller.buffer.getEdgeVisibleItem( - Direction.forward - ); - const oppositeItem = await fixture.scroller.buffer.getEdgeVisibleItem( - Direction.backward - ); - const result = new ItemsCounter(); +const testScroll = + (config: Config, golden: Golden) => (misc: Misc) => async () => { + await misc.relaxNext(); - if (!edgeItem || !oppositeItem) { - return result; - } + const { count, bouncing, mass } = config.custom; + const middleCycle = Math.ceil((count + 1) / 2); + let direction = config.custom.direction; - const startIndex = config.datasourceSettings.startIndex; + for (let cycle = 2; cycle <= count + 1; cycle++) { + if (bouncing || (mass && cycle === middleCycle)) { + direction = invert(direction); + } + await scrollToEdge(misc, direction); + } - result.set(Direction.forward, { - count: edgeItem.$index - startIndex + 1, - index: edgeItem.$index, - padding: 0, - size: 0 - }); - result.set(Direction.backward, { - count: startIndex - oppositeItem.$index, - index: oppositeItem.$index, - padding: 0, - size: NaN + misc.expect.bufferRange(golden.range); + expect(misc.padding[direction].getSize()).toBe(0); + expect(misc.padding[invert(direction)].getSize()).toBe( + golden.oppositePadding + ); + misc.expect.viewportFilled(); + }; + +const repeatedPlan = + (direction: Direction) => + (index: number): ScrollPlan => ({ + direction, + count: 3 + treatIndex(index) }); - return result; -}; - -const getFullHouseDiff = ( - viewportSize: number, - paddingDelta: number, - itemSize: number, - bufferSize: number -): number => { - const sizeToFill = viewportSize + 2 * paddingDelta; - const itemsToFillNotRounded = sizeToFill / itemSize; - const itemsToFillRounded = Math.ceil(sizeToFill / itemSize); - const itemsToFill = - itemsToFillRounded + (itemsToFillNotRounded === itemsToFillRounded ? 0 : 1); - const bufferSizeDiff = bufferSize - itemsToFill; - return Math.max(0, bufferSizeDiff); -}; - -const getCurrentItemsCounter = async ( - fixture: VScrollFixture, - direction: DirectionType, - previous: ItemsCounter, - config: IConfig -): Promise => { - const bufferSize = config.datasourceSettings.bufferSize; - const padding = config.datasourceSettings.padding; - const viewportSize = await fixture.scroller.viewport.getSize(); - const itemSize = await fixture.scroller.buffer.defaultSize; - const fwd = direction === Direction.forward; - const opposite = fwd ? Direction.backward : Direction.forward; - const delta = viewportSize * padding; - - // handle direction (fetch) - const fullHouseDiff = getFullHouseDiff( - viewportSize, - delta, - itemSize, - bufferSize - ); - const _singleFetchCount = Math.ceil(delta / itemSize); - const singleFetchCount = Math.max(bufferSize, _singleFetchCount); - const itemsToFetch = - previous.direction && previous.direction !== direction - ? _singleFetchCount + fullHouseDiff - : singleFetchCount; - const newDirIndex = - previous.get(direction).index + - (fwd ? 1 : -1) * (previous.get(direction).padding / itemSize) + - (fwd ? 1 : -1) * itemsToFetch; - - // handle opposite (clip) - const oppPadding = previous.get(opposite).padding; - const previousTotalSize = previous.total * itemSize + previous.paddings; - const sizeToClip = previousTotalSize - oppPadding - viewportSize - delta; - const itemsToClip = Math.floor(sizeToClip / itemSize); - const newOppIndex = - previous.get(opposite).index + (fwd ? 1 : -1) * itemsToClip; - const newOppPadding = itemsToClip * itemSize + oppPadding; - - const result = new ItemsCounter(direction); - result.set(direction, { - index: newDirIndex, - padding: 0, - count: NaN, - size: NaN - }); - result.set(opposite, { - index: newOppIndex, - padding: newOppPadding, - count: NaN, - size: NaN +const alternatingPlan = + (direction: Direction, mode: 'bouncing' | 'mass') => + (index: number): ScrollPlan => ({ + direction, + count: (3 + treatIndex(index)) * 2, + [mode]: true }); - return result; -}; -// Main test function -const shouldScroll = async (fixture: VScrollFixture, config: IConfig) => { - const custom = config.custom; - const wfCount = custom.count + 1; - const wfCountMiddle = Math.ceil(wfCount / 2); - let itemsCounter: ItemsCounter; - let cycles = 1; - - // Initial state - itemsCounter = await getInitialItemsCounter(fixture, config); - - // Perform scrolls - while (cycles < wfCount) { - cycles++; - - if (custom.bouncing) { - invertDirection(config); - } else if (custom.mass) { - if (cycles === wfCountMiddle) { - invertDirection(config); - } - } - - await doScrollMax(config, fixture); +const scenarios: Scenario[] = [ + { + suite: 'Single max fwd scroll event', + plan: () => ({ direction: Direction.forward, count: 1 }), + title: (_plan, index) => + `should process 1 forward max scroll (config ${index})`, + golden: [ + { range: [100, 108], oppositePadding: 80 }, + { range: [1, 11], oppositePadding: 100 }, + { range: [-10, 8], oppositePadding: 340 }, + { range: [1, 14], oppositePadding: 500 }, + { range: [-74, -64], oppositePadding: 300 } + ] + }, + { + suite: 'Single max bwd scroll event', + plan: () => ({ direction: Direction.backward, count: 1 }), + title: (_plan, index) => + `should process 1 backward max scroll (config ${index})`, + golden: [ + { range: [92, 100], oppositePadding: 80 }, + { range: [-9, 1], oppositePadding: 100 }, + { range: [-39, -21], oppositePadding: 340 }, + { range: [-9, 4], oppositePadding: 500 }, + { range: [-82, -72], oppositePadding: 300 } + ] + }, + { + suite: 'Mass max fwd scroll events', + plan: repeatedPlan(Direction.forward), + title: (plan, index) => + `should process ${plan.count} forward scrolls (config ${index})`, + golden: [ + { range: [108, 116], oppositePadding: 240 }, + { range: [16, 26], oppositePadding: 400 }, + { range: [38, 56], oppositePadding: 1300 }, + { range: [26, 39], oppositePadding: 3000 }, + { range: [-58, -48], oppositePadding: 1500 } + ] + }, + { + suite: 'Mass max bwd scroll events', + plan: repeatedPlan(Direction.backward), + title: (plan, index) => + `should process ${plan.count} backward scrolls (config ${index})`, + golden: [ + { range: [84, 92], oppositePadding: 240 }, + { range: [-24, -14], oppositePadding: 400 }, + { range: [-87, -69], oppositePadding: 1300 }, + { range: [-34, -21], oppositePadding: 3000 }, + { range: [-98, -88], oppositePadding: 1500 } + ] + }, + { + suite: 'Bouncing max two-directional scroll events (fwd started)', + plan: alternatingPlan(Direction.forward, 'bouncing'), + title: (plan, index) => + `should process ${plan.count} bouncing scrolls (config ${index})`, + golden: [ + { range: [102, 107], oppositePadding: 240 }, + { range: [4, 10], oppositePadding: 320 }, + { range: [10, 21], oppositePadding: 1380 }, + { range: [26, 39], oppositePadding: 6000 }, + { range: [-62, -53], oppositePadding: 2400 } + ] + }, + { + suite: 'Bouncing max two-directional scroll events (bwd started)', + plan: alternatingPlan(Direction.backward, 'bouncing'), + title: (plan, index) => + `should process ${plan.count} bouncing scrolls (config ${index})`, + golden: [ + { range: [93, 98], oppositePadding: 240 }, + { range: [-8, -2], oppositePadding: 320 }, + { range: [-52, -41], oppositePadding: 1380 }, + { range: [-34, -21], oppositePadding: 6000 }, + { range: [-93, -84], oppositePadding: 2400 } + ] + }, + { + suite: + 'Mass two-directional scroll events (first half fwd, second half bwd)', + plan: alternatingPlan(Direction.forward, 'mass'), + title: (plan, index) => + `should process ${plan.count} two-directional scrolls (config ${index})`, + golden: [ + { range: [83, 91], oppositePadding: 420 }, + { range: [-25, -15], oppositePadding: 720 }, + { range: [-92, -74], oppositePadding: 2360 }, + { range: [-39, -26], oppositePadding: 6000 }, + { range: [-101, -91], oppositePadding: 2925 } + ] + }, + { + suite: + 'Mass two-directional scroll events (first half bwd, second half fwd)', + plan: alternatingPlan(Direction.backward, 'mass'), + title: (plan, index) => + `should process ${plan.count} two-directional scrolls (config ${index})`, + golden: [ + { range: [109, 117], oppositePadding: 420 }, + { range: [17, 27], oppositePadding: 720 }, + { range: [43, 61], oppositePadding: 2360 }, + { range: [31, 44], oppositePadding: 6000 }, + { range: [-55, -45], oppositePadding: 2925 } + ] + } +]; - itemsCounter = await getCurrentItemsCounter( - fixture, - custom.direction, - itemsCounter, - config +const registerScenario = (scenario: Scenario): void => { + if (scenario.golden.length !== baseConfigs.length) { + throw new Error( + `golden mismatch in "${scenario.suite}": ` + + `${scenario.golden.length} vs ${baseConfigs.length} configs` ); } - - // Final expectations - const direction: DirectionType = custom.direction; - const opposite: DirectionType = - direction === Direction.forward ? Direction.backward : Direction.forward; - - const edgeItem = await fixture.scroller.buffer.getEdgeVisibleItem(direction); - const oppositeItem = - await fixture.scroller.buffer.getEdgeVisibleItem(opposite); - const edgeItemIndex = itemsCounter.get(direction).index; - const oppositeItemIndex = itemsCounter.get(opposite).index; - - // await new Promise(resolve => setTimeout(resolve, 1000000)); - expect(edgeItem && edgeItem.$index).toEqual(edgeItemIndex); - expect(oppositeItem && oppositeItem.$index).toEqual(oppositeItemIndex); - expect(await fixture.scroller.viewport.paddings[direction].size).toEqual( - itemsCounter.get(direction).padding - ); - expect(await fixture.scroller.viewport.paddings[opposite].size).toEqual( - itemsCounter.get(opposite).padding - ); - expect(await fixture.checkElementContentByIndex(edgeItemIndex)).toEqual(true); - expect(await fixture.checkElementContentByIndex(oppositeItemIndex)).toEqual( - true - ); -}; - -const makeTest = (title: string, config: IConfig) => { - test(title, async ({ page }) => { - config.datasourceGet = (index, count, success) => { - const data = []; - for (let i = index; i < index + count; i++) { - data.push({ id: i, text: `item #${i}` }); - } - setTimeout(() => success(data), 25); - }; - const fixture = await createFixture({ page, config }); - await shouldScroll(fixture, config); - await fixture.dispose(); + describe(scenario.suite, () => { + baseConfigs.forEach((baseConfig, index) => { + const plan = scenario.plan(index); + const config: Config = { ...baseConfig, custom: plan }; + makeTest({ + config, + title: scenario.title(plan, index), + it: testScroll(config, scenario.golden[index]) + }); + }); }); }; -// Test suites -test.describe('Basic Scroll Spec', () => { - test.describe('Single max fwd scroll event', () => - configList.forEach((config, index) => - makeTest(`should process 1 forward max scroll (config ${index})`, config) - )); - - test.describe('Single max bwd scroll event', () => - configList - .map(config => ({ - ...config, - custom: { - ...config.custom, - direction: Direction.backward - } - })) - .forEach((config, index) => - makeTest( - `should process 1 backward max scroll (config ${index})`, - config - ) - )); - - test.describe('Mass max fwd scroll events', () => - configList - .map((config, index) => ({ - ...config, - custom: { - direction: Direction.forward, - count: 3 + treatIndex(index) - } - })) - .forEach((config, index) => - makeTest( - `should process ${config.custom.count} forward scrolls (config ${index})`, - config - ) - )); - - test.describe('Mass max bwd scroll events', () => - configList - .map((config, index) => ({ - ...config, - custom: { - direction: Direction.backward, - count: 3 + treatIndex(index) - } - })) - .forEach((config, index) => - makeTest( - `should process ${config.custom.count} backward scrolls (config ${index})`, - config - ) - )); - - test.describe('Bouncing max two-directional scroll events (fwd started)', () => - configList - .map((config, index) => ({ - ...config, - custom: { - direction: Direction.forward, - count: (3 + treatIndex(index)) * 2, - bouncing: true - } - })) - .forEach((config, index) => - makeTest( - `should process ${config.custom.count} bouncing scrolls (config ${index})`, - config - ) - )); - - test.describe('Bouncing max two-directional scroll events (bwd started)', () => - configList - .map((config, index) => ({ - ...config, - custom: { - direction: Direction.backward, - count: (3 + treatIndex(index)) * 2, - bouncing: true - } - })) - .forEach((config, index) => - makeTest( - `should process ${config.custom.count} bouncing scrolls (config ${index})`, - config - ) - )); - - test.describe('Mass two-directional scroll events (first half fwd, second half bwd)', () => - configList - .map((config, index) => ({ - ...config, - custom: { - direction: Direction.forward, - count: (3 + treatIndex(index)) * 2, - mass: true - } - })) - .forEach((config, index) => - makeTest( - `should process ${config.custom.count} two-directional scrolls (config ${index})`, - config - ) - )); - - test.describe('Mass two-directional scroll events (first half bwd, second half fwd)', () => - configList - .map((config, index) => ({ - ...config, - custom: { - direction: Direction.backward, - count: (3 + treatIndex(index)) * 2, - mass: true - } - })) - .forEach((config, index) => - makeTest( - `should process ${config.custom.count} two-directional scrolls (config ${index})`, - config - ) - )); -}); +describe('Basic Scroll Spec', () => scenarios.forEach(registerScenario)); diff --git a/tests/e2e/specs/scroll-delay.spec.ts b/tests/e2e/specs/scroll-delay.spec.ts index bcbc0c14..6ff97e2b 100644 --- a/tests/e2e/specs/scroll-delay.spec.ts +++ b/tests/e2e/specs/scroll-delay.spec.ts @@ -1,152 +1,109 @@ -import { test, expect } from '@playwright/test'; -import { afterEachLogs } from '../fixture/after-each-logs.js'; -import { createFixture } from '../fixture/create-fixture.js'; -import { ITestConfig } from 'types/index.js'; - -test.afterEach(afterEachLogs); +import { + getDatasource, + makeTest, + Misc, + TestConfig +} from '../scaffolding'; + +const baseConfig: TestConfig = { + datasource: () => getDatasource({ delay: 150 }), + datasourceSettings: { + startIndex: 1, + bufferSize: 5, + padding: 0.5, + itemSize: 20 + }, + templateSettings: { viewportHeight: 200, itemHeight: 20 }, + timeout: 5000 +}; -const datasourceGet: ITestConfig['datasourceGet'] = (index, count, success) => { - const data = []; - for (let i = index; i < index + count; i++) { - data.push({ id: i, text: `item #${i}` }); +const waitUntilCycle = async (misc: Misc, target: number): Promise => { + while (misc.workflow.cyclesDone < target) { + await misc.waitNextCycle(); } - setTimeout(() => success(data), 150); + await misc.adapter.relax(); }; -/** - * Test: Throttled scroll event handling - * - * Rapidly triggers multiple scroll operations and verifies that the workflow - * throttles them into fewer cycles than the number of scroll events. - */ -test.describe('Delay Scroll Spec', () => { - test('should work with throttled scroll event handling', async ({ page }) => { - const config: ITestConfig = { - datasourceGet, - datasourceSettings: { - startIndex: 1, - bufferSize: 5, - padding: 0.5, - itemSize: 20 - }, - datasourceDevSettings: { - throttle: 500 - }, - templateSettings: { - viewportHeight: 200, - itemHeight: 20 +const runAlternatingBurst = ( + misc: Misc, + count: number, + interval: number +): Promise => + new Promise(resolve => { + let completed = 0; + const timer = setInterval(() => { + completed++; + if (completed % 2 === 0) { + misc.scrollMin(); + } else { + misc.scrollMax(); } - }; - - const fixture = await createFixture({ page, config }); - const initCyclesCount = await fixture.workflow.cyclesDone; - expect(initCyclesCount).toBe(1); - - // Start rapid scrolling and monitor cycles in browser context - const count = await page.evaluate(() => { - return new Promise(resolve => { - const COUNT = 10; - let scrollCount = 0; - let timer: ReturnType; - const workflow = window.__vscroll__.workflow; - const adapter = window.__vscroll__.datasource.adapter; - - // Subscribe to cyclesDone$ - resolve when cycle 3 completes - workflow.cyclesDone$.on(cycles => { - if (cycles === 3) { - resolve(scrollCount); - } - }); + if (completed === count) { + clearInterval(timer); + resolve(completed); + } + }, interval); + }); - // Start the scrolling timer after cycle 1 completes (already completed) - timer = setInterval(() => { - scrollCount++; - if (scrollCount % 2 === 0) { - adapter.fix({ scrollPosition: 0 }); // scrollMin - } else { - adapter.fix({ scrollPosition: Infinity }); // scrollMax - } - if (scrollCount === COUNT) { - clearInterval(timer); - } - }, 10); - }); +const startProgressiveBurst = (misc: Misc, startPosition: number) => { + let count = 0; + let endPosition = startPosition; + const frames = new Set(); + const timer = setInterval(() => { + const frame = requestAnimationFrame(() => { + frames.delete(frame); + endPosition = startPosition + ++count * 5; + misc.scrollTo(endPosition); }); + frames.add(frame); + }, 25); + + return { + stop: () => { + clearInterval(timer); + frames.forEach(cancelAnimationFrame); + frames.clear(); + }, + getEndPosition: () => endPosition + }; +}; - // Verify all scrolls were executed - expect(count).toBe(10); - - await fixture.dispose(); +describe('Delay Scroll Spec', () => { + makeTest({ + config: { + ...baseConfig, + datasourceDevSettings: { throttle: 500 } + }, + title: 'should work with throttled scroll event handling', + it: misc => async () => { + await misc.relaxNext(); + expect(misc.workflow.cyclesDone).toBe(1); + + const thirdCycle = waitUntilCycle(misc, 3); + const count = await runAlternatingBurst(misc, 10, 10); + await thirdCycle; + + expect(count).toBe(10); + } }); - test('should handle additional scrolling during slow fetch', async ({ - page - }) => { - const config: ITestConfig = { - datasourceGet, - datasourceSettings: { - startIndex: 1, - bufferSize: 5, - padding: 0.5, - itemSize: 20 - }, - templateSettings: { - viewportHeight: 200, - itemHeight: 20 - } - }; - - const fixture = await createFixture({ page, config }); - const initCyclesCount = await fixture.workflow.cyclesDone; - expect(initCyclesCount).toBe(1); - - const startPosition = await fixture.scroller.viewport.scrollPosition; - - // Start rapid scrolling and monitor cycles in browser context - const result = await page.evaluate( - ({ start }) => - new Promise<{ endPos: number; position: number }>(resolve => { - const COUNT = 10; - let scrollCount = 0; - let endPos: number; - let timer: ReturnType; - const workflow = window.__vscroll__.workflow; - const adapter = window.__vscroll__.datasource.adapter; - - // Subscribe to cyclesDone$ - stop timer when the nearest cycle completes (cycle 2) - workflow.cyclesDone$.on(count => { - if (count === 2) { - clearInterval(timer); - const position = workflow.scroller.viewport.scrollPosition; - resolve({ endPos, position }); - } - }); - - // Start the scrolling timer - timer = setInterval( - () => - requestAnimationFrame(() => { - scrollCount++; - endPos = start + scrollCount * 5; - - // Trigger scroll via adapter.fix - adapter.fix({ scrollPosition: endPos }); - - // Stop timer if all scrolls done - if (scrollCount === COUNT) { - clearInterval(timer); - } - }), - 25 - ); - }), - { start: startPosition } - ); - - // Verify final position - expect(result.position).toBe(result.endPos); - expect(result.endPos).toBeGreaterThan(startPosition); - - await fixture.dispose(); + makeTest({ + config: baseConfig, + title: 'should handle additional scrolling during slow fetch', + it: misc => async () => { + await misc.relaxNext(); + expect(misc.workflow.cyclesDone).toBe(1); + + const startPosition = misc.getScrollPosition(); + const nextCycle = misc.waitNextCycle(); + const burst = startProgressiveBurst(misc, startPosition); + await nextCycle; + burst.stop(); + await misc.adapter.relax(); + + const endPosition = burst.getEndPosition(); + expect(endPosition).toBeGreaterThan(startPosition); + expect(misc.getScrollPosition()).toBe(endPosition); + } }); }); diff --git a/tests/e2e/specs/scroll-fast.spec.ts b/tests/e2e/specs/scroll-fast.spec.ts index 7fba1fad..223aa462 100644 --- a/tests/e2e/specs/scroll-fast.spec.ts +++ b/tests/e2e/specs/scroll-fast.spec.ts @@ -1,47 +1,26 @@ -import { test, expect } from '@playwright/test'; -import { afterEachLogs } from '../fixture/after-each-logs.js'; -import { createFixture } from '../fixture/create-fixture.js'; import { Direction, - VScrollFixture, - type DirectionType -} from '../fixture/VScrollFixture.js'; -import { ITestConfig } from 'types/index.js'; + getDatasource, + makeTest, + Misc, + TestConfig +} from '../scaffolding'; -test.afterEach(afterEachLogs); - -interface ICustom { +interface FastScrollConfig { items: number; scrollCount: number; - start: DirectionType; + finalEdge: Direction; } -type IConfig = ITestConfig; -// Datasource generators for limited ranges -// Note: We can't use closure variables because the function gets serialized. -const datasourceGet1to100 = (index, count, success) => { - const data = []; - for (let i = index; i < index + count; i++) { - if (i >= 1 && i <= 100) { - data.push({ id: i, text: `item #${i}` }); - } - } - setTimeout(() => success(data), 25); +type Config = TestConfig & { + custom: FastScrollConfig; + datasourceSettings: NonNullable; + templateSettings: NonNullable; }; -const datasourceGet51to200 = (index, count, success) => { - const data = []; - for (let i = index; i < index + count; i++) { - if (i >= 51 && i <= 200) { - data.push({ id: i, text: `item #${i}` }); - } - } - setTimeout(() => success(data), 25); -}; - -const configList: IConfig[] = [ +const baseConfigs: Config[] = [ { - datasourceGet: datasourceGet1to100, + datasource: () => getDatasource({ min: 1, max: 100, delay: 25 }), datasourceSettings: { startIndex: 1, bufferSize: 5, @@ -50,10 +29,10 @@ const configList: IConfig[] = [ maxIndex: 100 }, templateSettings: { viewportHeight: 100, itemHeight: 20 }, - custom: { items: 100, scrollCount: 5, start: Direction.backward } + custom: { items: 100, scrollCount: 5, finalEdge: Direction.backward } }, { - datasourceGet: datasourceGet1to100, + datasource: () => getDatasource({ min: 1, max: 100, delay: 25 }), datasourceSettings: { startIndex: 1, bufferSize: 3, @@ -62,10 +41,10 @@ const configList: IConfig[] = [ maxIndex: 100 }, templateSettings: { viewportHeight: 110, itemHeight: 20 }, - custom: { items: 100, scrollCount: 8, start: Direction.backward } + custom: { items: 100, scrollCount: 8, finalEdge: Direction.backward } }, { - datasourceGet: datasourceGet51to200, + datasource: () => getDatasource({ min: 51, max: 200, delay: 25 }), datasourceSettings: { startIndex: 51, bufferSize: 7, @@ -74,10 +53,10 @@ const configList: IConfig[] = [ maxIndex: 200 }, templateSettings: { viewportHeight: 69, itemHeight: 20 }, - custom: { items: 150, scrollCount: 6, start: Direction.backward } + custom: { items: 150, scrollCount: 6, finalEdge: Direction.backward } }, { - datasourceGet: datasourceGet51to200, + datasource: () => getDatasource({ min: 51, max: 200, delay: 25 }), datasourceSettings: { startIndex: 51, bufferSize: 20, @@ -91,192 +70,65 @@ const configList: IConfig[] = [ viewportHeight: 0, itemHeight: 20 }, - custom: { items: 150, scrollCount: 5, start: Direction.backward } + custom: { items: 150, scrollCount: 5, finalEdge: Direction.backward } } ]; -const configEofList: IConfig[] = configList.map(config => ({ +const eofConfigs = baseConfigs.map(config => ({ ...config, - custom: { - ...config.custom, - start: Direction.forward - } + custom: { ...config.custom, finalEdge: Direction.forward } })); -/** - * Run fast alternating scrolls in browser context - */ -const runFastScroll = ( - fixture: VScrollFixture, - customConfig: ICustom -): Promise => - fixture.page.evaluate( - ({ scrollCount, start }) => - new Promise(resolve => { - const adapter = window.__vscroll__.datasource.adapter; - let iteration = 0; - - const doScroll = () => { - setTimeout(() => { - // scrollMax - adapter.fix({ scrollPosition: Infinity }); - - setTimeout(async () => { - // scrollMin (except on last iteration for EOF tests) - if (iteration < scrollCount || start === 'backward') { - adapter.fix({ scrollPosition: 0 }); - } - - iteration++; - if (iteration <= scrollCount) { - doScroll(); - } else { - resolve(); - } - }, 25); - }, 25); - }; - - doScroll(); - }), - { scrollCount: customConfig.scrollCount, start: customConfig.start } - ); - -/** - * Wait for expected edge element to be reached - * Simple feedback loop: Wait → Check → Correct if needed → Repeat - */ -const waitForExpectedEdge = async ( - fixture: VScrollFixture, - config: IConfig -): Promise => { - const startIdx = config.datasourceSettings.startIndex as number; - const itemsCount = config.custom.items; - const timeout = 10000; - const startTime = Date.now(); - - while (true) { - // Check timeout - if (Date.now() - startTime > timeout) { - throw new Error('Timeout waiting for expected edge element'); - } - - // Step 1: Wait for workflow to be idle - await fixture.adapter.relax(); - - // Step 2: Get all state in ONE call - const state = await fixture.page.evaluate(() => { - const vscroll = window.__vscroll__; - const { buffer, viewport } = vscroll.workflow.scroller; - const Direction = window.VScroll.Direction; - const position = viewport.scrollPosition; - const eof = position === 0; - const direction = eof ? Direction.backward : Direction.forward; - const edgeItem = buffer.getEdgeVisibleItem(direction); - - return { - bufferSize: buffer.size, - position, - eof, - edgeItem: edgeItem ? { $index: edgeItem.$index } : null - }; - }); - - // Step 3: Do calculations/checks in Node.js - if (!state.bufferSize || !state.edgeItem) { - continue; // Buffer or edge item not ready - } - - const expectedIndex = startIdx + (state.eof ? 0 : itemsCount - 1); - - // Step 4: Check if we're at target edge - if (state.edgeItem.$index === expectedIndex) { - break; // Success! - } - - // Step 5: Issue corrective scroll - await fixture.adapter.fix({ - scrollPosition: state.eof ? 0 : Infinity - }); - } -}; - -/** - * Verify final expectations after fast scrolling - */ -const verifyExpectations = async ( - fixture: VScrollFixture, - config: IConfig -): Promise => { - const startIndex = config.datasourceSettings.startIndex as number; - const position = await fixture.scroller.viewport.scrollPosition; - const itemHeight = config.templateSettings.itemHeight || 20; - - // Get buffer and padding info - const bufferSize = await fixture.scroller.buffer.size; - const backwardPadding = - await fixture.scroller.viewport.paddings[Direction.backward].size; - const forwardPadding = - await fixture.scroller.viewport.paddings[Direction.forward].size; - - const bufferHeight = bufferSize * itemHeight; - const totalSize = backwardPadding + forwardPadding + bufferHeight; - const totalItemsHeight = config.custom.items * itemHeight; - - // Verify total size matches expected - expect(totalSize).toBe(totalItemsHeight); - expect(bufferSize).toBeGreaterThan(0); - - if (bufferSize > 0) { - if (position === 0) { - // At BOF - check first element - const first = await fixture.scroller.buffer.getEdgeVisibleItem( - Direction.backward - ); - expect(first.$index).toBe(startIndex); - } else { - // At EOF - check last element - const last = await fixture.scroller.buffer.getEdgeVisibleItem( - Direction.forward - ); - expect(last.$index).toBe(startIndex + config.custom.items - 1); +const runFastScroll = async (misc: Misc, config: FastScrollConfig) => { + for (let iteration = 0; iteration <= config.scrollCount; iteration++) { + await misc.delay(25); + misc.scrollMax(); + await misc.delay(25); + if ( + iteration < config.scrollCount || + config.finalEdge === Direction.backward + ) { + misc.scrollMin(); } } }; -/** - * Main test function - */ -const shouldReachEdge = async (fixture: VScrollFixture, config: IConfig) => { - // Wait for initial load - await fixture.adapter.relax(); - - // Run fast scrolling and wait for it to complete - await runFastScroll(fixture, config.custom); +const expectCompleteDataset = (misc: Misc, config: Config) => { + misc.expect.consistent(); + const { buffer, viewport } = misc.scroller; + const itemSize = config.templateSettings.itemHeight || 20; + const totalSize = + viewport.paddings.backward.size + + buffer.size * itemSize + + viewport.paddings.forward.size; - // Wait for expected edge element - await waitForExpectedEdge(fixture, config); + expect(totalSize).toBe(config.custom.items * itemSize); + expect(buffer.size).toBeGreaterThan(0); - // Verify final state - await verifyExpectations(fixture, config); + const atBof = misc.getScrollPosition() === 0; + const edge = buffer.items[atBof ? 0 : buffer.items.length - 1]; + const startIndex = config.datasourceSettings.startIndex as number; + const expectedIndex = startIndex + (atBof ? 0 : config.custom.items - 1); + expect(edge?.$index).toBe(expectedIndex); }; -const makeTest = (title: string, config: IConfig) => { - test(title, async ({ page }) => { - const fixture = await createFixture({ page, config }); - await shouldReachEdge(fixture, config); - await fixture.dispose(); - }); +const testFastScroll = (config: Config) => (misc: Misc) => async () => { + await misc.relaxNext(); + await runFastScroll(misc, config.custom); + await misc.reachEdge(config.custom.finalEdge); + expectCompleteDataset(misc, config); }; -test.describe('Fast Scroll Spec', () => { - test.describe('multi-scroll to the BOF', () => - configList.forEach((config, index) => - makeTest(`should reach BOF without gaps (config ${index})`, config) - )); +const registerCases = (configs: Config[], edge: 'BOF' | 'EOF') => + configs.forEach((config, index) => + makeTest({ + config, + title: `should reach ${edge} without gaps (config ${index})`, + it: testFastScroll(config) + }) + ); - test.describe('multi-scroll to the EOF', () => - configEofList.forEach((config, index) => - makeTest(`should reach EOF without gaps (config ${index})`, config) - )); +describe('Fast Scroll Spec', () => { + describe('multi-scroll to the BOF', () => registerCases(baseConfigs, 'BOF')); + describe('multi-scroll to the EOF', () => registerCases(eofConfigs, 'EOF')); }); diff --git a/tests/e2e/specs/viewport.spec.ts b/tests/e2e/specs/viewport.spec.ts index d7575e14..544aed5d 100644 --- a/tests/e2e/specs/viewport.spec.ts +++ b/tests/e2e/specs/viewport.spec.ts @@ -1,110 +1,73 @@ -import { test, expect } from '@playwright/test'; -import { afterEachLogs } from '../fixture/after-each-logs.js'; -import { createFixture } from '../fixture/create-fixture.js'; -import { VScrollFixture } from '../fixture/VScrollFixture.js'; -import { ITestConfig } from 'types/index.js'; +import { + getDatasource, + makeTest, + Misc, + TestConfig +} from '../scaffolding'; -test.afterEach(afterEachLogs); - -interface ICustom { +interface CustomConfig { scrollTo?: number; } -type IConfig = ITestConfig; -// Datasource: limited callback (1-100) -const datasourceGet = (index, count, success) => { - const data = []; - const start = Math.max(1, index); - const end = index + count - 1; - if (start > 100 || end < 1) { - success(data); - return; - } - const first = Math.max(1, start); - const last = Math.min(100, end); - for (let i = first; i <= last; i++) { - data.push({ id: i, text: `item #${i}` }); - } - success(data); +type ViewportConfig = TestConfig & { + custom: CustomConfig; + datasourceSettings: NonNullable; + templateSettings: NonNullable; }; -// Base config: windowViewport with 50px header -const windowWith50HeaderConfig: IConfig = { - datasourceGet, +const baseConfig: ViewportConfig = { + datasource: () => getDatasource({ min: 1, max: 100, delay: 1 }), datasourceSettings: { startIndex: 1, windowViewport: true }, - templateSettings: { - itemHeight: 50, - noViewportClass: true, - viewportHeight: 0, - headerHeight: 50 - }, - custom: { scrollTo: undefined } + templateSettings: { itemHeight: 50, noViewportClass: true, headerHeight: 50 }, + custom: {} }; -// Config with 500px header -const windowWith500HeaderConfig: IConfig = { - ...windowWith50HeaderConfig, - templateSettings: { - ...windowWith50HeaderConfig.templateSettings, - headerHeight: 500 - } +const tallHeaderConfig: ViewportConfig = { + ...baseConfig, + templateSettings: { ...baseConfig.templateSettings, headerHeight: 500 } }; -// All test configurations -const windowWithHeaderConfigList: IConfig[] = [ - windowWith50HeaderConfig, - windowWith500HeaderConfig, - { ...windowWith50HeaderConfig, custom: { scrollTo: 99999 } }, - { ...windowWith500HeaderConfig, custom: { scrollTo: 99999 } }, - { ...windowWith500HeaderConfig, custom: { scrollTo: 450 } }, - { ...windowWith500HeaderConfig, custom: { scrollTo: 50 } }, - { ...windowWith500HeaderConfig, custom: { scrollTo: 500 } } +const configs: ViewportConfig[] = [ + baseConfig, + tallHeaderConfig, + { ...baseConfig, custom: { scrollTo: 99999 } }, + { ...tallHeaderConfig, custom: { scrollTo: 99999 } }, + { ...tallHeaderConfig, custom: { scrollTo: 450 } }, + { ...tallHeaderConfig, custom: { scrollTo: 50 } }, + { ...tallHeaderConfig, custom: { scrollTo: 500 } } ]; -// Test implementation -const shouldWorkOnWindowWithHeader = async ( - fixture: VScrollFixture, - config: IConfig -) => { - const itemHeight = config.templateSettings.itemHeight || 50; - const headerHeight = config.templateSettings.headerHeight || 0; - let position = 0; - let index = 1; - - // Wait for initial load - await fixture.adapter.relax(); +const testWindowViewport = + (config: ViewportConfig) => (misc: Misc) => async () => { + await misc.relaxNext(); - if (config.custom?.scrollTo !== undefined) { - // Scroll (synchronous in browser) - await fixture.adapter.fix({ scrollPosition: config.custom.scrollTo }); - // Read position immediately after scroll (before workflow processes it) - position = await fixture.scroller.viewport.scrollPosition; - index = Math.max(1, Math.ceil((position - headerHeight) / itemHeight)); - // Now wait for workflow to process the scroll - await fixture.adapter.relax(); - } + const itemHeight = config.templateSettings.itemHeight as number; + const headerHeight = config.templateSettings.headerHeight as number; + let position = 0; + let index = 1; - const actualPosition = await fixture.scroller.viewport.scrollPosition; - const firstVisible = await fixture.adapter.firstVisible; - - expect(actualPosition).toBe(position); - expect(firstVisible.$index).toBe(index); -}; + if (config.custom.scrollTo !== undefined) { + await misc.scrollToRelax(config.custom.scrollTo); + position = misc.getScrollPosition(); + index = Math.max(1, Math.ceil((position - headerHeight) / itemHeight)); + } -test.describe('Viewport Spec', () => { - test.describe('Entire Window with Header', () => { - windowWithHeaderConfigList.forEach((config, idx) => { - const scrollToText = - config.custom?.scrollTo !== undefined - ? ` scroll to ${config.custom.scrollTo}` - : ' not scroll'; - const headerText = `${config.templateSettings.headerHeight}-offset`; + expect(misc.getScrollPosition()).toEqual(position); + expect(misc.adapter.firstVisible.$index).toEqual(index); + }; - test(`should${scrollToText} with ${headerText}`, async ({ page }) => { - const fixture = await createFixture({ page, config }); - await shouldWorkOnWindowWithHeader(fixture, config); - await fixture.dispose(); - }); - }); +describe('Viewport Spec', () => { + describe('Entire Window with Header', () => { + configs.forEach(config => + makeTest({ + config, + title: `should${ + config.custom.scrollTo === undefined + ? ' not scroll' + : ` scroll to ${config.custom.scrollTo}` + } with ${config.templateSettings.headerHeight}-offset`, + it: testWindowViewport(config) + }) + ); }); }); diff --git a/tests/e2e/tsconfig.json b/tests/e2e/tsconfig.json index 3181a0c8..d0bae48a 100644 --- a/tests/e2e/tsconfig.json +++ b/tests/e2e/tsconfig.json @@ -1,18 +1,14 @@ { "compilerOptions": { - "baseUrl": ".", - "outDir": "./dist", - "types": ["node", "@playwright/test"], - "module": "ESNext", - "moduleResolution": "bundler", - "esModuleInterop": true, - "skipLibCheck": true, "target": "ESNext", + "module": "ESNext", + "moduleResolution": "Bundler", "lib": ["ESNext", "DOM"], - "strict": false, - "allowJs": true, - "resolveJsonModule": true + "types": ["vitest/globals"], + "strict": true, + "strictPropertyInitialization": false, + "skipLibCheck": true, + "noEmit": true }, - "include": ["**/*.ts", "types/**/*.d.ts"], - "exclude": ["node_modules", "dist"] + "include": ["./**/*.ts", "../../src/**/*.ts"] } diff --git a/tests/e2e/types.ts b/tests/e2e/types.ts new file mode 100644 index 00000000..328b41a0 --- /dev/null +++ b/tests/e2e/types.ts @@ -0,0 +1,44 @@ +import type { + DevSettings, + IDatasource, + Settings +} from './scaffolding/vscroll'; + +export interface TestItem { + id: number; + text: string; + size?: number; +} + +export interface TemplateSettings { + noViewportClass?: boolean; + viewportHeight?: number; + viewportWidth?: number | null; + itemHeight?: number; + itemWidth?: number | null; + horizontal?: boolean; + dynamicSize?: string | null; + viewportPadding?: number; + headerHeight?: number; +} + +export interface TestConfig { + datasource?: () => IDatasource; + datasourceSettings?: Settings; + datasourceDevSettings?: DevSettings; + templateSettings?: TemplateSettings; + noAdapter?: boolean; + custom?: Custom; + timeout?: number; + skipInvariantAutoCheck?: boolean; +} + +export interface IndexedItem { + $index: number; + data: Data; +} + +export type DatasourceProcessor = ( + items: IndexedItem[], + ...args: unknown[] +) => unknown; diff --git a/tests/e2e/types/global.d.ts b/tests/e2e/types/global.d.ts deleted file mode 100644 index 945eda01..00000000 --- a/tests/e2e/types/global.d.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Global type definitions for e2e tests - * Extends Window with vscroll-specific properties - */ - -import * as VScroll from '../../../src/index'; -import type { IAdapter } from '../../../src/interfaces/adapter'; -import type { Workflow } from '../../../src/workflow'; - -interface VScrollTest { - workflow: Workflow; - datasource: { - adapter?: IAdapter; - }; - makeScroller?: () => Workflow; // Creates Workflow with fresh oldItems closure - Direction: { - forward: string; - backward: string; - }; -} - -declare global { - interface Window { - VScroll: typeof VScroll; - __vscroll__: VScrollTest; - } -} - -export {}; diff --git a/tests/e2e/types/index.ts b/tests/e2e/types/index.ts deleted file mode 100644 index 1985afd8..00000000 --- a/tests/e2e/types/index.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { Page } from '@playwright/test'; -import type { IDatasource, Item, Workflow } from '../../../src/index.js'; -import type { - Settings, - DevSettings -} from '../../../src/interfaces/settings.js'; - -export type { Page }; -export type { IDatasource, Item, Workflow }; - -interface ITemplateSettings { - viewportHeight?: number; - viewportWidth?: number; - itemHeight?: number; - itemWidth?: number; - horizontal?: boolean; - noViewportClass?: boolean; - headerHeight?: number; -} - -export interface ITestConfig { - datasourceGet?: IDatasource['get']; - datasourceSettings: Settings; - datasourceDevSettings?: DevSettings; - templateSettings: ITemplateSettings; - noAdapter?: boolean; - noRelaxOnStart?: boolean; - custom?: Custom; - onBefore?: (page: Page) => Promise; - templateFn?: (item: unknown) => string; -} - -export interface VScrollFixtureConfig { - datasource: IDatasource; - noAdapter?: boolean; - templateSettings?: ITemplateSettings; - templateFn?: (item: unknown) => string; - onBefore?: (page: Page) => Promise; -} diff --git a/tests/e2e/vitest.config.ts b/tests/e2e/vitest.config.ts new file mode 100644 index 00000000..269823e2 --- /dev/null +++ b/tests/e2e/vitest.config.ts @@ -0,0 +1,54 @@ +import { fileURLToPath } from 'node:url'; +import { playwright } from '@vitest/browser-playwright'; +import { defineConfig } from 'vitest/config'; + +const testsRoot = fileURLToPath(new URL('../', import.meta.url)); +const repositoryRoot = fileURLToPath(new URL('../../', import.meta.url)); +const sourceRoot = fileURLToPath(new URL('../../src/', import.meta.url)).replaceAll( + '\\', + '/' +); +const testFilesRoot = testsRoot.replaceAll('\\', '/'); + +export default defineConfig({ + root: testsRoot, + server: { + fs: { + allow: [repositoryRoot] + } + }, + test: { + include: ['e2e/specs/**/*.spec.ts'], + fileParallelism: process.env.CI === 'true', + attachmentsDir: '.vitest-attachments', + globals: true, + testTimeout: 5000, + hookTimeout: 5000, + coverage: { + provider: 'istanbul', + allowExternal: true, + exclude: [ + `${testFilesRoot}**/*`, + '**/node_modules/**', + `${sourceRoot}interfaces/**/*.ts` + ], + reportsDirectory: '../coverage/e2e', + reporter: ['text', 'json', 'json-summary', 'lcov', 'cobertura'], + thresholds: { + branches: 80, + functions: 80, + lines: 85, + statements: 85 + } + }, + browser: { + enabled: true, + provider: playwright(), + headless: true, + screenshotFailures: true, + screenshotDirectory: '.vitest-attachments/screenshots', + viewport: { width: 1024, height: 768 }, + instances: [{ browser: 'chromium' }] + } + } +}); diff --git a/tests/jest.config.js b/tests/jest.config.js index 6bc5f98b..43e8f6ed 100644 --- a/tests/jest.config.js +++ b/tests/jest.config.js @@ -1,9 +1,38 @@ +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const testsRoot = fileURLToPath(new URL('./', import.meta.url)); +const dependency = (...parts) => path.join(testsRoot, 'node_modules', ...parts); + export default { - testMatch: ['**/unit/**/*.spec.ts'], + rootDir: '..', + testMatch: ['/tests/unit/**/*.spec.ts'], transform: { - '^.+\\.ts$': 'babel-jest' + '^.+\\.ts$': [ + dependency('babel-jest'), + { + presets: [ + [dependency('@babel', 'preset-env'), { targets: { node: 'current' } }], + dependency('@babel', 'preset-typescript') + ] + } + ] }, moduleFileExtensions: ['ts', 'js'], - collectCoverageFrom: ['../src/**/*.ts'], - coveragePathIgnorePatterns: ['/node_modules/'] + collectCoverageFrom: [ + '/src/**/*.ts', + '!/src/interfaces/**/*.ts' + ], + coverageDirectory: '/coverage/unit', + coverageProvider: 'v8', + coverageReporters: ['text', 'json', 'json-summary', 'lcov', 'cobertura'], + coveragePathIgnorePatterns: ['/node_modules/'], + coverageThreshold: { + global: { + branches: 85, + functions: 40, + lines: 55, + statements: 55 + } + } }; diff --git a/tests/package-lock.json b/tests/package-lock.json index ed7d1be1..545f2b8a 100644 --- a/tests/package-lock.json +++ b/tests/package-lock.json @@ -8,24 +8,27 @@ "name": "vscroll-tests", "version": "1.8.3", "devDependencies": { - "@babel/core": "^7.28.5", - "@babel/preset-env": "^7.28.5", - "@babel/preset-typescript": "^7.28.5", - "@playwright/test": "^1.57.0", + "@babel/core": "^7.29.7", + "@babel/preset-env": "^7.29.7", + "@babel/preset-typescript": "^7.29.7", "@types/jest": "^30.0.0", - "babel-jest": "^30.2.0", - "jest": "^30.2.0", - "typescript": "^5.9.3" + "@vitest/browser-playwright": "^4.1.10", + "@vitest/coverage-istanbul": "^4.1.10", + "babel-jest": "^30.4.1", + "jest": "^30.4.2", + "playwright": "^1.61.1", + "typescript": "^5.9.3", + "vitest": "^4.1.10" } }, "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", + "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -34,9 +37,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", - "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", "dev": true, "license": "MIT", "engines": { @@ -44,21 +47,21 @@ } }, "node_modules/@babel/core": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", - "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.4", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -69,72 +72,1225 @@ "engines": { "node": ">=6.9.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core/node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core/node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core/node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core/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/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/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/@babel/helper-create-class-features-plugin": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.7.tgz", + "integrity": "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/traverse": "^7.29.7", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/helper-annotate-as-pure": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz", + "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.29.7.tgz", + "integrity": "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/helper-optimise-call-expression": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.29.7.tgz", + "integrity": "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz", + "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/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/@babel/helper-create-regexp-features-plugin": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.29.7.tgz", + "integrity": "sha512-907Uymvqgg1dwUA+7IGwFAOSYzQOuzPXKNJ1yxzwPffzkYFg2q2eHi1fIOs6sXkG9NbIUMunnUlkYsfRFNvomg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "regexpu-core": "^6.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/@babel/helper-annotate-as-pure": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz", + "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/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/@babel/helper-define-polyfill-provider": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz", + "integrity": "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "debug": "^4.4.3", + "lodash.debounce": "^4.0.8", + "resolve": "^1.22.11" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-module-transforms/node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms/node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms/node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms/node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms/node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.29.7.tgz", + "integrity": "sha512-16AMiW26DbXWBbr3B8wNozKM0ydMLB892vaOaJW/fPJdnT8vJk5sdkQcU/isqUxyCE0cEoa8wZOcbgDuC4b6Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-wrap-function": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator/node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator/node_modules/@babel/helper-annotate-as-pure": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz", + "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator/node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator/node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator/node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.29.7.tgz", + "integrity": "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers/node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers/node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers/node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.29.7.tgz", + "integrity": "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers/node_modules/@babel/helper-optimise-call-expression": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.29.7.tgz", + "integrity": "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers/node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers/node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.29.7.tgz", + "integrity": "sha512-iES0Skag9ERIF68aXadpO6dbXa03mNWK3sEqJaMnLNs/eC3l0lkImdfoy6Y09/SfkpawdAB4RjQ7PVA7TcVGdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function/node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function/node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function/node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function/node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers/node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.29.7.tgz", + "integrity": "sha512-j8SrR0zLZrRsC09DlszEx8FpMiwukKffYXMK0d5LmOglO7vGG6sz/BR/20yHqWH+Lnn31JTt2PE3hIWNgM2J6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key/node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key/node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key/node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key/node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.29.7.tgz", + "integrity": "sha512-r8j8escF+U2FUHo0KOhPUdMzUO+jp9fInva6+ACVAF3Y97Ev+5iNZwiqTghmzNeWwDkOPlYuTcfb1vDaoZKmAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.29.7.tgz", + "integrity": "sha512-GE1TFSiuFeGsCxmYXZl8HwoPrVlwe4rHPFE8weieGKZqnDORK+Ar3vgWMgW+AOxQ6/2TgLSKx9p6W7O4rC6qgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array/-/plugin-bugfix-safari-rest-destructuring-rhs-array-7.29.7.tgz", + "integrity": "sha512-oBNVCvnO5tND+xSopWvV8WNGfpTfgP4Zr/YXXSj8zfmcPktp5Ku/aZlsIowgSD4fjmgHn6sGmB9APVsU5zOdhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array/node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array/node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array/node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz", + "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array/node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array/node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.29.7.tgz", + "integrity": "sha512-QQt9qKHZ2sg/kivaLr7lnQr8HVrQDdBNSfCsTjiDxRuX/K5ORyKq+Bu8Xr0cDE3Dfkv0cw28Ve0EKyKMvulkOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/plugin-transform-optional-chaining": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz", + "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.29.7.tgz", + "integrity": "sha512-pn6QacGLgvCcwc+syUhKE/qSjV2D1IHDB84RNxWYSt1mW3K/SCtjinZ2p0cETJxAWBjPy3K/1lHwG5BjjPxNlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.29.7.tgz", + "integrity": "sha512-/An1OCBN93thpBAGyfsK2pcf0jvju1SAtKkL2Ny++B5Sy6sqgzXDQH1cZxWbF96Wuk+bn41MDA9bLd4VVAw6rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/generator": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", - "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.29.7.tgz", + "integrity": "sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.5", - "@babel/types": "^7.28.5", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", - "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", + "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.27.3" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz", + "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.5.tgz", - "integrity": "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==", + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-member-expression-to-functions": "^7.28.5", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.28.5", - "semver": "^6.3.1" + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -143,335 +1299,397 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz", - "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==", + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.29.7.tgz", + "integrity": "sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "regexpu-core": "^6.3.1", - "semver": "^6.3.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz", - "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==", + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.7.tgz", + "integrity": "sha512-d98gXZkgswvkyohMBABkhm3GeXhYj8psWfwQ2C7gtfrKGTykQa/iOIi+JJhwMjPlZ6Vm2XN+DCf3Es1EoG4ZLA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "debug": "^4.4.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.22.10" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-remap-async-to-generator": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "node_modules/@babel/plugin-transform-async-generator-functions/node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions/node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", - "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", + "node_modules/@babel/plugin-transform-async-generator-functions/node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "node_modules/@babel/plugin-transform-async-generator-functions/node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", - "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.29.7.tgz", + "integrity": "sha512-pcUb2SS+RMo9TWVBwKGI5ShtoG7R+zBsFmCKDa6fe8c+hPr3XJlZgoE5j6i8W7gDjhyvy+85vmYexanvXh3d1w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-remap-async-to-generator": "^7.29.7" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", - "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "node_modules/@babel/plugin-transform-async-to-generator/node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.27.1" + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "node_modules/@babel/plugin-transform-async-to-generator/node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", - "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", + "node_modules/@babel/plugin-transform-async-to-generator/node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-wrap-function": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", - "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", + "node_modules/@babel/plugin-transform-async-to-generator/node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.27.1", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", - "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "node_modules/@babel/plugin-transform-async-to-generator/node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.29.7.tgz", + "integrity": "sha512-cUSmjh72N+rN4PrkFlN1dJwNCwjVp5d38/CQrEsFggkD10UiFlBFgdH3tv5dNsLuHY+3S8db2xCHjhZcv5WgvA==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.29.7.tgz", + "integrity": "sha512-ONyr4+AZhKh8yKWInVxU9AXA9EbsyeLcL6V0dJy6M2/62vuvpGm29zzuymbTpdc451GEpDIdAyPLP3r+P61yKQ==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.29.7.tgz", + "integrity": "sha512-GtcpjFvanPfzNQi3eTitsCqtRRmmqzpy/A+yhTR1HaZo1Ly3EA8ZXxlPyHdR8/IuRMYc3E4wdGBewB2QKQjAaA==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.3.tgz", - "integrity": "sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g==", + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.29.7.tgz", + "integrity": "sha512-kibJgmEdX2iMwsHY2tSZNDgj8PwIlCQz7FK9KuGKO8zsuoUwSEhoNnNVp/emKWrbY4HeO6kkXfdMqRKKKXBm2A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.3", - "@babel/types": "^7.28.2" + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" } }, - "node_modules/@babel/helpers": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", - "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "node_modules/@babel/plugin-transform-classes": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.29.7.tgz", + "integrity": "sha512-qV0OGGBVacduzQHE649JyCneOFI/maT+YKsO+K4Yi3xv2wTPNjM/W2o2gdzMwEAZz7fXNTHAe0NcSg30bIN69g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4" + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "node_modules/@babel/plugin-transform-classes/node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.5" - }, - "bin": { - "parser": "bin/babel-parser.js" + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" }, "engines": { - "node": ">=6.0.0" + "node": ">=6.9.0" } }, - "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", - "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", + "node_modules/@babel/plugin-transform-classes/node_modules/@babel/helper-annotate-as-pure": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz", + "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.5" + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", - "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", + "node_modules/@babel/plugin-transform-classes/node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-transform-classes/node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", - "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", + "node_modules/@babel/plugin-transform-classes/node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", - "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.29.7.tgz", + "integrity": "sha512-RK7/IyU5phpuCdBAuig5VkzG/EnbDaui5SQGdU9BFrHdV+mV4cUjLMQ9lJDjLNtWHsqtiefpGZUXQP2BiTYMsA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/template": "^7.29.7" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.13.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.3.tgz", - "integrity": "sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==", + "node_modules/@babel/plugin-transform-computed-properties/node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.3" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.29.7.tgz", + "integrity": "sha512-iPX8aD6H9zV5s7ZsqTdNocPN/MGQ5sSMnElKrktxjJRMnB2jN/1p2+R7GkfD6CAYoVFqy5A4XnSIUeGgJzIWpg==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, "engines": { "node": ">=6.9.0" }, @@ -479,53 +1697,76 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "node_modules/@babel/plugin-transform-destructuring/node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "node_modules/@babel/plugin-transform-destructuring/node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-transform-destructuring/node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "node_modules/@babel/plugin-transform-destructuring/node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.29.7.tgz", + "integrity": "sha512-3qc18hsD2RdZiyJNDNc7HQpv6xbncwh8FYtxNFFzclSyh/trPD9KkVR9BDECUjDLvb7yJVF15GfYUuC+LMkkiQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -534,14 +1775,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz", - "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==", + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.29.7.tgz", + "integrity": "sha512-6IvRRriEMqnBwD6chtxdLpMYCHWEzN+oL5cyQtjykya19UgzbmKhxmhZgKC/LHxS2nYr9Q/qYPZ5Lr6jOL9+yQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -550,56 +1791,64 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", - "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.7.tgz", + "integrity": "sha512-2wiIyo2BjtgU7HufSeDnL9L2O7zr8jmhFKuSr65VpRkUiRKRNpb0mdlk56+XPPKoIrfHqzbMuglDvZun0RISsA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.29.7.tgz", + "integrity": "sha512-giOlEm/EFjfjr+te9NsdjkUo2v4f8rS/SXPumRVHAtbNcyNlvtREkU1dZzaIDclNpnaVhlCqRdFKhJBjBikzLg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "node_modules/@babel/plugin-transform-explicit-resource-management": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.29.7.tgz", + "integrity": "sha512-Rstj7coNz8sE+7Ju7ihpHLI564lsK5pUpNNlvptCIC/16E/S5hbl6n3kESPKdNRmqEWlpn5xpS5Q2dvXBsySLw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-transform-destructuring": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", - "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.29.7.tgz", + "integrity": "sha512-zFpMOTLZBdW5LfObqcSbL6kefg4R4eLdmvS0wbN9M6D5Mym/sKm9toOoWyVOa+xDjvCnuWcHls2YonXwHvH3CQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -608,92 +1857,124 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.29.7.tgz", + "integrity": "sha512-24B2nOy2TeJSMheqwPD4DDQOV/elLSIlKxjZt4i05H5AgdPdWR3n18HnNrcJ+j76WJd9gbwb9jPjNYUy6RautA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.29.7.tgz", + "integrity": "sha512-zeSIHh0+E1Um1WJRXCFlHQYu2ieJNdivLLjlBEp+dIBu3S51n+SZZmIXjxnItw6pz56Cn+KvK68BIBVsxq2JiQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "node_modules/@babel/plugin-transform-for-of/node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "node_modules/@babel/plugin-transform-for-of/node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-transform-for-of/node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz", + "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "node_modules/@babel/plugin-transform-for-of/node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "node_modules/@babel/plugin-transform-for-of/node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.29.7.tgz", + "integrity": "sha512-otRWaHXE6fbAGkePvaj/kvs3HsqXfPhlnzwSOlnFgbqCPMd975dW+4wZ00WFBt+/YlBGcJwNrARQTOJOb4ZrIg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -702,81 +1983,75 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "node_modules/@babel/plugin-transform-function-name/node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", - "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "node_modules/@babel/plugin-transform-function-name/node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-unicode-sets-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", - "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "node_modules/@babel/plugin-transform-function-name/node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", - "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", + "node_modules/@babel/plugin-transform-function-name/node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz", - "integrity": "sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==", + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.29.7.tgz", + "integrity": "sha512-RRnE2+eon1rJAq8MnoF1b5kTpY1vU88twHcvcKMrsqP/jxIRqDVs9iJB5fqPuqyeFAW0wJo4MlUIPpQCq/aRsg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-remap-async-to-generator": "^7.27.1", - "@babel/traverse": "^7.28.0" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -785,16 +2060,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", - "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", + "node_modules/@babel/plugin-transform-literals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.29.7.tgz", + "integrity": "sha512-DZ/oLP21ZuWx1vKqnoNv6/tvEK48AQOBRai40CX9dTjGluvT/YZCyY3rryDtyUqCEoyNroy5KKPwX2iQCiRvyw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-remap-async-to-generator": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -803,14 +2076,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", - "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.29.7.tgz", + "integrity": "sha512-A0H91hh6W8MFRkp5TqJmMr39jzGD1A1E1Ysiv2O06Sfbhkapm+XyIzxWCEh5kqwOZ1/8QZ0dY3SeQ7XBqfJd5Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -819,14 +2092,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.5.tgz", - "integrity": "sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g==", + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.29.7.tgz", + "integrity": "sha512-hl1kwFZCCiDyfH25Xmco9jTrkPgnS9pmOzSG7W5I4SaGbLeqKv417hcU2RKmaxoPEgsoJh7ZPOrnPGq99bHoUg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -835,15 +2108,15 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", - "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.29.7.tgz", + "integrity": "sha512-fxtQoH3m5ywUSIfaH0FGCzWu4McsYon5bD3K4XnskC7f+OyQMj7rsOMi4NvvmJ83WwBAg4UCe+ov4VZlqEvyew==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -852,36 +2125,34 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz", - "integrity": "sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==", + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.29.7.tgz", + "integrity": "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.28.3", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.12.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz", - "integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==", + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.7.tgz", + "integrity": "sha512-TM2ZcQLoG2/y4HODiStCo10DibYhWhGWAwVv+EQKmG/7GFl0N+AAmUiXOMKM+aiJ9XBJ9AHVZBvTzMnJ2sM3cQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-globals": "^7.28.0", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", - "@babel/traverse": "^7.28.4" + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -890,65 +2161,76 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", - "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", + "node_modules/@babel/plugin-transform-modules-systemjs/node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/template": "^7.27.1" + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", - "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==", + "node_modules/@babel/plugin-transform-modules-systemjs/node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs/node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.5" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz", - "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==", + "node_modules/@babel/plugin-transform-modules-systemjs/node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", - "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.29.7.tgz", + "integrity": "sha512-B4UkaTK3QpgCwJnrxKfMPKdo92CN7OKXAlpAAnM3UPu0Q0lCCk57ylA9AJbRy2v8dDKOPAAWcoR6CMyeoHwRCA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -957,15 +2239,15 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz", - "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==", + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.7.tgz", + "integrity": "sha512-vuFoLwr4qnv2xbZ16SQd6uPcH5FNrLHhk/Jzo++0XJFcaDsr4gjJVg6j398oMHiC+83k/GiBzviwF5KBJkPUtQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -974,14 +2256,14 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", - "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.29.7.tgz", + "integrity": "sha512-fEo41GmsOUhOBlw8ioo6zvjX5Xc2Lqkzlyfqbpsk3eB6TReV18uhxZ0esfEokVbY2+PVJAQHNKxER6lGrzNd3A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -990,15 +2272,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-explicit-resource-management": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.0.tgz", - "integrity": "sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ==", + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.29.7.tgz", + "integrity": "sha512-idmp1dFaekP9GbcMvG24Kvw2BfhFZjHnNJCkV4WuIY4PskJzwI3f1N5OdgYke38T7rftO6ERulFRn2cFeZwRkg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.28.0" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1007,14 +2288,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.5.tgz", - "integrity": "sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw==", + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.29.7.tgz", + "integrity": "sha512-zR7fv/z14OjgHl4AgRtkDBvBMhIzCxqV/qN/2BCRC7LjFwvuzjYe7gDWxC4Wl/SNsLM6SE1IWvRPYMgSJaUvNw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1023,14 +2304,18 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", - "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.29.7.tgz", + "integrity": "sha512-Ld98jn4c0smUywL57m7SgsHq3OpThOa6LqZJif3G6jYOovPleoFhVrBJ1WegRApSFB2wu4+RelAj9AC9G08Z4A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-transform-destructuring": "^7.29.7", + "@babel/plugin-transform-parameters": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1039,81 +2324,76 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", - "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", + "node_modules/@babel/plugin-transform-object-rest-spread/node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", - "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", + "node_modules/@babel/plugin-transform-object-rest-spread/node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz", - "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==", + "node_modules/@babel/plugin-transform-object-rest-spread/node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", - "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", + "node_modules/@babel/plugin-transform-object-rest-spread/node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.5.tgz", - "integrity": "sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA==", + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.29.7.tgz", + "integrity": "sha512-Ea/diGcw0twB5IlZPO5sgET6fJsLJqPABqTuFWIR+iMPGPZJkATEIWx0wa+aEQ5UY1CBQyP/gkAiLEqn1vBiQA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1122,14 +2402,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", - "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.29.7.tgz", + "integrity": "sha512-sLsyndxK2VwX6yNUOakMb7Sh553ZTe/vVM1XJ+9Z5aW1ytsc8xOIwmyk05NNjN60vkc5/KqoTH6hB4V41LJhng==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1138,15 +2418,15 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", - "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.29.7.tgz", + "integrity": "sha512-6GM1dhvK3gNODkXcEcMCOLEDCLSoZ/sBbro2Ax8HURyasQ4NshagQixkRFdh5niI6E4gmA/jYI/4aT7rRos3ZQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1155,84 +2435,89 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", - "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", + "node_modules/@babel/plugin-transform-optional-chaining/node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz", - "integrity": "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==", + "node_modules/@babel/plugin-transform-optional-chaining/node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining/node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz", + "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.5" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", - "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", + "node_modules/@babel/plugin-transform-optional-chaining/node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", - "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", + "node_modules/@babel/plugin-transform-optional-chaining/node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", - "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.29.7.tgz", + "integrity": "sha512-ZDOBqV/qLYJI0YElr8DcENEyARsFQeESqWXH6gZlghYXuPPjvweuDhP4VyEi4BlUBlLRFZVjxoZDMjxhLW766g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1241,14 +2526,15 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", - "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.29.7.tgz", + "integrity": "sha512-/6Rz4DK1ETDEM/bWHsPHcaEe7ZaT1EqSXjtSP/L0DijOYuaUhiRiOKcwpZ8P7zR4xXEHc2ITdiCgBm9Tpyv9ug==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1257,14 +2543,16 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", - "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.29.7.tgz", + "integrity": "sha512-+BNo06dnrzdNNqCm1X6YUaVv0DKk8Q+JYcoZfOkLhYWNCXzlwTSRq8zGWayT1csjcpNXV9CQTBRRbmTLZac5cA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1273,35 +2561,27 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.4.tgz", - "integrity": "sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew==", + "node_modules/@babel/plugin-transform-private-property-in-object/node_modules/@babel/helper-annotate-as-pure": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz", + "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.28.0", - "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/traverse": "^7.28.4" + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", - "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.29.7.tgz", + "integrity": "sha512-bOMRLQuI0A5ZqHq3OWJ89/rXpJ/NJrbVhXiP4zwPGMs6kpcVsuTUNjwoE30K0Qm3mf48a/TnRYYD6vPNqcg6jA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1310,14 +2590,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", - "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.7.tgz", + "integrity": "sha512-rNNFV0DBAJp988xW2DOntfDoYn1eR8GGF5AT5vYc+rjyfaQkM242c9tZUHHPe7KYaiJizXPWhQTzzdbXySyhBw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1326,31 +2606,31 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.5.tgz", - "integrity": "sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ==", + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.29.7.tgz", + "integrity": "sha512-mB5Fs0VWrJ42ZCmc8114v60qetdaUVNkj9PmSZRmanCZM3S9hm0CFRLjRmYIsuXav14l2jvZ+4T8iiCGnhj3nQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.27.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", - "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.29.7.tgz", + "integrity": "sha512-5+YhdpVgmfSmwZyLMftfaiffLRMHjzIRHFHHLdibcSyJm2pasMrKHrO3Ptrt2DRshjvpgjEJJ1zVW14WPq/6QA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1359,15 +2639,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", - "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.29.7.tgz", + "integrity": "sha512-I+WYbGBAiCn7nA6xBrlgPH+MB7HWb4u8pv5S0Pv7OtwNvIFvCCb24YlttKEeUFVurfBCEaOTnuhlqsb7f0Z5Dg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1376,16 +2655,15 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", - "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", + "node_modules/@babel/plugin-transform-spread": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.29.7.tgz", + "integrity": "sha512-/u5K1QWada7tbYNqTjMh96718g9NTwh9tfPJMsSmVsQwGT447FskV+KcfeXkXq2GWki4EM/MuTdmBec+hOuVTQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1394,63 +2672,89 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", - "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", + "node_modules/@babel/plugin-transform-spread/node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz", - "integrity": "sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA==", + "node_modules/@babel/plugin-transform-spread/node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-transform-spread/node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz", + "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-regexp-modifiers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz", - "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==", + "node_modules/@babel/plugin-transform-spread/node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-transform-spread/node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", - "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.29.7.tgz", + "integrity": "sha512-BCHzNYJGe9l7EpwwDBN/ztlL2NYFFq8hp9ddjtUEM9f2O7S7kKV/lL6Fwo7IF7NSkYhPK2vO+86nIGltA90MsA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1459,14 +2763,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", - "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.29.7.tgz", + "integrity": "sha512-NCSEJ4sLFU2gqAub45HYh4fus2yQ36rr6ei6vpU7NdoJqCpxvEG8E6eJpscGyXP3VHD2Ny+fSXr04k1hoUrFqA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1475,15 +2779,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", - "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.29.7.tgz", + "integrity": "sha512-223mNGoTkBiTEWFoK+Q6Go3tueMRclO8vxxxxquNCYuNI4jWOofFKJRRDu6SDrB8Sgo1UEGW9T4GAQ8ZyRso1A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1492,14 +2795,18 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", - "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.7.tgz", + "integrity": "sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/plugin-syntax-typescript": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1508,66 +2815,102 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", - "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", + "node_modules/@babel/plugin-transform-typescript/node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", - "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", + "node_modules/@babel/plugin-transform-typescript/node_modules/@babel/helper-annotate-as-pure": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz", + "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-transform-typescript/node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-transform-typescript/node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz", + "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@babel/plugin-transform-typescript": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.5.tgz", - "integrity": "sha512-x2Qa+v/CuEoX7Dr31iAfr0IhInrVOWZU/2vJMJ00FOR/2nM0BcBEclpaf9sWCDc+v5e9dMrhSH8/atq/kX7+bA==", + "node_modules/@babel/plugin-transform-typescript/node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-create-class-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/plugin-syntax-typescript": "^7.27.1" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-transform-typescript/node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", - "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.29.7.tgz", + "integrity": "sha512-jCfXxSjf94lf4E0hKE0AByxF6F3/pVFqRdUUNkDJhsY0m1ZKjnN6ZYyMeHNpzflxb/0q5b7t3p+BE+SLF1WOtA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1577,14 +2920,14 @@ } }, "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz", - "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.29.7.tgz", + "integrity": "sha512-OgZ+zoAJgZLUCunsTRQ5LAjOywDv5zzZ2/hQ5aMw1pGXyY2rtE8/chXYUmu3AlVHKpm10KEdG9aMwbI/K76ZGw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1594,14 +2937,14 @@ } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", - "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.29.7.tgz", + "integrity": "sha512-7D/x/23/d/3VqZ0QA+LGbZMlGwZjztBygSWWWsfTPoQ1oQ6Q1P6Mr3d0kk42XabyUVw+fha3LqdRsFqeKqvCyA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1611,14 +2954,14 @@ } }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz", - "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.29.7.tgz", + "integrity": "sha512-BLOhLht9DOJwIxlmp91wHvkXv1lguuHS3/FwUO8HL1H0u8s4hR1gASVFyilu9iGtcTRYqjTZmlsFFeQletntEg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1628,81 +2971,82 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.5.tgz", - "integrity": "sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.28.5", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.3", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.7.tgz", + "integrity": "sha512-GYzX36n1nsciIb0uyH0GHwxwtNwPQIcpxSeiVLDtG/B7jB5xXgchnmL1f/jCX5o+pwnaDBtO60ONSJhEBJfxYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.29.7", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.29.7", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.29.7", + "@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": "^7.29.7", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.29.7", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.29.7", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-import-assertions": "^7.27.1", - "@babel/plugin-syntax-import-attributes": "^7.27.1", + "@babel/plugin-syntax-import-assertions": "^7.29.7", + "@babel/plugin-syntax-import-attributes": "^7.29.7", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.27.1", - "@babel/plugin-transform-async-generator-functions": "^7.28.0", - "@babel/plugin-transform-async-to-generator": "^7.27.1", - "@babel/plugin-transform-block-scoped-functions": "^7.27.1", - "@babel/plugin-transform-block-scoping": "^7.28.5", - "@babel/plugin-transform-class-properties": "^7.27.1", - "@babel/plugin-transform-class-static-block": "^7.28.3", - "@babel/plugin-transform-classes": "^7.28.4", - "@babel/plugin-transform-computed-properties": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.28.5", - "@babel/plugin-transform-dotall-regex": "^7.27.1", - "@babel/plugin-transform-duplicate-keys": "^7.27.1", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", - "@babel/plugin-transform-dynamic-import": "^7.27.1", - "@babel/plugin-transform-explicit-resource-management": "^7.28.0", - "@babel/plugin-transform-exponentiation-operator": "^7.28.5", - "@babel/plugin-transform-export-namespace-from": "^7.27.1", - "@babel/plugin-transform-for-of": "^7.27.1", - "@babel/plugin-transform-function-name": "^7.27.1", - "@babel/plugin-transform-json-strings": "^7.27.1", - "@babel/plugin-transform-literals": "^7.27.1", - "@babel/plugin-transform-logical-assignment-operators": "^7.28.5", - "@babel/plugin-transform-member-expression-literals": "^7.27.1", - "@babel/plugin-transform-modules-amd": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.27.1", - "@babel/plugin-transform-modules-systemjs": "^7.28.5", - "@babel/plugin-transform-modules-umd": "^7.27.1", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", - "@babel/plugin-transform-new-target": "^7.27.1", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", - "@babel/plugin-transform-numeric-separator": "^7.27.1", - "@babel/plugin-transform-object-rest-spread": "^7.28.4", - "@babel/plugin-transform-object-super": "^7.27.1", - "@babel/plugin-transform-optional-catch-binding": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.28.5", - "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/plugin-transform-private-methods": "^7.27.1", - "@babel/plugin-transform-private-property-in-object": "^7.27.1", - "@babel/plugin-transform-property-literals": "^7.27.1", - "@babel/plugin-transform-regenerator": "^7.28.4", - "@babel/plugin-transform-regexp-modifiers": "^7.27.1", - "@babel/plugin-transform-reserved-words": "^7.27.1", - "@babel/plugin-transform-shorthand-properties": "^7.27.1", - "@babel/plugin-transform-spread": "^7.27.1", - "@babel/plugin-transform-sticky-regex": "^7.27.1", - "@babel/plugin-transform-template-literals": "^7.27.1", - "@babel/plugin-transform-typeof-symbol": "^7.27.1", - "@babel/plugin-transform-unicode-escapes": "^7.27.1", - "@babel/plugin-transform-unicode-property-regex": "^7.27.1", - "@babel/plugin-transform-unicode-regex": "^7.27.1", - "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", + "@babel/plugin-transform-arrow-functions": "^7.29.7", + "@babel/plugin-transform-async-generator-functions": "^7.29.7", + "@babel/plugin-transform-async-to-generator": "^7.29.7", + "@babel/plugin-transform-block-scoped-functions": "^7.29.7", + "@babel/plugin-transform-block-scoping": "^7.29.7", + "@babel/plugin-transform-class-properties": "^7.29.7", + "@babel/plugin-transform-class-static-block": "^7.29.7", + "@babel/plugin-transform-classes": "^7.29.7", + "@babel/plugin-transform-computed-properties": "^7.29.7", + "@babel/plugin-transform-destructuring": "^7.29.7", + "@babel/plugin-transform-dotall-regex": "^7.29.7", + "@babel/plugin-transform-duplicate-keys": "^7.29.7", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.7", + "@babel/plugin-transform-dynamic-import": "^7.29.7", + "@babel/plugin-transform-explicit-resource-management": "^7.29.7", + "@babel/plugin-transform-exponentiation-operator": "^7.29.7", + "@babel/plugin-transform-export-namespace-from": "^7.29.7", + "@babel/plugin-transform-for-of": "^7.29.7", + "@babel/plugin-transform-function-name": "^7.29.7", + "@babel/plugin-transform-json-strings": "^7.29.7", + "@babel/plugin-transform-literals": "^7.29.7", + "@babel/plugin-transform-logical-assignment-operators": "^7.29.7", + "@babel/plugin-transform-member-expression-literals": "^7.29.7", + "@babel/plugin-transform-modules-amd": "^7.29.7", + "@babel/plugin-transform-modules-commonjs": "^7.29.7", + "@babel/plugin-transform-modules-systemjs": "^7.29.7", + "@babel/plugin-transform-modules-umd": "^7.29.7", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.7", + "@babel/plugin-transform-new-target": "^7.29.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.29.7", + "@babel/plugin-transform-numeric-separator": "^7.29.7", + "@babel/plugin-transform-object-rest-spread": "^7.29.7", + "@babel/plugin-transform-object-super": "^7.29.7", + "@babel/plugin-transform-optional-catch-binding": "^7.29.7", + "@babel/plugin-transform-optional-chaining": "^7.29.7", + "@babel/plugin-transform-parameters": "^7.29.7", + "@babel/plugin-transform-private-methods": "^7.29.7", + "@babel/plugin-transform-private-property-in-object": "^7.29.7", + "@babel/plugin-transform-property-literals": "^7.29.7", + "@babel/plugin-transform-regenerator": "^7.29.7", + "@babel/plugin-transform-regexp-modifiers": "^7.29.7", + "@babel/plugin-transform-reserved-words": "^7.29.7", + "@babel/plugin-transform-shorthand-properties": "^7.29.7", + "@babel/plugin-transform-spread": "^7.29.7", + "@babel/plugin-transform-sticky-regex": "^7.29.7", + "@babel/plugin-transform-template-literals": "^7.29.7", + "@babel/plugin-transform-typeof-symbol": "^7.29.7", + "@babel/plugin-transform-unicode-escapes": "^7.29.7", + "@babel/plugin-transform-unicode-property-regex": "^7.29.7", + "@babel/plugin-transform-unicode-regex": "^7.29.7", + "@babel/plugin-transform-unicode-sets-regex": "^7.29.7", "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.14", - "babel-plugin-polyfill-corejs3": "^0.13.0", - "babel-plugin-polyfill-regenerator": "^0.6.5", - "core-js-compat": "^3.43.0", + "babel-plugin-polyfill-corejs2": "^0.4.15", + "babel-plugin-polyfill-corejs3": "^0.14.0", + "babel-plugin-polyfill-regenerator": "^0.6.6", + "core-js-compat": "^3.48.0", "semver": "^6.3.1" }, "engines": { @@ -1712,6 +3056,16 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/preset-env/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/@babel/preset-modules": { "version": "0.1.6-no-external-plugins", "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", @@ -1728,17 +3082,17 @@ } }, "node_modules/@babel/preset-typescript": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz", - "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.29.7.tgz", + "integrity": "sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.27.1", - "@babel/plugin-transform-typescript": "^7.28.5" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "@babel/plugin-syntax-jsx": "^7.29.7", + "@babel/plugin-transform-modules-commonjs": "^7.29.7", + "@babel/plugin-transform-typescript": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1747,49 +3101,15 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", - "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.5", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1802,22 +3122,29 @@ "dev": true, "license": "MIT" }, + "node_modules/@blazediff/core": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@blazediff/core/-/core-1.9.1.tgz", + "integrity": "sha512-ehg3jIkYKulZh+8om/O25vkvSsXXwC+skXmyA87FFx6A/45eqOkZsBltMw/TVteb0mloiGT8oGRTcjRAz66zaA==", + "dev": true, + "license": "MIT" + }, "node_modules/@emnapi/core": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.0.tgz", - "integrity": "sha512-pJdKGq/1iquWYtv1RRSljZklxHCOCAJFJrImO5ZLKPJVJlVUcs8yFwNQlqS0Lo8xT1VAXXTCZocF9n26FWEKsw==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@emnapi/wasi-threads": "1.1.0", + "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "node_modules/@emnapi/runtime": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.0.tgz", - "integrity": "sha512-oAYoQnCYaQZKVS53Fq23ceWMRxq5EhQsE0x0RdQ55jT7wagMu5k+fS39v1fiSLrtrLQlXwVINenqhLMtTrV/1Q==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", "dev": true, "license": "MIT", "optional": true, @@ -1826,9 +3153,9 @@ } }, "node_modules/@emnapi/wasi-threads": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", - "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", "dev": true, "license": "MIT", "optional": true, @@ -1882,17 +3209,17 @@ } }, "node_modules/@jest/console": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.2.0.tgz", - "integrity": "sha512-+O1ifRjkvYIkBqASKWgLxrpEhQAAE7hY77ALLUufSk5717KfOShg6IbqLmdsLMPdUiFvA2kTs0R7YZy+l0IzZQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.4.1.tgz", + "integrity": "sha512-v3bhyxUh9Hgmo5p6hAOXe14/R3ZxZDOsvHleh4B07z3m/x4/ngPUXEm9XwK4sF4u+f+P2ORb0Ge+MgpaqRMVDA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.2.0", + "@jest/types": "30.4.1", "@types/node": "*", "chalk": "^4.1.2", - "jest-message-util": "30.2.0", - "jest-util": "30.2.0", + "jest-message-util": "30.4.1", + "jest-util": "30.4.1", "slash": "^3.0.0" }, "engines": { @@ -1900,39 +3227,39 @@ } }, "node_modules/@jest/core": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.2.0.tgz", - "integrity": "sha512-03W6IhuhjqTlpzh/ojut/pDB2LPRygyWX8ExpgHtQA8H/3K7+1vKmcINx5UzeOX1se6YEsBsOHQ1CRzf3fOwTQ==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.4.2.tgz", + "integrity": "sha512-TZJA6cPJUFxoWhxaLo8t0VX/MZX2wPWr0uIDvLSHIvN4gu9h02vSzqI2kBADG1ExqQlC+cY09xKMSreivvrChQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.2.0", - "@jest/pattern": "30.0.1", - "@jest/reporters": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", + "@jest/console": "30.4.1", + "@jest/pattern": "30.4.0", + "@jest/reporters": "30.4.1", + "@jest/test-result": "30.4.1", + "@jest/transform": "30.4.1", + "@jest/types": "30.4.1", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", "ci-info": "^4.2.0", "exit-x": "^0.2.2", + "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.11", - "jest-changed-files": "30.2.0", - "jest-config": "30.2.0", - "jest-haste-map": "30.2.0", - "jest-message-util": "30.2.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.2.0", - "jest-resolve-dependencies": "30.2.0", - "jest-runner": "30.2.0", - "jest-runtime": "30.2.0", - "jest-snapshot": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", - "jest-watcher": "30.2.0", - "micromatch": "^4.0.8", - "pretty-format": "30.2.0", + "jest-changed-files": "30.4.1", + "jest-config": "30.4.2", + "jest-haste-map": "30.4.1", + "jest-message-util": "30.4.1", + "jest-regex-util": "30.4.0", + "jest-resolve": "30.4.1", + "jest-resolve-dependencies": "30.4.2", + "jest-runner": "30.4.2", + "jest-runtime": "30.4.2", + "jest-snapshot": "30.4.1", + "jest-util": "30.4.1", + "jest-validate": "30.4.1", + "jest-watcher": "30.4.1", + "pretty-format": "30.4.1", "slash": "^3.0.0" }, "engines": { @@ -1948,9 +3275,9 @@ } }, "node_modules/@jest/diff-sequences": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", - "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.4.0.tgz", + "integrity": "sha512-zOpzlfUs45l6u7jm39qr87JCHUDsaeCtvL+kQe/Vn9jSnRB4/5IPXISm0h9I1vZW/o00Kn4UTJ2MOlhnUGwv3g==", "dev": true, "license": "MIT", "engines": { @@ -1958,39 +3285,39 @@ } }, "node_modules/@jest/environment": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.2.0.tgz", - "integrity": "sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.4.1.tgz", + "integrity": "sha512-AK9yNRqgKxiabqMoe4oW+3/TSSeV8vkdC7BGaxZdU0AFXfOpofTLqdru2GXKZghP3sdgwE9XXpnVwfZ8JnFV4w==", "dev": true, "license": "MIT", "dependencies": { - "@jest/fake-timers": "30.2.0", - "@jest/types": "30.2.0", + "@jest/fake-timers": "30.4.1", + "@jest/types": "30.4.1", "@types/node": "*", - "jest-mock": "30.2.0" + "jest-mock": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.2.0.tgz", - "integrity": "sha512-V9yxQK5erfzx99Sf+7LbhBwNWEZ9eZay8qQ9+JSC0TrMR1pMDHLMY+BnVPacWU6Jamrh252/IKo4F1Xn/zfiqA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.4.1.tgz", + "integrity": "sha512-ginrj6TMgh2GshLUGCjO94Ptx9HhdZA/I6A9iUfyeLKFtdAjnKzHDgzgP9HYQgbxM1lbXScQ2eUBz2lGeVDPWA==", "dev": true, "license": "MIT", "dependencies": { - "expect": "30.2.0", - "jest-snapshot": "30.2.0" + "expect": "30.4.1", + "jest-snapshot": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.2.0.tgz", - "integrity": "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.4.1.tgz", + "integrity": "sha512-ZBn5CglH8fBsQsvs4VWNzD4aWfUYks+IdOOQU3MEK71ol/BcVm+P+rtb1KpiFBpSWSCE27uOahyyf1vfqOVbcQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2001,18 +3328,18 @@ } }, "node_modules/@jest/fake-timers": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.2.0.tgz", - "integrity": "sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.4.1.tgz", + "integrity": "sha512-iW5umdmfPeWzehrVhugFQZqCchSCud5S1l2YT0O9ZhjRR0ExclANDZkiSBwzqtnlOn0J1JXvO+HZ6rkuyOVOgQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.2.0", - "@sinonjs/fake-timers": "^13.0.0", + "@jest/types": "30.4.1", + "@sinonjs/fake-timers": "^15.4.0", "@types/node": "*", - "jest-message-util": "30.2.0", - "jest-mock": "30.2.0", - "jest-util": "30.2.0" + "jest-message-util": "30.4.1", + "jest-mock": "30.4.1", + "jest-util": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -2029,62 +3356,62 @@ } }, "node_modules/@jest/globals": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.2.0.tgz", - "integrity": "sha512-b63wmnKPaK+6ZZfpYhz9K61oybvbI1aMcIs80++JI1O1rR1vaxHUCNqo3ITu6NU0d4V34yZFoHMn/uoKr/Rwfw==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.4.1.tgz", + "integrity": "sha512-ZbuY4cmXC8DkxYjfvT2DbcHWL2T6vmsMhXCDcmTB2T0y0gaezBI77ufq5ZAIdcRkYZ7NEQEDg1xFeKbxUJ5v5Q==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.2.0", - "@jest/expect": "30.2.0", - "@jest/types": "30.2.0", - "jest-mock": "30.2.0" + "@jest/environment": "30.4.1", + "@jest/expect": "30.4.1", + "@jest/types": "30.4.1", + "jest-mock": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/pattern": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", - "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz", + "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", - "jest-regex-util": "30.0.1" + "jest-regex-util": "30.4.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/reporters": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.2.0.tgz", - "integrity": "sha512-DRyW6baWPqKMa9CzeiBjHwjd8XeAyco2Vt8XbcLFjiwCOEKOvy82GJ8QQnJE9ofsxCMPjH4MfH8fCWIHHDKpAQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.4.1.tgz", + "integrity": "sha512-/SnkPCzEQpUaBH81kjdEdDdo2WZl5hxw+BmLDGWjRkm8o7XlhjwsU36cqwe5PGBE5WYpBvDzRSdXx9rbGuJtNA==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", + "@jest/console": "30.4.1", + "@jest/test-result": "30.4.1", + "@jest/transform": "30.4.1", + "@jest/types": "30.4.1", "@jridgewell/trace-mapping": "^0.3.25", "@types/node": "*", "chalk": "^4.1.2", "collect-v8-coverage": "^1.0.2", "exit-x": "^0.2.2", - "glob": "^10.3.10", + "glob": "^10.5.0", "graceful-fs": "^4.2.11", "istanbul-lib-coverage": "^3.0.0", "istanbul-lib-instrument": "^6.0.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^5.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "30.2.0", - "jest-util": "30.2.0", - "jest-worker": "30.2.0", + "jest-message-util": "30.4.1", + "jest-util": "30.4.1", + "jest-worker": "30.4.1", "slash": "^3.0.0", "string-length": "^4.0.2", "v8-to-istanbul": "^9.0.1" @@ -2102,9 +3429,9 @@ } }, "node_modules/@jest/schemas": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", - "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", + "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", "dev": true, "license": "MIT", "dependencies": { @@ -2115,13 +3442,13 @@ } }, "node_modules/@jest/snapshot-utils": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.2.0.tgz", - "integrity": "sha512-0aVxM3RH6DaiLcjj/b0KrIBZhSX1373Xci4l3cW5xiUWPctZ59zQ7jj4rqcJQ/Z8JuN/4wX3FpJSa3RssVvCug==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.4.1.tgz", + "integrity": "sha512-ObY4ljvQ95mt6iwKtVLetR/4yXiAgl3H4nJxhztr0MTjrN97TwDYrnCp/kF60Ec9HdhkWTHSu+Hg05aXfngpOA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.2.0", + "@jest/types": "30.4.1", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "natural-compare": "^1.4.0" @@ -2146,14 +3473,14 @@ } }, "node_modules/@jest/test-result": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.2.0.tgz", - "integrity": "sha512-RF+Z+0CCHkARz5HT9mcQCBulb1wgCP3FBvl9VFokMX27acKphwyQsNuWH3c+ojd1LeWBLoTYoxF0zm6S/66mjg==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.4.1.tgz", + "integrity": "sha512-/ZG7pgEiOmmWkN9TplKbOu4id2N5lh7FHwRwlkgBVAzGdRH+OkkQ8wX/kIxg4zmd3ZQvAL1RwL2yWsvNYYECTw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.2.0", - "@jest/types": "30.2.0", + "@jest/console": "30.4.1", + "@jest/types": "30.4.1", "@types/istanbul-lib-coverage": "^2.0.6", "collect-v8-coverage": "^1.0.2" }, @@ -2162,15 +3489,15 @@ } }, "node_modules/@jest/test-sequencer": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.2.0.tgz", - "integrity": "sha512-wXKgU/lk8fKXMu/l5Hog1R61bL4q5GCdT6OJvdAFz1P+QrpoFuLU68eoKuVc4RbrTtNnTL5FByhWdLgOPSph+Q==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.4.1.tgz", + "integrity": "sha512-PeYE+4td5rKjoRPxztObrXU+H8hsjZfxKMXOcmrr34JerSyB/ROOxbbicz8B7A5j9R9VayDnVPvBmedqCsFCdw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "30.2.0", + "@jest/test-result": "30.4.1", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", + "jest-haste-map": "30.4.1", "slash": "^3.0.0" }, "engines": { @@ -2178,24 +3505,23 @@ } }, "node_modules/@jest/transform": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.2.0.tgz", - "integrity": "sha512-XsauDV82o5qXbhalKxD7p4TZYYdwcaEXC77PPD2HixEFF+6YGppjrAAQurTl2ECWcEomHBMMNS9AH3kcCFx8jA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.4.1.tgz", + "integrity": "sha512-Wz0LyktlTvRefoymh+n64hQ84KNXsRGcwdoZ8CSa0Ea+fgYcHZlnk+hDP7v2MS7il2bQ5uTEIxf4/NNfhMN4KQ==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", - "@jest/types": "30.2.0", + "@jest/types": "30.4.1", "@jridgewell/trace-mapping": "^0.3.25", "babel-plugin-istanbul": "^7.0.1", "chalk": "^4.1.2", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", - "jest-regex-util": "30.0.1", - "jest-util": "30.2.0", - "micromatch": "^4.0.8", + "jest-haste-map": "30.4.1", + "jest-regex-util": "30.4.0", + "jest-util": "30.4.1", "pirates": "^4.0.7", "slash": "^3.0.0", "write-file-atomic": "^5.0.1" @@ -2205,14 +3531,14 @@ } }, "node_modules/@jest/types": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz", - "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz", + "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.5", + "@jest/pattern": "30.4.0", + "@jest/schemas": "30.4.1", "@types/istanbul-lib-coverage": "^2.0.6", "@types/istanbul-reports": "^3.0.4", "@types/node": "*", @@ -2220,111 +3546,434 @@ "chalk": "^4.1.2" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.139.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz", + "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "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, + "license": "MIT", + "optional": true, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz", + "integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz", + "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz", + "integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz", + "integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz", + "integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz", + "integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz", + "integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz", + "integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz", + "integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz", + "integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz", + "integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==", + "cpu": [ + "x64" + ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz", + "integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz", + "integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==", + "cpu": [ + "wasm32" + ], "dev": true, "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.6" + }, "engines": { - "node": ">=6.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "tslib": "^2.4.0" } }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", - "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@tybys/wasm-util": "^0.10.0" + "tslib": "^2.4.0" } }, - "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/@rolldown/binding-win32-arm64-msvc": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz", + "integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=14" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@pkgr/core": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", - "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz", + "integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/pkgr" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@playwright/test": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz", - "integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==", + "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": "Apache-2.0", - "dependencies": { - "playwright": "1.57.0" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - } + "license": "MIT" }, "node_modules/@sinclair/typebox": { "version": "0.34.41", @@ -2344,19 +3993,26 @@ } }, "node_modules/@sinonjs/fake-timers": { - "version": "13.0.5", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", - "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", + "version": "15.4.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.4.0.tgz", + "integrity": "sha512-DsG+8/LscQIQg68J6Ef3dv10u6nVyetYn923s3/sus5eaGfTo1of5WMZSLf0UJc9KDuKPilPH0UDJCjvNbDNCA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { "@sinonjs/commons": "^3.0.1" } }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", "dev": true, "license": "MIT", "optional": true, @@ -2409,6 +4065,31 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@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/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "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", @@ -2482,16 +4163,16 @@ "license": "MIT" }, "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.3.tgz", + "integrity": "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==", "dev": true, "license": "ISC" }, "node_modules/@unrs/resolver-binding-android-arm-eabi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", - "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.12.2.tgz", + "integrity": "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==", "cpu": [ "arm" ], @@ -2503,9 +4184,9 @@ ] }, "node_modules/@unrs/resolver-binding-android-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", - "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.12.2.tgz", + "integrity": "sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==", "cpu": [ "arm64" ], @@ -2517,9 +4198,9 @@ ] }, "node_modules/@unrs/resolver-binding-darwin-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", - "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.12.2.tgz", + "integrity": "sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==", "cpu": [ "arm64" ], @@ -2531,9 +4212,9 @@ ] }, "node_modules/@unrs/resolver-binding-darwin-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", - "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.12.2.tgz", + "integrity": "sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==", "cpu": [ "x64" ], @@ -2545,9 +4226,9 @@ ] }, "node_modules/@unrs/resolver-binding-freebsd-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", - "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.12.2.tgz", + "integrity": "sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==", "cpu": [ "x64" ], @@ -2559,9 +4240,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", - "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.12.2.tgz", + "integrity": "sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==", "cpu": [ "arm" ], @@ -2573,9 +4254,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", - "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.12.2.tgz", + "integrity": "sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==", "cpu": [ "arm" ], @@ -2587,13 +4268,16 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", - "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.12.2.tgz", + "integrity": "sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2601,13 +4285,50 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", - "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.12.2.tgz", + "integrity": "sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-gnu/-/resolver-binding-linux-loong64-gnu-1.12.2.tgz", + "integrity": "sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-musl/-/resolver-binding-linux-loong64-musl-1.12.2.tgz", + "integrity": "sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2615,13 +4336,16 @@ ] }, "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", - "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.12.2.tgz", + "integrity": "sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==", "cpu": [ "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2629,13 +4353,16 @@ ] }, "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", - "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.12.2.tgz", + "integrity": "sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==", "cpu": [ "riscv64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2643,13 +4370,16 @@ ] }, "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", - "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.12.2.tgz", + "integrity": "sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==", "cpu": [ "riscv64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2657,13 +4387,16 @@ ] }, "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", - "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.12.2.tgz", + "integrity": "sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==", "cpu": [ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2671,13 +4404,16 @@ ] }, "node_modules/@unrs/resolver-binding-linux-x64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", - "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.12.2.tgz", + "integrity": "sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2685,23 +4421,40 @@ ] }, "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", - "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.12.2.tgz", + "integrity": "sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ "linux" ] }, + "node_modules/@unrs/resolver-binding-openharmony-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-openharmony-arm64/-/resolver-binding-openharmony-arm64-1.12.2.tgz", + "integrity": "sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, "node_modules/@unrs/resolver-binding-wasm32-wasi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", - "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.12.2.tgz", + "integrity": "sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==", "cpu": [ "wasm32" ], @@ -2709,16 +4462,18 @@ "license": "MIT", "optional": true, "dependencies": { - "@napi-rs/wasm-runtime": "^0.2.11" + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", - "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.12.2.tgz", + "integrity": "sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==", "cpu": [ "arm64" ], @@ -2730,9 +4485,9 @@ ] }, "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", - "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.12.2.tgz", + "integrity": "sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==", "cpu": [ "ia32" ], @@ -2744,18 +4499,203 @@ ] }, "node_modules/@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", - "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.12.2.tgz", + "integrity": "sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==", "cpu": [ "x64" ], "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@vitest/browser": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-4.1.10.tgz", + "integrity": "sha512-UDwuWGwXj646CBx/bQHOaJSX7np0I8JL/UKQYa1e4QrVHH8VdWtx8eaOuf8sy0ShwDgR6NjJAsp5eF6vjF6qng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@blazediff/core": "1.9.1", + "@vitest/mocker": "4.1.10", + "@vitest/utils": "4.1.10", + "magic-string": "^0.30.21", + "pngjs": "^7.0.0", + "sirv": "^3.0.2", + "tinyrainbow": "^3.1.0", + "ws": "^8.19.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": "4.1.10" + } + }, + "node_modules/@vitest/browser-playwright": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-4.1.10.tgz", + "integrity": "sha512-nMoXGEiRpT7m3W7NsbvrM2aKNwiNHZf+zEpUCvMteGjZFvfT96Q9fh7QyB98dvDWXiKvrLxA7bJ1mCOOv+JQPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/browser": "4.1.10", + "@vitest/mocker": "4.1.10", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "playwright": "*", + "vitest": "4.1.10" + }, + "peerDependenciesMeta": { + "playwright": { + "optional": false + } + } + }, + "node_modules/@vitest/coverage-istanbul": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/coverage-istanbul/-/coverage-istanbul-4.1.10.tgz", + "integrity": "sha512-AyNJ5pQRFqCX7pwB9PSTmoVKPaZ4H5IEVJfJsT+q1DYkXvZMEFYgJlyk5sfStmt9rVYRyYYRRsuBeImCOc39ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.29.0", + "@istanbuljs/schema": "^0.1.3", + "@jridgewell/gen-mapping": "^0.3.13", + "@jridgewell/trace-mapping": "0.3.31", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.2", + "obug": "^2.1.1", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": "4.1.10" + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", + "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", + "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.10", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", + "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz", + "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.10", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz", + "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.10", + "@vitest/utils": "4.1.10", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", + "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", + "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.10", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } }, "node_modules/ansi-escapes": { "version": "4.3.2", @@ -2816,6 +4756,19 @@ "node": ">= 8" } }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -2826,17 +4779,27 @@ "sprintf-js": "~1.0.2" } }, + "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": { + "node": ">=12" + } + }, "node_modules/babel-jest": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.2.0.tgz", - "integrity": "sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.4.1.tgz", + "integrity": "sha512-fATAbM8piYxkiXQp3RBXmZHxZVNJZAVXXfyeyCN2Tida3+qJ8ea9UxhiJ2y4fLO90ZImKt6k9FlcH2+rLkJGhw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/transform": "30.2.0", + "@jest/transform": "30.4.1", "@types/babel__core": "^7.20.5", "babel-plugin-istanbul": "^7.0.1", - "babel-preset-jest": "30.2.0", + "babel-preset-jest": "30.4.0", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "slash": "^3.0.0" @@ -2869,9 +4832,9 @@ } }, "node_modules/babel-plugin-jest-hoist": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.2.0.tgz", - "integrity": "sha512-ftzhzSGMUnOzcCXd6WHdBGMyuwy15Wnn0iyyWGKgBDLxf9/s5ABuraCSpBX2uG0jUg4rqJnxsLc5+oYBqoxVaA==", + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.4.0.tgz", + "integrity": "sha512-9EdtWM/sSfXLOGLwSn+GS6pIXyBnL07/8gyJlwFXjWy4DxMOyItqyUT29d4lQiS380EZwYlX7/At4PgBS+m2aA==", "dev": true, "license": "MIT", "dependencies": { @@ -2882,42 +4845,52 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", - "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==", + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", + "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.27.7", - "@babel/helper-define-polyfill-provider": "^0.6.5", + "@babel/compat-data": "^7.28.6", + "@babel/helper-define-polyfill-provider": "^0.6.8", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/babel-plugin-polyfill-corejs2/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/babel-plugin-polyfill-corejs3": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", - "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.2.tgz", + "integrity": "sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.5", - "core-js-compat": "^3.43.0" + "@babel/helper-define-polyfill-provider": "^0.6.8", + "core-js-compat": "^3.48.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz", - "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==", + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz", + "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.5" + "@babel/helper-define-polyfill-provider": "^0.6.8" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -2950,14 +4923,205 @@ "@babel/core": "^7.0.0 || ^8.0.0-0" } }, + "node_modules/babel-preset-current-node-syntax/node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-preset-current-node-syntax/node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-preset-current-node-syntax/node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-preset-current-node-syntax/node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-preset-current-node-syntax/node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-preset-current-node-syntax/node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-preset-current-node-syntax/node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-preset-current-node-syntax/node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-preset-current-node-syntax/node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-preset-current-node-syntax/node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-preset-current-node-syntax/node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-preset-current-node-syntax/node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-preset-current-node-syntax/node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-preset-current-node-syntax/node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/babel-preset-jest": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.2.0.tgz", - "integrity": "sha512-US4Z3NOieAQumwFnYdUWKvUKh8+YSnS/gB3t6YBiz0bskpu7Pine8pPCheNxlPEW4wnUkma2a94YuW2q3guvCQ==", + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.4.0.tgz", + "integrity": "sha512-lBY4jxsNmCnSiu7kquw8ZC9F4+XLMOKypT3RnNHPvU2Kpd4W0xaPuLr5ZkRyOsvLYAY4yaW1ZwTW4xB7NIiZzg==", "dev": true, "license": "MIT", "dependencies": { - "babel-plugin-jest-hoist": "30.2.0", + "babel-plugin-jest-hoist": "30.4.0", "babel-preset-current-node-syntax": "^1.2.0" }, "engines": { @@ -2975,42 +5139,32 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.26", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.26.tgz", - "integrity": "sha512-73lC1ugzwoaWCLJ1LvOgrR5xsMLTqSKIEoMHVtL9E/HNk0PXtTM76ZIm84856/SF7Nv8mPZxKoBsgpm0tR1u1Q==", + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.1.tgz", + "integrity": "sha512-HYXq73DDpCtNzOmrFsm9eSwCvWCql0RzqjpDzXN9EadiLJ4DNat0nsZ/Bzmy+Ud12mb4/zKDY0cQ805ZzN+i0A==", "dev": true, "license": "Apache-2.0", "bin": { - "baseline-browser-mapping": "dist/cli.js" + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" } }, "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/browserslist": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", - "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==", + "version": "4.28.7", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.7.tgz", + "integrity": "sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==", "dev": true, "funding": [ { @@ -3028,11 +5182,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.8.25", - "caniuse-lite": "^1.0.30001754", - "electron-to-chromium": "^1.5.249", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.1.4" + "baseline-browser-mapping": "^2.10.44", + "caniuse-lite": "^1.0.30001806", + "electron-to-chromium": "^1.5.393", + "node-releases": "^2.0.51", + "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" @@ -3079,9 +5233,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001754", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001754.tgz", - "integrity": "sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==", + "version": "1.0.30001806", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz", + "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==", "dev": true, "funding": [ { @@ -3099,6 +5253,16 @@ ], "license": "CC-BY-4.0" }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -3143,9 +5307,9 @@ } }, "node_modules/cjs-module-lexer": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.1.1.tgz", - "integrity": "sha512-+CmxIZ/L2vNcEfvNtLdU0ZQ6mbq3FZnwAP2PPTiKP+1QOoKwlKlPgb8UKV0Dds7QVaMnHm+FwSft2VB0s/SLjQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", + "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", "dev": true, "license": "MIT" }, @@ -3280,13 +5444,13 @@ "license": "MIT" }, "node_modules/core-js-compat": { - "version": "3.46.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.46.0.tgz", - "integrity": "sha512-p9hObIIEENxSV8xIu+V68JjSeARg6UVMG5mR+JEUguG3sI6MsiS1njz2jHmyJDvA+8jX/sytkBHup6kxhM9law==", + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.49.0.tgz", + "integrity": "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==", "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.26.3" + "browserslist": "^4.28.1" }, "funding": { "type": "opencollective", @@ -3327,9 +5491,9 @@ } }, "node_modules/dedent": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.0.tgz", - "integrity": "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", "dev": true, "license": "MIT", "peerDependencies": { @@ -3351,6 +5515,16 @@ "node": ">=0.10.0" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -3369,9 +5543,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.250", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.250.tgz", - "integrity": "sha512-/5UMj9IiGDMOFBnN4i7/Ry5onJrAGSbOGo3s9FEKmwobGq6xw832ccET0CE3CkkMBZ8GJSlUIesZofpyurqDXw==", + "version": "1.5.396", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.396.tgz", + "integrity": "sha512-yHiw2Y3C3H9U6TMbOfoWK/BPreiOPXRfTWPBwQBoZG6/8TB6eOPnsy5oaRYuatR7Fw2SJ4kKforgufeo7fq0EQ==", "dev": true, "license": "ISC" }, @@ -3405,6 +5579,23 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz", + "integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==", + "dev": true, + "license": "MIT" + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -3439,6 +5630,16 @@ "node": ">=4" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -3491,23 +5692,33 @@ } }, "node_modules/expect": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz", - "integrity": "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.4.1.tgz", + "integrity": "sha512-PMARsyh/JtqC20HoGqlFcIlQAyqUtW4PlI1rup1uhYJtKuwAjbvWi3GQMAn+STdHum/dk8xrKfUM1+5SAwpolA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/expect-utils": "30.2.0", + "@jest/expect-utils": "30.4.1", "@jest/get-type": "30.1.0", - "jest-matcher-utils": "30.2.0", - "jest-message-util": "30.2.0", - "jest-mock": "30.2.0", - "jest-util": "30.2.0" + "jest-matcher-utils": "30.4.1", + "jest-message-util": "30.4.1", + "jest-mock": "30.4.1", + "jest-util": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "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": { + "node": ">=12.0.0" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -3525,17 +5736,22 @@ "bser": "2.1.1" } }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, "engines": { - "node": ">=8" + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, "node_modules/find-up": { @@ -3648,6 +5864,7 @@ "version": "10.5.0", "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { @@ -3683,9 +5900,9 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "dev": true, "license": "MIT", "dependencies": { @@ -3769,13 +5986,13 @@ "license": "MIT" }, "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", "dev": true, "license": "MIT", "dependencies": { - "hasown": "^2.0.2" + "hasown": "^2.0.3" }, "engines": { "node": ">= 0.4" @@ -3804,16 +6021,6 @@ "node": ">=6" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -3861,19 +6068,6 @@ "node": ">=10" } }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/istanbul-lib-report": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", @@ -3935,16 +6129,16 @@ } }, "node_modules/jest": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-30.2.0.tgz", - "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.4.2.tgz", + "integrity": "sha512-Yi1jqNC/Oq0N4hBgNH/YvBpP1P57QqundgytzYqy3yqAa7NZPNjSoi4SGbRAXDMdBzNE6xBCi5U7RgfrvMEUVQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "30.2.0", - "@jest/types": "30.2.0", + "@jest/core": "30.4.2", + "@jest/types": "30.4.1", "import-local": "^3.2.0", - "jest-cli": "30.2.0" + "jest-cli": "30.4.2" }, "bin": { "jest": "bin/jest.js" @@ -3962,14 +6156,14 @@ } }, "node_modules/jest-changed-files": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.2.0.tgz", - "integrity": "sha512-L8lR1ChrRnSdfeOvTrwZMlnWV8G/LLjQ0nG9MBclwWZidA2N5FviRki0Bvh20WRMOX31/JYvzdqTJrk5oBdydQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.4.1.tgz", + "integrity": "sha512-IuctmYrxi21iOSOaIXpJWalHyPAsVv0GeBHKDn8C1CA4W5htHn7INL+wdnL4Bo0+olEndvAFkmb++tIQJG+vvg==", "dev": true, "license": "MIT", "dependencies": { "execa": "^5.1.1", - "jest-util": "30.2.0", + "jest-util": "30.4.1", "p-limit": "^3.1.0" }, "engines": { @@ -3977,29 +6171,29 @@ } }, "node_modules/jest-circus": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.2.0.tgz", - "integrity": "sha512-Fh0096NC3ZkFx05EP2OXCxJAREVxj1BcW/i6EWqqymcgYKWjyyDpral3fMxVcHXg6oZM7iULer9wGRFvfpl+Tg==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.4.2.tgz", + "integrity": "sha512-rvHH7VlY6LgbJXJTQ87GW62g1FntOtbhh0zT+v04kC+pgL6aBKyYINXxWukCpj3dcIBMw5/XUbtDS9dU9JTXeQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.2.0", - "@jest/expect": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/types": "30.2.0", + "@jest/environment": "30.4.1", + "@jest/expect": "30.4.1", + "@jest/test-result": "30.4.1", + "@jest/types": "30.4.1", "@types/node": "*", "chalk": "^4.1.2", "co": "^4.6.0", "dedent": "^1.6.0", "is-generator-fn": "^2.1.0", - "jest-each": "30.2.0", - "jest-matcher-utils": "30.2.0", - "jest-message-util": "30.2.0", - "jest-runtime": "30.2.0", - "jest-snapshot": "30.2.0", - "jest-util": "30.2.0", + "jest-each": "30.4.1", + "jest-matcher-utils": "30.4.1", + "jest-message-util": "30.4.1", + "jest-runtime": "30.4.2", + "jest-snapshot": "30.4.1", + "jest-util": "30.4.1", "p-limit": "^3.1.0", - "pretty-format": "30.2.0", + "pretty-format": "30.4.1", "pure-rand": "^7.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.6" @@ -4009,21 +6203,21 @@ } }, "node_modules/jest-cli": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.2.0.tgz", - "integrity": "sha512-Os9ukIvADX/A9sLt6Zse3+nmHtHaE6hqOsjQtNiugFTbKRHYIYtZXNGNK9NChseXy7djFPjndX1tL0sCTlfpAA==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.4.2.tgz", + "integrity": "sha512-jfA2ocvVHMXS2QijrJ0d31ektP+d/W0T5RpcTX2Pq+3sVqHlsXVCM2+FmwpL+bdY8OfHpIg9xMxLF17Zg0U49Q==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/types": "30.2.0", + "@jest/core": "30.4.2", + "@jest/test-result": "30.4.1", + "@jest/types": "30.4.1", "chalk": "^4.1.2", "exit-x": "^0.2.2", "import-local": "^3.2.0", - "jest-config": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", + "jest-config": "30.4.2", + "jest-util": "30.4.1", + "jest-validate": "30.4.1", "yargs": "^17.7.2" }, "bin": { @@ -4042,34 +6236,33 @@ } }, "node_modules/jest-config": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.2.0.tgz", - "integrity": "sha512-g4WkyzFQVWHtu6uqGmQR4CQxz/CH3yDSlhzXMWzNjDx843gYjReZnMRanjRCq5XZFuQrGDxgUaiYWE8BRfVckA==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.4.2.tgz", + "integrity": "sha512-rNHAShJQqQwFNoL0hbf3BphSBOWnpOUAKvidLS/AjNVLPfoj5mSf4jQMfW3cYOs6hXeZC7nF7mDHaBnbxELOzg==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", "@jest/get-type": "30.1.0", - "@jest/pattern": "30.0.1", - "@jest/test-sequencer": "30.2.0", - "@jest/types": "30.2.0", - "babel-jest": "30.2.0", + "@jest/pattern": "30.4.0", + "@jest/test-sequencer": "30.4.1", + "@jest/types": "30.4.1", + "babel-jest": "30.4.1", "chalk": "^4.1.2", "ci-info": "^4.2.0", "deepmerge": "^4.3.1", - "glob": "^10.3.10", + "glob": "^10.5.0", "graceful-fs": "^4.2.11", - "jest-circus": "30.2.0", - "jest-docblock": "30.2.0", - "jest-environment-node": "30.2.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.2.0", - "jest-runner": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", - "micromatch": "^4.0.8", + "jest-circus": "30.4.2", + "jest-docblock": "30.4.0", + "jest-environment-node": "30.4.1", + "jest-regex-util": "30.4.0", + "jest-resolve": "30.4.1", + "jest-runner": "30.4.2", + "jest-util": "30.4.1", + "jest-validate": "30.4.1", "parse-json": "^5.2.0", - "pretty-format": "30.2.0", + "pretty-format": "30.4.1", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, @@ -4094,25 +6287,25 @@ } }, "node_modules/jest-diff": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz", - "integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.4.1.tgz", + "integrity": "sha512-CRpFK0RtLriVDGcPPAnR6HMVI8bSR2jnUIgralhauzYQZIb4RH9AtEInTuQr65LmmGggGcRT6HIASxwqsVsmlA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/diff-sequences": "30.0.1", + "@jest/diff-sequences": "30.4.0", "@jest/get-type": "30.1.0", "chalk": "^4.1.2", - "pretty-format": "30.2.0" + "pretty-format": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-docblock": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz", - "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==", + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.4.0.tgz", + "integrity": "sha512-ZPMabUZCx5MpbZ2eBYSvZ0J8fvo3dR9oM+eeUpb3aKNQFuS2tu3Duw1TNlMoP8k3WQgKGJuhcMFvwcVuq6T7oA==", "dev": true, "license": "MIT", "dependencies": { @@ -4123,57 +6316,57 @@ } }, "node_modules/jest-each": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.2.0.tgz", - "integrity": "sha512-lpWlJlM7bCUf1mfmuqTA8+j2lNURW9eNafOy99knBM01i5CQeY5UH1vZjgT9071nDJac1M4XsbyI44oNOdhlDQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.4.1.tgz", + "integrity": "sha512-/8MJbH6fuj48TstjrMf+u/pd06Qezz5xOXvZA6442heNOWr8bdeoGZX2d9fCn028CoMgYmroH9//zky5GfyYmA==", "dev": true, "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0", - "@jest/types": "30.2.0", + "@jest/types": "30.4.1", "chalk": "^4.1.2", - "jest-util": "30.2.0", - "pretty-format": "30.2.0" + "jest-util": "30.4.1", + "pretty-format": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-environment-node": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.2.0.tgz", - "integrity": "sha512-ElU8v92QJ9UrYsKrxDIKCxu6PfNj4Hdcktcn0JX12zqNdqWHB0N+hwOnnBBXvjLd2vApZtuLUGs1QSY+MsXoNA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.4.1.tgz", + "integrity": "sha512-4FZYVOk85hz2AyT6BbarKy9u37g6DbrDyCdFhsnDdXqyrueYQvB+0zO4f/kqLCRD0BsPRXPMNJeQwihKZV8naw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.2.0", - "@jest/fake-timers": "30.2.0", - "@jest/types": "30.2.0", + "@jest/environment": "30.4.1", + "@jest/fake-timers": "30.4.1", + "@jest/types": "30.4.1", "@types/node": "*", - "jest-mock": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0" + "jest-mock": "30.4.1", + "jest-util": "30.4.1", + "jest-validate": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-haste-map": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.2.0.tgz", - "integrity": "sha512-sQA/jCb9kNt+neM0anSj6eZhLZUIhQgwDt7cPGjumgLM4rXsfb9kpnlacmvZz3Q5tb80nS+oG/if+NBKrHC+Xw==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.4.1.tgz", + "integrity": "sha512-rFrcONd8jeFsyw+Z9CrScJgglRf2+NFmNam8dKu7n+SoHqNYT47mn0DdEcVUZJpvh7Iz6/si7f7yUH7GJHVgnw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.2.0", + "@jest/types": "30.4.1", "@types/node": "*", "anymatch": "^3.1.3", "fb-watchman": "^2.0.2", "graceful-fs": "^4.2.11", - "jest-regex-util": "30.0.1", - "jest-util": "30.2.0", - "jest-worker": "30.2.0", - "micromatch": "^4.0.8", + "jest-regex-util": "30.4.0", + "jest-util": "30.4.1", + "jest-worker": "30.4.1", + "picomatch": "^4.0.3", "walker": "^1.0.8" }, "engines": { @@ -4184,49 +6377,50 @@ } }, "node_modules/jest-leak-detector": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.2.0.tgz", - "integrity": "sha512-M6jKAjyzjHG0SrQgwhgZGy9hFazcudwCNovY/9HPIicmNSBuockPSedAP9vlPK6ONFJ1zfyH/M2/YYJxOz5cdQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.4.1.tgz", + "integrity": "sha512-IpmyiioeHxiWDhesHnUFmOxcTzwCwKpgACgWajtAP+nYQXiY7DakTxB6Bx9JFiRMljr0AX1PvnQdaU1KFoz6NQ==", "dev": true, "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0", - "pretty-format": "30.2.0" + "pretty-format": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz", - "integrity": "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.4.1.tgz", + "integrity": "sha512-zvYfX5CaeEkFrrLS9suWe9rvJrm9J1Iv3ua8kIBv9GEPzcnsfBf0bob37la7s67fs0nlBC3EuvkOLnXQKxtx4A==", "dev": true, "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0", "chalk": "^4.1.2", - "jest-diff": "30.2.0", - "pretty-format": "30.2.0" + "jest-diff": "30.4.1", + "pretty-format": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-message-util": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.2.0.tgz", - "integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.4.1.tgz", + "integrity": "sha512-kwCKIvq0MCW1HzLoGola9Te6JUdzgV0loyKJ3Qghrkz9i5/RRIHsL95BMQc2HBBhlBKC4j22K9p11TGHH8RBpQ==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@jest/types": "30.2.0", + "@jest/types": "30.4.1", "@types/stack-utils": "^2.0.3", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", - "micromatch": "^4.0.8", - "pretty-format": "30.2.0", + "jest-util": "30.4.1", + "picomatch": "^4.0.3", + "pretty-format": "30.4.1", "slash": "^3.0.0", "stack-utils": "^2.0.6" }, @@ -4235,15 +6429,15 @@ } }, "node_modules/jest-mock": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.2.0.tgz", - "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.4.1.tgz", + "integrity": "sha512-/i8SVb8/NSB7RfNi8gfqu8gxLV23KaL5EpAttyb9iz8qWRIqXRLflycz/32wXsYkOnaUlx8NAKnJYtpsmXUmfw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.2.0", + "@jest/types": "30.4.1", "@types/node": "*", - "jest-util": "30.2.0" + "jest-util": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -4268,9 +6462,9 @@ } }, "node_modules/jest-regex-util": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", - "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz", + "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==", "dev": true, "license": "MIT", "engines": { @@ -4278,18 +6472,18 @@ } }, "node_modules/jest-resolve": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.2.0.tgz", - "integrity": "sha512-TCrHSxPlx3tBY3hWNtRQKbtgLhsXa1WmbJEqBlTBrGafd5fiQFByy2GNCEoGR+Tns8d15GaL9cxEzKOO3GEb2A==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.4.1.tgz", + "integrity": "sha512-Zry8Yq/yJcNAZ7dJ5F2heic8AheXvbFZ7XI5V+h28nrYZ7Qoyy4dItq8OodjnYD270mvX+ZudmrNV9cysqhW5Q==", "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.1.2", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", + "jest-haste-map": "30.4.1", "jest-pnp-resolver": "^1.2.3", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", + "jest-util": "30.4.1", + "jest-validate": "30.4.1", "slash": "^3.0.0", "unrs-resolver": "^1.7.11" }, @@ -4298,46 +6492,46 @@ } }, "node_modules/jest-resolve-dependencies": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.2.0.tgz", - "integrity": "sha512-xTOIGug/0RmIe3mmCqCT95yO0vj6JURrn1TKWlNbhiAefJRWINNPgwVkrVgt/YaerPzY3iItufd80v3lOrFJ2w==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.4.2.tgz", + "integrity": "sha512-gDiVh1I+GxYzz9oXlyw+1wv6VOYX1WYxMOfjsA3iGKePV2oxmbHhwxfkALxNxYy1ciw6APWwkW2zZONwP97aEQ==", "dev": true, "license": "MIT", "dependencies": { - "jest-regex-util": "30.0.1", - "jest-snapshot": "30.2.0" + "jest-regex-util": "30.4.0", + "jest-snapshot": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-runner": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.2.0.tgz", - "integrity": "sha512-PqvZ2B2XEyPEbclp+gV6KO/F1FIFSbIwewRgmROCMBo/aZ6J1w8Qypoj2pEOcg3G2HzLlaP6VUtvwCI8dM3oqQ==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.4.2.tgz", + "integrity": "sha512-2dw0PslVYXxffXGpLo+Ejad+KcI1Qkjn7f4X4619gf21oCUmL+SPfjqIa/losUem3yEOvfNZe/F1HWUcNpODcg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.2.0", - "@jest/environment": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", + "@jest/console": "30.4.1", + "@jest/environment": "30.4.1", + "@jest/test-result": "30.4.1", + "@jest/transform": "30.4.1", + "@jest/types": "30.4.1", "@types/node": "*", "chalk": "^4.1.2", "emittery": "^0.13.1", "exit-x": "^0.2.2", "graceful-fs": "^4.2.11", - "jest-docblock": "30.2.0", - "jest-environment-node": "30.2.0", - "jest-haste-map": "30.2.0", - "jest-leak-detector": "30.2.0", - "jest-message-util": "30.2.0", - "jest-resolve": "30.2.0", - "jest-runtime": "30.2.0", - "jest-util": "30.2.0", - "jest-watcher": "30.2.0", - "jest-worker": "30.2.0", + "jest-docblock": "30.4.0", + "jest-environment-node": "30.4.1", + "jest-haste-map": "30.4.1", + "jest-leak-detector": "30.4.1", + "jest-message-util": "30.4.1", + "jest-resolve": "30.4.1", + "jest-runtime": "30.4.2", + "jest-util": "30.4.1", + "jest-watcher": "30.4.1", + "jest-worker": "30.4.1", "p-limit": "^3.1.0", "source-map-support": "0.5.13" }, @@ -4346,32 +6540,32 @@ } }, "node_modules/jest-runtime": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.2.0.tgz", - "integrity": "sha512-p1+GVX/PJqTucvsmERPMgCPvQJpFt4hFbM+VN3n8TMo47decMUcJbt+rgzwrEme0MQUA/R+1de2axftTHkKckg==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.4.2.tgz", + "integrity": "sha512-3/5e8iPz2k/VLqlr8DgTftYyLUv8Su3FkCAO2/Od81UsUTpSxOrS6O5x5KkoQwyUjmpYyDJKeyAvg2T2nvpNkQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.2.0", - "@jest/fake-timers": "30.2.0", - "@jest/globals": "30.2.0", + "@jest/environment": "30.4.1", + "@jest/fake-timers": "30.4.1", + "@jest/globals": "30.4.1", "@jest/source-map": "30.0.1", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", + "@jest/test-result": "30.4.1", + "@jest/transform": "30.4.1", + "@jest/types": "30.4.1", "@types/node": "*", "chalk": "^4.1.2", "cjs-module-lexer": "^2.1.0", "collect-v8-coverage": "^1.0.2", - "glob": "^10.3.10", + "glob": "^10.5.0", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", - "jest-message-util": "30.2.0", - "jest-mock": "30.2.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.2.0", - "jest-snapshot": "30.2.0", - "jest-util": "30.2.0", + "jest-haste-map": "30.4.1", + "jest-message-util": "30.4.1", + "jest-mock": "30.4.1", + "jest-regex-util": "30.4.0", + "jest-resolve": "30.4.1", + "jest-snapshot": "30.4.1", + "jest-util": "30.4.1", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, @@ -4380,9 +6574,9 @@ } }, "node_modules/jest-snapshot": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.2.0.tgz", - "integrity": "sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.4.1.tgz", + "integrity": "sha512-tEOkkfOMppUyeiHwjZswOQ3lcnoTnws/q5FnGIaeIh/jmoU0ZlgMYRR8sTlTj+nNGCoJ0RDq6SfxGxCsyMTPmw==", "dev": true, "license": "MIT", "dependencies": { @@ -4391,20 +6585,20 @@ "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/types": "^7.27.3", - "@jest/expect-utils": "30.2.0", + "@jest/expect-utils": "30.4.1", "@jest/get-type": "30.1.0", - "@jest/snapshot-utils": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", + "@jest/snapshot-utils": "30.4.1", + "@jest/transform": "30.4.1", + "@jest/types": "30.4.1", "babel-preset-current-node-syntax": "^1.2.0", "chalk": "^4.1.2", - "expect": "30.2.0", + "expect": "30.4.1", "graceful-fs": "^4.2.11", - "jest-diff": "30.2.0", - "jest-matcher-utils": "30.2.0", - "jest-message-util": "30.2.0", - "jest-util": "30.2.0", - "pretty-format": "30.2.0", + "jest-diff": "30.4.1", + "jest-matcher-utils": "30.4.1", + "jest-message-util": "30.4.1", + "jest-util": "30.4.1", + "pretty-format": "30.4.1", "semver": "^7.7.2", "synckit": "^0.11.8" }, @@ -4412,63 +6606,54 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "node_modules/jest-snapshot/node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" }, "engines": { - "node": ">=10" + "node": ">=6.9.0" } }, "node_modules/jest-util": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz", - "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.4.1.tgz", + "integrity": "sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.2.0", + "@jest/types": "30.4.1", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", "graceful-fs": "^4.2.11", - "picomatch": "^4.0.2" + "picomatch": "^4.0.3" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-util/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/jest-validate": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.2.0.tgz", - "integrity": "sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.4.1.tgz", + "integrity": "sha512-PDWi4SOwLnwqNDfHZjOcsEFyZ4fc/2W2gVL3DEoyqnB6jCQMLRtfBong8s6omIw3lI0HWOus12xfnFmQtjW3fw==", "dev": true, "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0", - "@jest/types": "30.2.0", + "@jest/types": "30.4.1", "camelcase": "^6.3.0", "chalk": "^4.1.2", "leven": "^3.1.0", - "pretty-format": "30.2.0" + "pretty-format": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -4488,19 +6673,19 @@ } }, "node_modules/jest-watcher": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.2.0.tgz", - "integrity": "sha512-PYxa28dxJ9g777pGm/7PrbnMeA0Jr7osHP9bS7eJy9DuAjMgdGtxgf0uKMyoIsTWAkIbUW5hSDdJ3urmgXBqxg==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.4.1.tgz", + "integrity": "sha512-/l9UonmvCwjHH7d2h3iAwIloLc1H0S8mJZ/LNK3i86hqwPAz8otUJjP9MfYtz9Tt77Su5FD2xGjZn8d31IZHlw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "30.2.0", - "@jest/types": "30.2.0", + "@jest/test-result": "30.4.1", + "@jest/types": "30.4.1", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", "emittery": "^0.13.1", - "jest-util": "30.2.0", + "jest-util": "30.4.1", "string-length": "^4.0.2" }, "engines": { @@ -4508,15 +6693,15 @@ } }, "node_modules/jest-worker": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.2.0.tgz", - "integrity": "sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.4.1.tgz", + "integrity": "sha512-SHynN/q/QD++iNyvMdy+WMmbCGk8jIsNcRxycXbWubSOhvo6T+j2afcfUSl+3hYsiBebOTo0cT7c2H7CXugu1g==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", "@ungap/structured-clone": "^1.3.0", - "jest-util": "30.2.0", + "jest-util": "30.4.1", "merge-stream": "^2.0.0", "supports-color": "^8.1.1" }, @@ -4548,9 +6733,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.0.tgz", + "integrity": "sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog==", "dev": true, "license": "MIT", "dependencies": { @@ -4604,6 +6789,279 @@ "node": ">=6" } }, + "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" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "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" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "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" + ], + "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" + } + }, + "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" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "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" + ], + "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" + } + }, + "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" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "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" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -4641,6 +7099,28 @@ "yallist": "^3.0.2" } }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.3.tgz", + "integrity": "sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.3", + "@babel/types": "^7.29.0", + "source-map-js": "^1.2.1" + } + }, "node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", @@ -4657,19 +7137,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/make-dir/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -4680,26 +7147,12 @@ "tmpl": "1.0.5" } }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" }, "node_modules/mimic-fn": { "version": "2.1.0", @@ -4712,13 +7165,13 @@ } }, "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -4728,15 +7181,25 @@ } }, "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -4744,6 +7207,25 @@ "dev": true, "license": "MIT" }, + "node_modules/nanoid": { + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/napi-postinstall": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", @@ -4775,11 +7257,14 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "version": "2.0.51", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/normalize-path": { "version": "3.0.0", @@ -4804,6 +7289,20 @@ "node": ">=8" } }, + "node_modules/obug": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.3.tgz", + "integrity": "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -4972,6 +7471,13 @@ "dev": true, "license": "ISC" }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -4980,13 +7486,13 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, "license": "MIT", "engines": { - "node": ">=8.6" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" @@ -5016,13 +7522,13 @@ } }, "node_modules/playwright": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", - "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.1.tgz", + "integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.57.0" + "playwright-core": "1.61.1" }, "bin": { "playwright": "cli.js" @@ -5035,9 +7541,9 @@ } }, "node_modules/playwright-core": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz", - "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==", + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz", + "integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==", "dev": true, "license": "Apache-2.0", "bin": { @@ -5062,16 +7568,56 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/pngjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", + "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.19.0" + } + }, + "node_modules/postcss": { + "version": "8.5.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz", + "integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.16", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/pretty-format": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "30.0.5", + "@jest/schemas": "30.4.1", "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -5107,13 +7653,22 @@ ], "license": "MIT" }, - "node_modules/react-is": { + "node_modules/react-is-18": { + "name": "react-is", "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, "license": "MIT" }, + "node_modules/react-is-19": { + "name": "react-is", + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.8.tgz", + "integrity": "sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ==", + "dev": true, + "license": "MIT" + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -5160,9 +7715,9 @@ "license": "MIT" }, "node_modules/regjsparser": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz", - "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==", + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.2.tgz", + "integrity": "sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -5183,12 +7738,13 @@ } }, "node_modules/resolve": { - "version": "1.22.11", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", "dev": true, "license": "MIT", "dependencies": { + "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" @@ -5226,14 +7782,51 @@ "node": ">=8" } }, + "node_modules/rolldown": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz", + "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.139.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.1.5", + "@rolldown/binding-darwin-arm64": "1.1.5", + "@rolldown/binding-darwin-x64": "1.1.5", + "@rolldown/binding-freebsd-x64": "1.1.5", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", + "@rolldown/binding-linux-arm64-gnu": "1.1.5", + "@rolldown/binding-linux-arm64-musl": "1.1.5", + "@rolldown/binding-linux-ppc64-gnu": "1.1.5", + "@rolldown/binding-linux-s390x-gnu": "1.1.5", + "@rolldown/binding-linux-x64-gnu": "1.1.5", + "@rolldown/binding-linux-x64-musl": "1.1.5", + "@rolldown/binding-openharmony-arm64": "1.1.5", + "@rolldown/binding-wasm32-wasi": "1.1.5", + "@rolldown/binding-win32-arm64-msvc": "1.1.5", + "@rolldown/binding-win32-x64-msvc": "1.1.5" + } + }, "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/shebang-command": { @@ -5259,6 +7852,13 @@ "node": ">=8" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -5272,6 +7872,21 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/sirv": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -5292,6 +7907,16 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-support": { "version": "0.5.13", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", @@ -5323,6 +7948,20 @@ "node": ">=10" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", + "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", + "dev": true, + "license": "MIT" + }, "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -5425,13 +8064,13 @@ } }, "node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" @@ -5524,13 +8163,13 @@ } }, "node_modules/synckit": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", - "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.13.tgz", + "integrity": "sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg==", "dev": true, "license": "MIT", "dependencies": { - "@pkgr/core": "^0.2.9" + "@pkgr/core": "^0.3.6" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -5555,9 +8194,9 @@ } }, "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", "dev": true, "license": "MIT", "dependencies": { @@ -5588,9 +8227,9 @@ } }, "node_modules/test-exclude/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -5600,6 +8239,50 @@ "node": "*" } }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", + "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -5607,17 +8290,14 @@ "dev": true, "license": "BSD-3-Clause" }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", "dev": true, "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, "engines": { - "node": ">=8.0" + "node": ">=6" } }, "node_modules/tslib": { @@ -5717,44 +8397,47 @@ } }, "node_modules/unrs-resolver": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", - "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.12.2.tgz", + "integrity": "sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==", "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { - "napi-postinstall": "^0.3.0" + "napi-postinstall": "^0.3.4" }, "funding": { "url": "https://opencollective.com/unrs-resolver" }, "optionalDependencies": { - "@unrs/resolver-binding-android-arm-eabi": "1.11.1", - "@unrs/resolver-binding-android-arm64": "1.11.1", - "@unrs/resolver-binding-darwin-arm64": "1.11.1", - "@unrs/resolver-binding-darwin-x64": "1.11.1", - "@unrs/resolver-binding-freebsd-x64": "1.11.1", - "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", - "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", - "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", - "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", - "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", - "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-x64-musl": "1.11.1", - "@unrs/resolver-binding-wasm32-wasi": "1.11.1", - "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", - "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", - "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" + "@unrs/resolver-binding-android-arm-eabi": "1.12.2", + "@unrs/resolver-binding-android-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-x64": "1.12.2", + "@unrs/resolver-binding-freebsd-x64": "1.12.2", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-arm64-musl": "1.12.2", + "@unrs/resolver-binding-linux-loong64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-loong64-musl": "1.12.2", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-musl": "1.12.2", + "@unrs/resolver-binding-linux-s390x-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-musl": "1.12.2", + "@unrs/resolver-binding-openharmony-arm64": "1.12.2", + "@unrs/resolver-binding-wasm32-wasi": "1.12.2", + "@unrs/resolver-binding-win32-arm64-msvc": "1.12.2", + "@unrs/resolver-binding-win32-ia32-msvc": "1.12.2", + "@unrs/resolver-binding-win32-x64-msvc": "1.12.2" } }, "node_modules/update-browserslist-db": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", - "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "dev": true, "funding": [ { @@ -5797,6 +8480,174 @@ "node": ">=10.12.0" } }, + "node_modules/vite": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.5.tgz", + "integrity": "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.17", + "rolldown": "~1.1.5", + "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.3.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/vitest": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz", + "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.10", + "@vitest/mocker": "4.1.10", + "@vitest/pretty-format": "4.1.10", + "@vitest/runner": "4.1.10", + "@vitest/snapshot": "4.1.10", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.10", + "@vitest/browser-preview": "4.1.10", + "@vitest/browser-webdriverio": "4.1.10", + "@vitest/coverage-istanbul": "4.1.10", + "@vitest/coverage-v8": "4.1.10", + "@vitest/ui": "4.1.10", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -5823,6 +8674,23 @@ "node": ">= 8" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", @@ -5939,6 +8807,28 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/ws": { + "version": "8.21.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz", + "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -5957,9 +8847,9 @@ "license": "ISC" }, "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", "dev": true, "license": "MIT", "dependencies": { @@ -6044,4 +8934,4 @@ } } } -} \ No newline at end of file +} diff --git a/tests/package.json b/tests/package.json index 13fac5cc..8347ab22 100644 --- a/tests/package.json +++ b/tests/package.json @@ -5,21 +5,26 @@ "description": "Test suite for vscroll", "type": "module", "scripts": { - "test:unit": "jest", - "test:e2e": "playwright test --config=e2e/playwright.config.ts", - "test:e2e:ui": "playwright test --config=e2e/playwright.config.ts --ui", - "test:e2e:debug": "playwright test --config=e2e/playwright.config.ts --debug", + "test:unit": "jest --runInBand", + "test:unit:coverage": "jest --runInBand --coverage", + "test:e2e": "vitest run --config=e2e/vitest.config.ts", + "test:e2e:coverage": "vitest run --config=e2e/vitest.config.ts --coverage.enabled", + "test:e2e:ui": "vitest --config=e2e/vitest.config.ts --browser.headless=false", "test": "npm run test:unit && npm run test:e2e", + "test:coverage": "npm run test:unit:coverage && npm run test:e2e:coverage", "watch": "jest --watch" }, "devDependencies": { - "@babel/core": "^7.28.5", - "@babel/preset-env": "^7.28.5", - "@babel/preset-typescript": "^7.28.5", - "@playwright/test": "^1.57.0", + "@babel/core": "^7.29.7", + "@babel/preset-env": "^7.29.7", + "@babel/preset-typescript": "^7.29.7", "@types/jest": "^30.0.0", - "babel-jest": "^30.2.0", - "jest": "^30.2.0", - "typescript": "^5.9.3" + "@vitest/browser-playwright": "^4.1.10", + "@vitest/coverage-istanbul": "^4.1.10", + "babel-jest": "^30.4.1", + "jest": "^30.4.2", + "playwright": "^1.61.1", + "typescript": "^5.9.3", + "vitest": "^4.1.10" } -} \ No newline at end of file +} diff --git a/tests/unit/adapter.spec.ts b/tests/unit/adapter.spec.ts index 98779c83..6245cf11 100644 --- a/tests/unit/adapter.spec.ts +++ b/tests/unit/adapter.spec.ts @@ -1,7 +1,9 @@ import { Scroller } from '../../src/scroller'; import { AdapterPropName } from '../../src/classes/adapter/props'; -import { Datasource } from '../../src/classes/datasource'; +import { Datasource, makeDatasource } from '../../src/classes/datasource'; +import { Reactive } from '../../src/classes/reactive'; import { wantedUtils } from '../../src/classes/adapter/wanted'; +import type { AdapterMethodResult, ProcessSubject } from '../../src/interfaces'; import version from '../../src/version'; const MOCK = { @@ -19,6 +21,60 @@ const MOCK = { } }; +const createDatasource = () => + new Datasource({ + get: (_index: number, _count: number) => Promise.resolve([]) + }); + +const disposedResult: AdapterMethodResult = { + immediate: false, + success: false, + details: 'Adapter was disposed' +}; + +const createHost = () => { + const adapterRun$ = new Reactive(); + const datasource = createDatasource(); + const scroller = new Scroller({ + datasource, + element: MOCK.element, + workflow: { + onDataChanged: () => null, + call: process => adapterRun$.set(process) + } + }); + scroller.viewport.reset = () => null; + scroller.init(adapterRun$); + scroller.state.cycle.busy.set(true); + + let disposed = false; + return { + adapter: datasource.adapter, + dispose: () => { + if (disposed) { + return; + } + disposed = true; + adapterRun$.dispose(); + scroller.dispose(true); + datasource.dispose(); + } + }; +}; + +const expectSettled = ( + promise: Promise +): Promise => + Promise.race([ + promise, + new Promise((_resolve, reject) => + setTimeout( + () => reject(new Error('Adapter promise did not settle on dispose')), + 0 + ) + ) + ]); + describe('Adapter Init Spec', () => { const ds = new Datasource(MOCK.datasource); @@ -33,6 +89,100 @@ describe('Adapter Init Spec', () => { }); }); +describe('Adapter Custom Reactive Source Spec', () => { + it('should preserve the source and forward values across recreation', () => { + const source = {}; + const values: unknown[] = []; + const CustomDatasource = makeDatasource(() => ({ + mock: false, + reactive: { + [AdapterPropName.isLoading$]: { + source, + emit: (target, value) => { + expect(target).toBe(source); + values.push(value); + } + } + } + })); + const datasource = new CustomDatasource(MOCK.datasource); + const createScroller = () => { + const scroller = new Scroller({ + datasource, + element: MOCK.element, + workflow: MOCK.workflow + }); + scroller.viewport.reset = () => null; + scroller.init(); + return scroller; + }; + let scroller = createScroller(); + + try { + expect(datasource.adapter.isLoading$).toBe(source); + scroller.state.cycle.busy.set(true); + scroller.dispose(true); + + scroller = createScroller(); + expect(datasource.adapter.isLoading$).toBe(source); + scroller.state.cycle.busy.set(true); + + expect(values).toEqual([true, false, true]); + } finally { + scroller.dispose(true); + datasource.dispose(); + } + }); +}); + +describe('Adapter Lifecycle Spec', () => { + describe('before initialization', () => { + test.each(['relax', 'reload', 'reset', 'check', 'clip'] as const)( + '%s resolves immediately', + async method => { + const datasource = createDatasource(); + try { + await expect(datasource.adapter[method]()).resolves.toEqual({ + immediate: true, + success: true, + details: 'Adapter is not initialized' + }); + } finally { + datasource.dispose(); + } + } + ); + }); + + describe('on dispose', () => { + it('settles a pending relax without running its callback', async () => { + const host = createHost(); + const callback = jest.fn(); + try { + const result = host.adapter.relax(callback); + host.dispose(); + + await expect(expectSettled(result)).resolves.toEqual(disposedResult); + expect(callback).not.toHaveBeenCalled(); + } finally { + host.dispose(); + } + }); + + it('settles a pending workflow method', async () => { + const host = createHost(); + try { + const result = host.adapter.check(); + host.dispose(); + + await expect(expectSettled(result)).resolves.toEqual(disposedResult); + } finally { + host.dispose(); + } + }); + }); +}); + describe('Adapter Wanted Spec', () => { let scroller; diff --git a/tests/unit/cache.spec.ts b/tests/unit/cache.spec.ts index 101a9ef0..33bfd7e4 100644 --- a/tests/unit/cache.spec.ts +++ b/tests/unit/cache.spec.ts @@ -660,6 +660,17 @@ describe('Cache Spec', () => { Array.from({ length: 10 }).map((j, i) => ({ [100 + i]: 20 })) )); + it('should keep frequent size on a tie regardless of insertion order', () => + checkDefaultSize({ + sizeStrategy: SizeStrategy.Frequent, + cacheSize: 3, + itemSize: 12, + setItemSize: item => (item.size = item.$index === 1 ? 20 : 12), + updateCache: cache => + cache.add({ $index: 4, size: 20, data: {} } as Item), + before: 12 + })); + it('should change frequent size on add (new, more)', () => checkFrequentOnAdd( Array.from({ length: 11 }).map((j, i) => ({ [100 + i]: 20 })), diff --git a/tests/unit/datasource-bad.spec.ts b/tests/unit/datasource-bad.spec.ts index 8c54c7c5..6638879c 100644 --- a/tests/unit/datasource-bad.spec.ts +++ b/tests/unit/datasource-bad.spec.ts @@ -1,14 +1,10 @@ import { INVALID_DATASOURCE_PREFIX, Scroller } from '../../src/scroller'; describe('Wrong Datasource on Scroller instantiation', () => { - const checkScrollerError = (datasource, error) => { - try { - new Scroller({ datasource }); - } catch (e) { - expect(e.message).toContain(INVALID_DATASOURCE_PREFIX); - expect(e.message).toContain(error); - } - }; + const checkScrollerError = (datasource, error) => + expect(() => new Scroller({ datasource })).toThrow( + `${INVALID_DATASOURCE_PREFIX} ${error}` + ); it('should throw "get" must be set', () => [void 0, null, 'wrong', {}, { get: void 0 }].forEach(ds => diff --git a/tests/unit/fetch.spec.ts b/tests/unit/fetch.spec.ts new file mode 100644 index 00000000..e0030957 --- /dev/null +++ b/tests/unit/fetch.spec.ts @@ -0,0 +1,117 @@ +import { Scroller } from '../../src/scroller'; +import Fetch from '../../src/processes/fetch'; +import { CommonProcess, ProcessStatus } from '../../src/processes/misc'; + +type Success = (data: unknown[]) => void; +type Failure = (error: unknown) => void; +type Get = ( + index: number, + count: number, + success: Success, + fail: Failure +) => unknown; + +interface ErrorSource { + get: Get; + release?: () => void; +} + +const error = 0; + +const sources: Array<[string, () => ErrorSource]> = [ + [ + 'synchronous callback', + () => ({ + get: (_index, _count, _success, fail) => fail(error) + }) + ], + [ + 'asynchronous callback', + () => { + let fail: Failure; + return { + get: (_index, _count, _success, onError) => { + fail = onError; + }, + release: () => fail(error) + }; + } + ], + [ + 'rejected Promise', + () => ({ + get: () => Promise.reject(error) + }) + ], + [ + 'synchronous Observable', + () => ({ + get: () => ({ + subscribe: (_success: Success, fail: Failure) => { + fail(error); + return { unsubscribe: () => null }; + } + }) + }) + ], + [ + 'asynchronous Observable', + () => { + let fail: Failure; + return { + get: () => ({ + subscribe: (_success: Success, onError: Failure) => { + fail = onError; + return { unsubscribe: () => null }; + } + }), + release: () => fail(error) + }; + } + ] +]; + +const createScroller = (get: Get) => { + const call = jest.fn(); + const scroller = { + datasource: { get }, + workflow: { call }, + logger: { log: jest.fn() }, + state: { + fetch: { + index: 1, + count: 2, + cancel: null + }, + scroll: { + positionBeforeAsync: null + } + }, + viewport: { + scrollPosition: 0 + } + } as unknown as Scroller; + + return { call, scroller }; +}; + +describe('Datasource errors', () => { + test.each(sources)( + '%s produces one Fetch error', + async (_title, createSource) => { + const source = createSource(); + const { call, scroller } = createScroller(source.get); + + Fetch.run(scroller); + source.release?.(); + await Promise.resolve(); + + expect(call).toHaveBeenCalledTimes(1); + expect(call).toHaveBeenCalledWith({ + process: CommonProcess.fetch, + status: ProcessStatus.error, + payload: { error } + }); + } + ); +}); diff --git a/tests/unit/reactive.spec.ts b/tests/unit/reactive.spec.ts index 1b15351d..881e03f6 100644 --- a/tests/unit/reactive.spec.ts +++ b/tests/unit/reactive.spec.ts @@ -64,6 +64,20 @@ describe('Reactive', () => { expect($.get()).toEqual(VALUES[VALUES.length - 1]); expect(call).toEqual(1); }); + + it('should emit once when subscription emits immediately', () => { + const values: string[] = []; + const $ = new Reactive(VALUES[0], { emitOnSubscribe: true }); + + $.once(value => { + values.push(value); + $.set(VALUES[1]); + }); + $.set(VALUES[2]); + + expect(values).toEqual([VALUES[0]]); + expect($.get()).toBe(VALUES[2]); + }); }); describe('emitOnSubscribe', () => {