H4 + H2 formatter half: carry raw numbers across the progress boundary
Closes H4 and the remaining formatter half of H2. - New @shared/format.ts is the single home for fmtBytes/fmtSpeed/fmtEta, imported by both main and renderer. main/lib/formatters.ts re-exports them. - DownloadProgress now carries raw speedBytesPerSec/etaSeconds instead of pre-formatted speed/eta strings. main's parseProgress emits the raw numbers. - The renderer stores the raw numbers on DownloadItem; QueueItem formats them for per-item display, and summarizeQueue sums/maxes them directly. The lossy string->number->string round-trip in queueStats (parseSpeed / parseEtaSeconds / a local formatSpeed / formatEta) is deleted, along with a duplicate fmtEta in store/downloads.ts. - Per-item and aggregate speed now use the same 1024-based scale; the aggregate previously used a 1000-based formatter (a latent inconsistency). Tests updated for the raw-number contract (parseProgress, summarizeQueue). typecheck + 248 tests + eslint + prettier green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -66,7 +66,8 @@ describe('parseProgress', () => {
|
||||
const p = parseProgress(raw)
|
||||
expect(p).not.toBeNull()
|
||||
expect(p!.progress).toBeCloseTo(0.5)
|
||||
expect(p!.eta).toBe('0:02')
|
||||
expect(p!.speedBytesPerSec).toBe(262144)
|
||||
expect(p!.etaSeconds).toBe(2)
|
||||
expect(p!.sizeLabel).toBe('1.0 MB')
|
||||
expect(p!.sizeUnknown).toBe(false)
|
||||
})
|
||||
@@ -84,8 +85,8 @@ describe('parseProgress', () => {
|
||||
const p = parseProgress(raw)
|
||||
expect(p).not.toBeNull()
|
||||
expect(p!.sizeUnknown).toBe(true)
|
||||
expect(p!.eta).toBeUndefined()
|
||||
expect(p!.speed).toBeUndefined()
|
||||
expect(p!.etaSeconds).toBeUndefined()
|
||||
expect(p!.speedBytesPerSec).toBeUndefined()
|
||||
})
|
||||
|
||||
it('clamps progress to 1.0', () => {
|
||||
@@ -94,9 +95,10 @@ describe('parseProgress', () => {
|
||||
expect(p!.progress).toBe(1)
|
||||
})
|
||||
|
||||
it('formats an ETA over 1 hour correctly (L166)', () => {
|
||||
it('carries a raw ETA in seconds; the renderer formats it (L166)', () => {
|
||||
const raw = makeRaw(['downloading', '100', '1000', '1000', '10', '3661'])
|
||||
const p = parseProgress(raw)
|
||||
expect(p!.eta).toBe('1:01:01')
|
||||
expect(p!.etaSeconds).toBe(3661)
|
||||
expect(fmtEta(p!.etaSeconds)).toBe('1:01:01')
|
||||
})
|
||||
})
|
||||
|
||||
+12
-6
@@ -48,19 +48,25 @@ describe('summarizeQueue', () => {
|
||||
expect(s.progress).toBeCloseTo(0.4, 5)
|
||||
})
|
||||
|
||||
it('sums download speeds across MB/s and MiB/s strings', () => {
|
||||
it('sums raw download rates into one formatted label', () => {
|
||||
const MB = 1024 * 1024
|
||||
const s = summarizeQueue([
|
||||
item({ status: 'downloading', progress: 0.1, speed: '4.0 MB/s' }),
|
||||
item({ status: 'downloading', progress: 0.1, speed: '2000 KiB/s' })
|
||||
item({ status: 'downloading', progress: 0.1, speedBytesPerSec: 4 * MB }),
|
||||
item({ status: 'downloading', progress: 0.1, speedBytesPerSec: 2 * MB })
|
||||
])
|
||||
// 4.0 MB/s + ~2.0 MB/s ≈ 6.0 MB/s
|
||||
// 4 MB/s + 2 MB/s = 6 MB/s
|
||||
expect(s.speedLabel).toBe('6.0 MB/s')
|
||||
})
|
||||
|
||||
it('leaves the speed label empty when no item reports a rate', () => {
|
||||
const s = summarizeQueue([item({ status: 'downloading', progress: 0.1 })])
|
||||
expect(s.speedLabel).toBe('')
|
||||
})
|
||||
|
||||
it('reports the longest active ETA as the time left', () => {
|
||||
const s = summarizeQueue([
|
||||
item({ status: 'downloading', progress: 0.5, eta: '0:30' }),
|
||||
item({ status: 'downloading', progress: 0.2, eta: '2:10' })
|
||||
item({ status: 'downloading', progress: 0.5, etaSeconds: 30 }),
|
||||
item({ status: 'downloading', progress: 0.2, etaSeconds: 130 })
|
||||
])
|
||||
expect(s.etaLabel).toBe('2:10')
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user