refactor(web): reduz a complexidade do DownloadItem e adiciona testes com vitest

This commit is contained in:
evandro committed 2026-10-10 03:55:02 -03:00
1 parent cc5147946c
commit 1bcb35d169
5 files changed
+515 -150

No files matched your search

+317 -72
View File
@@ -40,6 +40,9 @@ importers:
typescript:
specifier: ^5.9.3
version: 5.9.3
vitest:
specifier: ^5.0.3
version: 5.0.3(@types/[email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected]))
web:
dependencies:
@@ -82,19 +85,19 @@ importers:
version: 19.2.3(@types/[email protected])
'@vitejs/plugin-react':
specifier: ^5.1.1
version: 5.1.1([email protected](@types/[email protected])([email protected])([email protected])([email protected]))
version: 5.1.1([email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected]))
autoprefixer:
specifier: ^10.4.24
version: 10.4.24([email protected])
eslint:
specifier: ^9.39.1
version: 9.39.1([email protected])
version: 9.39.1([email protected])([email protected])
eslint-plugin-react-hooks:
specifier: ^7.0.1
version: 7.0.1([email protected]([email protected]))
version: 7.0.1([email protected]([email protected])([email protected]))([email protected])
eslint-plugin-react-refresh:
specifier: ^0.4.24
version: 0.4.24([email protected]([email protected]))
version: 0.4.24([email protected]([email protected])([email protected]))
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([email protected]([email protected]))([email protected])
version: 8.47.0([email protected]([email protected])([email protected]))([email protected])([email protected])
vite:
specifier: ^7.2.4
version: 7.2.4(@types/[email protected])([email protected])([email protected])([email protected])
vitest:
specifier: ^5.0.3
version: 5.0.3(@types/[email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected]))
packages:
@@ -616,6 +622,9 @@ packages:
'@jridgewell/[email protected]':
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
'@jridgewell/[email protected]':
resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==}
'@jridgewell/[email protected]':
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
@@ -851,6 +860,12 @@ packages:
'@types/[email protected]':
resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
'@types/[email protected]':
resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
'@types/[email protected]':
resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
'@types/[email protected]':
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/[email protected]':
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/[email protected]':
resolution: {integrity: sha512-XhFysQTB8AZ+P4gMi+Lpo99vg2AZi0qKpaB9yXQl37+CaMEAPO3iH/wGVnSyL5MPERiLezpqTVtrR6UZH5GCXg==}
[email protected]:
resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==}
@@ -970,6 +999,10 @@ packages:
[email protected]:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
[email protected]:
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
engines: {node: '>=12'}
[email protected]:
resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==}
engines: {node: '>=8.0.0'}
@@ -1033,6 +1066,10 @@ packages:
[email protected]:
resolution: {integrity: sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==}
[email protected]:
resolution: {integrity: sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==}
engines: {node: '>=18'}
[email protected]:
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'}
[email protected]:
resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==}
[email protected]:
resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==}
engines: {node: '>=18'}
@@ -1150,6 +1190,7 @@ packages:
[email protected]:
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'}
[email protected]:
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
[email protected]:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'}
[email protected]:
resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==}
engines: {node: '>=12.0.0'}
[email protected]:
resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==}
@@ -1478,6 +1526,9 @@ packages:
[email protected]:
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
[email protected]:
resolution: {integrity: sha512-z12OxmaPGE0F4xlpGdjuAUckipLpQlTAuAmyGhNoD7ODpYUzvDfvtxUbjM/jwznyP178oju/KDdyi220wNJ0RQ==}
[email protected]:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'}
@@ -1520,6 +1571,10 @@ packages:
[email protected]:
resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
[email protected]:
resolution: {integrity: sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==}
engines: {node: '>=12.20.0'}
[email protected]:
resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==}
engines: {node: '>=14.0.0'}
@@ -1563,6 +1618,10 @@ packages:
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
engines: {node: '>=12'}
[email protected]:
resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==}
engines: {node: '>=12'}
[email protected]:
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'}
[email protected]:
resolution: {integrity: sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==}
[email protected]:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
@@ -1720,10 +1782,22 @@ packages:
resolution: {integrity: sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==}
engines: {node: '>=20'}
[email protected]:
resolution: {integrity: sha512-IAbQaPJIIdhVxi0mqy9lez1wEwje0UQf9F1vwtbelEG5Nbgn0nPOxCvU+mNeNS9gyJTAhrHp74CbUaSynQpZcQ==}
engines: {node: '>=20.0.0'}
[email protected]:
resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==}
engines: {node: '>=18'}
[email protected]:
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
engines: {node: '>=12.0.0'}
[email protected]:
resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
engines: {node: '>=12.0.0'}
[email protected]:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
@@ -1829,11 +1903,57 @@ packages:
yaml:
optional: true
[email protected]:
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
[email protected]:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
engines: {node: '>= 8'}
hasBin: true
[email protected]:
resolution: {integrity: sha512-Tb2FUhB4vUsGQlfSquQLYkApkuPAFQXGFzxWKHHumVz2dK+X1RUm/HnID4+TfIGYJ1kTcwOaCk/buYCEJr6YjQ==}
engines: {node: '>=20.11'}
hasBin: true
[email protected]:
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
engines: {node: '>=0.10.0'}
@@ -1866,20 +1986,20 @@ snapshots:
'@babel/[email protected]': {}
'@babel/[email protected]':
'@babel/[email protected]([email protected])':
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/[email protected])
'@babel/helper-module-transforms': 7.28.3(@babel/[email protected]([email protected]))([email protected])
'@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([email protected])
'@babel/types': 7.28.5
'@jridgewell/remapping': 2.3.5
convert-source-map: 2.0.0
debug: 4.4.3
debug: 4.4.3([email protected])
gensync: 1.0.0-beta.2
json5: 2.2.3
semver: 6.3.1
@@ -1904,19 +2024,19 @@ snapshots:
'@babel/[email protected]': {}
'@babel/[email protected]':
'@babel/[email protected]([email protected])':
dependencies:
'@babel/traverse': 7.28.5
'@babel/traverse': 7.28.5([email protected])
'@babel/types': 7.28.5
transitivePeerDependencies:
- supports-color
'@babel/[email protected](@babel/[email protected])':
'@babel/[email protected](@babel/[email protected]([email protected]))([email protected])':
dependencies:
'@babel/core': 7.28.5
'@babel/helper-module-imports': 7.27.1
'@babel/core': 7.28.5([email protected])
'@babel/helper-module-imports': 7.27.1([email protected])
'@babel/helper-validator-identifier': 7.28.5
'@babel/traverse': 7.28.5
'@babel/traverse': 7.28.5([email protected])
transitivePeerDependencies:
- supports-color
@@ -1937,14 +2057,14 @@ snapshots:
dependencies:
'@babel/types': 7.28.5
'@babel/[email protected](@babel/[email protected])':
'@babel/[email protected](@babel/[email protected]([email protected]))':
dependencies:
'@babel/core': 7.28.5
'@babel/core': 7.28.5([email protected])
'@babel/helper-plugin-utils': 7.27.1
'@babel/[email protected](@babel/[email protected])':
'@babel/[email protected](@babel/[email protected]([email protected]))':
dependencies:
'@babel/core': 7.28.5
'@babel/core': 7.28.5([email protected])
'@babel/helper-plugin-utils': 7.27.1
'@babel/[email protected]':
@@ -1953,7 +2073,7 @@ snapshots:
'@babel/parser': 7.28.5
'@babel/types': 7.28.5
'@babel/[email protected]':
'@babel/[email protected]([email protected])':
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([email protected])
transitivePeerDependencies:
- supports-color
@@ -2126,17 +2246,17 @@ snapshots:
'@esbuild/[email protected]':
optional: true
'@eslint-community/[email protected]([email protected]([email protected]))':
'@eslint-community/[email protected]([email protected]([email protected])([email protected]))':
dependencies:
eslint: 9.39.1([email protected])
eslint: 9.39.1([email protected])([email protected])
eslint-visitor-keys: 3.4.3
'@eslint-community/[email protected]': {}
'@eslint/[email protected]':
'@eslint/[email protected]([email protected])':
dependencies:
'@eslint/object-schema': 2.1.7
debug: 4.4.3
debug: 4.4.3([email protected])
minimatch: 3.1.2
transitivePeerDependencies:
- supports-color
@@ -2149,10 +2269,10 @@ snapshots:
dependencies:
'@types/json-schema': 7.0.15
'@eslint/[email protected]':
'@eslint/[email protected]([email protected])':
dependencies:
ajv: 6.12.6
debug: 4.4.3
debug: 4.4.3([email protected])
espree: 10.4.0
globals: 14.0.0
ignore: 5.3.2
@@ -2246,10 +2366,12 @@ snapshots:
'@jridgewell/[email protected]': {}
'@jridgewell/[email protected]': {}
'@jridgewell/[email protected]':
dependencies:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5
'@jridgewell/sourcemap-codec': 1.6.0
'@lukeed/[email protected]': {}
@@ -2425,6 +2547,13 @@ snapshots:
dependencies:
'@babel/types': 7.28.5
'@types/[email protected]':
dependencies:
'@types/deep-eql': 4.0.2
assertion-error: 2.0.1
'@types/[email protected]': {}
'@types/[email protected]': {}
'@types/[email protected]': {}
@@ -2445,15 +2574,15 @@ snapshots:
dependencies:
csstype: 3.2.3
'@typescript-eslint/[email protected](@typescript-eslint/[email protected]([email protected]([email protected]))([email protected]))([email protected]([email protected]))([email protected])':
'@typescript-eslint/[email protected](@typescript-eslint/[email protected]([email protected]([email protected])([email protected]))([email protected])([email protected]))([email protected]([email protected])([email protected]))([email protected])([email protected])':
dependencies:
'@eslint-community/regexpp': 4.12.2
'@typescript-eslint/parser': 8.47.0([email protected]([email protected]))([email protected])
'@typescript-eslint/parser': 8.47.0([email protected]([email protected])([email protected]))([email protected])([email protected])
'@typescript-eslint/scope-manager': 8.47.0
'@typescript-eslint/type-utils': 8.47.0([email protected]([email protected]))([email protected])
'@typescript-eslint/utils': 8.47.0([email protected]([email protected]))([email protected])
'@typescript-eslint/type-utils': 8.47.0([email protected]([email protected])([email protected]))([email protected])([email protected])
'@typescript-eslint/utils': 8.47.0([email protected]([email protected])([email protected]))([email protected])([email protected])
'@typescript-eslint/visitor-keys': 8.47.0
eslint: 9.39.1([email protected])
eslint: 9.39.1([email protected])([email protected])
graphemer: 1.4.0
ignore: 7.0.5
natural-compare: 1.4.0
@@ -2462,23 +2591,23 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/[email protected]([email protected]([email protected]))([email protected])':
'@typescript-eslint/[email protected]([email protected]([email protected])([email protected]))([email protected])([email protected])':
dependencies:
'@typescript-eslint/scope-manager': 8.47.0
'@typescript-eslint/types': 8.47.0
'@typescript-eslint/typescript-estree': 8.47.0([email protected])
'@typescript-eslint/typescript-estree': 8.47.0([email protected])([email protected])
'@typescript-eslint/visitor-keys': 8.47.0
debug: 4.4.3
eslint: 9.39.1([email protected])
debug: 4.4.3([email protected])
eslint: 9.39.1([email protected])([email protected])
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/[email protected]([email protected])':
'@typescript-eslint/[email protected]([email protected])([email protected])':
dependencies:
'@typescript-eslint/tsconfig-utils': 8.47.0([email protected])
'@typescript-eslint/types': 8.47.0
debug: 4.4.3
debug: 4.4.3([email protected])
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
@@ -2492,13 +2621,13 @@ snapshots:
dependencies:
typescript: 5.9.3
'@typescript-eslint/[email protected]([email protected]([email protected]))([email protected])':
'@typescript-eslint/[email protected]([email protected]([email protected])([email protected]))([email protected])([email protected])':
dependencies:
'@typescript-eslint/types': 8.47.0
'@typescript-eslint/typescript-estree': 8.47.0([email protected])
'@typescript-eslint/utils': 8.47.0([email protected]([email protected]))([email protected])
debug: 4.4.3
eslint: 9.39.1([email protected])
'@typescript-eslint/typescript-estree': 8.47.0([email protected])([email protected])
'@typescript-eslint/utils': 8.47.0([email protected]([email protected])([email protected]))([email protected])([email protected])
debug: 4.4.3([email protected])
eslint: 9.39.1([email protected])([email protected])
ts-api-utils: 2.1.0([email protected])
typescript: 5.9.3
transitivePeerDependencies:
@@ -2506,13 +2635,13 @@ snapshots:
'@typescript-eslint/[email protected]': {}
'@typescript-eslint/[email protected]([email protected])':
'@typescript-eslint/[email protected]([email protected])([email protected])':
dependencies:
'@typescript-eslint/project-service': 8.47.0([email protected])
'@typescript-eslint/project-service': 8.47.0([email protected])([email protected])
'@typescript-eslint/tsconfig-utils': 8.47.0([email protected])
'@typescript-eslint/types': 8.47.0
'@typescript-eslint/visitor-keys': 8.47.0
debug: 4.4.3
debug: 4.4.3([email protected])
fast-glob: 3.3.3
is-glob: 4.0.3
minimatch: 9.0.5
@@ -2522,13 +2651,13 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/[email protected]([email protected]([email protected]))([email protected])':
'@typescript-eslint/[email protected]([email protected]([email protected])([email protected]))([email protected])([email protected])':
dependencies:
'@eslint-community/eslint-utils': 4.9.0([email protected]([email protected]))
'@eslint-community/eslint-utils': 4.9.0([email protected]([email protected])([email protected]))
'@typescript-eslint/scope-manager': 8.47.0
'@typescript-eslint/types': 8.47.0
'@typescript-eslint/typescript-estree': 8.47.0([email protected])
eslint: 9.39.1([email protected])
'@typescript-eslint/typescript-estree': 8.47.0([email protected])([email protected])
eslint: 9.39.1([email protected])([email protected])
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/[email protected]([email protected](@types/[email protected])([email protected])([email protected])([email protected]))':
'@vitejs/[email protected]([email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected]))':
dependencies:
'@babel/core': 7.28.5
'@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/[email protected])
'@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/[email protected])
'@babel/core': 7.28.5([email protected])
'@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/[email protected]([email protected]))
'@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/[email protected]([email protected]))
'@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/[email protected]([email protected](@types/[email protected])([email protected])([email protected])([email protected]))':
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/[email protected])([email protected])([email protected])([email protected])
'@vitest/[email protected]([email protected](@types/[email protected])([email protected])([email protected])([email protected]))':
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/[email protected])([email protected])([email protected])([email protected])
'@vitest/[email protected]': {}
[email protected]: {}
[email protected]([email protected]):
@@ -2582,6 +2731,8 @@ snapshots:
[email protected]: {}
[email protected]: {}
[email protected]: {}
[email protected]([email protected]):
@@ -2647,6 +2798,8 @@ snapshots:
[email protected]: {}
[email protected]: {}
[email protected]:
dependencies:
ansi-styles: 4.3.0
@@ -2676,9 +2829,11 @@ snapshots:
[email protected]: {}
[email protected]:
[email protected]([email protected]):
dependencies:
ms: 2.1.3
optionalDependencies:
supports-color: 7.2.0
[email protected]: {}
@@ -2697,6 +2852,8 @@ snapshots:
graceful-fs: 4.2.11
tapable: 2.3.0
[email protected]: {}
[email protected]:
optionalDependencies:
'@esbuild/aix-ppc64': 0.25.12
@@ -2761,20 +2918,20 @@ snapshots:
[email protected]: {}
[email protected]([email protected]([email protected])):
[email protected]([email protected]([email protected])([email protected]))([email protected]):
dependencies:
'@babel/core': 7.28.5
'@babel/core': 7.28.5([email protected])
'@babel/parser': 7.28.5
eslint: 9.39.1([email protected])
eslint: 9.39.1([email protected])([email protected])
hermes-parser: 0.25.1
zod: 3.25.76
zod-validation-error: 4.0.2([email protected])
transitivePeerDependencies:
- supports-color
[email protected]([email protected]([email protected])):
[email protected]([email protected]([email protected])([email protected])):
dependencies:
eslint: 9.39.1([email protected])
eslint: 9.39.1([email protected])([email protected])
[email protected]:
dependencies:
@@ -2785,14 +2942,14 @@ snapshots:
[email protected]: {}
[email protected]([email protected]):
[email protected]([email protected])([email protected]):
dependencies:
'@eslint-community/eslint-utils': 4.9.0([email protected]([email protected]))
'@eslint-community/eslint-utils': 4.9.0([email protected]([email protected])([email protected]))
'@eslint-community/regexpp': 4.12.2
'@eslint/config-array': 0.21.1
'@eslint/config-array': 0.21.1([email protected])
'@eslint/config-helpers': 0.4.2
'@eslint/core': 0.17.0
'@eslint/eslintrc': 3.3.1
'@eslint/eslintrc': 3.3.1([email protected])
'@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([email protected])
escape-string-regexp: 4.0.0
eslint-scope: 8.4.0
eslint-visitor-keys: 4.2.1
@@ -2842,8 +2999,14 @@ snapshots:
[email protected]: {}
[email protected]:
dependencies:
'@types/estree': 1.0.8
[email protected]: {}
[email protected]: {}
[email protected]: {}
[email protected]: {}
@@ -2903,6 +3066,10 @@ snapshots:
optionalDependencies:
picomatch: 4.0.3
[email protected]([email protected]):
optionalDependencies:
picomatch: 4.0.7
[email protected]:
dependencies:
flat-cache: 4.0.1
@@ -3115,6 +3282,10 @@ snapshots:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
[email protected]:
dependencies:
'@jridgewell/sourcemap-codec': 1.6.0
[email protected]: {}
[email protected]:
@@ -3146,6 +3317,8 @@ snapshots:
[email protected]: {}
[email protected]: {}
[email protected]: {}
[email protected]:
@@ -3184,6 +3357,8 @@ snapshots:
[email protected]: {}
[email protected]: {}
[email protected]:
dependencies:
split2: 4.2.0
@@ -3313,6 +3488,8 @@ snapshots:
[email protected]: {}
[email protected]: {}
[email protected]: {}
[email protected]:
@@ -3329,11 +3506,20 @@ snapshots:
dependencies:
real-require: 0.2.0
[email protected]: {}
[email protected]: {}
[email protected]:
dependencies:
fdir: 6.5.0([email protected])
picomatch: 4.0.3
[email protected]:
dependencies:
fdir: 6.5.0([email protected])
picomatch: 4.0.7
[email protected]:
dependencies:
is-number: 7.0.0
@@ -3357,13 +3543,13 @@ snapshots:
dependencies:
prelude-ls: 1.2.1
[email protected]([email protected]([email protected]))([email protected]):
[email protected]([email protected]([email protected])([email protected]))([email protected])([email protected]):
dependencies:
'@typescript-eslint/eslint-plugin': 8.47.0(@typescript-eslint/[email protected]([email protected]([email protected]))([email protected]))([email protected]([email protected]))([email protected])
'@typescript-eslint/parser': 8.47.0([email protected]([email protected]))([email protected])
'@typescript-eslint/typescript-estree': 8.47.0([email protected])
'@typescript-eslint/utils': 8.47.0([email protected]([email protected]))([email protected])
eslint: 9.39.1([email protected])
'@typescript-eslint/eslint-plugin': 8.47.0(@typescript-eslint/[email protected]([email protected]([email protected])([email protected]))([email protected])([email protected]))([email protected]([email protected])([email protected]))([email protected])([email protected])
'@typescript-eslint/parser': 8.47.0([email protected]([email protected])([email protected]))([email protected])([email protected])
'@typescript-eslint/typescript-estree': 8.47.0([email protected])([email protected])
'@typescript-eslint/utils': 8.47.0([email protected]([email protected])([email protected]))([email protected])([email protected])
eslint: 9.39.1([email protected])([email protected])
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
@@ -3407,10 +3593,69 @@ snapshots:
lightningcss: 1.30.2
tsx: 4.21.0
[email protected](@types/[email protected])([email protected])([email protected])([email protected]):
dependencies:
esbuild: 0.25.12
fdir: 6.5.0([email protected])
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
[email protected](@types/[email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected])):
dependencies:
'@types/chai': 5.2.3
'@vitest/mocker': 5.0.3([email protected](@types/[email protected])([email protected])([email protected])([email protected]))
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/[email protected])([email protected])([email protected])([email protected])
why-is-node-running: 3.2.1
optionalDependencies:
'@types/node': 24.10.1
transitivePeerDependencies:
- msw
[email protected](@types/[email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected])):
dependencies:
'@types/chai': 5.2.3
'@vitest/mocker': 5.0.3([email protected](@types/[email protected])([email protected])([email protected])([email protected]))
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/[email protected])([email protected])([email protected])([email protected])
why-is-node-running: 3.2.1
optionalDependencies:
'@types/node': 25.2.3
transitivePeerDependencies:
- msw
[email protected]:
dependencies:
isexe: 2.0.0
[email protected]: {}
[email protected]: {}
[email protected]: {}
+2 -1
View File
@@ -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"
}
}
+4 -1
View File
@@ -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"
}
}
@@ -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<DownloadItemData> = {}) =>
renderToStaticMarkup(<DownloadItem item={{ ...base, ...patch }} />);
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%");
});
});
});
+83 -76
View File
@@ -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<DownloadStatus, { box: string; icon: ReactNode }> = {
completed: { box: "bg-green-500/10 text-green-500", icon: <FileCheck className="h-5 w-5" /> },
error: { box: "bg-destructive/10 text-destructive", icon: <FileWarning className="h-5 w-5" /> },
queued: {
box: "bg-secondary text-secondary-foreground",
icon: <Server className="h-5 w-5 animate-pulse" />,
},
downloading: { box: "bg-primary/10 text-primary", icon: <Loader2 className="h-5 w-5 animate-spin" /> },
};
function StatusIcon({ status }: { status: DownloadStatus }) {
const { box, icon } = STATUS_ICON[status];
return <div className={`${ICON_BOX} ${box}`}>{icon}</div>;
}
function ProgressSection({ item }: { item: DownloadItemData }) {
const total = item.totalBytes ? formatBytes(item.totalBytes) : "??";
return (
<div className="space-y-2 pt-2">
<div className="flex justify-between text-xs text-muted-foreground">
<span>{item.status === "queued" ? "Queued on server..." : item.status}</span>
<span>{formatBytes(item.receivedBytes || 0)} / {total}</span>
</div>
<Progress value={item.progress || 0} className="h-1.5" />
<div className="flex justify-between text-xs text-muted-foreground">
<span>{item.speed ? formatSpeed(item.speed) : "0 B/s"}</span>
<span>{item.progress ? `${Math.round(item.progress)}%` : "0%"}</span>
</div>
</div>
);
}
function StatusDetails({ item }: { item: DownloadItemData }) {
if (item.status === "completed") {
return (
<div className="pt-2 flex items-center gap-2 text-xs text-green-500">
<FileCheck className="w-3 h-3" />
<span>Ready for download ({formatBytes(item.totalBytes)})</span>
</div>
);
}
if (item.status === "error") {
return (
<div className="text-sm text-destructive pt-1">
{item.error || "Unknown error occurred"}
</div>
);
}
return <ProgressSection item={item} />;
}
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 (
<Card className="overflow-hidden transition-all hover:bg-muted/50">
<CardContent className="p-4">
<div className="flex items-start gap-4">
<div className="mt-1">
{isCompleted ? (
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-green-500/10 text-green-500">
<FileCheck className="h-5 w-5" />
</div>
) : isError ? (
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-destructive/10 text-destructive">
<FileWarning className="h-5 w-5" />
</div>
) : isQueued ? (
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-secondary text-secondary-foreground">
<Server className="h-5 w-5 animate-pulse" />
</div>
) : (
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-primary/10 text-primary">
<Loader2 className="h-5 w-5 animate-spin" />
</div>
)}
<StatusIcon status={item.status} />
</div>
<div className="flex-1 space-y-1 min-w-0">
@@ -99,41 +132,15 @@ export function DownloadItem({ item }: DownloadItemProps) {
{item.url}
</p>
</div>
{isCompleted && (
<Button size="sm" onClick={handleDownloadFile} className="shrink-0 gap-2">
{item.status === "completed" && (
<Button size="sm" onClick={() => saveToDisk(item)} className="shrink-0 gap-2">
<HardDrive className="w-4 h-4" />
Save to Disk
</Button>
)}
</div>
{/* Progress Section */}
{!isCompleted && !isError && (
<div className="space-y-2 pt-2">
<div className="flex justify-between text-xs text-muted-foreground">
<span>{isQueued ? "Queued on server..." : item.status}</span>
<span>{formatBytes(item.receivedBytes || 0)} / {item.totalBytes ? formatBytes(item.totalBytes) : "??"}</span>
</div>
<Progress value={item.progress || 0} className="h-1.5" />
<div className="flex justify-between text-xs text-muted-foreground">
<span>{item.speed ? formatSpeed(item.speed) : "0 B/s"}</span>
<span>{item.progress ? `${Math.round(item.progress)}%` : "0%"}</span>
</div>
</div>
)}
{isCompleted && (
<div className="pt-2 flex items-center gap-2 text-xs text-green-500">
<FileCheck className="w-3 h-3" />
<span>Ready for download ({formatBytes(item.totalBytes)})</span>
</div>
)}
{isError && (
<div className="text-sm text-destructive pt-1">
{item.error || "Unknown error occurred"}
</div>
)}
<StatusDetails item={item} />
</div>
</div>
</CardContent>