Files
AeroFetch/test/queueStats.test.ts
debont80 9134e7d216 feat(audit): Batch 25 — project reorg (CC12), leading DI args (CC7), CC10 by-design
CC12: renderer views/ (5 screens + Onboarding + settings cards) +
lib/ (theme/thumb/datetime/id/qualityOptions/useClipboardLink/queueStats);
main core/ (buildArgs/validation/indexerCore/ytdlpPolicy). git mv preserved
history; all src+test imports updated. Build emits view chunks from views/,
344 tests + typecheck green, live probe rendered all screens.
CC7: wc/onProgress is the leading arg everywhere — downloadAppUpdate(wc,url),
indexSource(onProgress,url,signal), indexSourceCancelable(onProgress,url).
CC10: closed by-design — jsonStore backs all records; settings stay in
electron-store for DPAPI secret encryption (a deliberate two-store split).
Also closes the UI24/W4 context-menu cross-reference.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 15:37:28 -04:00

105 lines
3.3 KiB
TypeScript

import { describe, it, expect } from 'vitest'
import { summarizeQueue, sameVideo } from '../src/renderer/src/lib/queueStats'
import type { DownloadItem } from '../src/renderer/src/store/downloads'
// Minimal item factory — only the fields summarizeQueue reads.
function item(overrides: Partial<DownloadItem>): DownloadItem {
return {
id: Math.random().toString(36).slice(2),
url: 'https://youtube.com/watch?v=x',
title: 'x',
kind: 'video',
quality: '1080p',
status: 'queued',
progress: 0,
...overrides
}
}
describe('summarizeQueue', () => {
it('counts statuses and flags active', () => {
const s = summarizeQueue([
item({ status: 'downloading', progress: 0.5 }),
item({ status: 'queued' }),
item({ status: 'error' }),
item({ status: 'completed', progress: 1 }),
item({ status: 'canceled' })
])
expect(s.downloading).toBe(1)
expect(s.queued).toBe(1)
expect(s.failed).toBe(1)
expect(s.active).toBe(true)
})
it('is inactive and zeroed when nothing is downloading or queued', () => {
const s = summarizeQueue([item({ status: 'completed', progress: 1 })])
expect(s.active).toBe(false)
expect(s.progress).toBe(0)
expect(s.speedLabel).toBe('')
expect(s.etaLabel).toBe('')
})
it('averages progress over downloading + queued (queued counts as 0%)', () => {
// one at 80%, one queued at 0% → 40%
const s = summarizeQueue([
item({ status: 'downloading', progress: 0.8 }),
item({ status: 'queued' })
])
expect(s.progress).toBeCloseTo(0.4, 5)
})
it('sums raw download rates into one formatted label', () => {
const MB = 1024 * 1024
const s = summarizeQueue([
item({ status: 'downloading', progress: 0.1, speedBytesPerSec: 4 * MB }),
item({ status: 'downloading', progress: 0.1, speedBytesPerSec: 2 * MB })
])
// 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, etaSeconds: 30 }),
item({ status: 'downloading', progress: 0.2, etaSeconds: 130 })
])
expect(s.etaLabel).toBe('2:10')
})
})
describe('sameVideo', () => {
it('matches identical URLs', () => {
expect(sameVideo('https://x.com/a', 'https://x.com/a')).toBe(true)
})
it('matches YouTube links by video id across watch/youtu.be/extra params', () => {
expect(
sameVideo('https://www.youtube.com/watch?v=dQw4w9WgXcQ', 'https://youtu.be/dQw4w9WgXcQ')
).toBe(true)
expect(
sameVideo(
'https://youtube.com/watch?v=dQw4w9WgXcQ&list=PL123',
'https://www.youtube.com/watch?v=dQw4w9WgXcQ'
)
).toBe(true)
})
it('does not match different videos', () => {
expect(
sameVideo(
'https://youtube.com/watch?v=aaaaaaaaaaa',
'https://youtube.com/watch?v=bbbbbbbbbbb'
)
).toBe(false)
})
it('normalises www. and trailing slash for non-YouTube URLs', () => {
expect(sameVideo('https://www.vimeo.com/123/', 'https://vimeo.com/123')).toBe(true)
})
})