Files
AeroFetch/test/queueStats.test.ts
T
debont80 1376c2dee8 Harden audit findings: correctness, type-safety, Windows conventions & polish
Audit-pass over CODE-AUDIT.md (~48 items closed this pass; all verified —
typecheck + 234 tests + eslint + prettier green).

Correctness / bugs:
- B3: match the release checksum to the asset's filename line (no wrong-hash verify)
- B4: newline-safe metadata probe (one --print with a unit-separator delimiter)
- B5 / L88: guard the meta event against canceled items; progress no longer promotes
  a queued item outside pump()
- B7: cookie-login promise always resolves (handles destroy-without-close)
- L146: trim parser rejects >2 colon-group times; M36: Library selection counts only
  actionable rows
- L11 / L50 / L156 / L57 / L159 / L15 / L3: live queue count, empty-cookie message,
  schedule picker min, dead-code/comment cleanup

Type safety:
- Enable noUncheckedIndexedAccess + noFallthroughCasesInSwitch (15 real edge cases fixed)

Resilience / Windows / metadata:
- R5: settings write failure handled (no unhandled IPC rejection; reconciles to truth)
- W1 / W5 / W6: min window size, seeded folder picker, parented sign-in window;
  L147 dead macOS branches removed
- CL1: shared stdout markers; package/builder metadata (license, homepage, repository,
  copyright, tsbuildinfo glob)

Copy / docs / tests:
- M37 / SR9 dev-jargon cleanup in hints; M8 / M25 / M26 / L66 / L80 / L81 reconciled
- New unit tests for L35 (isValidMediaItem) and L36 (compareVersions)

This commit also checkpoints the previously-uncommitted feat/tray-background-clipboard
work it builds on: background running + auto-download, library clipboard detection,
tray, binary management & library scale, credential encryption at rest, the shared
jsonStore and ui/ primitives, and the eslint/prettier tooling.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 13:02:54 -04:00

99 lines
3.1 KiB
TypeScript

import { describe, it, expect } from 'vitest'
import { summarizeQueue, sameVideo } from '../src/renderer/src/store/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 download speeds across MB/s and MiB/s strings', () => {
const s = summarizeQueue([
item({ status: 'downloading', progress: 0.1, speed: '4.0 MB/s' }),
item({ status: 'downloading', progress: 0.1, speed: '2000 KiB/s' })
])
// 4.0 MB/s + ~2.0 MB/s ≈ 6.0 MB/s
expect(s.speedLabel).toBe('6.0 MB/s')
})
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' })
])
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)
})
})