Snapshot of active development by separate Claude instance. Includes: event bus, progress parser, WebSocket route, download progress bar component, SSE contexts/hooks. Not tested or validated — commit for migration to dev01. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
229 lines
6.4 KiB
TypeScript
229 lines
6.4 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { parseProgressLine, isProgressLine } from '../services/progress-parser';
|
|
|
|
describe('parseProgressLine', () => {
|
|
describe('standard progress lines', () => {
|
|
it('parses a normal progress line', () => {
|
|
const result = parseProgressLine(
|
|
'[download] 45.2% of ~150.00MiB at 1.23MiB/s ETA 00:42'
|
|
);
|
|
expect(result).toEqual({
|
|
percent: 45.2,
|
|
speed: '1.23MiB/s',
|
|
eta: '00:42',
|
|
totalSize: '~150.00MiB',
|
|
});
|
|
});
|
|
|
|
it('parses 100% completion', () => {
|
|
const result = parseProgressLine(
|
|
'[download] 100% of 150.00MiB at 2.50MiB/s ETA 00:00'
|
|
);
|
|
expect(result).toEqual({
|
|
percent: 100,
|
|
speed: '2.50MiB/s',
|
|
eta: '00:00',
|
|
totalSize: '150.00MiB',
|
|
});
|
|
});
|
|
|
|
it('parses a line with 0% progress', () => {
|
|
const result = parseProgressLine(
|
|
'[download] 0.0% of ~500.00MiB at 0.50MiB/s ETA 16:40'
|
|
);
|
|
expect(result).toEqual({
|
|
percent: 0,
|
|
speed: '0.50MiB/s',
|
|
eta: '16:40',
|
|
totalSize: '~500.00MiB',
|
|
});
|
|
});
|
|
|
|
it('parses a line with GiB total size', () => {
|
|
const result = parseProgressLine(
|
|
'[download] 12.3% of ~2.50GiB at 10.00MiB/s ETA 03:45'
|
|
);
|
|
expect(result).toEqual({
|
|
percent: 12.3,
|
|
speed: '10.00MiB/s',
|
|
eta: '03:45',
|
|
totalSize: '~2.50GiB',
|
|
});
|
|
});
|
|
|
|
it('parses a line with KiB speed', () => {
|
|
const result = parseProgressLine(
|
|
'[download] 5.0% of ~80.00MiB at 512.00KiB/s ETA 02:37'
|
|
);
|
|
expect(result).toEqual({
|
|
percent: 5.0,
|
|
speed: '512.00KiB/s',
|
|
eta: '02:37',
|
|
totalSize: '~80.00MiB',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('unknown values', () => {
|
|
it('handles Unknown speed', () => {
|
|
const result = parseProgressLine(
|
|
'[download] 10.0% of ~150.00MiB at Unknown speed ETA Unknown'
|
|
);
|
|
expect(result).toEqual({
|
|
percent: 10.0,
|
|
speed: '',
|
|
eta: '',
|
|
totalSize: '~150.00MiB',
|
|
});
|
|
});
|
|
|
|
it('handles Unknown ETA with known speed', () => {
|
|
const result = parseProgressLine(
|
|
'[download] 25.0% of ~300.00MiB at 5.00MiB/s ETA Unknown'
|
|
);
|
|
expect(result).toEqual({
|
|
percent: 25.0,
|
|
speed: '5.00MiB/s',
|
|
eta: '',
|
|
totalSize: '~300.00MiB',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('non-progress lines (returns null)', () => {
|
|
it('returns null for empty string', () => {
|
|
expect(parseProgressLine('')).toBeNull();
|
|
});
|
|
|
|
it('returns null for whitespace', () => {
|
|
expect(parseProgressLine(' ')).toBeNull();
|
|
});
|
|
|
|
it('returns null for info lines', () => {
|
|
expect(
|
|
parseProgressLine('[info] Writing video metadata as JSON to: file.json')
|
|
).toBeNull();
|
|
});
|
|
|
|
it('returns null for merge lines', () => {
|
|
expect(
|
|
parseProgressLine('[Merger] Merging formats into "video.mp4"')
|
|
).toBeNull();
|
|
});
|
|
|
|
it('returns null for postprocessor lines', () => {
|
|
expect(
|
|
parseProgressLine('[EmbedSubtitle] Embedding subtitles in "video.mp4"')
|
|
).toBeNull();
|
|
});
|
|
|
|
it('returns null for destination lines', () => {
|
|
expect(
|
|
parseProgressLine('[download] Destination: /path/to/file.mp4')
|
|
).toBeNull();
|
|
});
|
|
|
|
it('returns null for "has already been downloaded" lines', () => {
|
|
expect(
|
|
parseProgressLine('[download] Video abc123 has already been downloaded')
|
|
).toBeNull();
|
|
});
|
|
|
|
it('returns null for plain text output', () => {
|
|
expect(parseProgressLine('/path/to/downloaded/file.mp4')).toBeNull();
|
|
});
|
|
|
|
it('returns null for Deleting lines', () => {
|
|
expect(
|
|
parseProgressLine('Deleting original file /tmp/video.webm')
|
|
).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('edge cases', () => {
|
|
it('handles lines with leading whitespace', () => {
|
|
const result = parseProgressLine(
|
|
' [download] 50.0% of ~200.00MiB at 3.00MiB/s ETA 00:33 '
|
|
);
|
|
expect(result).toEqual({
|
|
percent: 50.0,
|
|
speed: '3.00MiB/s',
|
|
eta: '00:33',
|
|
totalSize: '~200.00MiB',
|
|
});
|
|
});
|
|
|
|
it('clamps percent to 100 maximum', () => {
|
|
// Shouldn't happen in practice, but defensive
|
|
const result = parseProgressLine(
|
|
'[download] 105.0% of ~100.00MiB at 1.00MiB/s ETA 00:00'
|
|
);
|
|
expect(result).not.toBeNull();
|
|
expect(result!.percent).toBe(100);
|
|
});
|
|
|
|
it('handles no tilde prefix on total size', () => {
|
|
const result = parseProgressLine(
|
|
'[download] 75.0% of 120.00MiB at 4.00MiB/s ETA 00:10'
|
|
);
|
|
expect(result).toEqual({
|
|
percent: 75.0,
|
|
speed: '4.00MiB/s',
|
|
eta: '00:10',
|
|
totalSize: '120.00MiB',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('multi-stream download lines', () => {
|
|
it('parses the first stream progress normally', () => {
|
|
// When downloading video+audio separately, yt-dlp shows progress for each
|
|
const result = parseProgressLine(
|
|
'[download] 30.0% of ~80.00MiB at 2.00MiB/s ETA 00:28'
|
|
);
|
|
expect(result).not.toBeNull();
|
|
expect(result!.percent).toBe(30.0);
|
|
});
|
|
|
|
it('parses the second stream progress (resets to 0%)', () => {
|
|
// The second stream starts at 0% again
|
|
const result = parseProgressLine(
|
|
'[download] 5.0% of ~20.00MiB at 1.50MiB/s ETA 00:13'
|
|
);
|
|
expect(result).not.toBeNull();
|
|
expect(result!.percent).toBe(5.0);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('isProgressLine', () => {
|
|
it('returns true for a progress line', () => {
|
|
expect(
|
|
isProgressLine('[download] 45.2% of ~150.00MiB at 1.23MiB/s ETA 00:42')
|
|
).toBe(true);
|
|
});
|
|
|
|
it('returns true for 100% line', () => {
|
|
expect(
|
|
isProgressLine('[download] 100% of 150.00MiB at 2.50MiB/s ETA 00:00')
|
|
).toBe(true);
|
|
});
|
|
|
|
it('returns false for destination line (no %)', () => {
|
|
expect(
|
|
isProgressLine('[download] Destination: /path/to/file.mp4')
|
|
).toBe(false);
|
|
});
|
|
|
|
it('returns false for info line', () => {
|
|
expect(isProgressLine('[info] Available formats for abc123')).toBe(false);
|
|
});
|
|
|
|
it('returns false for empty line', () => {
|
|
expect(isProgressLine('')).toBe(false);
|
|
});
|
|
|
|
it('returns false for filepath output', () => {
|
|
expect(isProgressLine('/media/youtube/channel/video.mp4')).toBe(false);
|
|
});
|
|
});
|