diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8baec13..ec668fe 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -40,6 +40,9 @@ importers: typescript: specifier: ^5.9.3 version: 5.9.3 + vitest: + specifier: ^5.0.3 + version: 5.0.3(@types/node@25.2.3)(vite@7.2.4(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)) web: dependencies: @@ -82,19 +85,19 @@ importers: version: 19.2.3(@types/react@19.2.6) '@vitejs/plugin-react': specifier: ^5.1.1 - version: 5.1.1(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)) + version: 5.1.1(supports-color@7.2.0)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)) autoprefixer: specifier: ^10.4.24 version: 10.4.24(postcss@8.5.6) eslint: specifier: ^9.39.1 - version: 9.39.1(jiti@2.6.1) + version: 9.39.1(jiti@2.6.1)(supports-color@7.2.0) eslint-plugin-react-hooks: specifier: ^7.0.1 - version: 7.0.1(eslint@9.39.1(jiti@2.6.1)) + version: 7.0.1(eslint@9.39.1(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0) eslint-plugin-react-refresh: specifier: ^0.4.24 - version: 0.4.24(eslint@9.39.1(jiti@2.6.1)) + version: 0.4.24(eslint@9.39.1(jiti@2.6.1)(supports-color@7.2.0)) globals: specifier: ^16.5.0 version: 16.5.0 @@ -109,10 +112,13 @@ importers: version: 5.9.3 typescript-eslint: specifier: ^8.46.4 - version: 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + version: 8.47.0(eslint@9.39.1(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) vite: specifier: ^7.2.4 version: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0) + vitest: + specifier: ^5.0.3 + version: 5.0.3(@types/node@24.10.1)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)) packages: @@ -616,6 +622,9 @@ packages: '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + '@jridgewell/sourcemap-codec@1.6.0': + resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} + '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} @@ -851,6 +860,12 @@ packages: '@types/babel__traverse@7.28.0': resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -936,6 +951,20 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + '@vitest/mocker@5.0.3': + resolution: {integrity: sha512-T8sWAIbkSyAjkwTcaEc3Iu0o9A27X1/kdXrizhZkGuSKScRQtRzclfAMpOTcGdXCsqxeWlpGy3XjqaW8CpLORg==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/spy@5.0.3': + resolution: {integrity: sha512-XhFysQTB8AZ+P4gMi+Lpo99vg2AZi0qKpaB9yXQl37+CaMEAPO3iH/wGVnSyL5MPERiLezpqTVtrR6UZH5GCXg==} + abstract-logging@2.0.1: resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==} @@ -970,6 +999,10 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + atomic-sleep@1.0.0: resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} engines: {node: '>=8.0.0'} @@ -1033,6 +1066,10 @@ packages: caniuse-lite@1.0.30001769: resolution: {integrity: sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==} + chai@6.3.0: + resolution: {integrity: sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==} + engines: {node: '>=18'} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -1103,6 +1140,9 @@ packages: resolution: {integrity: sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==} engines: {node: '>=10.13.0'} + es-module-lexer@2.3.2: + resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==} + esbuild@0.25.12: resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} engines: {node: '>=18'} @@ -1150,6 +1190,7 @@ packages: eslint@9.39.1: resolution: {integrity: sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true peerDependencies: jiti: '*' @@ -1173,10 +1214,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'} + fast-decode-uri-component@1.0.1: resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} @@ -1478,6 +1526,9 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + magic-string@1.4.3: + resolution: {integrity: sha512-z12OxmaPGE0F4xlpGdjuAUckipLpQlTAuAmyGhNoD7ODpYUzvDfvtxUbjM/jwznyP178oju/KDdyi220wNJ0RQ==} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -1520,6 +1571,10 @@ packages: node-releases@2.0.27: resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + obug@2.2.1: + resolution: {integrity: sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==} + engines: {node: '>=12.20.0'} + on-exit-leak-free@2.1.2: resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} engines: {node: '>=14.0.0'} @@ -1563,6 +1618,10 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + picomatch@4.0.7: + resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} + engines: {node: '>=12'} + pino-abstract-transport@3.0.0: resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==} @@ -1698,6 +1757,9 @@ packages: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} + std-env@4.3.0: + resolution: {integrity: sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==} + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -1720,10 +1782,22 @@ packages: resolution: {integrity: sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==} engines: {node: '>=20'} + tinybench@6.2.1: + resolution: {integrity: sha512-IAbQaPJIIdhVxi0mqy9lez1wEwje0UQf9F1vwtbelEG5Nbgn0nPOxCvU+mNeNS9gyJTAhrHp74CbUaSynQpZcQ==} + engines: {node: '>=20.0.0'} + + tinyexec@1.3.1: + resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==} + engines: {node: '>=18'} + tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -1829,11 +1903,57 @@ packages: yaml: optional: true + vitest@5.0.3: + resolution: {integrity: sha512-xMw97S3rjdtj5dkVat7jCsqWBpvchs3RlpQctUqwJD0KkERk40vz2fJ77lDwW/Vzh/pk18eItYAzkodhSes3jQ==} + engines: {node: ^22.12.0 || ^24.0.0 || >=26.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 5.0.3 + '@vitest/browser-preview': 5.0.3 + '@vitest/browser-webdriverio': ^5.0.0-beta.5 || >=5.0.0 + '@vitest/coverage-istanbul': 5.0.3 + '@vitest/coverage-v8': 5.0.3 + '@vitest/ui': 5.0.3 + happy-dom: '*' + jsdom: '*' + vite: ^6.4.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 + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} hasBin: true + why-is-node-running@3.2.1: + resolution: {integrity: sha512-Tb2FUhB4vUsGQlfSquQLYkApkuPAFQXGFzxWKHHumVz2dK+X1RUm/HnID4+TfIGYJ1kTcwOaCk/buYCEJr6YjQ==} + engines: {node: '>=20.11'} + hasBin: true + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -1866,20 +1986,20 @@ snapshots: '@babel/compat-data@7.28.5': {} - '@babel/core@7.28.5': + '@babel/core@7.28.5(supports-color@7.2.0)': dependencies: '@babel/code-frame': 7.27.1 '@babel/generator': 7.28.5 '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5(supports-color@7.2.0))(supports-color@7.2.0) '@babel/helpers': 7.28.4 '@babel/parser': 7.28.5 '@babel/template': 7.27.2 - '@babel/traverse': 7.28.5 + '@babel/traverse': 7.28.5(supports-color@7.2.0) '@babel/types': 7.28.5 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -1904,19 +2024,19 @@ snapshots: '@babel/helper-globals@7.28.0': {} - '@babel/helper-module-imports@7.27.1': + '@babel/helper-module-imports@7.27.1(supports-color@7.2.0)': dependencies: - '@babel/traverse': 7.28.5 + '@babel/traverse': 7.28.5(supports-color@7.2.0) '@babel/types': 7.28.5 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)': + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5(supports-color@7.2.0))(supports-color@7.2.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-module-imports': 7.27.1 + '@babel/core': 7.28.5(supports-color@7.2.0) + '@babel/helper-module-imports': 7.27.1(supports-color@7.2.0) '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.28.5 + '@babel/traverse': 7.28.5(supports-color@7.2.0) transitivePeerDependencies: - supports-color @@ -1937,14 +2057,14 @@ snapshots: dependencies: '@babel/types': 7.28.5 - '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.5(supports-color@7.2.0))': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.5(supports-color@7.2.0) '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.5(supports-color@7.2.0))': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.5(supports-color@7.2.0) '@babel/helper-plugin-utils': 7.27.1 '@babel/template@7.27.2': @@ -1953,7 +2073,7 @@ snapshots: '@babel/parser': 7.28.5 '@babel/types': 7.28.5 - '@babel/traverse@7.28.5': + '@babel/traverse@7.28.5(supports-color@7.2.0)': dependencies: '@babel/code-frame': 7.27.1 '@babel/generator': 7.28.5 @@ -1961,7 +2081,7 @@ snapshots: '@babel/parser': 7.28.5 '@babel/template': 7.27.2 '@babel/types': 7.28.5 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) transitivePeerDependencies: - supports-color @@ -2126,17 +2246,17 @@ snapshots: '@esbuild/win32-x64@0.27.3': optional: true - '@eslint-community/eslint-utils@4.9.0(eslint@9.39.1(jiti@2.6.1))': + '@eslint-community/eslint-utils@4.9.0(eslint@9.39.1(jiti@2.6.1)(supports-color@7.2.0))': dependencies: - eslint: 9.39.1(jiti@2.6.1) + eslint: 9.39.1(jiti@2.6.1)(supports-color@7.2.0) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} - '@eslint/config-array@0.21.1': + '@eslint/config-array@0.21.1(supports-color@7.2.0)': dependencies: '@eslint/object-schema': 2.1.7 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -2149,10 +2269,10 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/eslintrc@3.3.1': + '@eslint/eslintrc@3.3.1(supports-color@7.2.0)': dependencies: ajv: 6.12.6 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) espree: 10.4.0 globals: 14.0.0 ignore: 5.3.2 @@ -2246,10 +2366,12 @@ snapshots: '@jridgewell/sourcemap-codec@1.5.5': {} + '@jridgewell/sourcemap-codec@1.6.0': {} + '@jridgewell/trace-mapping@0.3.31': dependencies: '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/sourcemap-codec': 1.6.0 '@lukeed/ms@2.0.2': {} @@ -2425,6 +2547,13 @@ snapshots: dependencies: '@babel/types': 7.28.5 + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + '@types/estree@1.0.8': {} '@types/json-schema@7.0.15': {} @@ -2445,15 +2574,15 @@ snapshots: dependencies: csstype: 3.2.3 - '@typescript-eslint/eslint-plugin@8.47.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.47.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.47.0(eslint@9.39.1(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) '@typescript-eslint/scope-manager': 8.47.0 - '@typescript-eslint/type-utils': 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/utils': 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/type-utils': 8.47.0(eslint@9.39.1(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) + '@typescript-eslint/utils': 8.47.0(eslint@9.39.1(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.47.0 - eslint: 9.39.1(jiti@2.6.1) + eslint: 9.39.1(jiti@2.6.1)(supports-color@7.2.0) graphemer: 1.4.0 ignore: 7.0.5 natural-compare: 1.4.0 @@ -2462,23 +2591,23 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)': dependencies: '@typescript-eslint/scope-manager': 8.47.0 '@typescript-eslint/types': 8.47.0 - '@typescript-eslint/typescript-estree': 8.47.0(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.47.0(supports-color@7.2.0)(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.47.0 - debug: 4.4.3 - eslint: 9.39.1(jiti@2.6.1) + debug: 4.4.3(supports-color@7.2.0) + eslint: 9.39.1(jiti@2.6.1)(supports-color@7.2.0) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.47.0(typescript@5.9.3)': + '@typescript-eslint/project-service@8.47.0(supports-color@7.2.0)(typescript@5.9.3)': dependencies: '@typescript-eslint/tsconfig-utils': 8.47.0(typescript@5.9.3) '@typescript-eslint/types': 8.47.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -2492,13 +2621,13 @@ snapshots: dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.47.0(eslint@9.39.1(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)': dependencies: '@typescript-eslint/types': 8.47.0 - '@typescript-eslint/typescript-estree': 8.47.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) - debug: 4.4.3 - eslint: 9.39.1(jiti@2.6.1) + '@typescript-eslint/typescript-estree': 8.47.0(supports-color@7.2.0)(typescript@5.9.3) + '@typescript-eslint/utils': 8.47.0(eslint@9.39.1(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) + debug: 4.4.3(supports-color@7.2.0) + eslint: 9.39.1(jiti@2.6.1)(supports-color@7.2.0) ts-api-utils: 2.1.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: @@ -2506,13 +2635,13 @@ snapshots: '@typescript-eslint/types@8.47.0': {} - '@typescript-eslint/typescript-estree@8.47.0(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.47.0(supports-color@7.2.0)(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.47.0(typescript@5.9.3) + '@typescript-eslint/project-service': 8.47.0(supports-color@7.2.0)(typescript@5.9.3) '@typescript-eslint/tsconfig-utils': 8.47.0(typescript@5.9.3) '@typescript-eslint/types': 8.47.0 '@typescript-eslint/visitor-keys': 8.47.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 @@ -2522,13 +2651,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/utils@8.47.0(eslint@9.39.1(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1)(supports-color@7.2.0)) '@typescript-eslint/scope-manager': 8.47.0 '@typescript-eslint/types': 8.47.0 - '@typescript-eslint/typescript-estree': 8.47.0(typescript@5.9.3) - eslint: 9.39.1(jiti@2.6.1) + '@typescript-eslint/typescript-estree': 8.47.0(supports-color@7.2.0)(typescript@5.9.3) + eslint: 9.39.1(jiti@2.6.1)(supports-color@7.2.0) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -2538,11 +2667,11 @@ snapshots: '@typescript-eslint/types': 8.47.0 eslint-visitor-keys: 4.2.1 - '@vitejs/plugin-react@5.1.1(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0))': + '@vitejs/plugin-react@5.1.1(supports-color@7.2.0)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0))': dependencies: - '@babel/core': 7.28.5 - '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.5) + '@babel/core': 7.28.5(supports-color@7.2.0) + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5(supports-color@7.2.0)) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.5(supports-color@7.2.0)) '@rolldown/pluginutils': 1.0.0-beta.47 '@types/babel__core': 7.20.5 react-refresh: 0.18.0 @@ -2550,6 +2679,26 @@ snapshots: transitivePeerDependencies: - supports-color + '@vitest/mocker@5.0.3(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0))': + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + '@vitest/spy': 5.0.3 + estree-walker: 3.0.3 + magic-string: 1.4.3 + optionalDependencies: + vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0) + + '@vitest/mocker@5.0.3(vite@7.2.4(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0))': + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + '@vitest/spy': 5.0.3 + estree-walker: 3.0.3 + magic-string: 1.4.3 + optionalDependencies: + vite: 7.2.4(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0) + + '@vitest/spy@5.0.3': {} + abstract-logging@2.0.1: {} acorn-jsx@5.3.2(acorn@8.15.0): @@ -2582,6 +2731,8 @@ snapshots: argparse@2.0.1: {} + assertion-error@2.0.1: {} + atomic-sleep@1.0.0: {} autoprefixer@10.4.24(postcss@8.5.6): @@ -2647,6 +2798,8 @@ snapshots: caniuse-lite@1.0.30001769: {} + chai@6.3.0: {} + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -2676,9 +2829,11 @@ snapshots: csstype@3.2.3: {} - debug@4.4.3: + debug@4.4.3(supports-color@7.2.0): dependencies: ms: 2.1.3 + optionalDependencies: + supports-color: 7.2.0 deep-is@0.1.4: {} @@ -2697,6 +2852,8 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.3.0 + es-module-lexer@2.3.2: {} + esbuild@0.25.12: optionalDependencies: '@esbuild/aix-ppc64': 0.25.12 @@ -2761,20 +2918,20 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-plugin-react-hooks@7.0.1(eslint@9.39.1(jiti@2.6.1)): + eslint-plugin-react-hooks@7.0.1(eslint@9.39.1(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0): dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.5(supports-color@7.2.0) '@babel/parser': 7.28.5 - eslint: 9.39.1(jiti@2.6.1) + eslint: 9.39.1(jiti@2.6.1)(supports-color@7.2.0) hermes-parser: 0.25.1 zod: 3.25.76 zod-validation-error: 4.0.2(zod@3.25.76) transitivePeerDependencies: - supports-color - eslint-plugin-react-refresh@0.4.24(eslint@9.39.1(jiti@2.6.1)): + eslint-plugin-react-refresh@0.4.24(eslint@9.39.1(jiti@2.6.1)(supports-color@7.2.0)): dependencies: - eslint: 9.39.1(jiti@2.6.1) + eslint: 9.39.1(jiti@2.6.1)(supports-color@7.2.0) eslint-scope@8.4.0: dependencies: @@ -2785,14 +2942,14 @@ snapshots: eslint-visitor-keys@4.2.1: {} - eslint@9.39.1(jiti@2.6.1): + eslint@9.39.1(jiti@2.6.1)(supports-color@7.2.0): dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1)(supports-color@7.2.0)) '@eslint-community/regexpp': 4.12.2 - '@eslint/config-array': 0.21.1 + '@eslint/config-array': 0.21.1(supports-color@7.2.0) '@eslint/config-helpers': 0.4.2 '@eslint/core': 0.17.0 - '@eslint/eslintrc': 3.3.1 + '@eslint/eslintrc': 3.3.1(supports-color@7.2.0) '@eslint/js': 9.39.1 '@eslint/plugin-kit': 0.4.1 '@humanfs/node': 0.16.7 @@ -2802,7 +2959,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) escape-string-regexp: 4.0.0 eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 @@ -2842,8 +2999,14 @@ snapshots: estraverse@5.3.0: {} + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + esutils@2.0.3: {} + expect-type@1.4.0: {} + fast-decode-uri-component@1.0.1: {} fast-deep-equal@3.1.3: {} @@ -2903,6 +3066,10 @@ snapshots: optionalDependencies: picomatch: 4.0.3 + fdir@6.5.0(picomatch@4.0.7): + optionalDependencies: + picomatch: 4.0.7 + file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 @@ -3115,6 +3282,10 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + magic-string@1.4.3: + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + merge2@1.4.1: {} micromatch@4.0.8: @@ -3146,6 +3317,8 @@ snapshots: node-releases@2.0.27: {} + obug@2.2.1: {} + on-exit-leak-free@2.1.2: {} optionator@0.9.4: @@ -3184,6 +3357,8 @@ snapshots: picomatch@4.0.3: {} + picomatch@4.0.7: {} + pino-abstract-transport@3.0.0: dependencies: split2: 4.2.0 @@ -3313,6 +3488,8 @@ snapshots: statuses@2.0.1: {} + std-env@4.3.0: {} + strip-json-comments@3.1.1: {} supports-color@7.2.0: @@ -3329,11 +3506,20 @@ snapshots: dependencies: real-require: 0.2.0 + tinybench@6.2.1: {} + + tinyexec@1.3.1: {} + tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -3357,13 +3543,13 @@ snapshots: dependencies: prelude-ls: 1.2.1 - typescript-eslint@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3): + typescript-eslint@8.47.0(eslint@9.39.1(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.47.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/parser': 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.47.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) - eslint: 9.39.1(jiti@2.6.1) + '@typescript-eslint/eslint-plugin': 8.47.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) + '@typescript-eslint/parser': 8.47.0(eslint@9.39.1(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.47.0(supports-color@7.2.0)(typescript@5.9.3) + '@typescript-eslint/utils': 8.47.0(eslint@9.39.1(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) + eslint: 9.39.1(jiti@2.6.1)(supports-color@7.2.0) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -3407,10 +3593,69 @@ snapshots: lightningcss: 1.30.2 tsx: 4.21.0 + vite@7.2.4(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0): + dependencies: + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.53.3 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 25.2.3 + fsevents: 2.3.3 + jiti: 2.6.1 + lightningcss: 1.30.2 + tsx: 4.21.0 + + vitest@5.0.3(@types/node@24.10.1)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)): + dependencies: + '@types/chai': 5.2.3 + '@vitest/mocker': 5.0.3(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)) + chai: 6.3.0 + es-module-lexer: 2.3.2 + expect-type: 1.4.0 + magic-string: 1.4.3 + obug: 2.2.1 + picomatch: 4.0.7 + std-env: 4.3.0 + tinybench: 6.2.1 + tinyexec: 1.3.1 + tinyglobby: 0.2.17 + vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0) + why-is-node-running: 3.2.1 + optionalDependencies: + '@types/node': 24.10.1 + transitivePeerDependencies: + - msw + + vitest@5.0.3(@types/node@25.2.3)(vite@7.2.4(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)): + dependencies: + '@types/chai': 5.2.3 + '@vitest/mocker': 5.0.3(vite@7.2.4(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)) + chai: 6.3.0 + es-module-lexer: 2.3.2 + expect-type: 1.4.0 + magic-string: 1.4.3 + obug: 2.2.1 + picomatch: 4.0.7 + std-env: 4.3.0 + tinybench: 6.2.1 + tinyexec: 1.3.1 + tinyglobby: 0.2.17 + vite: 7.2.4(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0) + why-is-node-running: 3.2.1 + optionalDependencies: + '@types/node': 25.2.3 + transitivePeerDependencies: + - msw + which@2.0.2: dependencies: isexe: 2.0.0 + why-is-node-running@3.2.1: {} + word-wrap@1.2.5: {} yallist@3.1.1: {} diff --git a/server/package.json b/server/package.json index 3eda9cf..75c2a43 100644 --- a/server/package.json +++ b/server/package.json @@ -19,6 +19,7 @@ "devDependencies": { "@types/node": "^25.2.3", "tsx": "^4.21.0", - "typescript": "^5.9.3" + "typescript": "^5.9.3", + "vitest": "^5.0.3" } } diff --git a/web/package.json b/web/package.json index f50a571..ee6bacc 100644 --- a/web/package.json +++ b/web/package.json @@ -7,6 +7,8 @@ "dev": "vite", "build": "tsc -b && vite build", "lint": "eslint .", + "test": "vitest run", + "typecheck": "tsc -b", "preview": "vite preview" }, "dependencies": { @@ -34,6 +36,7 @@ "tailwindcss": "^4.1.18", "typescript": "~5.9.3", "typescript-eslint": "^8.46.4", - "vite": "^7.2.4" + "vite": "^7.2.4", + "vitest": "^5.0.3" } } diff --git a/web/src/components/feature/DownloadItem.test.tsx b/web/src/components/feature/DownloadItem.test.tsx new file mode 100644 index 0000000..b3ec5c7 --- /dev/null +++ b/web/src/components/feature/DownloadItem.test.tsx @@ -0,0 +1,109 @@ +import { describe, expect, it } from "vitest"; +import { renderToStaticMarkup } from "react-dom/server"; +import { DownloadItem, type DownloadItemData } from "./DownloadItem"; + +const base: DownloadItemData = { + id: "t1", + sessionId: "s1", + url: "https://example.com/file.zip", + filename: "file.zip", + status: "downloading", + progress: 40, + totalBytes: 10 * 1024 * 1024, + receivedBytes: 4 * 1024 * 1024, + speed: 2 * 1024 * 1024, + createdAt: 0, +}; + +const render = (patch: Partial = {}) => + renderToStaticMarkup(); + +describe("DownloadItem", () => { + describe("em andamento (caminho principal)", () => { + const html = render(); + + it("mostra status, bytes, velocidade e porcentagem", () => { + expect(html).toContain("downloading"); + expect(html).toContain("4.0 MB"); + expect(html).toContain("10.0 MB"); + expect(html).toContain("2.00 MB/s"); + expect(html).toContain("40%"); + }); + + it("usa o spinner e não mostra botão nem erro", () => { + expect(html).toContain("animate-spin"); + expect(html).not.toContain("Save to Disk"); + expect(html).not.toContain("Ready for download"); + }); + }); + + describe("status queued", () => { + it("mostra a mensagem de fila e o ícone pulsante", () => { + const html = render({ status: "queued" }); + expect(html).toContain("Queued on server..."); + expect(html).toContain("animate-pulse"); + expect(html).not.toContain("animate-spin"); + }); + }); + + describe("status completed", () => { + const html = render({ status: "completed", progress: 100 }); + + it("mostra o botão de salvar e o tamanho final", () => { + expect(html).toContain("Save to Disk"); + expect(html).toContain("Ready for download (10.0 MB)"); + }); + + it("esconde a seção de progresso", () => { + expect(html).not.toContain("MB/s"); + expect(html).not.toContain("100%"); + }); + }); + + describe("status error", () => { + it("mostra a mensagem de erro da task", () => { + const html = render({ status: "error", error: "HTTP Error 404" }); + expect(html).toContain("HTTP Error 404"); + expect(html).not.toContain("Save to Disk"); + expect(html).not.toContain("MB/s"); + }); + + it("usa a mensagem padrão sem detalhe de erro", () => { + expect(render({ status: "error" })).toContain("Unknown error occurred"); + }); + }); + + describe("título", () => { + it("usa o nome do arquivo", () => { + expect(render()).toContain('title="file.zip"'); + }); + + it("cai para a URL quando não há nome do arquivo", () => { + expect(render({ filename: "" })).toContain( + 'title="https://example.com/file.zip"' + ); + }); + }); + + describe("valores zerados durante o download", () => { + it("tamanho total desconhecido mostra ??", () => { + expect(render({ totalBytes: 0 })).toContain("??"); + }); + + it("sem velocidade mostra 0 B/s", () => { + expect(render({ speed: 0 })).toContain("0 B/s"); + }); + + it("sem progresso mostra 0%", () => { + expect(render({ progress: 0 })).toContain(">0%<"); + }); + + it("sem bytes recebidos mostra hífen", () => { + expect(render({ receivedBytes: 0 })).toContain("- / 10.0 MB"); + }); + + it("arredonda a porcentagem", () => { + expect(render({ progress: 40.6 })).toContain("41%"); + }); + }); +}); diff --git a/web/src/components/feature/DownloadItem.tsx b/web/src/components/feature/DownloadItem.tsx index 053a230..195d549 100644 --- a/web/src/components/feature/DownloadItem.tsx +++ b/web/src/components/feature/DownloadItem.tsx @@ -1,3 +1,4 @@ +import type { ReactNode } from "react"; import { FileCheck, FileWarning, @@ -34,59 +35,91 @@ interface DownloadItemProps { item: DownloadItemData; } +const ICON_BOX = "flex h-10 w-10 items-center justify-center rounded-full"; + +const STATUS_ICON: Record = { + completed: { box: "bg-green-500/10 text-green-500", icon: }, + error: { box: "bg-destructive/10 text-destructive", icon: }, + queued: { + box: "bg-secondary text-secondary-foreground", + icon: , + }, + downloading: { box: "bg-primary/10 text-primary", icon: }, +}; + +function StatusIcon({ status }: { status: DownloadStatus }) { + const { box, icon } = STATUS_ICON[status]; + return
{icon}
; +} + +function ProgressSection({ item }: { item: DownloadItemData }) { + const total = item.totalBytes ? formatBytes(item.totalBytes) : "??"; + + return ( +
+
+ {item.status === "queued" ? "Queued on server..." : item.status} + {formatBytes(item.receivedBytes || 0)} / {total} +
+ +
+ {item.speed ? formatSpeed(item.speed) : "0 B/s"} + {item.progress ? `${Math.round(item.progress)}%` : "0%"} +
+
+ ); +} + +function StatusDetails({ item }: { item: DownloadItemData }) { + if (item.status === "completed") { + return ( +
+ + Ready for download ({formatBytes(item.totalBytes)}) +
+ ); + } + + if (item.status === "error") { + return ( +
+ {item.error || "Unknown error occurred"} +
+ ); + } + + return ; +} + +async function saveToDisk(item: DownloadItemData) { + try { + const sessionId = localStorage.getItem("downloader_session_id") || ""; + const res = await fetch(`${API_URL}/api/download/${item.id}`, { + headers: { "x-session-id": sessionId } + }); + if (!res.ok) throw new Error("Download failed"); + + const blob = await res.blob(); + const url = window.URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = item.filename; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + document.body.removeChild(a); + } catch { + alert("Failed to download file"); + } +} + export function DownloadItem({ item }: DownloadItemProps) { - const isCompleted = item.status === "completed"; - const isError = item.status === "error"; - const isQueued = item.status === "queued"; - - const handleDownloadFile = () => { - const download = async () => { - try { - const sessionId = localStorage.getItem("downloader_session_id") || ""; - const res = await fetch(`${API_URL}/api/download/${item.id}`, { - headers: { "x-session-id": sessionId } - }); - if (!res.ok) throw new Error("Download failed"); - - const blob = await res.blob(); - const url = window.URL.createObjectURL(blob); - const a = document.createElement("a"); - a.href = url; - a.download = item.filename; - document.body.appendChild(a); - a.click(); - window.URL.revokeObjectURL(url); - document.body.removeChild(a); - } catch { - alert("Failed to download file"); - } - }; - - download(); - }; - return (
- {isCompleted ? ( -
- -
- ) : isError ? ( -
- -
- ) : isQueued ? ( -
- -
- ) : ( -
- -
- )} +
@@ -99,41 +132,15 @@ export function DownloadItem({ item }: DownloadItemProps) { {item.url}

- {isCompleted && ( - )}
- {/* Progress Section */} - {!isCompleted && !isError && ( -
-
- {isQueued ? "Queued on server..." : item.status} - {formatBytes(item.receivedBytes || 0)} / {item.totalBytes ? formatBytes(item.totalBytes) : "??"} -
- -
- {item.speed ? formatSpeed(item.speed) : "0 B/s"} - {item.progress ? `${Math.round(item.progress)}%` : "0%"} -
-
- )} - - {isCompleted && ( -
- - Ready for download ({formatBytes(item.totalBytes)}) -
- )} - - {isError && ( -
- {item.error || "Unknown error occurred"} -
- )} +