diff --git a/.github/workflows/ci_framework-majors.yml b/.github/workflows/ci_framework-majors.yml new file mode 100644 index 0000000..606c288 --- /dev/null +++ b/.github/workflows/ci_framework-majors.yml @@ -0,0 +1,61 @@ +name: '🔬 CI — Framework Majors' + +on: + push: + branches: + - main + paths: + - '.github/workflows/ci_framework-majors.yml' + - 'src/**' + - 'tests/**' + - 'run.test.ts' + - 'package-lock.json' + - 'package.json' + pull_request: + paths: + - '.github/workflows/ci_framework-majors.yml' + - 'src/**' + - 'tests/**' + - 'run.test.ts' + - 'package-lock.json' + - 'package.json' + workflow_dispatch: + +jobs: + framework-majors: + runs-on: ubuntu-latest + timeout-minutes: 20 + strategy: + fail-fast: false + matrix: + react-major: ['18', '19'] + name: React ${{ matrix.react-major }} + steps: + - name: ➕ Actions - Checkout + uses: actions/checkout@v4 + + - name: ➕ Actions - Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: ➕ Cache dependencies + uses: actions/cache@v4 + with: + path: ~/.npm + key: npm-linux-${{ hashFiles('package-lock.json') }} + restore-keys: npm-linux- + + - name: 📦 Installing Dependencies + run: npm ci + + - name: 🔁 Pin React ${{ matrix.react-major }} + run: | + npm install --no-save \ + react@${{ matrix.react-major }} \ + react-dom@${{ matrix.react-major }} \ + @types/react@${{ matrix.react-major }} \ + @types/react-dom@${{ matrix.react-major }} + + - name: 🔬 React ${{ matrix.react-major }} + run: npm test \ No newline at end of file diff --git a/README.md b/README.md index 0732aa4..0ad27b3 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,35 @@ test('renders a heading', () => { ## Compatibility +### Library Support + +| Package | Supported range | +| ------- | :-------------: | +| `react` | `>=18` | +| `react-dom` | `>=18` | +| `poku` | `>=4.1.0` | +| `happy-dom` | `>=20` | +| `jsdom` | `>=22` | + +### Isolation Support + +| Isolation mode | Node validation | +| -------------- | :-------------: | +| `none` | ✅ | +| `process` | ✅ | + +Shared-process runs rely on scoped cleanup slots from `@pokujs/dom`, so one test's `cleanup()` no longer tears down another concurrent test's mounted tree. + +### Multi-Major Suite + +Use this suite to verify React major compatibility locally: + +```bash +npm run test:multi-major +``` + +It executes the full adapter tests twice, pinning React 18 and React 19 in sequence. + ### Runtime × DOM Adapter | | Node.js ≥ 20 | Bun ≥ 1 | Deno ≥ 2 | diff --git a/deno.lock b/deno.lock index 41c7116..87d2076 100644 --- a/deno.lock +++ b/deno.lock @@ -3,10 +3,9 @@ "specifiers": { "npm:@happy-dom/global-registrator@^20.8.9": "20.8.9", "npm:@ianvs/prettier-plugin-sort-imports@^4.7.0": "4.7.1_prettier@3.8.1", - "npm:@pokujs/dom@^1.1.2": "1.1.2_happy-dom@20.8.9_jsdom@26.1.0_poku@4.2.0", + "npm:@pokujs/dom@^1.2.0": "1.2.0_happy-dom@20.8.9_jsdom@26.1.0_poku@4.2.0", "npm:@testing-library/dom@^10.4.1": "10.4.1", "npm:@types/jsdom@^28.0.1": "28.0.1", - "npm:@types/node@*": "22.15.15", "npm:@types/node@^25.5.0": "25.5.2", "npm:@types/react-dom@^19.2.3": "19.2.3_@types+react@19.2.14", "npm:@types/react@^19.2.14": "19.2.14", @@ -14,10 +13,9 @@ "npm:happy-dom@^20.8.9": "20.8.9", "npm:jsdom@^26.1.0": "26.1.0", "npm:poku@*": "4.2.0", - "npm:poku@4.2.0": "4.2.0", "npm:prettier@^3.6.2": "3.8.1", - "npm:react-dom@^19.2.4": "19.2.4_react@19.2.4", - "npm:react@^19.2.4": "19.2.4", + "npm:react-dom@^19.2.5": "19.2.5_react@19.2.5", + "npm:react@^19.2.5": "19.2.5", "npm:rimraf@^6.0.1": "6.1.3", "npm:tsup@^8.5.0": "8.5.1_typescript@6.0.2_esbuild@0.27.7_tsx@4.21.0", "npm:tsx@^4.21.0": "4.21.0", @@ -297,8 +295,8 @@ "@jridgewell/sourcemap-codec" ] }, - "@pokujs/dom@1.1.2_happy-dom@20.8.9_jsdom@26.1.0_poku@4.2.0": { - "integrity": "sha512-Bs9blOGDABsgNdKBoMt7EoB0attpI83VF+LCfc2qU1BVFJQ6bRWFqniNIFWZAJOfT+qlyRcxIITmpQHYlRmLew==", + "@pokujs/dom@1.2.0_happy-dom@20.8.9_jsdom@26.1.0_poku@4.2.0": { + "integrity": "sha512-RafJKjW+7skIPF6dl2GLV7nMnvPJzIkWd6nNehJOq3m82OahEDWWBfeWvX/PumZj9liq11/JCMsifUvywfJhAQ==", "dependencies": [ "@testing-library/dom", "happy-dom", @@ -884,8 +882,8 @@ "punycode@2.3.1": { "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==" }, - "react-dom@19.2.4_react@19.2.4": { - "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "react-dom@19.2.5_react@19.2.5": { + "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==", "dependencies": [ "react", "scheduler" @@ -894,8 +892,8 @@ "react-is@17.0.2": { "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, - "react@19.2.4": { - "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==" + "react@19.2.5": { + "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==" }, "readdirp@4.1.2": { "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==" @@ -1149,13 +1147,12 @@ }, "workspace": { "dependencies": [ - "npm:@pokujs/dom@^1.1.2" + "npm:@pokujs/dom@^1.2.0" ], "packageJson": { "dependencies": [ "npm:@happy-dom/global-registrator@^20.8.9", "npm:@ianvs/prettier-plugin-sort-imports@^4.7.0", - "npm:@pokujs/dom@^1.1.2", "npm:@testing-library/dom@^10.4.1", "npm:@types/jsdom@^28.0.1", "npm:@types/node@^25.5.0", @@ -1164,10 +1161,9 @@ "npm:cross-env@^10.1.0", "npm:happy-dom@^20.8.9", "npm:jsdom@^26.1.0", - "npm:poku@4.2.0", "npm:prettier@^3.6.2", - "npm:react-dom@^19.2.4", - "npm:react@^19.2.4", + "npm:react-dom@^19.2.5", + "npm:react@^19.2.5", "npm:rimraf@^6.0.1", "npm:tsup@^8.5.0", "npm:tsx@^4.21.0", diff --git a/package-lock.json b/package-lock.json index 218f59e..00c634d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "devDependencies": { "@happy-dom/global-registrator": "^20.8.9", "@ianvs/prettier-plugin-sort-imports": "^4.7.0", - "@pokujs/dom": "^1.2.0", + "@pokujs/dom": "^1.3.0", "@types/jsdom": "^28.0.1", "@types/node": "^25.5.0", "@types/react": "^19.2.14", @@ -22,10 +22,10 @@ "cross-env": "^10.1.0", "happy-dom": "^20.8.9", "jsdom": "^26.1.0", - "poku": "4.2.0", + "poku": "^4.3.0", "prettier": "^3.6.2", - "react": "^19.2.4", - "react-dom": "^19.2.4", + "react": "^19.2.5", + "react-dom": "^19.2.5", "rimraf": "^6.0.1", "tsup": "^8.5.0", "tsx": "^4.21.0", @@ -769,14 +769,14 @@ } }, "node_modules/@happy-dom/global-registrator": { - "version": "20.8.9", - "resolved": "https://registry.npmjs.org/@happy-dom/global-registrator/-/global-registrator-20.8.9.tgz", - "integrity": "sha512-DtZeRRHY9A/bisTJziUBBPrdnPui7+R185G/hzi6/Boymhqh7/wi53AY+IvQHS1+7OPaqfO/1XNpngNwthLz+A==", + "version": "20.9.0", + "resolved": "https://registry.npmjs.org/@happy-dom/global-registrator/-/global-registrator-20.9.0.tgz", + "integrity": "sha512-lBW6/m5BIFl3pMuWPNN0lIOYw9LMCmPfix53ExS3FBi4E+NELEljQ3xH6aAV9IYiQRfn9YIIgzzMrD0vIcD7tw==", "dev": true, "license": "MIT", "dependencies": { "@types/node": ">=20.0.0", - "happy-dom": "^20.8.9" + "happy-dom": "^20.9.0" }, "engines": { "node": ">=20.0.0" @@ -857,12 +857,13 @@ } }, "node_modules/@pokujs/dom": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@pokujs/dom/-/dom-1.2.0.tgz", - "integrity": "sha512-RafJKjW+7skIPF6dl2GLV7nMnvPJzIkWd6nNehJOq3m82OahEDWWBfeWvX/PumZj9liq11/JCMsifUvywfJhAQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@pokujs/dom/-/dom-1.3.0.tgz", + "integrity": "sha512-O7aZdRoeq0FrU5uSIVSA10/pHtVpedmkkVggi3qooGCU8+5SGzAFKYRzlwCwAr3gQjdaU34JqVflNgPmz61JGw==", "dev": true, "license": "MIT", "dependencies": { + "@pokujs/scope-hooks": "^1.1.0", "@testing-library/dom": "^10.4.1" }, "engines": { @@ -885,10 +886,26 @@ } } }, + "node_modules/@pokujs/scope-hooks": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@pokujs/scope-hooks/-/scope-hooks-1.1.0.tgz", + "integrity": "sha512-EhUy0aP4k+mMoHxd6eK+Cjuo5YFcpSKfySgT/1+6u7D3UD37JKAbb8cAvnyzhLi+Vfb4y9CQgcyiHksGYtGMZw==", + "dev": true, + "license": "MIT", + "engines": { + "bun": ">=1.x.x", + "deno": ">=2.x.x", + "node": ">=20.x.x", + "typescript": ">=6.x.x" + }, + "peerDependencies": { + "poku": ">=4.3.0" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz", - "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz", + "integrity": "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==", "cpu": [ "arm" ], @@ -900,9 +917,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz", - "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz", + "integrity": "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==", "cpu": [ "arm64" ], @@ -914,9 +931,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz", - "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz", + "integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==", "cpu": [ "arm64" ], @@ -928,9 +945,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz", - "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz", + "integrity": "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==", "cpu": [ "x64" ], @@ -942,9 +959,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz", - "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz", + "integrity": "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==", "cpu": [ "arm64" ], @@ -956,9 +973,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz", - "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz", + "integrity": "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==", "cpu": [ "x64" ], @@ -970,9 +987,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz", - "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz", + "integrity": "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==", "cpu": [ "arm" ], @@ -984,9 +1001,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz", - "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz", + "integrity": "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==", "cpu": [ "arm" ], @@ -998,9 +1015,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz", - "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz", + "integrity": "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==", "cpu": [ "arm64" ], @@ -1012,9 +1029,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz", - "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz", + "integrity": "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==", "cpu": [ "arm64" ], @@ -1026,9 +1043,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz", - "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz", + "integrity": "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==", "cpu": [ "loong64" ], @@ -1040,9 +1057,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz", - "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz", + "integrity": "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==", "cpu": [ "loong64" ], @@ -1054,9 +1071,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz", - "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz", + "integrity": "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==", "cpu": [ "ppc64" ], @@ -1068,9 +1085,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz", - "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz", + "integrity": "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==", "cpu": [ "ppc64" ], @@ -1082,9 +1099,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz", - "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz", + "integrity": "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==", "cpu": [ "riscv64" ], @@ -1096,9 +1113,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz", - "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz", + "integrity": "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==", "cpu": [ "riscv64" ], @@ -1110,9 +1127,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz", - "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz", + "integrity": "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==", "cpu": [ "s390x" ], @@ -1124,9 +1141,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz", - "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz", + "integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==", "cpu": [ "x64" ], @@ -1138,9 +1155,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz", - "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz", + "integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==", "cpu": [ "x64" ], @@ -1152,9 +1169,9 @@ ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz", - "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz", + "integrity": "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==", "cpu": [ "x64" ], @@ -1166,9 +1183,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz", - "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz", + "integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==", "cpu": [ "arm64" ], @@ -1180,9 +1197,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz", - "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz", + "integrity": "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==", "cpu": [ "arm64" ], @@ -1194,9 +1211,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz", - "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz", + "integrity": "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==", "cpu": [ "ia32" ], @@ -1208,9 +1225,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz", - "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz", + "integrity": "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==", "cpu": [ "x64" ], @@ -1222,9 +1239,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz", - "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.2.tgz", + "integrity": "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==", "cpu": [ "x64" ], @@ -1281,19 +1298,19 @@ } }, "node_modules/@types/node": { - "version": "25.5.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.2.tgz", - "integrity": "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg==", + "version": "25.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", + "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.18.0" + "undici-types": "~7.19.0" } }, "node_modules/@types/node/node_modules/undici-types": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", - "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "version": "7.19.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", + "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", "dev": true, "license": "MIT" }, @@ -1712,9 +1729,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.13.7", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz", - "integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", "dev": true, "license": "MIT", "dependencies": { @@ -1743,9 +1760,9 @@ } }, "node_modules/happy-dom": { - "version": "20.8.9", - "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.8.9.tgz", - "integrity": "sha512-Tz23LR9T9jOGVZm2x1EPdXqwA37G/owYMxRwU0E4miurAtFsPMQ1d2Jc2okUaSjZqAFz2oEn3FLXC5a0a+siyA==", + "version": "20.9.0", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.9.0.tgz", + "integrity": "sha512-GZZ9mKe8r646NUAf/zemnGbjYh4Bt8/MqASJY+pSm5ZDtc3YQox+4gsLI7yi1hba6o+eCsGxpHn5+iEVn31/FQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2099,9 +2116,9 @@ } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.2.7", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", - "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz", + "integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -2157,9 +2174,9 @@ } }, "node_modules/poku": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/poku/-/poku-4.2.0.tgz", - "integrity": "sha512-GygMGFGgEJ9kfs6Z+QPg/ODs9OF3oGHN8+hYIxtBox3pwYISO+Vu660vH1e+YzjpGoaoy2o5y6YwE1tX5yZx3Q==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/poku/-/poku-4.3.0.tgz", + "integrity": "sha512-s6xHA93lzirvScBuW5UxUAbx4Cw6C/5MEMTe/27jTtLkDmIsWNpUH2CiMbSOKMxLGj7C3JoM2zfacu3kCrlk3Q==", "dev": true, "license": "MIT", "bin": { @@ -2220,9 +2237,9 @@ } }, "node_modules/prettier": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", - "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", "dev": true, "license": "MIT", "bin": { @@ -2260,9 +2277,9 @@ } }, "node_modules/react": { - "version": "19.2.4", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", - "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz", + "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==", "dev": true, "license": "MIT", "engines": { @@ -2270,16 +2287,16 @@ } }, "node_modules/react-dom": { - "version": "19.2.4", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", - "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz", + "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==", "dev": true, "license": "MIT", "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.2.4" + "react": "^19.2.5" } }, "node_modules/react-is": { @@ -2343,9 +2360,9 @@ } }, "node_modules/rollup": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz", - "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz", + "integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2359,31 +2376,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.60.1", - "@rollup/rollup-android-arm64": "4.60.1", - "@rollup/rollup-darwin-arm64": "4.60.1", - "@rollup/rollup-darwin-x64": "4.60.1", - "@rollup/rollup-freebsd-arm64": "4.60.1", - "@rollup/rollup-freebsd-x64": "4.60.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", - "@rollup/rollup-linux-arm-musleabihf": "4.60.1", - "@rollup/rollup-linux-arm64-gnu": "4.60.1", - "@rollup/rollup-linux-arm64-musl": "4.60.1", - "@rollup/rollup-linux-loong64-gnu": "4.60.1", - "@rollup/rollup-linux-loong64-musl": "4.60.1", - "@rollup/rollup-linux-ppc64-gnu": "4.60.1", - "@rollup/rollup-linux-ppc64-musl": "4.60.1", - "@rollup/rollup-linux-riscv64-gnu": "4.60.1", - "@rollup/rollup-linux-riscv64-musl": "4.60.1", - "@rollup/rollup-linux-s390x-gnu": "4.60.1", - "@rollup/rollup-linux-x64-gnu": "4.60.1", - "@rollup/rollup-linux-x64-musl": "4.60.1", - "@rollup/rollup-openbsd-x64": "4.60.1", - "@rollup/rollup-openharmony-arm64": "4.60.1", - "@rollup/rollup-win32-arm64-msvc": "4.60.1", - "@rollup/rollup-win32-ia32-msvc": "4.60.1", - "@rollup/rollup-win32-x64-gnu": "4.60.1", - "@rollup/rollup-win32-x64-msvc": "4.60.1", + "@rollup/rollup-android-arm-eabi": "4.60.2", + "@rollup/rollup-android-arm64": "4.60.2", + "@rollup/rollup-darwin-arm64": "4.60.2", + "@rollup/rollup-darwin-x64": "4.60.2", + "@rollup/rollup-freebsd-arm64": "4.60.2", + "@rollup/rollup-freebsd-x64": "4.60.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", + "@rollup/rollup-linux-arm-musleabihf": "4.60.2", + "@rollup/rollup-linux-arm64-gnu": "4.60.2", + "@rollup/rollup-linux-arm64-musl": "4.60.2", + "@rollup/rollup-linux-loong64-gnu": "4.60.2", + "@rollup/rollup-linux-loong64-musl": "4.60.2", + "@rollup/rollup-linux-ppc64-gnu": "4.60.2", + "@rollup/rollup-linux-ppc64-musl": "4.60.2", + "@rollup/rollup-linux-riscv64-gnu": "4.60.2", + "@rollup/rollup-linux-riscv64-musl": "4.60.2", + "@rollup/rollup-linux-s390x-gnu": "4.60.2", + "@rollup/rollup-linux-x64-gnu": "4.60.2", + "@rollup/rollup-linux-x64-musl": "4.60.2", + "@rollup/rollup-openbsd-x64": "4.60.2", + "@rollup/rollup-openharmony-arm64": "4.60.2", + "@rollup/rollup-win32-arm64-msvc": "4.60.2", + "@rollup/rollup-win32-ia32-msvc": "4.60.2", + "@rollup/rollup-win32-x64-gnu": "4.60.2", + "@rollup/rollup-win32-x64-msvc": "4.60.2", "fsevents": "~2.3.2" } }, @@ -2528,14 +2545,14 @@ "license": "MIT" }, "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", - "picomatch": "^4.0.3" + "picomatch": "^4.0.4" }, "engines": { "node": ">=12.0.0" @@ -2681,9 +2698,9 @@ } }, "node_modules/typescript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", - "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -2702,9 +2719,9 @@ "license": "MIT" }, "node_modules/undici-types": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.7.tgz", - "integrity": "sha512-XA+gOBkzYD3C74sZowtCLTpgtaCdqZhqCvR6y9LXvrKTt/IVU6bz49T4D+BPi475scshCCkb0IklJRw6T1ZlgQ==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.25.0.tgz", + "integrity": "sha512-AXNgS1Byr27fTI+2bsPEkV9CxkT8H6xNyRI68b3TatlZo3RkzlqQBLL+w7SmGPVpokjHbcuNVQUWE7FRTg+LRA==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index ca939c6..7d20382 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,9 @@ "test:deno": "deno run -A run.test.ts", "test:deno:none": "cross-env POKU_REACT_TEST_DOM=happy-dom deno run -A npm:poku tests --showLogs --isolation=none", "test:deno:process": "cross-env POKU_REACT_TEST_DOM=happy-dom deno run -A npm:poku tests --showLogs --isolation=process", + "test:major:18": "npm install --no-save react@18 react-dom@18 @types/react@18 @types/react-dom@18 && npm test", + "test:major:19": "npm install --no-save react@19 react-dom@19 @types/react@19 @types/react-dom@19 && npm test", + "test:multi-major": "npm run test:major:18 && npm run test:major:19", "clean": "rimraf dist", "build": "tsup src/index.ts src/plugin.ts src/react-testing.ts src/dom-setup-happy.ts src/dom-setup-jsdom.ts --format esm --dts --target node20 --sourcemap --clean --tsconfig tsconfig.tsup.json", "typecheck": "tsc -p tsconfig.build.json --noEmit", @@ -100,7 +103,7 @@ "devDependencies": { "@happy-dom/global-registrator": "^20.8.9", "@ianvs/prettier-plugin-sort-imports": "^4.7.0", - "@pokujs/dom": "^1.2.0", + "@pokujs/dom": "^1.3.0", "@types/jsdom": "^28.0.1", "@types/node": "^25.5.0", "@types/react": "^19.2.14", @@ -108,10 +111,10 @@ "cross-env": "^10.1.0", "happy-dom": "^20.8.9", "jsdom": "^26.1.0", - "poku": "4.2.0", + "poku": "^4.3.0", "prettier": "^3.6.2", - "react": "^19.2.4", - "react-dom": "^19.2.4", + "react": "^19.2.5", + "react-dom": "^19.2.5", "rimraf": "^6.0.1", "tsup": "^8.5.0", "tsx": "^4.21.0", diff --git a/src/react-testing.ts b/src/react-testing.ts index 55af847..8b6b061 100644 --- a/src/react-testing.ts +++ b/src/react-testing.ts @@ -7,6 +7,7 @@ import type { ComponentType, PropsWithChildren, ReactElement } from 'react'; import type { Root } from 'react-dom/client'; import { getQueriesForElement, queries } from '@testing-library/dom'; import * as domTestingLibrary from '@testing-library/dom'; +import * as pokuDom from '@pokujs/dom'; import React from 'react'; import { createRoot } from 'react-dom/client'; import { @@ -29,9 +30,92 @@ type InternalMounted = { ownsContainer: boolean; }; -const mountedRoots = new Set(); +const fallbackMountedRoots = new Set(); -const unmountMounted = (mounted: InternalMounted) => { +type ScopeSlot = { + readonly value: T; +}; + +type ScopeLike = { + getOrCreateSlot(key: symbol, init: () => T): ScopeSlot; + getSlot?(key: symbol): ScopeSlot | undefined; + addCleanup?(fn: () => void | Promise): void; +}; + +type DomScopeApi = { + defineSlotKey?: (name: string) => symbol; + getOrCreateScope?: () => ScopeLike | undefined; + getCurrentScope?: () => ScopeLike | undefined; +}; + +const domScopeApi = pokuDom as unknown as DomScopeApi; + +const MOUNTED_ROOTS_SLOT_KEY = + typeof domScopeApi.defineSlotKey === 'function' + ? domScopeApi.defineSlotKey>( + '@pokujs/react.mounted-roots' + ) + : undefined; + +const CLEANUP_STATE_SLOT_KEY = + typeof domScopeApi.defineSlotKey === 'function' + ? domScopeApi.defineSlotKey<{ registered: boolean }>( + '@pokujs/react.cleanup-registered' + ) + : undefined; + +const cleanupMountedRoots = (mountedRoots: Set) => { + for (const mounted of [...mountedRoots]) { + unmountMounted(mountedRoots, mounted); + } +}; + +const getScopedMountedRoots = (): Set | undefined => { + if (!MOUNTED_ROOTS_SLOT_KEY) return undefined; + if (typeof domScopeApi.getOrCreateScope !== 'function') return undefined; + + const scope = domScopeApi.getOrCreateScope(); + if (!scope) return undefined; + + const mountedRoots = scope.getOrCreateSlot(MOUNTED_ROOTS_SLOT_KEY, () => + new Set() + ).value; + + if (!CLEANUP_STATE_SLOT_KEY || typeof scope.addCleanup !== 'function') { + return mountedRoots; + } + + const cleanupState = scope.getOrCreateSlot(CLEANUP_STATE_SLOT_KEY, () => ({ + registered: false, + })).value; + + if (!cleanupState.registered) { + cleanupState.registered = true; + scope.addCleanup(() => { + cleanupMountedRoots(mountedRoots); + metrics.flushMetricBuffer(); + }); + } + + return mountedRoots; +}; + +const getMountedRoots = (): Set => + getScopedMountedRoots() ?? fallbackMountedRoots; + +const getCurrentScopedMountedRoots = (): Set | undefined => { + if (!MOUNTED_ROOTS_SLOT_KEY) return undefined; + if (typeof domScopeApi.getCurrentScope !== 'function') return undefined; + + const scope = domScopeApi.getCurrentScope(); + const slot = scope?.getSlot?.>(MOUNTED_ROOTS_SLOT_KEY); + return slot?.value; +}; + +const unmountMounted = ( + mountedRoots: Set, + mounted: InternalMounted +) => { try { act(() => { mounted.root?.unmount(); @@ -84,6 +168,7 @@ export const render = ( ui: ReactElement, options: RenderOptions = {} ): RenderResult => { + const mountedRoots = getMountedRoots(); const baseElement = options.baseElement || document.body; const container = options.container || document.createElement('div'); const ownsContainer = !options.container; @@ -109,7 +194,7 @@ export const render = ( const unmount = () => { if (!mountedRoots.has(mounted)) return; - unmountMounted(mounted); + unmountMounted(mountedRoots, mounted); }; const rerender = (nextUi: ReactElement) => { @@ -160,12 +245,20 @@ export const renderHook = < const initialProps = options.initialProps ?? ({} as Props); const view = render(React.createElement(HookHarness, initialProps), options); + let currentProps = initialProps; + + const resultRef: { current: Result } = { + get current() { + return currentResult; + }, + } as { current: Result }; return { get result() { - return { current: currentResult }; + return resultRef; }, - rerender(nextProps = initialProps) { + rerender(nextProps = currentProps) { + currentProps = nextProps; view.rerender(React.createElement(HookHarness, nextProps)); }, unmount: view.unmount, @@ -173,9 +266,10 @@ export const renderHook = < }; export const cleanup = () => { - for (const mounted of [...mountedRoots]) { - unmountMounted(mounted); - } + const scopedMountedRoots = getCurrentScopedMountedRoots(); + if (scopedMountedRoots) cleanupMountedRoots(scopedMountedRoots); + + cleanupMountedRoots(fallbackMountedRoots); metrics.flushMetricBuffer(); }; diff --git a/tests/react-concurrency.test.tsx b/tests/react-concurrency.test.tsx index ebee293..8af0141 100644 --- a/tests/react-concurrency.test.tsx +++ b/tests/react-concurrency.test.tsx @@ -1,15 +1,29 @@ import { afterEach, assert, test } from 'poku'; -import { Suspense, use, useState, useTransition } from 'react'; +import * as React from 'react'; import { cleanup, fireEvent, render, screen } from '../src/index.ts'; afterEach(cleanup); +const { Suspense, useState, useTransition } = React; +const useResource = + typeof (React as Record).use === 'function' + ? ((React as Record).use as (value: Promise) => T) + : undefined; + const ResourceView = ({ resource }: { resource: Promise }) => { - const value = use(resource); + if (!useResource) { + throw new Error('React.use() is unavailable in this React major.'); + } + + const value = useResource(resource); return

{value}

; }; -test('renders a resolved use() resource under Suspense', async () => { +test('renders a resolved use() resource under Suspense', () => { + if (!useResource) { + return; + } + const value = 'Loaded from use() resource'; const resolvedResource = { status: 'fulfilled' as const, @@ -30,7 +44,7 @@ test('renders a resolved use() resource under Suspense', async () => { ); }); -test('runs urgent and transition update pipeline', async () => { +test('runs urgent and transition update pipeline', () => { const TransitionPipeline = () => { const [urgentState, setUrgentState] = useState('idle'); const [deferredState, setDeferredState] = useState('idle'); diff --git a/tests/react-hooks.test.tsx b/tests/react-hooks.test.tsx index d83eeed..575dfb3 100644 --- a/tests/react-hooks.test.tsx +++ b/tests/react-hooks.test.tsx @@ -56,3 +56,18 @@ test('tests hook logic directly with renderHook', async () => { assert.strictEqual(result.current.enabled, true); }); + +test('renderHook.rerender keeps result.current live after destructuring', async () => { + const { result, rerender } = renderHook( + ({ value }: { value: string }) => value, + { + initialProps: { value: 'first' }, + } + ); + + assert.strictEqual(result.current, 'first'); + + rerender({ value: 'second' }); + + assert.strictEqual(result.current, 'second'); +}); diff --git a/tests/react-scope-isolation.test.tsx b/tests/react-scope-isolation.test.tsx new file mode 100644 index 0000000..ca06aa3 --- /dev/null +++ b/tests/react-scope-isolation.test.tsx @@ -0,0 +1,127 @@ +import * as pokuDom from '@pokujs/dom'; +import { assert, describe, it } from 'poku'; +import { cleanup, render, screen } from '../src/index.ts'; + +const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + +const SCOPE_HOOKS_KEY = Symbol.for('@pokujs/poku.test-scope-hooks'); + +type ScopeHooks = { + createHolder: () => { scope: unknown }; + runScoped: ( + holder: { scope: unknown }, + fn: () => Promise | unknown + ) => Promise; +}; + +const hasDomScopeApi = + typeof (pokuDom as Record).defineSlotKey === 'function' && + typeof (pokuDom as Record).getOrCreateScope === 'function'; + +const testHooksDisabled = async () => { + let resolveARendered!: () => void; + let resolveBRendered!: () => void; + let resolveACleaned!: () => void; + + const aRendered = new Promise((resolve) => { + resolveARendered = resolve; + }); + const bRendered = new Promise((resolve) => { + resolveBRendered = resolve; + }); + const aCleaned = new Promise((resolve) => { + resolveACleaned = resolve; + }); + + await Promise.all([ + it('suite A cleanup removes suite B tree when isolation is unavailable', async () => { + render(
suite-a
); + assert.strictEqual(screen.getByTestId('suite-a').textContent, 'suite-a'); + + resolveARendered(); + await bRendered; + + cleanup(); + resolveACleaned(); + + assert.throws(() => screen.getByTestId('suite-a')); + }), + + it('suite B is contaminated by suite A cleanup when isolation is unavailable', async () => { + render(
suite-b
); + assert.strictEqual(screen.getByTestId('suite-b').textContent, 'suite-b'); + + resolveBRendered(); + await aRendered; + await aCleaned; + await sleep(0); + + assert.throws(() => screen.getByTestId('suite-b')); + }), + ]); +}; + +const testHooksEnabled = async () => { + let resolveARendered!: () => void; + let resolveBRendered!: () => void; + let resolveACleaned!: () => void; + + const aRendered = new Promise((resolve) => { + resolveARendered = resolve; + }); + const bRendered = new Promise((resolve) => { + resolveBRendered = resolve; + }); + const aCleaned = new Promise((resolve) => { + resolveACleaned = resolve; + }); + + await Promise.all([ + it('suite A cleanup does not remove suite B tree', async () => { + render(
suite-a
); + assert.strictEqual(screen.getByTestId('suite-a').textContent, 'suite-a'); + + resolveARendered(); + await bRendered; + + cleanup(); + resolveACleaned(); + + assert.throws(() => screen.getByTestId('suite-a')); + }), + + it('suite B remains mounted while suite A cleans up', async () => { + render(
suite-b
); + assert.strictEqual(screen.getByTestId('suite-b').textContent, 'suite-b'); + + resolveBRendered(); + await aRendered; + await aCleaned; + await sleep(0); + + assert.strictEqual(screen.getByTestId('suite-b').textContent, 'suite-b'); + + cleanup(); + assert.throws(() => screen.getByTestId('suite-b')); + }), + ]); +}; + +describe('react scope isolation', () => { + let hasRegisteredHooks = false; + + it('scope-hook contract probe', () => { + const g = globalThis as Record; + hasRegisteredHooks = typeof g[SCOPE_HOOKS_KEY] === 'object'; + assert.ok(true, 'runtime probe'); + }); + + if (!hasRegisteredHooks || !hasDomScopeApi) { + return it( + 'test hooks are disabled when scope hooks are unavailable', + testHooksDisabled + ); + } + + it('test hooks are enabled when scope hooks are available', testHooksEnabled); +});