mirror of
https://github.com/YuzuZensai/VRC-Circle.git
synced 2026-07-15 14:27:48 +00:00
🧪 test: Add unit tests
This commit is contained in:
@@ -16,7 +16,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
check: [lint, typecheck, format]
|
||||
check: [lint, typecheck, format, test]
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
|
||||
+5
-2
@@ -10,11 +10,13 @@
|
||||
"dev": "electron-vite dev",
|
||||
"build": "pnpm run check && electron-vite build",
|
||||
"preview": "electron-vite preview",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"start": "electron-vite preview",
|
||||
"typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false",
|
||||
"typecheck:web": "tsc --noEmit -p tsconfig.web.json --composite false",
|
||||
"typecheck": "pnpm run typecheck:node && pnpm run typecheck:web",
|
||||
"check": "pnpm run lint && pnpm run typecheck && pnpm run format",
|
||||
"check": "pnpm run lint && pnpm run typecheck && pnpm run format && pnpm run test",
|
||||
"lint": "eslint .",
|
||||
"format": "prettier --check .",
|
||||
"format:fix": "prettier --write .",
|
||||
@@ -61,7 +63,8 @@
|
||||
"tailwindcss": "^4.3.1",
|
||||
"typescript": "^5.7.3",
|
||||
"typescript-eslint": "^8.62.0",
|
||||
"vite": "^6.0.7"
|
||||
"vite": "^6.0.7",
|
||||
"vitest": "^4.1.9"
|
||||
},
|
||||
"packageManager": "pnpm@10.33.0"
|
||||
}
|
||||
|
||||
Generated
+242
@@ -108,6 +108,9 @@ importers:
|
||||
vite:
|
||||
specifier: ^6.0.7
|
||||
version: 6.4.3(@types/node@22.20.0)(jiti@2.7.0)(lightningcss@1.32.0)
|
||||
vitest:
|
||||
specifier: ^4.1.9
|
||||
version: 4.1.9(@types/node@22.20.0)(vite@6.4.3(@types/node@22.20.0)(jiti@2.7.0)(lightningcss@1.32.0))
|
||||
|
||||
packages:
|
||||
|
||||
@@ -948,6 +951,9 @@ packages:
|
||||
resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
'@standard-schema/spec@1.1.0':
|
||||
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
|
||||
|
||||
'@szmarczak/http-timer@4.0.6':
|
||||
resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -1065,9 +1071,15 @@ packages:
|
||||
'@types/cacheable-request@6.0.3':
|
||||
resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==}
|
||||
|
||||
'@types/chai@5.2.3':
|
||||
resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
|
||||
|
||||
'@types/debug@4.1.13':
|
||||
resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==}
|
||||
|
||||
'@types/deep-eql@4.0.2':
|
||||
resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
|
||||
|
||||
'@types/esrecurse@4.3.1':
|
||||
resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==}
|
||||
|
||||
@@ -1183,6 +1195,35 @@ packages:
|
||||
peerDependencies:
|
||||
vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
|
||||
|
||||
'@vitest/expect@4.1.9':
|
||||
resolution: {integrity: sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==}
|
||||
|
||||
'@vitest/mocker@4.1.9':
|
||||
resolution: {integrity: sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==}
|
||||
peerDependencies:
|
||||
msw: ^2.4.9
|
||||
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||
peerDependenciesMeta:
|
||||
msw:
|
||||
optional: true
|
||||
vite:
|
||||
optional: true
|
||||
|
||||
'@vitest/pretty-format@4.1.9':
|
||||
resolution: {integrity: sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==}
|
||||
|
||||
'@vitest/runner@4.1.9':
|
||||
resolution: {integrity: sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==}
|
||||
|
||||
'@vitest/snapshot@4.1.9':
|
||||
resolution: {integrity: sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==}
|
||||
|
||||
'@vitest/spy@4.1.9':
|
||||
resolution: {integrity: sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==}
|
||||
|
||||
'@vitest/utils@4.1.9':
|
||||
resolution: {integrity: sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==}
|
||||
|
||||
'@xmldom/xmldom@0.9.10':
|
||||
resolution: {integrity: sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw==}
|
||||
engines: {node: '>=14.6'}
|
||||
@@ -1277,6 +1318,10 @@ packages:
|
||||
resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==}
|
||||
engines: {node: '>=0.8'}
|
||||
|
||||
assertion-error@2.0.1:
|
||||
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
astral-regex@2.0.0:
|
||||
resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -1384,6 +1429,10 @@ packages:
|
||||
caniuse-lite@1.0.30001799:
|
||||
resolution: {integrity: sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==}
|
||||
|
||||
chai@6.2.2:
|
||||
resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
chalk@4.1.2:
|
||||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -1631,6 +1680,9 @@ packages:
|
||||
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
es-module-lexer@2.2.0:
|
||||
resolution: {integrity: sha512-3lGxdTXCLfe1MYfTz1y2ksAAUM4NAOP6rPEjxGJVKO7TZ5+tvHCaQWGpC4Y3IXvW3ece0Cz1cIP4FWBxOnGCTQ==}
|
||||
|
||||
es-object-atoms@1.1.2:
|
||||
resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -1709,10 +1761,17 @@ packages:
|
||||
resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
|
||||
engines: {node: '>=4.0'}
|
||||
|
||||
estree-walker@3.0.3:
|
||||
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
|
||||
|
||||
esutils@2.0.3:
|
||||
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
expect-type@1.4.0:
|
||||
resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
exponential-backoff@3.1.3:
|
||||
resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==}
|
||||
|
||||
@@ -2392,6 +2451,10 @@ packages:
|
||||
resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
obug@2.1.3:
|
||||
resolution: {integrity: sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==}
|
||||
engines: {node: '>=12.20.0'}
|
||||
|
||||
once@1.4.0:
|
||||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||
|
||||
@@ -2442,6 +2505,9 @@ packages:
|
||||
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
|
||||
engines: {node: '>=16 || 14 >=14.18'}
|
||||
|
||||
pathe@2.0.3:
|
||||
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
|
||||
|
||||
pe-library@0.4.1:
|
||||
resolution: {integrity: sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw==}
|
||||
engines: {node: '>=12', npm: '>=6'}
|
||||
@@ -2642,6 +2708,9 @@ packages:
|
||||
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
siginfo@2.0.0:
|
||||
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
|
||||
|
||||
signal-exit@3.0.7:
|
||||
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
|
||||
|
||||
@@ -2687,10 +2756,16 @@ packages:
|
||||
resolution: {integrity: sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==}
|
||||
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
|
||||
|
||||
stackback@0.0.2:
|
||||
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
|
||||
|
||||
stat-mode@1.0.0:
|
||||
resolution: {integrity: sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
std-env@4.1.0:
|
||||
resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==}
|
||||
|
||||
string-width@4.2.3:
|
||||
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -2743,10 +2818,21 @@ packages:
|
||||
tiny-typed-emitter@2.1.0:
|
||||
resolution: {integrity: sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==}
|
||||
|
||||
tinybench@2.9.0:
|
||||
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
|
||||
|
||||
tinyexec@1.2.4:
|
||||
resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
tinyglobby@0.2.17:
|
||||
resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
tinyrainbow@3.1.0:
|
||||
resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
tmp-promise@3.0.3:
|
||||
resolution: {integrity: sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==}
|
||||
|
||||
@@ -2875,6 +2961,47 @@ packages:
|
||||
yaml:
|
||||
optional: true
|
||||
|
||||
vitest@4.1.9:
|
||||
resolution: {integrity: sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==}
|
||||
engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@edge-runtime/vm': '*'
|
||||
'@opentelemetry/api': ^1.9.0
|
||||
'@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0
|
||||
'@vitest/browser-playwright': 4.1.9
|
||||
'@vitest/browser-preview': 4.1.9
|
||||
'@vitest/browser-webdriverio': 4.1.9
|
||||
'@vitest/coverage-istanbul': 4.1.9
|
||||
'@vitest/coverage-v8': 4.1.9
|
||||
'@vitest/ui': 4.1.9
|
||||
happy-dom: '*'
|
||||
jsdom: '*'
|
||||
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||
peerDependenciesMeta:
|
||||
'@edge-runtime/vm':
|
||||
optional: true
|
||||
'@opentelemetry/api':
|
||||
optional: true
|
||||
'@types/node':
|
||||
optional: true
|
||||
'@vitest/browser-playwright':
|
||||
optional: true
|
||||
'@vitest/browser-preview':
|
||||
optional: true
|
||||
'@vitest/browser-webdriverio':
|
||||
optional: true
|
||||
'@vitest/coverage-istanbul':
|
||||
optional: true
|
||||
'@vitest/coverage-v8':
|
||||
optional: true
|
||||
'@vitest/ui':
|
||||
optional: true
|
||||
happy-dom:
|
||||
optional: true
|
||||
jsdom:
|
||||
optional: true
|
||||
|
||||
void-elements@3.1.0:
|
||||
resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -2890,6 +3017,11 @@ packages:
|
||||
engines: {node: '>= 8'}
|
||||
hasBin: true
|
||||
|
||||
why-is-node-running@2.3.0:
|
||||
resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
|
||||
engines: {node: '>=8'}
|
||||
hasBin: true
|
||||
|
||||
wide-align@1.1.5:
|
||||
resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==}
|
||||
|
||||
@@ -3624,6 +3756,8 @@ snapshots:
|
||||
|
||||
'@sindresorhus/is@4.6.0': {}
|
||||
|
||||
'@standard-schema/spec@1.1.0': {}
|
||||
|
||||
'@szmarczak/http-timer@4.0.6':
|
||||
dependencies:
|
||||
defer-to-connect: 2.0.1
|
||||
@@ -3726,10 +3860,17 @@ snapshots:
|
||||
'@types/node': 22.20.0
|
||||
'@types/responselike': 1.0.3
|
||||
|
||||
'@types/chai@5.2.3':
|
||||
dependencies:
|
||||
'@types/deep-eql': 4.0.2
|
||||
assertion-error: 2.0.1
|
||||
|
||||
'@types/debug@4.1.13':
|
||||
dependencies:
|
||||
'@types/ms': 2.1.0
|
||||
|
||||
'@types/deep-eql@4.0.2': {}
|
||||
|
||||
'@types/esrecurse@4.3.1': {}
|
||||
|
||||
'@types/estree@1.0.9': {}
|
||||
@@ -3889,6 +4030,47 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@vitest/expect@4.1.9':
|
||||
dependencies:
|
||||
'@standard-schema/spec': 1.1.0
|
||||
'@types/chai': 5.2.3
|
||||
'@vitest/spy': 4.1.9
|
||||
'@vitest/utils': 4.1.9
|
||||
chai: 6.2.2
|
||||
tinyrainbow: 3.1.0
|
||||
|
||||
'@vitest/mocker@4.1.9(vite@6.4.3(@types/node@22.20.0)(jiti@2.7.0)(lightningcss@1.32.0))':
|
||||
dependencies:
|
||||
'@vitest/spy': 4.1.9
|
||||
estree-walker: 3.0.3
|
||||
magic-string: 0.30.21
|
||||
optionalDependencies:
|
||||
vite: 6.4.3(@types/node@22.20.0)(jiti@2.7.0)(lightningcss@1.32.0)
|
||||
|
||||
'@vitest/pretty-format@4.1.9':
|
||||
dependencies:
|
||||
tinyrainbow: 3.1.0
|
||||
|
||||
'@vitest/runner@4.1.9':
|
||||
dependencies:
|
||||
'@vitest/utils': 4.1.9
|
||||
pathe: 2.0.3
|
||||
|
||||
'@vitest/snapshot@4.1.9':
|
||||
dependencies:
|
||||
'@vitest/pretty-format': 4.1.9
|
||||
'@vitest/utils': 4.1.9
|
||||
magic-string: 0.30.21
|
||||
pathe: 2.0.3
|
||||
|
||||
'@vitest/spy@4.1.9': {}
|
||||
|
||||
'@vitest/utils@4.1.9':
|
||||
dependencies:
|
||||
'@vitest/pretty-format': 4.1.9
|
||||
convert-source-map: 2.0.0
|
||||
tinyrainbow: 3.1.0
|
||||
|
||||
'@xmldom/xmldom@0.9.10': {}
|
||||
|
||||
abbrev@1.1.1: {}
|
||||
@@ -4027,6 +4209,8 @@ snapshots:
|
||||
assert-plus@1.0.0:
|
||||
optional: true
|
||||
|
||||
assertion-error@2.0.1: {}
|
||||
|
||||
astral-regex@2.0.0:
|
||||
optional: true
|
||||
|
||||
@@ -4175,6 +4359,8 @@ snapshots:
|
||||
|
||||
caniuse-lite@1.0.30001799: {}
|
||||
|
||||
chai@6.2.2: {}
|
||||
|
||||
chalk@4.1.2:
|
||||
dependencies:
|
||||
ansi-styles: 4.3.0
|
||||
@@ -4461,6 +4647,8 @@ snapshots:
|
||||
|
||||
es-errors@1.3.0: {}
|
||||
|
||||
es-module-lexer@2.2.0: {}
|
||||
|
||||
es-object-atoms@1.1.2:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
@@ -4613,8 +4801,14 @@ snapshots:
|
||||
|
||||
estraverse@5.3.0: {}
|
||||
|
||||
estree-walker@3.0.3:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.9
|
||||
|
||||
esutils@2.0.3: {}
|
||||
|
||||
expect-type@1.4.0: {}
|
||||
|
||||
exponential-backoff@3.1.3: {}
|
||||
|
||||
extract-zip@2.0.1:
|
||||
@@ -5281,6 +5475,8 @@ snapshots:
|
||||
object-keys@1.1.1:
|
||||
optional: true
|
||||
|
||||
obug@2.1.3: {}
|
||||
|
||||
once@1.4.0:
|
||||
dependencies:
|
||||
wrappy: 1.0.2
|
||||
@@ -5337,6 +5533,8 @@ snapshots:
|
||||
lru-cache: 10.4.3
|
||||
minipass: 7.1.3
|
||||
|
||||
pathe@2.0.3: {}
|
||||
|
||||
pe-library@0.4.1: {}
|
||||
|
||||
pend@1.2.0: {}
|
||||
@@ -5561,6 +5759,8 @@ snapshots:
|
||||
|
||||
shebang-regex@3.0.0: {}
|
||||
|
||||
siginfo@2.0.0: {}
|
||||
|
||||
signal-exit@3.0.7: {}
|
||||
|
||||
signal-exit@4.1.0: {}
|
||||
@@ -5607,8 +5807,12 @@ snapshots:
|
||||
dependencies:
|
||||
minipass: 3.3.6
|
||||
|
||||
stackback@0.0.2: {}
|
||||
|
||||
stat-mode@1.0.0: {}
|
||||
|
||||
std-env@4.1.0: {}
|
||||
|
||||
string-width@4.2.3:
|
||||
dependencies:
|
||||
emoji-regex: 8.0.0
|
||||
@@ -5675,11 +5879,17 @@ snapshots:
|
||||
|
||||
tiny-typed-emitter@2.1.0: {}
|
||||
|
||||
tinybench@2.9.0: {}
|
||||
|
||||
tinyexec@1.2.4: {}
|
||||
|
||||
tinyglobby@0.2.17:
|
||||
dependencies:
|
||||
fdir: 6.5.0(picomatch@4.0.4)
|
||||
picomatch: 4.0.4
|
||||
|
||||
tinyrainbow@3.1.0: {}
|
||||
|
||||
tmp-promise@3.0.3:
|
||||
dependencies:
|
||||
tmp: 0.2.7
|
||||
@@ -5776,6 +5986,33 @@ snapshots:
|
||||
jiti: 2.7.0
|
||||
lightningcss: 1.32.0
|
||||
|
||||
vitest@4.1.9(@types/node@22.20.0)(vite@6.4.3(@types/node@22.20.0)(jiti@2.7.0)(lightningcss@1.32.0)):
|
||||
dependencies:
|
||||
'@vitest/expect': 4.1.9
|
||||
'@vitest/mocker': 4.1.9(vite@6.4.3(@types/node@22.20.0)(jiti@2.7.0)(lightningcss@1.32.0))
|
||||
'@vitest/pretty-format': 4.1.9
|
||||
'@vitest/runner': 4.1.9
|
||||
'@vitest/snapshot': 4.1.9
|
||||
'@vitest/spy': 4.1.9
|
||||
'@vitest/utils': 4.1.9
|
||||
es-module-lexer: 2.2.0
|
||||
expect-type: 1.4.0
|
||||
magic-string: 0.30.21
|
||||
obug: 2.1.3
|
||||
pathe: 2.0.3
|
||||
picomatch: 4.0.4
|
||||
std-env: 4.1.0
|
||||
tinybench: 2.9.0
|
||||
tinyexec: 1.2.4
|
||||
tinyglobby: 0.2.17
|
||||
tinyrainbow: 3.1.0
|
||||
vite: 6.4.3(@types/node@22.20.0)(jiti@2.7.0)(lightningcss@1.32.0)
|
||||
why-is-node-running: 2.3.0
|
||||
optionalDependencies:
|
||||
'@types/node': 22.20.0
|
||||
transitivePeerDependencies:
|
||||
- msw
|
||||
|
||||
void-elements@3.1.0: {}
|
||||
|
||||
vrchat@2.21.8-nightly.16:
|
||||
@@ -5799,6 +6036,11 @@ snapshots:
|
||||
dependencies:
|
||||
isexe: 2.0.0
|
||||
|
||||
why-is-node-running@2.3.0:
|
||||
dependencies:
|
||||
siginfo: 2.0.0
|
||||
stackback: 0.0.2
|
||||
|
||||
wide-align@1.1.5:
|
||||
dependencies:
|
||||
string-width: 4.2.3
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import { TtlCache } from "../src/main/cache/cache";
|
||||
|
||||
const policy = { ttl: 1000, staleWhileRevalidate: 5000 };
|
||||
|
||||
beforeEach(() => vi.useFakeTimers());
|
||||
afterEach(() => vi.useRealTimers());
|
||||
|
||||
describe("TtlCache.get — fresh vs miss", () => {
|
||||
it("calls the loader once and serves the cached value while fresh", async () => {
|
||||
const cache = new TtlCache();
|
||||
const loader = vi.fn().mockResolvedValue("v1");
|
||||
|
||||
expect(await cache.get("k", policy, loader)).toBe("v1");
|
||||
expect(await cache.get("k", policy, loader)).toBe("v1");
|
||||
expect(loader).toHaveBeenCalledTimes(1);
|
||||
expect(cache.stats().hits).toBe(1);
|
||||
expect(cache.stats().misses).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("TtlCache.get — single-flight de-dup", () => {
|
||||
it("shares one in-flight loader across concurrent gets of the same key", async () => {
|
||||
const cache = new TtlCache();
|
||||
let resolve!: (v: string) => void;
|
||||
const loader = vi.fn(() => new Promise<string>((r) => (resolve = r)));
|
||||
|
||||
const a = cache.get("k", policy, loader);
|
||||
const b = cache.get("k", policy, loader);
|
||||
resolve("shared");
|
||||
|
||||
expect(await a).toBe("shared");
|
||||
expect(await b).toBe("shared");
|
||||
expect(loader).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("TtlCache.get — stale-while-revalidate", () => {
|
||||
it("returns the stale value and revalidates in the background past ttl", async () => {
|
||||
const cache = new TtlCache();
|
||||
const loader = vi.fn().mockResolvedValueOnce("v1").mockResolvedValueOnce("v2");
|
||||
|
||||
expect(await cache.get("k", policy, loader)).toBe("v1");
|
||||
|
||||
vi.setSystemTime(Date.now() + 2000);
|
||||
expect(await cache.get("k", policy, loader)).toBe("v1");
|
||||
await vi.waitFor(() => expect(loader).toHaveBeenCalledTimes(2));
|
||||
expect(await cache.get("k", policy, loader)).toBe("v2");
|
||||
});
|
||||
|
||||
it("reloads synchronously once past the hard expiry", async () => {
|
||||
const cache = new TtlCache();
|
||||
const loader = vi.fn().mockResolvedValueOnce("v1").mockResolvedValueOnce("v2");
|
||||
|
||||
expect(await cache.get("k", policy, loader)).toBe("v1");
|
||||
vi.setSystemTime(Date.now() + 7000);
|
||||
expect(await cache.get("k", policy, loader)).toBe("v2");
|
||||
});
|
||||
});
|
||||
|
||||
describe("TtlCache.get — rate-limit backoff", () => {
|
||||
it("after a 429 loader failure, serves stale and suppresses revalidation", async () => {
|
||||
const cache = new TtlCache();
|
||||
const loader = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce("v1")
|
||||
.mockRejectedValueOnce({ status: 429, response: { headers: { "retry-after": "8" } } });
|
||||
|
||||
expect(await cache.get("k", policy, loader)).toBe("v1");
|
||||
|
||||
vi.setSystemTime(Date.now() + 2000);
|
||||
await cache.get("k", policy, loader);
|
||||
await vi.waitFor(() => expect(loader).toHaveBeenCalledTimes(2));
|
||||
|
||||
await cache.get("k", policy, loader);
|
||||
expect(loader).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("throws 429 on a cold miss while rate limited", async () => {
|
||||
const cache = new TtlCache();
|
||||
const failing = vi.fn().mockRejectedValue({ status: 429 });
|
||||
|
||||
await expect(cache.get("a", policy, failing)).rejects.toMatchObject({ status: 429 });
|
||||
await expect(cache.get("b", policy, vi.fn())).rejects.toMatchObject({ status: 429 });
|
||||
});
|
||||
});
|
||||
|
||||
describe("TtlCache.set / patch / invalidate / clear", () => {
|
||||
it("patch merges into an existing entry", async () => {
|
||||
const cache = new TtlCache();
|
||||
cache.set("k", { a: 1, b: 2 }, policy);
|
||||
cache.patch("k", { b: 3 });
|
||||
const val = await cache.get("k", policy, async () => ({ a: 0, b: 0 }));
|
||||
expect(val).toEqual({ a: 1, b: 3 });
|
||||
});
|
||||
|
||||
it("patch is a no-op on a missing key", () => {
|
||||
const cache = new TtlCache();
|
||||
cache.patch("missing", { x: 1 });
|
||||
expect(cache.stats().entries).toBe(0);
|
||||
});
|
||||
|
||||
it("invalidate forces the next get to reload", async () => {
|
||||
const cache = new TtlCache();
|
||||
const loader = vi.fn().mockResolvedValueOnce("v1").mockResolvedValueOnce("v2");
|
||||
await cache.get("k", policy, loader);
|
||||
cache.invalidate("k");
|
||||
expect(await cache.get("k", policy, loader)).toBe("v2");
|
||||
});
|
||||
|
||||
it("clear empties the store", async () => {
|
||||
const cache = new TtlCache();
|
||||
cache.set("k", "v", policy);
|
||||
cache.clear();
|
||||
expect(cache.stats().entries).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("TtlCache — LRU eviction", () => {
|
||||
it("evicts least-recently-accessed entries past maxEntries", async () => {
|
||||
const cache = new TtlCache(2);
|
||||
cache.set("a", 1, policy);
|
||||
vi.setSystemTime(Date.now() + 10);
|
||||
cache.set("b", 2, policy);
|
||||
vi.setSystemTime(Date.now() + 10);
|
||||
await cache.get("a", policy, async () => 1);
|
||||
vi.setSystemTime(Date.now() + 10);
|
||||
cache.set("c", 3, policy);
|
||||
|
||||
expect(cache.stats().entries).toBe(2);
|
||||
const loader = vi.fn().mockResolvedValue(99);
|
||||
await cache.get("b", policy, loader);
|
||||
expect(loader).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,70 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { httpStatusOf, isTransientError, toApiError } from "../src/main/vrchat/errors";
|
||||
|
||||
describe("httpStatusOf", () => {
|
||||
it("reads status from the many shapes vrchat/SDK errors take", () => {
|
||||
expect(httpStatusOf({ status: 404 })).toBe(404);
|
||||
expect(httpStatusOf({ statusCode: 401 })).toBe(401);
|
||||
expect(httpStatusOf({ status_code: 500 })).toBe(500);
|
||||
expect(httpStatusOf({ response: { status: 429 } })).toBe(429);
|
||||
expect(httpStatusOf({ error: { status_code: 403 } })).toBe(403);
|
||||
});
|
||||
|
||||
it("returns undefined for a shapeless error", () => {
|
||||
expect(httpStatusOf(null)).toBeUndefined();
|
||||
expect(httpStatusOf({})).toBeUndefined();
|
||||
expect(httpStatusOf("boom")).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("isTransientError", () => {
|
||||
it("treats 429 and 5xx as transient", () => {
|
||||
expect(isTransientError({ status: 429 })).toBe(true);
|
||||
expect(isTransientError({ status: 500 })).toBe(true);
|
||||
expect(isTransientError({ status: 503 })).toBe(true);
|
||||
});
|
||||
|
||||
it("treats 4xx (other than 429) as permanent", () => {
|
||||
expect(isTransientError({ status: 404 })).toBe(false);
|
||||
expect(isTransientError({ status: 401 })).toBe(false);
|
||||
});
|
||||
|
||||
it("treats a status-less error (network failure) as transient", () => {
|
||||
expect(isTransientError(new Error("ECONNRESET"))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("toApiError", () => {
|
||||
it("maps well-known statuses to codes", () => {
|
||||
expect(toApiError({ status: 401 }).code).toBe("unauthorized");
|
||||
expect(toApiError({ status: 404 }).code).toBe("not_found");
|
||||
expect(toApiError({ status: 429 }).code).toBe("rate_limited");
|
||||
});
|
||||
|
||||
it("classifies status-less network-ish messages as network", () => {
|
||||
expect(toApiError({ message: "fetch failed: ENOTFOUND" }).code).toBe("network");
|
||||
});
|
||||
|
||||
it("passes through a pre-coded error and its 2FA methods", () => {
|
||||
const e = toApiError({ code: "two_factor_required", methods: ["totp"] });
|
||||
expect(e.code).toBe("two_factor_required");
|
||||
expect(e.methods).toEqual(["totp"]);
|
||||
});
|
||||
|
||||
it("uses a friendly rate-limit message and parses retry-after", () => {
|
||||
const e = toApiError({ status: 429, response: { headers: { "retry-after": "12" } } });
|
||||
expect(e.code).toBe("rate_limited");
|
||||
expect(e.retryAfter).toBe(12);
|
||||
expect(e.message).toMatch(/rate limit/i);
|
||||
});
|
||||
|
||||
it("gives a temporary-unavailable message for 5xx", () => {
|
||||
expect(toApiError({ status: 502 }).message).toMatch(/temporarily unavailable/i);
|
||||
});
|
||||
|
||||
it("falls back to unknown with the original message", () => {
|
||||
const e = toApiError({ message: "weird" });
|
||||
expect(e.code).toBe("unknown");
|
||||
expect(e.message).toBe("weird");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,147 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import {
|
||||
trustRankFromTags,
|
||||
toIso,
|
||||
toUserProfile,
|
||||
toWorld,
|
||||
toAvatar,
|
||||
} from "../src/main/vrchat/mappers";
|
||||
|
||||
describe("trustRankFromTags", () => {
|
||||
it("maps each trust tag to the rank one level below its label", () => {
|
||||
expect(trustRankFromTags([])).toBe("visitor");
|
||||
expect(trustRankFromTags(["system_trust_basic"])).toBe("new");
|
||||
expect(trustRankFromTags(["system_trust_known"])).toBe("user");
|
||||
expect(trustRankFromTags(["system_trust_trusted"])).toBe("known");
|
||||
expect(trustRankFromTags(["system_trust_veteran"])).toBe("trusted");
|
||||
expect(trustRankFromTags(["system_trust_legend"])).toBe("veteran");
|
||||
});
|
||||
|
||||
it("defaults to visitor when no trust tag present", () => {
|
||||
expect(trustRankFromTags(["language_eng", "something_else"])).toBe("visitor");
|
||||
expect(trustRankFromTags()).toBe("visitor");
|
||||
});
|
||||
|
||||
it("picks the highest rank when several trust tags are present", () => {
|
||||
expect(trustRankFromTags(["system_trust_basic", "system_trust_legend"])).toBe("veteran");
|
||||
});
|
||||
|
||||
it("troll tags override everything", () => {
|
||||
expect(trustRankFromTags(["system_trust_legend", "system_troll"])).toBe("troll");
|
||||
expect(trustRankFromTags(["system_probable_troll"])).toBe("troll");
|
||||
});
|
||||
});
|
||||
|
||||
describe("toIso", () => {
|
||||
it("returns undefined for empty/null values", () => {
|
||||
expect(toIso(undefined)).toBeUndefined();
|
||||
expect(toIso(null)).toBeUndefined();
|
||||
expect(toIso("")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("passes Date objects through to ISO", () => {
|
||||
const d = new Date("2024-01-02T03:04:05.000Z");
|
||||
expect(toIso(d)).toBe("2024-01-02T03:04:05.000Z");
|
||||
});
|
||||
|
||||
it("normalizes parseable date strings to ISO", () => {
|
||||
expect(toIso("2024-01-02T03:04:05Z")).toBe("2024-01-02T03:04:05.000Z");
|
||||
});
|
||||
|
||||
it("returns undefined for unparseable strings", () => {
|
||||
expect(toIso("not a date")).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("toUserProfile", () => {
|
||||
const base = { id: "usr_1", displayName: "Alice" };
|
||||
|
||||
it("fills defaults for missing fields", () => {
|
||||
const p = toUserProfile({ ...base }, "self");
|
||||
expect(p.bio).toBe("");
|
||||
expect(p.tags).toEqual([]);
|
||||
expect(p.badges).toEqual([]);
|
||||
expect(p.isFriend).toBe(false);
|
||||
expect(p.status).toBe("offline");
|
||||
expect(p.trustRank).toBe("visitor");
|
||||
});
|
||||
|
||||
it("sets isSelf when the id matches selfId", () => {
|
||||
expect(toUserProfile({ ...base, id: "self" }, "self").isSelf).toBe(true);
|
||||
expect(toUserProfile({ ...base, id: "other" }, "self").isSelf).toBe(false);
|
||||
});
|
||||
|
||||
it("accepts both camelCase and snake_case timestamp aliases", () => {
|
||||
const camel = toUserProfile({ ...base, lastLogin: "2024-01-01T00:00:00Z" }, "self");
|
||||
const snake = toUserProfile({ ...base, last_login: "2024-01-01T00:00:00Z" }, "self");
|
||||
expect(camel.lastLogin).toBe("2024-01-01T00:00:00.000Z");
|
||||
expect(snake.lastLogin).toBe("2024-01-01T00:00:00.000Z");
|
||||
});
|
||||
|
||||
it("extracts languages from language_ tags", () => {
|
||||
const p = toUserProfile(
|
||||
{ ...base, tags: ["language_eng", "language_jpn", "system_trust_known"] },
|
||||
"self",
|
||||
);
|
||||
expect(p.languages).toEqual(["eng", "jpn"]);
|
||||
});
|
||||
|
||||
it("normalizes an unknown status to offline", () => {
|
||||
expect(toUserProfile({ ...base, status: "bogus" }, "self").status).toBe("offline");
|
||||
expect(toUserProfile({ ...base, status: "join me" }, "self").status).toBe("join me");
|
||||
});
|
||||
|
||||
it("blanks a falsy note to undefined", () => {
|
||||
expect(toUserProfile({ ...base, note: "" }, "self").note).toBeUndefined();
|
||||
expect(toUserProfile({ ...base, note: "hi" }, "self").note).toBe("hi");
|
||||
});
|
||||
});
|
||||
|
||||
describe("toWorld", () => {
|
||||
const base = { id: "wrld_1", authorId: "usr_1" };
|
||||
|
||||
it("blanks vrchat's '???' placeholder for hidden name/author", () => {
|
||||
const w = toWorld({ ...base, name: "???", authorName: "???" } as never);
|
||||
expect(w.name).toBe("");
|
||||
expect(w.authorName).toBe("");
|
||||
});
|
||||
|
||||
it("marks a world detailed only when it carries visit counts", () => {
|
||||
expect(toWorld({ ...base, name: "W", visits: 10 } as never).detailed).toBe(true);
|
||||
expect(toWorld({ ...base, name: "W" } as never).detailed).toBe(false);
|
||||
});
|
||||
|
||||
it("derives platforms from unity packages", () => {
|
||||
const w = toWorld({
|
||||
...base,
|
||||
name: "W",
|
||||
unityPackages: [{ platform: "standalonewindows" }, { platform: "android" }],
|
||||
} as never);
|
||||
expect(w.platforms).toEqual({ pc: true, android: true });
|
||||
});
|
||||
|
||||
it("defaults releaseStatus to private", () => {
|
||||
expect(toWorld({ ...base, name: "W" } as never).releaseStatus).toBe("private");
|
||||
});
|
||||
});
|
||||
|
||||
describe("toAvatar", () => {
|
||||
const base = { id: "avtr_1", name: "Av" };
|
||||
|
||||
it("treats a 'None' performance rating as no build for that platform", () => {
|
||||
const a = toAvatar({
|
||||
...base,
|
||||
performance: { standalonewindows: "Excellent", android: "None" },
|
||||
});
|
||||
expect(a.platforms).toEqual({ pc: true, android: false });
|
||||
expect(a.performance.pc).toBe("Excellent");
|
||||
expect(a.performance.android).toBeUndefined();
|
||||
});
|
||||
|
||||
it("defaults releaseStatus and empty collections", () => {
|
||||
const a = toAvatar({ ...base });
|
||||
expect(a.releaseStatus).toBe("private");
|
||||
expect(a.tags).toEqual([]);
|
||||
expect(a.favorites).toBe(0);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,115 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import {
|
||||
isOnline,
|
||||
locationLabel,
|
||||
presenceOf,
|
||||
avatarOf,
|
||||
bannerOf,
|
||||
regionFlag,
|
||||
languageLabel,
|
||||
} from "../src/renderer/src/lib/vrchat";
|
||||
|
||||
describe("isOnline", () => {
|
||||
it("counts self as always online", () => {
|
||||
expect(isOnline({ isSelf: true, state: "offline" })).toBe(true);
|
||||
});
|
||||
|
||||
it("counts online/active state as online", () => {
|
||||
expect(isOnline({ state: "online" })).toBe(true);
|
||||
expect(isOnline({ state: "active" })).toBe(true);
|
||||
});
|
||||
|
||||
it("counts a real location as online even without state", () => {
|
||||
expect(isOnline({ location: "wrld_1:12345" })).toBe(true);
|
||||
});
|
||||
|
||||
it("treats the 'offline' location sentinel and empty location as offline", () => {
|
||||
expect(isOnline({ state: "offline", location: "offline" })).toBe(false);
|
||||
expect(isOnline({ state: "offline" })).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("locationLabel", () => {
|
||||
it("returns undefined for null/empty/offline sentinels", () => {
|
||||
expect(locationLabel(undefined)).toBeUndefined();
|
||||
expect(locationLabel("")).toBeUndefined();
|
||||
expect(locationLabel("offline")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("labels the private and traveling sentinels", () => {
|
||||
expect(locationLabel("private")).toBe("In a private world");
|
||||
expect(locationLabel("traveling")).toBe("Traveling…");
|
||||
});
|
||||
|
||||
it("labels a real location generically", () => {
|
||||
expect(locationLabel("wrld_1:12345~region(us)")).toBe("In a world");
|
||||
});
|
||||
});
|
||||
|
||||
describe("presenceOf", () => {
|
||||
it("shows the real status color when online", () => {
|
||||
const p = presenceOf({ status: "join me", state: "online" });
|
||||
expect(p.online).toBe(true);
|
||||
expect(p.effective.label).toBe("Join Me");
|
||||
});
|
||||
|
||||
it("falls back to the offline swatch when offline, keeping the raw status", () => {
|
||||
const p = presenceOf({ status: "join me", state: "offline" });
|
||||
expect(p.online).toBe(false);
|
||||
expect(p.effective.label).toBe("Offline");
|
||||
expect(p.status.label).toBe("Join Me");
|
||||
});
|
||||
});
|
||||
|
||||
describe("avatar/banner fallback chains", () => {
|
||||
it("prefers userIcon, then thumbnail, then full image", () => {
|
||||
expect(
|
||||
avatarOf({
|
||||
userIcon: "icon",
|
||||
currentAvatarThumbnailImageUrl: "thumb",
|
||||
currentAvatarImageUrl: "full",
|
||||
}),
|
||||
).toBe("icon");
|
||||
expect(
|
||||
avatarOf({
|
||||
userIcon: "",
|
||||
currentAvatarThumbnailImageUrl: "thumb",
|
||||
currentAvatarImageUrl: "full",
|
||||
}),
|
||||
).toBe("thumb");
|
||||
expect(
|
||||
avatarOf({ userIcon: "", currentAvatarThumbnailImageUrl: "", currentAvatarImageUrl: "full" }),
|
||||
).toBe("full");
|
||||
});
|
||||
|
||||
it("banner prefers the profile override, then full image", () => {
|
||||
expect(
|
||||
bannerOf({
|
||||
profilePicOverride: "ovr",
|
||||
currentAvatarImageUrl: "full",
|
||||
currentAvatarThumbnailImageUrl: "thumb",
|
||||
}),
|
||||
).toBe("ovr");
|
||||
expect(
|
||||
bannerOf({
|
||||
profilePicOverride: "",
|
||||
currentAvatarImageUrl: "full",
|
||||
currentAvatarThumbnailImageUrl: "thumb",
|
||||
}),
|
||||
).toBe("full");
|
||||
});
|
||||
});
|
||||
|
||||
describe("regionFlag / languageLabel", () => {
|
||||
it("maps known regions case-insensitively and unknown to undefined", () => {
|
||||
expect(regionFlag("US")).toBe("🇺🇸");
|
||||
expect(regionFlag("jp")).toBe("🇯🇵");
|
||||
expect(regionFlag("xx")).toBeUndefined();
|
||||
expect(regionFlag(undefined)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("maps known language codes and falls back to uppercased code", () => {
|
||||
expect(languageLabel("eng")).toBe("English");
|
||||
expect(languageLabel("zzz")).toBe("ZZZ");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
import { defineConfig } from "vitest/config";
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
include: ["tests/**/*.test.ts"],
|
||||
environment: "node",
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user