fix: tighten ci typecheck coverage
This commit is contained in:
@@ -138,6 +138,28 @@ jobs:
|
||||
- name: Build desktop
|
||||
run: npm run build:desktop
|
||||
|
||||
typecheck:
|
||||
name: Typecheck
|
||||
if: github.event_name == 'pull_request'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: npm
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci --prefer-offline --no-audit --no-fund
|
||||
|
||||
- name: Run typecheck
|
||||
run: npm run typecheck
|
||||
|
||||
schema-sqlite:
|
||||
name: Schema Check (SQLite)
|
||||
if: github.event_name == 'pull_request'
|
||||
|
||||
Generated
+42
-19
@@ -43,6 +43,7 @@
|
||||
"@types/pg": "^8.15.6",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@types/react-test-renderer": "^19.1.0",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"concurrently": "^9.1.0",
|
||||
"cross-env": "^7.0.3",
|
||||
@@ -66,7 +67,7 @@
|
||||
"wait-on": "^9.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
"node": ">=22.15.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@algolia/abtesting": {
|
||||
@@ -230,6 +231,7 @@
|
||||
"integrity": "sha512-Nt9hri7nbOo0RipAsGjIssHkpLMHHN/P7QqENywAq5TLsoYDzUyJGny8FEiD/9KJUxtGH8blGpMedilI6kK3rA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@algolia/client-common": "5.49.1",
|
||||
"@algolia/requester-browser-xhr": "5.49.1",
|
||||
@@ -443,6 +445,7 @@
|
||||
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.29.0",
|
||||
"@babel/generator": "^7.29.0",
|
||||
@@ -846,6 +849,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
},
|
||||
@@ -894,6 +898,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
}
|
||||
@@ -922,6 +927,7 @@
|
||||
"integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
@@ -967,6 +973,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz",
|
||||
"integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@dnd-kit/accessibility": "^3.1.1",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
@@ -1456,7 +1463,6 @@
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"cross-dirname": "^0.1.0",
|
||||
"debug": "^4.3.4",
|
||||
@@ -1478,7 +1484,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
@@ -1495,7 +1500,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
@@ -1510,7 +1514,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
@@ -4679,6 +4682,7 @@
|
||||
"integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
@@ -5088,6 +5092,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.11.tgz",
|
||||
"integrity": "sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
@@ -5115,6 +5120,7 @@
|
||||
"integrity": "sha512-gT+oueVQkqnj6ajGJXblFR4iavIXWsGAFCk3dP4Kki5+a9R4NMt0JARdk6s8cUKcfUoqP5dAtDSLU8xYUTFV+Q==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"pg-protocol": "*",
|
||||
@@ -5146,6 +5152,7 @@
|
||||
"integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^3.2.2"
|
||||
@@ -5161,6 +5168,16 @@
|
||||
"@types/react": "^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-test-renderer": {
|
||||
"version": "19.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-19.1.0.tgz",
|
||||
"integrity": "sha512-XD0WZrHqjNrxA/MaR9O22w/RNidWR9YZmBdRGI7wcnWGrv/3dA8wKCJ8m63Sn+tLJhcjmuhOi629N66W6kgWzQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/responselike": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz",
|
||||
@@ -5883,6 +5900,7 @@
|
||||
"integrity": "sha512-X3Pp2aRQhg4xUC6PQtkubn5NpRKuUPQ9FPDQlx36SmpFwwH2N0/tw4c+NXV3nw3PsgeUs+BuWGP0gjz3TvENLQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@algolia/abtesting": "1.15.1",
|
||||
"@algolia/client-abtesting": "5.49.1",
|
||||
@@ -6317,6 +6335,7 @@
|
||||
"integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.11",
|
||||
"form-data": "^4.0.5",
|
||||
@@ -6371,6 +6390,7 @@
|
||||
"integrity": "sha512-RxD2Vd96sQDjQr20kdP+F+dK/1OUNiVOl200vKBZY8u0vTwysfolF6Hq+3ZK2+h8My9YvZhHsF+RSGZW2VYrPQ==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"bindings": "^1.5.0",
|
||||
"prebuild-install": "^7.1.1"
|
||||
@@ -6460,6 +6480,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.9.0",
|
||||
"caniuse-lite": "^1.0.30001759",
|
||||
@@ -6863,6 +6884,7 @@
|
||||
"integrity": "sha512-opLQzEVriiH1uUQ4Kctsd49bRoFDXGGSC4GUqj7pGyxM3RehRhvTlZJc1FL/Flew2p5uwxa1tUDWKzI4wNM8pg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@chevrotain/cst-dts-gen": "11.1.2",
|
||||
"@chevrotain/gast": "11.1.2",
|
||||
@@ -7218,8 +7240,7 @@
|
||||
"integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/cross-env": {
|
||||
"version": "7.0.3",
|
||||
@@ -7282,6 +7303,7 @@
|
||||
"integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
@@ -7715,6 +7737,7 @@
|
||||
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
@@ -8170,6 +8193,7 @@
|
||||
"integrity": "sha512-glMJgnTreo8CFINujtAhCgN96QAqApDMZ8Vl1r8f0QT8QprvC1UCltV4CcWj20YoIyLZx6IUskaJZ0NV8fokcg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"app-builder-lib": "26.8.1",
|
||||
"builder-util": "26.8.1",
|
||||
@@ -8765,7 +8789,6 @@
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@electron/asar": "^3.2.1",
|
||||
"debug": "^4.1.1",
|
||||
@@ -8786,7 +8809,6 @@
|
||||
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.1.2",
|
||||
"jsonfile": "^4.0.0",
|
||||
@@ -9407,6 +9429,7 @@
|
||||
"integrity": "sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tabbable": "^6.4.0"
|
||||
}
|
||||
@@ -11500,7 +11523,6 @@
|
||||
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.6"
|
||||
},
|
||||
@@ -11545,6 +11567,7 @@
|
||||
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.20.0.tgz",
|
||||
"integrity": "sha512-eCLUs7BNbgA6nf/MZXsaBO1SfGs0LtLVrJD3WeWq+jPLDWkSufTD+aGMwykfUVPdZnblaUK1a8G/P63cl9FkKg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"aws-ssl-profiles": "^1.1.2",
|
||||
"denque": "^2.1.0",
|
||||
@@ -12113,6 +12136,7 @@
|
||||
"resolved": "https://registry.npmjs.org/pg/-/pg-8.20.0.tgz",
|
||||
"integrity": "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"pg-connection-string": "^2.12.0",
|
||||
"pg-pool": "^3.13.0",
|
||||
@@ -12210,6 +12234,7 @@
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -12390,7 +12415,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"commander": "^9.4.0"
|
||||
},
|
||||
@@ -12408,7 +12432,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^12.20.0 || >=14"
|
||||
}
|
||||
@@ -12596,6 +12619,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
},
|
||||
@@ -12608,6 +12632,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.2"
|
||||
@@ -12896,7 +12921,6 @@
|
||||
"deprecated": "Rimraf versions prior to v4 are no longer supported",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"glob": "^7.1.3"
|
||||
},
|
||||
@@ -12909,8 +12933,7 @@
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/rimraf/node_modules/brace-expansion": {
|
||||
"version": "1.1.12",
|
||||
@@ -12918,7 +12941,6 @@
|
||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
@@ -12931,7 +12953,6 @@
|
||||
"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",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
@@ -12953,7 +12974,6 @@
|
||||
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
@@ -13953,7 +13973,6 @@
|
||||
"integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"mkdirp": "^0.5.1",
|
||||
"rimraf": "~2.6.2"
|
||||
@@ -14765,6 +14784,7 @@
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -15018,6 +15038,7 @@
|
||||
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.4.4",
|
||||
@@ -16675,6 +16696,7 @@
|
||||
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.21.3",
|
||||
"postcss": "^8.4.43",
|
||||
@@ -16790,6 +16812,7 @@
|
||||
"integrity": "sha512-BZqN4Ze6mDQVNAni0IHeMJ5mwr8VAJ3MQC9FmprRhcBYENw+wOAAjRj8jfmN6FLl0j96OXbR+CjWhmAmM+QGnA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.5.29",
|
||||
"@vue/compiler-sfc": "3.5.29",
|
||||
|
||||
@@ -34,6 +34,11 @@
|
||||
"build:server": "tsc -p tsconfig.server.json && tsx scripts/dev/copy-runtime-db-generated.ts",
|
||||
"build:desktop": "tsc -p tsconfig.desktop.json",
|
||||
"build": "npm run build:web && npm run build:server && npm run build:desktop",
|
||||
"typecheck:web": "tsc -p tsconfig.web.json",
|
||||
"typecheck:web:test": "tsc -p tsconfig.web.test.json",
|
||||
"typecheck:server": "tsc --noEmit -p tsconfig.server.json",
|
||||
"typecheck:desktop": "tsc --noEmit -p tsconfig.desktop.json",
|
||||
"typecheck": "npm run typecheck:web && npm run typecheck:web:test && npm run typecheck:server && npm run typecheck:desktop",
|
||||
"dist:desktop": "npm run build && electron-builder --config electron-builder.yml --publish never",
|
||||
"dist:desktop:mac:intel": "npm run build && electron-builder --config electron-builder.yml --publish never --mac --x64",
|
||||
"package:desktop": "npm run desktop:icons && electron-builder --config electron-builder.yml --publish never",
|
||||
@@ -93,6 +98,7 @@
|
||||
"@types/pg": "^8.15.6",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@types/react-test-renderer": "^19.1.0",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"concurrently": "^9.1.0",
|
||||
"cross-env": "^7.0.3",
|
||||
|
||||
@@ -155,7 +155,7 @@ describe('App mobile layout', () => {
|
||||
'uses the shared breakpoint at width $width',
|
||||
async ({ width, expectedLayout, hasHamburger }) => {
|
||||
setupRuntime(width);
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
|
||||
try {
|
||||
await act(async () => {
|
||||
|
||||
@@ -151,7 +151,7 @@ describe('App mobile sidebar', () => {
|
||||
|
||||
it('opens the mobile drawer from the hamburger trigger and exposes the close affordance', async () => {
|
||||
setupRuntime(768);
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
|
||||
try {
|
||||
await act(async () => {
|
||||
|
||||
@@ -42,7 +42,7 @@ describe('ModelAnalysisPanel token summaries', () => {
|
||||
});
|
||||
|
||||
it('renders total token summaries with compact units', () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
|
||||
act(() => {
|
||||
root = create(
|
||||
@@ -70,10 +70,10 @@ describe('ModelAnalysisPanel token summaries', () => {
|
||||
globalThis.document = {
|
||||
documentElement: {},
|
||||
} as unknown as Document;
|
||||
delete (globalThis as typeof globalThis & { getComputedStyle?: typeof getComputedStyle }).getComputedStyle;
|
||||
delete (globalThis as typeof globalThis & { MutationObserver?: typeof MutationObserver }).MutationObserver;
|
||||
Reflect.deleteProperty(globalThis as typeof globalThis & Record<string, unknown>, 'getComputedStyle');
|
||||
Reflect.deleteProperty(globalThis as typeof globalThis & Record<string, unknown>, 'MutationObserver');
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
|
||||
await expect(act(async () => {
|
||||
root = create(
|
||||
|
||||
@@ -7,7 +7,7 @@ import CenteredModal from './CenteredModal.js';
|
||||
describe('CenteredModal component', () => {
|
||||
it('does not close on backdrop click by default and exposes an explicit close button', async () => {
|
||||
const onClose = vi.fn();
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
|
||||
try {
|
||||
await act(async () => {
|
||||
|
||||
@@ -17,8 +17,8 @@ describe('SiteDistributionChart', () => {
|
||||
getAttribute: vi.fn(),
|
||||
},
|
||||
} as unknown as Document;
|
||||
delete (globalThis as typeof globalThis & { getComputedStyle?: typeof getComputedStyle }).getComputedStyle;
|
||||
delete (globalThis as typeof globalThis & { MutationObserver?: typeof MutationObserver }).MutationObserver;
|
||||
Reflect.deleteProperty(globalThis as typeof globalThis & Record<string, unknown>, 'getComputedStyle');
|
||||
Reflect.deleteProperty(globalThis as typeof globalThis & Record<string, unknown>, 'MutationObserver');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -28,7 +28,7 @@ describe('SiteDistributionChart', () => {
|
||||
});
|
||||
|
||||
it('renders with fallback label color when browser theme APIs are unavailable', async () => {
|
||||
let renderer: ReturnType<typeof create> | null = null;
|
||||
let renderer!: WebTestRenderer;
|
||||
|
||||
await expect(act(async () => {
|
||||
renderer = create(
|
||||
@@ -46,6 +46,6 @@ describe('SiteDistributionChart', () => {
|
||||
);
|
||||
})).resolves.toBeUndefined();
|
||||
|
||||
renderer?.unmount();
|
||||
renderer.unmount();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,6 +17,10 @@ interface SiteDistributionChartProps {
|
||||
|
||||
type ViewMode = 'balance' | 'spend';
|
||||
|
||||
function coerceDatumRecord(datum: unknown): Record<string, unknown> {
|
||||
return datum && typeof datum === 'object' ? datum as Record<string, unknown> : {};
|
||||
}
|
||||
|
||||
function safeNumber(value: unknown): number {
|
||||
if (typeof value !== 'number' || Number.isNaN(value) || !Number.isFinite(value)) return 0;
|
||||
return value;
|
||||
@@ -120,24 +124,32 @@ export default function SiteDistributionChart({ data, loading }: SiteDistributio
|
||||
mark: {
|
||||
content: [
|
||||
{
|
||||
key: (datum: Record<string, unknown>) => datum.siteName as string,
|
||||
value: (datum: Record<string, unknown>) => {
|
||||
const val = safeNumber(datum.value);
|
||||
key: (datum: unknown) => {
|
||||
const item = coerceDatumRecord(datum);
|
||||
return String(item.siteName || '-');
|
||||
},
|
||||
value: (datum: unknown) => {
|
||||
const item = coerceDatumRecord(datum);
|
||||
const val = safeNumber(item.value);
|
||||
return `$${val.toFixed(2)}`;
|
||||
},
|
||||
},
|
||||
{
|
||||
key: '占比',
|
||||
value: (datum: Record<string, unknown>) => {
|
||||
const pct = datum._percent_ as number;
|
||||
value: (datum: unknown) => {
|
||||
const item = coerceDatumRecord(datum);
|
||||
const pct = safeNumber(item._percent_);
|
||||
return `${safeNumber(pct).toFixed(1)}%`;
|
||||
},
|
||||
},
|
||||
{
|
||||
key: '账户数',
|
||||
value: (datum: Record<string, unknown>) => `${datum.accountCount}`,
|
||||
value: (datum: unknown) => {
|
||||
const item = coerceDatumRecord(datum);
|
||||
return String(item.accountCount || 0);
|
||||
},
|
||||
},
|
||||
],
|
||||
] as any,
|
||||
},
|
||||
},
|
||||
color: PIE_COLORS,
|
||||
|
||||
@@ -11,7 +11,7 @@ vi.mock('react-dom', () => ({
|
||||
describe('MobileDrawer', () => {
|
||||
it('renders content, locks body scroll, and exposes explicit close affordances', async () => {
|
||||
const onClose = vi.fn();
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
vi.stubGlobal('document', {
|
||||
body: {
|
||||
style: {
|
||||
|
||||
@@ -92,7 +92,7 @@ describe('SearchModal results', () => {
|
||||
});
|
||||
|
||||
const onClose = vi.fn();
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
|
||||
@@ -22,7 +22,7 @@ describe('useIsMobile', () => {
|
||||
removeEventListener: (event: string, handler: () => void) => {
|
||||
listeners.get(event)?.delete(handler);
|
||||
},
|
||||
dispatchEvent: (event: { type: string }) => {
|
||||
dispatchEvent: (event: Event) => {
|
||||
listeners.get(event.type)?.forEach((handler) => handler());
|
||||
return true;
|
||||
},
|
||||
@@ -39,7 +39,7 @@ describe('useIsMobile', () => {
|
||||
innerWidth: 767,
|
||||
});
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
|
||||
try {
|
||||
await act(async () => {
|
||||
@@ -66,7 +66,7 @@ describe('useIsMobile', () => {
|
||||
}),
|
||||
});
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
|
||||
try {
|
||||
await act(async () => {
|
||||
@@ -84,7 +84,7 @@ describe('useIsMobile', () => {
|
||||
});
|
||||
|
||||
it('treats 767 and 768 as mobile, but 769 as desktop', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
|
||||
try {
|
||||
await act(async () => {
|
||||
@@ -97,7 +97,7 @@ describe('useIsMobile', () => {
|
||||
widthRef.current = 767;
|
||||
window.innerWidth = 767;
|
||||
await act(async () => {
|
||||
window.dispatchEvent({ type: 'resize' });
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
});
|
||||
|
||||
expect(root.root.findByType('div').props['data-mobile']).toBe('true');
|
||||
@@ -105,7 +105,7 @@ describe('useIsMobile', () => {
|
||||
widthRef.current = 768;
|
||||
window.innerWidth = 768;
|
||||
await act(async () => {
|
||||
window.dispatchEvent({ type: 'resize' });
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
});
|
||||
|
||||
expect(root.root.findByType('div').props['data-mobile']).toBe('true');
|
||||
@@ -113,7 +113,7 @@ describe('useIsMobile', () => {
|
||||
widthRef.current = 769;
|
||||
window.innerWidth = 769;
|
||||
await act(async () => {
|
||||
window.dispatchEvent({ type: 'resize' });
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
});
|
||||
|
||||
expect(root.root.findByType('div').props['data-mobile']).toBe('false');
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ import './index.css';
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<BrowserRouter future={{ v7_startTransition: true, v7_relativeSplatPath: true }}>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</React.StrictMode>
|
||||
|
||||
@@ -190,7 +190,7 @@ describe('DownstreamKeys mobile layout', () => {
|
||||
});
|
||||
|
||||
it('renders mobile cards and supports select-all-visible batch actions', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
|
||||
try {
|
||||
await act(async () => {
|
||||
|
||||
@@ -165,7 +165,7 @@ afterEach(() => {
|
||||
|
||||
describe('DownstreamKeys page', () => {
|
||||
it('loads management data and renders merged row content', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -193,7 +193,7 @@ describe('DownstreamKeys page', () => {
|
||||
expect(text).toContain('主分组');
|
||||
expect(text).toContain('移动端');
|
||||
} finally {
|
||||
root?.unmount();
|
||||
root.unmount();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -213,7 +213,7 @@ describe('DownstreamKeys page', () => {
|
||||
],
|
||||
});
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -236,7 +236,7 @@ describe('DownstreamKeys page', () => {
|
||||
|
||||
const select = root!.root.findAllByType('select').find((node) => collectText(node).includes('仅禁用'));
|
||||
await act(async () => {
|
||||
select.props.onChange({ target: { value: 'disabled' } });
|
||||
select!.props.onChange({ target: { value: 'disabled' } });
|
||||
});
|
||||
await flushMicrotasks();
|
||||
expect(collectText(root!.root)).toContain('batch-key');
|
||||
@@ -247,7 +247,7 @@ describe('DownstreamKeys page', () => {
|
||||
});
|
||||
|
||||
it('supports create flow and drawer trend loading', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -327,7 +327,7 @@ describe('DownstreamKeys page', () => {
|
||||
],
|
||||
});
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -415,7 +415,7 @@ describe('DownstreamKeys page', () => {
|
||||
})
|
||||
.mockImplementationOnce(() => new Promise(() => {}));
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -447,12 +447,12 @@ describe('DownstreamKeys page', () => {
|
||||
expect(collectText(root!.root)).not.toContain('固定窗口对比');
|
||||
expect(collectText(root!.root)).not.toContain('trend:2');
|
||||
} finally {
|
||||
root?.unmount();
|
||||
root.unmount();
|
||||
}
|
||||
});
|
||||
|
||||
it('separates exact models from group routes in advanced config and uses single-column layout', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -503,7 +503,7 @@ describe('DownstreamKeys page', () => {
|
||||
{ id: 14, modelPattern: 'claude-opus-4-6', displayName: 'Claude Opus 4.6', enabled: true },
|
||||
]);
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -570,7 +570,7 @@ describe('DownstreamKeys page', () => {
|
||||
});
|
||||
|
||||
it('uses backend batch api for selected rows', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -606,7 +606,7 @@ describe('DownstreamKeys page', () => {
|
||||
});
|
||||
|
||||
it('supports group and tag editing plus batch metadata update', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
|
||||
@@ -283,7 +283,7 @@ describe('ImportExport', () => {
|
||||
});
|
||||
|
||||
it('shows ALL-API-Hub V2 preview counts and ignored sections', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -309,7 +309,7 @@ describe('ImportExport', () => {
|
||||
});
|
||||
|
||||
it('does not label native metapi backups as ALL-API-Hub V2', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -335,7 +335,7 @@ describe('ImportExport', () => {
|
||||
|
||||
it('uses backend import summary in the completion toast', async () => {
|
||||
const confirmSpy = vi.mocked((window as { confirm: (...args: unknown[]) => boolean }).confirm);
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
|
||||
try {
|
||||
await act(async () => {
|
||||
@@ -385,7 +385,7 @@ describe('ImportExport', () => {
|
||||
});
|
||||
|
||||
it('loads webdav config and saves updates from the import/export page', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
|
||||
try {
|
||||
await act(async () => {
|
||||
@@ -422,7 +422,7 @@ describe('ImportExport', () => {
|
||||
});
|
||||
|
||||
it('shows v2.1 config-backup wording and local-state notice', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
|
||||
try {
|
||||
await act(async () => {
|
||||
@@ -445,7 +445,7 @@ describe('ImportExport', () => {
|
||||
});
|
||||
|
||||
it('renders webdav export type with ModernSelect instead of a native select', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
|
||||
try {
|
||||
await act(async () => {
|
||||
@@ -493,7 +493,7 @@ describe('ImportExport', () => {
|
||||
});
|
||||
|
||||
it('disables webdav actions while config has unsaved changes', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
|
||||
try {
|
||||
await act(async () => {
|
||||
@@ -552,7 +552,7 @@ describe('ImportExport', () => {
|
||||
},
|
||||
});
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
|
||||
try {
|
||||
await act(async () => {
|
||||
|
||||
@@ -41,12 +41,14 @@ import {
|
||||
type DebugTab,
|
||||
type ModelTesterInputs,
|
||||
type ModelTesterModeState,
|
||||
type ParameterEnabled,
|
||||
type PlaygroundMode,
|
||||
type PlaygroundMultipartFile,
|
||||
type TestTargetFormat,
|
||||
type TestChatPayload,
|
||||
} from './helpers/modelTesterSession.js';
|
||||
type ParameterEnabled,
|
||||
type PlaygroundMode,
|
||||
type PlaygroundProtocol,
|
||||
type PlaygroundMultipartFile,
|
||||
type ProxyTestEnvelope,
|
||||
type TestTargetFormat,
|
||||
type TestChatPayload,
|
||||
} from './helpers/modelTesterSession.js';
|
||||
import {
|
||||
buildConversationFileAccept,
|
||||
buildConversationFileHint,
|
||||
|
||||
@@ -82,7 +82,7 @@ describe('Models marketplace text', () => {
|
||||
});
|
||||
|
||||
it('renders readable Chinese labels and fallback descriptions for marketplace models', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
|
||||
try {
|
||||
await act(async () => {
|
||||
@@ -172,7 +172,7 @@ describe('Models marketplace text', () => {
|
||||
],
|
||||
});
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
|
||||
try {
|
||||
await act(async () => {
|
||||
@@ -213,7 +213,7 @@ describe('Models marketplace text', () => {
|
||||
globalThis.window = nextWindow;
|
||||
globalThis.matchMedia = nextWindow.matchMedia;
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
|
||||
try {
|
||||
await act(async () => {
|
||||
@@ -241,7 +241,7 @@ describe('Models marketplace text', () => {
|
||||
} as unknown as Window & typeof globalThis;
|
||||
apiMock.getModelsMarketplace.mockImplementation(() => new Promise(() => {}));
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
|
||||
try {
|
||||
await act(async () => {
|
||||
@@ -331,7 +331,7 @@ describe('Models marketplace text', () => {
|
||||
],
|
||||
});
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
|
||||
try {
|
||||
await act(async () => {
|
||||
@@ -470,7 +470,7 @@ describe('Models marketplace text', () => {
|
||||
],
|
||||
});
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
|
||||
try {
|
||||
await act(async () => {
|
||||
@@ -547,7 +547,7 @@ describe('Models marketplace text', () => {
|
||||
],
|
||||
});
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
|
||||
try {
|
||||
await act(async () => {
|
||||
|
||||
@@ -68,7 +68,7 @@ describe('NotificationSettings', () => {
|
||||
});
|
||||
|
||||
it('loads and saves telegram api base url and topic id', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -139,7 +139,7 @@ describe('NotificationSettings', () => {
|
||||
notifyCooldownSec: 300,
|
||||
});
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
|
||||
@@ -108,7 +108,7 @@ describe('OAuthManagement page', () => {
|
||||
offset: 0,
|
||||
});
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -206,7 +206,7 @@ describe('OAuthManagement page', () => {
|
||||
accountId: 7,
|
||||
});
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -304,7 +304,7 @@ describe('OAuthManagement page', () => {
|
||||
});
|
||||
apiMock.submitOAuthManualCallback.mockResolvedValue({ success: true });
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -398,7 +398,7 @@ describe('OAuthManagement page', () => {
|
||||
offset: 0,
|
||||
});
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -464,7 +464,7 @@ describe('OAuthManagement page', () => {
|
||||
offset: 0,
|
||||
});
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -526,7 +526,7 @@ describe('OAuthManagement page', () => {
|
||||
status: 'pending',
|
||||
});
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -618,7 +618,7 @@ describe('OAuthManagement page', () => {
|
||||
status: 'pending',
|
||||
});
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -717,7 +717,7 @@ describe('OAuthManagement page', () => {
|
||||
.mockResolvedValueOnce({ items: [], total: 0, limit: 100, offset: 0 });
|
||||
apiMock.deleteOAuthConnection.mockResolvedValue({ success: true });
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -860,7 +860,7 @@ describe('OAuthManagement page', () => {
|
||||
},
|
||||
});
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
|
||||
@@ -54,7 +54,7 @@ describe('ProgramLogs status label', () => {
|
||||
},
|
||||
]);
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
await act(async () => {
|
||||
root = create(
|
||||
<MemoryRouter initialEntries={['/events']}>
|
||||
@@ -90,7 +90,7 @@ describe('ProgramLogs status label', () => {
|
||||
},
|
||||
]);
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
await act(async () => {
|
||||
root = create(
|
||||
<MemoryRouter initialEntries={['/events']}>
|
||||
|
||||
@@ -158,7 +158,7 @@ describe('ProxyLogs server-driven page', () => {
|
||||
});
|
||||
|
||||
it('requests paginated data from the server and renders server summary counts', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
|
||||
try {
|
||||
await act(async () => {
|
||||
@@ -194,7 +194,7 @@ describe('ProxyLogs server-driven page', () => {
|
||||
});
|
||||
|
||||
it('keeps the model badge sized to the model name in desktop rows', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
|
||||
try {
|
||||
await act(async () => {
|
||||
@@ -250,7 +250,7 @@ describe('ProxyLogs server-driven page', () => {
|
||||
],
|
||||
}));
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
|
||||
try {
|
||||
await act(async () => {
|
||||
@@ -277,7 +277,7 @@ describe('ProxyLogs server-driven page', () => {
|
||||
});
|
||||
|
||||
it('re-queries the server for status, client, and search changes instead of filtering locally', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
|
||||
try {
|
||||
await act(async () => {
|
||||
@@ -342,7 +342,7 @@ describe('ProxyLogs server-driven page', () => {
|
||||
});
|
||||
|
||||
it('loads detail on first expand and reuses the cached detail on re-expand', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
|
||||
try {
|
||||
await act(async () => {
|
||||
@@ -385,7 +385,7 @@ describe('ProxyLogs server-driven page', () => {
|
||||
});
|
||||
|
||||
it('hydrates site and time filters from the route query', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
|
||||
try {
|
||||
await act(async () => {
|
||||
|
||||
@@ -34,6 +34,12 @@ type ProxyLogDetailState = {
|
||||
error?: string;
|
||||
};
|
||||
|
||||
type ProxyLogSiteFilterOption = {
|
||||
id: number;
|
||||
name: string;
|
||||
status: string | null;
|
||||
};
|
||||
|
||||
const PAGE_SIZES = [20, 50, 100];
|
||||
const DEFAULT_PAGE_SIZE = 50;
|
||||
const PROXY_LOG_CLIENT_FAMILY_LABELS: Record<string, string> = {
|
||||
@@ -353,14 +359,17 @@ export default function ProxyLogs() {
|
||||
try {
|
||||
const result = await api.getSites();
|
||||
const rows = Array.isArray(result) ? result : (result?.sites || []);
|
||||
const normalized = rows
|
||||
const normalized: ProxyLogSiteFilterOption[] = rows
|
||||
.map((site: any) => ({
|
||||
id: Number(site?.id || 0),
|
||||
name: String(site?.name || '').trim() || `站点 #${site?.id ?? ''}`,
|
||||
status: typeof site?.status === 'string' ? site.status : null,
|
||||
}))
|
||||
.filter((site) => site.id > 0)
|
||||
.sort((left, right) => left.name.localeCompare(right.name, 'zh-CN'));
|
||||
.filter((site: ProxyLogSiteFilterOption) => site.id > 0)
|
||||
.sort(
|
||||
(left: ProxyLogSiteFilterOption, right: ProxyLogSiteFilterOption) =>
|
||||
left.name.localeCompare(right.name, 'zh-CN'),
|
||||
);
|
||||
if (!cancelled) setSites(normalized);
|
||||
} catch (error) {
|
||||
console.error('Failed to load sites for proxy log filters:', error);
|
||||
@@ -701,7 +710,7 @@ export default function ProxyLogs() {
|
||||
const detailState = detailById[log.id];
|
||||
const detail = detailState?.data;
|
||||
const detailLog: ProxyLogRenderItem = detail ? { ...log, ...detail } : log;
|
||||
const pathMeta = parseProxyLogPathMeta(detailLog.errorMessage);
|
||||
const pathMeta = parseProxyLogPathMeta(detailLog.errorMessage ?? undefined);
|
||||
const billingDetailSummary = detail ? formatBillingDetailSummary(detailLog) : null;
|
||||
const billingProcessLines = detail ? buildBillingProcessLines(detailLog) : [];
|
||||
const downstreamKeySummary = renderDownstreamKeySummary(detailLog);
|
||||
@@ -808,7 +817,7 @@ export default function ProxyLogs() {
|
||||
const detailState = detailById[log.id];
|
||||
const detail = detailState?.data;
|
||||
const detailLog: ProxyLogRenderItem = detail ? { ...log, ...detail } : log;
|
||||
const pathMeta = parseProxyLogPathMeta(detailLog.errorMessage);
|
||||
const pathMeta = parseProxyLogPathMeta(detailLog.errorMessage ?? undefined);
|
||||
const billingDetailSummary = detail ? formatBillingDetailSummary(detailLog) : null;
|
||||
const billingProcessLines = detail ? buildBillingProcessLines(detailLog) : [];
|
||||
const downstreamKeySummary = renderDownstreamKeySummary(detailLog);
|
||||
|
||||
@@ -43,7 +43,7 @@ export default function SiteAnnouncements() {
|
||||
const [serverTimeZone, setServerTimeZone] = useState<string | undefined>(undefined);
|
||||
const [highlightAnnouncementId, setHighlightAnnouncementId] = useState<number | null>(null);
|
||||
const rowRefs = useRef<Map<number, HTMLDivElement>>(new Map());
|
||||
const highlightTimerRef = useRef<number | null>(null);
|
||||
const highlightTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const viewerTimeZone = useMemo(() => readClientTimeZone(), []);
|
||||
const displayTimeZone = resolveSiteAnnouncementTimeZone(viewerTimeZone, serverTimeZone);
|
||||
|
||||
|
||||
@@ -238,7 +238,8 @@ export default function TokenRoutes() {
|
||||
if (candidatesPromiseRef.current && !force) return;
|
||||
const seq = ++candidatesSeqRef.current;
|
||||
candidatesLoadedRef.current = true;
|
||||
const promise = (async () => {
|
||||
let promise!: Promise<void>;
|
||||
promise = (async () => {
|
||||
try {
|
||||
const candidateRows = await api.getModelTokenCandidates();
|
||||
if (candidatesSeqRef.current !== seq) return; // stale
|
||||
|
||||
@@ -44,6 +44,20 @@ type AccountTokenSyncResult = {
|
||||
};
|
||||
};
|
||||
|
||||
type SyncableAccount = {
|
||||
id: number;
|
||||
username?: string | null;
|
||||
accessToken?: string | null;
|
||||
status?: string | null;
|
||||
credentialMode?: string | null;
|
||||
capabilities?: {
|
||||
proxyOnly?: boolean;
|
||||
} | null;
|
||||
site?: {
|
||||
status?: string | null;
|
||||
name?: string | null;
|
||||
} | null;
|
||||
};
|
||||
const isAccountSyncable = (account: any) =>
|
||||
resolveAccountCredentialMode(account) === 'session'
|
||||
&& account?.status === 'active'
|
||||
@@ -162,11 +176,11 @@ export function TokensPanel({ embedded = false, onEmbeddedActionsChange }: Token
|
||||
const nextTokens = tokenRows || [];
|
||||
setTokens(nextTokens);
|
||||
setSelectedTokenIds((current) => current.filter((id) => nextTokens.some((token: any) => token.id === id)));
|
||||
const latestAccounts = accountRows || [];
|
||||
const latestAccounts: SyncableAccount[] = Array.isArray(accountRows) ? accountRows : [];
|
||||
setAccounts(latestAccounts);
|
||||
|
||||
const syncableAccounts = latestAccounts.filter(isAccountSyncable);
|
||||
const hasCurrentSelected = syncableAccounts.some((account) => account.id === syncingAccountId);
|
||||
const hasCurrentSelected = syncableAccounts.some((account: SyncableAccount) => account.id === syncingAccountId);
|
||||
if (!hasCurrentSelected) {
|
||||
setSyncingAccountId(syncableAccounts[0]?.id || 0);
|
||||
}
|
||||
@@ -209,7 +223,7 @@ export function TokensPanel({ embedded = false, onEmbeddedActionsChange }: Token
|
||||
api.getAccountTokenGroups(form.accountId)
|
||||
.then((res: any) => {
|
||||
if (cancelled) return;
|
||||
const groups = Array.isArray(res?.groups)
|
||||
const groups: string[] = Array.isArray(res?.groups)
|
||||
? res.groups.map((item: any) => String(item || '').trim()).filter(Boolean)
|
||||
: [];
|
||||
const normalized = Array.from(new Set(groups));
|
||||
|
||||
@@ -61,7 +61,7 @@ describe('Accounts batch actions', () => {
|
||||
});
|
||||
|
||||
it('refreshes balance for selected accounts through the batch toolbar', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -97,7 +97,7 @@ describe('Accounts batch actions', () => {
|
||||
});
|
||||
|
||||
it('selects an account when clicking the row instead of only the checkbox', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -147,7 +147,7 @@ describe('Accounts batch actions', () => {
|
||||
},
|
||||
]);
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
|
||||
@@ -31,7 +31,7 @@ async function renderAccounts(initialEntry: string) {
|
||||
]);
|
||||
apiMock.getAccountTokens.mockResolvedValue([]);
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
await act(async () => {
|
||||
root = create(
|
||||
<MemoryRouter initialEntries={[initialEntry]}>
|
||||
|
||||
@@ -94,7 +94,7 @@ describe('Accounts edit panel', () => {
|
||||
});
|
||||
|
||||
it('opens edit panel from account row action', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -140,7 +140,7 @@ describe('Accounts edit panel', () => {
|
||||
disabledCount: 0,
|
||||
});
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -205,7 +205,7 @@ describe('Accounts edit panel', () => {
|
||||
return Promise.resolve({ siteId: accountId, siteName: 'Unknown', models: [] });
|
||||
});
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -269,7 +269,7 @@ describe('Accounts edit panel', () => {
|
||||
});
|
||||
apiMock.rebuildRoutes.mockRejectedValue(new Error('rebuild failed'));
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
|
||||
@@ -80,7 +80,7 @@ describe('Accounts mobile actions', () => {
|
||||
});
|
||||
|
||||
it('supports select-all-visible from the mobile toolbar', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -115,7 +115,7 @@ describe('Accounts mobile actions', () => {
|
||||
});
|
||||
|
||||
it('clears only the visible segment selection when toggling mobile select-all off', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
apiMock.getAccounts.mockResolvedValue([
|
||||
{
|
||||
|
||||
@@ -62,7 +62,7 @@ describe('Accounts proxy-only expired state', () => {
|
||||
]);
|
||||
apiMock.getSites.mockResolvedValue([{ id: 10, name: '小呆api', platform: 'new-api', status: 'active' }]);
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
|
||||
@@ -58,7 +58,7 @@ describe('Accounts rebind modal', () => {
|
||||
]);
|
||||
apiMock.getSites.mockResolvedValue([{ id: 10, name: 'Demo Site', platform: 'new-api', status: 'active' }]);
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
|
||||
@@ -75,7 +75,7 @@ describe('Accounts refresh action', () => {
|
||||
apiMock.getSites.mockResolvedValue([{ id: 10, name: 'Demo Site', platform: 'new-api', status: 'active' }]);
|
||||
apiMock.refreshBalance.mockRejectedValueOnce(new Error('无权进行此操作,access token 无效'));
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
|
||||
@@ -67,7 +67,7 @@ describe('Accounts segmented connections view', () => {
|
||||
]);
|
||||
apiMock.getAccountTokens.mockResolvedValue([]);
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -116,7 +116,7 @@ describe('Accounts segmented connections view', () => {
|
||||
]);
|
||||
apiMock.getAccountTokens.mockResolvedValue([]);
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
|
||||
@@ -59,7 +59,7 @@ describe('Accounts tokens embedded header', () => {
|
||||
]);
|
||||
apiMock.getAccountTokens.mockResolvedValue([]);
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
|
||||
@@ -53,7 +53,7 @@ describe('Accounts verify feedback', () => {
|
||||
]);
|
||||
apiMock.verifyToken.mockRejectedValueOnce(new Error('Failed to fetch'));
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
|
||||
@@ -74,7 +74,7 @@ describe('Dashboard performance stat card', () => {
|
||||
modelAnalysis: null,
|
||||
});
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
|
||||
try {
|
||||
await act(async () => {
|
||||
@@ -107,14 +107,14 @@ describe('Dashboard performance stat card', () => {
|
||||
});
|
||||
|
||||
it('shows five skeleton stat cards while dashboard data is still loading', async () => {
|
||||
let resolveDashboard: ((value: Record<string, unknown>) => void) | null = null;
|
||||
let resolveDashboard: ((value: Record<string, unknown>) => void) | undefined;
|
||||
apiMock.getDashboard.mockImplementation(() => (
|
||||
new Promise((resolve) => {
|
||||
resolveDashboard = resolve as (value: Record<string, unknown>) => void;
|
||||
})
|
||||
));
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
|
||||
try {
|
||||
await act(async () => {
|
||||
@@ -133,18 +133,20 @@ describe('Dashboard performance stat card', () => {
|
||||
|
||||
expect(statCards).toHaveLength(5);
|
||||
} finally {
|
||||
resolveDashboard?.({
|
||||
totalBalance: 0,
|
||||
totalUsed: 0,
|
||||
todaySpend: 0,
|
||||
todayReward: 0,
|
||||
activeAccounts: 0,
|
||||
totalAccounts: 0,
|
||||
todayCheckin: { success: 0, total: 0 },
|
||||
proxy24h: { success: 0, total: 0, totalTokens: 0 },
|
||||
performance: { windowSeconds: 60, requestsPerMinute: 0, tokensPerMinute: 0 },
|
||||
modelAnalysis: null,
|
||||
});
|
||||
if (resolveDashboard) {
|
||||
resolveDashboard({
|
||||
totalBalance: 0,
|
||||
totalUsed: 0,
|
||||
todaySpend: 0,
|
||||
todayReward: 0,
|
||||
activeAccounts: 0,
|
||||
totalAccounts: 0,
|
||||
todayCheckin: { success: 0, total: 0 },
|
||||
proxy24h: { success: 0, total: 0, totalTokens: 0 },
|
||||
performance: { windowSeconds: 60, requestsPerMinute: 0, tokensPerMinute: 0 },
|
||||
modelAnalysis: null,
|
||||
});
|
||||
}
|
||||
root?.unmount();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -85,7 +85,7 @@ describe('Dashboard site observability panel', () => {
|
||||
});
|
||||
|
||||
it('renders site availability strips and summary metrics', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
|
||||
try {
|
||||
await act(async () => {
|
||||
|
||||
@@ -69,7 +69,7 @@ describe('Dashboard site speed buttons', () => {
|
||||
});
|
||||
|
||||
it('updates site speed status without imperative document lookups', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
|
||||
try {
|
||||
await act(async () => {
|
||||
@@ -99,7 +99,7 @@ describe('Dashboard site speed buttons', () => {
|
||||
});
|
||||
|
||||
it('updates bulk site speed status without imperative document lookups', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
|
||||
try {
|
||||
await act(async () => {
|
||||
|
||||
@@ -40,7 +40,7 @@ describe('Dashboard hook order', () => {
|
||||
});
|
||||
|
||||
it('keeps hook order stable when switching from loading to loaded render', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
let renderedTree = '';
|
||||
const dashboardData = {
|
||||
totalBalance: 0,
|
||||
|
||||
@@ -180,7 +180,7 @@ describe('DownstreamKeys mobile layout', () => {
|
||||
});
|
||||
|
||||
it('renders mobile cards and supports select-all-visible batch actions', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
|
||||
try {
|
||||
await act(async () => {
|
||||
|
||||
@@ -72,7 +72,7 @@ describe('buildSiteSaveAction', () => {
|
||||
it('throws when edit mode has no site id', () => {
|
||||
expect(() =>
|
||||
buildSiteSaveAction(
|
||||
{ mode: 'edit' },
|
||||
{ mode: 'edit' } as unknown as Parameters<typeof buildSiteSaveAction>[0],
|
||||
{
|
||||
name: 'site-c',
|
||||
url: 'https://c.example.com',
|
||||
@@ -88,10 +88,7 @@ describe('buildSiteSaveAction', () => {
|
||||
});
|
||||
|
||||
it('does not expose deprecated apiKey in site editor state', () => {
|
||||
expect(emptySiteForm()).not.toHaveProperty('apiKey');
|
||||
expect(emptySiteForm().customHeaders).toEqual([emptySiteCustomHeader()]);
|
||||
expect(emptySiteForm().proxyUrl).toBe('');
|
||||
expect(siteFormFromSite({
|
||||
const legacySite = {
|
||||
name: 'site-d',
|
||||
url: 'https://d.example.com',
|
||||
externalCheckinUrl: null,
|
||||
@@ -100,7 +97,12 @@ describe('buildSiteSaveAction', () => {
|
||||
customHeaders: '{"x-site-token":"alpha"}',
|
||||
globalWeight: 1,
|
||||
apiKey: 'sk-legacy-site-key',
|
||||
})).not.toHaveProperty('apiKey');
|
||||
} as unknown as Parameters<typeof siteFormFromSite>[0];
|
||||
|
||||
expect(emptySiteForm()).not.toHaveProperty('apiKey');
|
||||
expect(emptySiteForm().customHeaders).toEqual([emptySiteCustomHeader()]);
|
||||
expect(emptySiteForm().proxyUrl).toBe('');
|
||||
expect(siteFormFromSite(legacySite)).not.toHaveProperty('apiKey');
|
||||
expect(siteFormFromSite({
|
||||
proxyUrl: 'http://127.0.0.1:8080',
|
||||
}).proxyUrl).toBe('http://127.0.0.1:8080');
|
||||
|
||||
@@ -105,7 +105,7 @@ describe('Models mobile layout', () => {
|
||||
});
|
||||
|
||||
it('keeps a mobile filter entry and hides the table-view toggle on small screens', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
|
||||
try {
|
||||
await act(async () => {
|
||||
@@ -130,7 +130,7 @@ describe('Models mobile layout', () => {
|
||||
});
|
||||
|
||||
it('renders stacked account detail cards instead of tables when a mobile card expands', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
|
||||
try {
|
||||
await act(async () => {
|
||||
|
||||
@@ -68,7 +68,7 @@ describe('ProgramLogs mobile layout', () => {
|
||||
});
|
||||
|
||||
it('renders mobile cards instead of only the desktop table on small screens', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
|
||||
try {
|
||||
await act(async () => {
|
||||
|
||||
@@ -116,7 +116,7 @@ describe('Settings factory reset', () => {
|
||||
});
|
||||
|
||||
it('shows a 3 second danger confirmation and clears local state after reset', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
|
||||
@@ -77,7 +77,7 @@ describe('Settings log cleanup schedule', () => {
|
||||
});
|
||||
|
||||
it('saves schedule mode and interval fields together with other schedule settings', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -119,7 +119,7 @@ describe('Settings log cleanup schedule', () => {
|
||||
it('triggers a one-off checkin from the schedule card', async () => {
|
||||
apiMock.triggerCheckinAll.mockResolvedValue({ success: true });
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -150,7 +150,7 @@ describe('Settings log cleanup schedule', () => {
|
||||
});
|
||||
|
||||
it('renders schedule mode controls with modern selects and ghost action styling', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
|
||||
@@ -87,7 +87,7 @@ describe('Settings system proxy', () => {
|
||||
});
|
||||
|
||||
it('saves system proxy url from settings', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -127,7 +127,7 @@ describe('Settings system proxy', () => {
|
||||
});
|
||||
|
||||
it('tests system proxy from settings', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
|
||||
@@ -85,7 +85,7 @@ describe('SiteAnnouncements page', () => {
|
||||
});
|
||||
|
||||
it('loads announcements, highlights the focused row, and clears local rows', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
|
||||
@@ -42,7 +42,7 @@ async function createSiteAndCollectLocation(createdSite: { id: number; platform?
|
||||
apiMock.getSites.mockResolvedValue([]);
|
||||
apiMock.addSite.mockResolvedValue(createdSite);
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
|
||||
@@ -70,8 +70,8 @@ describe('Sites disabled models save', () => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
async function renderSitesEditor() {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
async function renderSitesEditor(): Promise<WebTestRenderer> {
|
||||
let root!: WebTestRenderer;
|
||||
await act(async () => {
|
||||
root = create(
|
||||
<MemoryRouter initialEntries={['/sites']}>
|
||||
@@ -83,7 +83,7 @@ describe('Sites disabled models save', () => {
|
||||
});
|
||||
await flushMicrotasks();
|
||||
|
||||
const editButton = root.root.find((node) => (
|
||||
const editButton = root.root.find((node: ReactTestInstance) => (
|
||||
node.type === 'button'
|
||||
&& typeof node.props.onClick === 'function'
|
||||
&& collectText(node).trim() === '编辑'
|
||||
@@ -100,7 +100,7 @@ describe('Sites disabled models save', () => {
|
||||
it('reports route rebuild failure after saving disabled models from sites editor', async () => {
|
||||
apiMock.rebuildRoutes.mockRejectedValue(new Error('rebuild failed'));
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
root = await renderSitesEditor();
|
||||
|
||||
@@ -129,11 +129,11 @@ describe('Sites disabled models save', () => {
|
||||
apiMock.getSiteAvailableModels.mockRejectedValue(new Error('available models failed'));
|
||||
apiMock.rebuildRoutes.mockResolvedValue({ success: true });
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
root = await renderSitesEditor();
|
||||
|
||||
const saveButton = root.root.find((node) => (
|
||||
const saveButton = root.root.find((node: ReactTestInstance) => (
|
||||
node.type === 'button'
|
||||
&& typeof node.props.onClick === 'function'
|
||||
&& collectText(node).includes('保存禁用列表')
|
||||
@@ -146,18 +146,18 @@ describe('Sites disabled models save', () => {
|
||||
|
||||
expect(apiMock.updateSiteDisabledModels).toHaveBeenCalledWith(1, ['gpt-4o']);
|
||||
} finally {
|
||||
root?.unmount();
|
||||
root.unmount();
|
||||
}
|
||||
});
|
||||
|
||||
it('allows adding a disabled model manually when no available models are discovered', async () => {
|
||||
apiMock.rebuildRoutes.mockResolvedValue({ success: true });
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
root = await renderSitesEditor();
|
||||
|
||||
const manualInput = root.root.find((node) => (
|
||||
const manualInput = root.root.find((node: ReactTestInstance) => (
|
||||
node.type === 'input'
|
||||
&& node.props.placeholder === '输入模型名称,如 gpt-4o'
|
||||
));
|
||||
@@ -166,7 +166,7 @@ describe('Sites disabled models save', () => {
|
||||
manualInput.props.onChange({ target: { value: 'gpt-4o' } });
|
||||
});
|
||||
|
||||
const addButton = root.root.find((node) => (
|
||||
const addButton = root.root.find((node: ReactTestInstance) => (
|
||||
node.type === 'button'
|
||||
&& typeof node.props.onClick === 'function'
|
||||
&& collectText(node).trim() === '添加模型'
|
||||
@@ -177,7 +177,7 @@ describe('Sites disabled models save', () => {
|
||||
});
|
||||
await flushMicrotasks();
|
||||
|
||||
const saveButton = root.root.find((node) => (
|
||||
const saveButton = root.root.find((node: ReactTestInstance) => (
|
||||
node.type === 'button'
|
||||
&& typeof node.props.onClick === 'function'
|
||||
&& collectText(node).includes('保存禁用列表')
|
||||
@@ -190,7 +190,7 @@ describe('Sites disabled models save', () => {
|
||||
|
||||
expect(apiMock.updateSiteDisabledModels).toHaveBeenCalledWith(1, ['gpt-4o']);
|
||||
} finally {
|
||||
root?.unmount();
|
||||
root.unmount();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -58,7 +58,7 @@ describe('Sites edit behavior', () => {
|
||||
value: scrollToMock,
|
||||
});
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -91,7 +91,7 @@ describe('Sites edit behavior', () => {
|
||||
it('restores header add button label after closing add modal', async () => {
|
||||
apiMock.getSites.mockResolvedValue([]);
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
|
||||
@@ -59,7 +59,7 @@ describe('Sites mobile actions', () => {
|
||||
});
|
||||
|
||||
it('supports select-all-visible and preserves the primary site url in mobile cards', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
|
||||
@@ -55,7 +55,7 @@ describe('Sites system proxy bulk actions', () => {
|
||||
});
|
||||
|
||||
it('sends selected site ids to enable system proxy', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -92,7 +92,7 @@ describe('Sites system proxy bulk actions', () => {
|
||||
});
|
||||
|
||||
it('selects a site when clicking the row instead of only the checkbox', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import type { BrandInfo } from '../../components/BrandIcon.js';
|
||||
import type { RouteDecision, RouteDecisionCandidate, RouteMode } from '../../../shared/tokenRouteContract.js';
|
||||
export type { RouteDecision, RouteDecisionCandidate, RouteMode } from '../../../shared/tokenRouteContract.js';
|
||||
|
||||
export type RouteSortBy = 'modelPattern' | 'channelCount';
|
||||
export type RouteSortDir = 'asc' | 'desc';
|
||||
|
||||
@@ -25,8 +25,11 @@ export function useRouteChannels() {
|
||||
}
|
||||
}, []);
|
||||
|
||||
const invalidateChannels = useCallback((routeId: number) => {
|
||||
const invalidateChannels = useCallback((routeId?: number) => {
|
||||
setChannelsByRouteId((prev) => {
|
||||
if (routeId === undefined) {
|
||||
return {};
|
||||
}
|
||||
const next = { ...prev };
|
||||
delete next[routeId];
|
||||
return next;
|
||||
|
||||
@@ -122,7 +122,7 @@ describe('TokenRoutes grouped source models', () => {
|
||||
},
|
||||
]);
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -181,7 +181,7 @@ describe('TokenRoutes grouped source models', () => {
|
||||
]);
|
||||
apiMock.getRouteChannels.mockResolvedValue(channels);
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -241,7 +241,7 @@ describe('TokenRoutes grouped source models', () => {
|
||||
]);
|
||||
apiMock.getRouteChannels.mockResolvedValue(channels);
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -296,7 +296,7 @@ describe('TokenRoutes grouped source models', () => {
|
||||
});
|
||||
apiMock.getRouteChannels.mockResolvedValue([]);
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -336,7 +336,7 @@ describe('TokenRoutes grouped source models', () => {
|
||||
},
|
||||
});
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -382,7 +382,7 @@ describe('TokenRoutes grouped source models', () => {
|
||||
},
|
||||
});
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -449,7 +449,7 @@ describe('TokenRoutes grouped source models', () => {
|
||||
},
|
||||
});
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -497,7 +497,7 @@ describe('TokenRoutes grouped source models', () => {
|
||||
});
|
||||
apiMock.getRouteChannels.mockResolvedValue([]);
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -543,7 +543,7 @@ describe('TokenRoutes grouped source models', () => {
|
||||
},
|
||||
});
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -594,7 +594,7 @@ describe('TokenRoutes grouped source models', () => {
|
||||
},
|
||||
]);
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -640,7 +640,7 @@ describe('TokenRoutes grouped source models', () => {
|
||||
endpointTypesByModel: {},
|
||||
});
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -683,7 +683,7 @@ describe('TokenRoutes grouped source models', () => {
|
||||
endpointTypesByModel: {},
|
||||
});
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -735,7 +735,7 @@ describe('TokenRoutes grouped source models', () => {
|
||||
},
|
||||
]);
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -774,7 +774,7 @@ describe('TokenRoutes grouped source models', () => {
|
||||
},
|
||||
});
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -824,7 +824,7 @@ describe('TokenRoutes grouped source models', () => {
|
||||
},
|
||||
]);
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -855,7 +855,7 @@ describe('TokenRoutes grouped source models', () => {
|
||||
},
|
||||
]);
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -928,7 +928,7 @@ describe('TokenRoutes grouped source models', () => {
|
||||
},
|
||||
});
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -1035,7 +1035,7 @@ describe('TokenRoutes grouped source models', () => {
|
||||
},
|
||||
]);
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -1089,7 +1089,7 @@ describe('TokenRoutes grouped source models', () => {
|
||||
},
|
||||
]);
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -1159,7 +1159,7 @@ describe('TokenRoutes grouped source models', () => {
|
||||
},
|
||||
]);
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -1231,7 +1231,7 @@ describe('TokenRoutes grouped source models', () => {
|
||||
]);
|
||||
apiMock.getRouteChannels.mockResolvedValue([]);
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -1317,7 +1317,7 @@ describe('TokenRoutes grouped source models', () => {
|
||||
]);
|
||||
apiMock.getRouteChannels.mockResolvedValue([]);
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -1412,7 +1412,7 @@ describe('TokenRoutes grouped source models', () => {
|
||||
},
|
||||
]);
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
|
||||
@@ -137,7 +137,7 @@ describe('TokenRoutes mobile layout', () => {
|
||||
});
|
||||
|
||||
it('shows direct mobile actions and reveals the management panel after expansion', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
|
||||
try {
|
||||
await act(async () => {
|
||||
@@ -178,7 +178,7 @@ describe('TokenRoutes mobile layout', () => {
|
||||
});
|
||||
|
||||
it('lets mobile users toggle route enabled state from the summary card', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
|
||||
try {
|
||||
await act(async () => {
|
||||
|
||||
@@ -125,7 +125,7 @@ describe('TokenRoutes mobile actions', () => {
|
||||
});
|
||||
|
||||
it('shows mobile detail expansion and direct management actions', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
|
||||
try {
|
||||
await act(async () => {
|
||||
|
||||
@@ -81,7 +81,7 @@ describe('TokenRoutes refresh decision action', () => {
|
||||
});
|
||||
|
||||
it('passes refreshPricingCatalog and persistSnapshots when user clicks refresh selection probability', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
|
||||
@@ -79,7 +79,7 @@ describe('TokenRoutes routing strategy updates', () => {
|
||||
});
|
||||
|
||||
it('keeps the optimistic routing strategy when refresh fails after a successful save', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -129,7 +129,7 @@ describe('TokenRoutes routing strategy updates', () => {
|
||||
});
|
||||
|
||||
it('supports switching to stable_first and keeps the optimistic label when refresh fails', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
|
||||
@@ -113,7 +113,7 @@ describe('TokenRoutes cached snapshot load', () => {
|
||||
});
|
||||
|
||||
it('shows cached probabilities immediately from getRoutes snapshot data', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
|
||||
@@ -71,7 +71,7 @@ describe('Tokens batch actions', () => {
|
||||
});
|
||||
|
||||
it('deletes selected tokens through the batch toolbar', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
@@ -117,7 +117,7 @@ describe('Tokens batch actions', () => {
|
||||
});
|
||||
|
||||
it('selects a token when clicking the row instead of only the checkbox', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
|
||||
@@ -143,7 +143,7 @@ describe('Tokens edit modal and row selection', () => {
|
||||
});
|
||||
|
||||
it('opens the centered edit modal when editing a token', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = buildRoot();
|
||||
@@ -191,7 +191,7 @@ describe('Tokens edit modal and row selection', () => {
|
||||
});
|
||||
|
||||
it('selects a token when clicking the row body, but not when clicking an action button', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = buildRoot();
|
||||
@@ -232,7 +232,7 @@ describe('Tokens edit modal and row selection', () => {
|
||||
it('does not repeatedly refetch groups when edit-group loading fails once', async () => {
|
||||
apiMock.getAccountTokenGroups.mockRejectedValueOnce(new Error('账号会话可能已过期,请重新登录后再拉取分组'));
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = buildRoot();
|
||||
@@ -274,7 +274,7 @@ describe('Tokens edit modal and row selection', () => {
|
||||
]);
|
||||
apiMock.getAccountTokenValue.mockRejectedValueOnce(new Error('当前仅保存了脱敏令牌,无法展开/复制。请在站点重新生成并同步,或手动更新为完整令牌。'));
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = buildTokensRoot();
|
||||
@@ -356,7 +356,7 @@ describe('Tokens edit modal and row selection', () => {
|
||||
});
|
||||
apiMock.getAccountTokenValue.mockRejectedValueOnce(new Error('当前仅保存了脱敏令牌,无法展开/复制。请在站点重新生成并同步,或手动更新为完整令牌。'));
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = buildTokensRoot();
|
||||
|
||||
@@ -71,7 +71,7 @@ describe('Tokens focus navigation', () => {
|
||||
},
|
||||
]);
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
|
||||
@@ -77,7 +77,7 @@ describe('Tokens mobile actions', () => {
|
||||
});
|
||||
|
||||
it('supports select-all-visible and expandable mobile token details', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
|
||||
@@ -23,7 +23,7 @@ function LocationProbe() {
|
||||
|
||||
describe('Tokens legacy route redirect', () => {
|
||||
it('redirects /tokens to the merged accounts tokens segment', async () => {
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
await act(async () => {
|
||||
root = create(
|
||||
<ToastProvider>
|
||||
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
type WebTestRenderer = import('react-test-renderer').ReactTestRenderer;
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["src/web/**/*.ts", "src/web/**/*.tsx"],
|
||||
"exclude": ["src/web/**/*.test.ts", "src/web/**/*.test.tsx"]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["src/web/**/*.test.ts", "src/web/**/*.test.tsx", "src/web/**/*.d.ts"]
|
||||
}
|
||||
Reference in New Issue
Block a user