Complete Phase C (custom commands & yt-dlp updater) and Phase D (library, UX & notifications)
Phase C had been implemented in the working tree but never committed: named custom-command templates (CRUD + per-download override), command preview, and the in-app yt-dlp self-updater. Phase D adds: history search/kind-filter/multi-select/bulk-delete/ re-download; native OS notifications on completion/failure; a private/ incognito download mode that skips history; settings+template backup/ restore via JSON; and a persistent error log surfaced as a Diagnostics report in Settings. See ROADMAP.md for the full per-item breakdown. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+80
-3
@@ -1,5 +1,12 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { buildArgs, CROP_SQUARE_PPA, ARIA2C_ARGS, type AccessOptions } from '../src/main/buildArgs'
|
||||
import {
|
||||
buildArgs,
|
||||
parseExtraArgs,
|
||||
formatCommandLine,
|
||||
CROP_SQUARE_PPA,
|
||||
ARIA2C_ARGS,
|
||||
type AccessOptions
|
||||
} from '../src/main/buildArgs'
|
||||
import {
|
||||
DEFAULT_DOWNLOAD_OPTIONS,
|
||||
type DownloadOptions,
|
||||
@@ -25,9 +32,10 @@ function dlo(overrides: Partial<DownloadOptions> = {}): DownloadOptions {
|
||||
function build(
|
||||
o: StartDownloadOptions,
|
||||
d: DownloadOptions,
|
||||
access: AccessOptions = NO_ACCESS
|
||||
access: AccessOptions = NO_ACCESS,
|
||||
extraArgs: string[] = []
|
||||
): string[] {
|
||||
return buildArgs(o, OUT, d, BIN, access)
|
||||
return buildArgs(o, OUT, d, BIN, access, extraArgs)
|
||||
}
|
||||
|
||||
/** True when `seq` appears as a contiguous run inside `argv`. */
|
||||
@@ -283,3 +291,72 @@ describe('cropThumbnail → --ppa crop', () => {
|
||||
expect(argv).not.toContain('--embed-thumbnail')
|
||||
})
|
||||
})
|
||||
|
||||
// --- Custom commands (Phase C): extra args + display formatting ------------
|
||||
|
||||
describe('parseExtraArgs', () => {
|
||||
it('splits on whitespace', () => {
|
||||
expect(parseExtraArgs('--write-thumbnail --no-mtime')).toEqual([
|
||||
'--write-thumbnail',
|
||||
'--no-mtime'
|
||||
])
|
||||
})
|
||||
|
||||
it('keeps a double-quoted span with spaces as one token', () => {
|
||||
expect(parseExtraArgs('--proxy "http://example.com:8080"')).toEqual([
|
||||
'--proxy',
|
||||
'http://example.com:8080'
|
||||
])
|
||||
})
|
||||
|
||||
it('keeps a single-quoted span with spaces as one token', () => {
|
||||
expect(parseExtraArgs("--output-na 'My Title - %(id)s.%(ext)s'")).toEqual([
|
||||
'--output-na',
|
||||
'My Title - %(id)s.%(ext)s'
|
||||
])
|
||||
})
|
||||
|
||||
it('returns an empty array for blank input', () => {
|
||||
expect(parseExtraArgs('')).toEqual([])
|
||||
expect(parseExtraArgs(' ')).toEqual([])
|
||||
})
|
||||
|
||||
it('collapses repeated whitespace between tokens', () => {
|
||||
expect(parseExtraArgs('--verbose --no-progress')).toEqual(['--verbose', '--no-progress'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatCommandLine', () => {
|
||||
it('joins the exe and args with spaces when nothing needs quoting', () => {
|
||||
expect(formatCommandLine('yt-dlp.exe', ['-f', 'best', '--', 'https://x.test/v'])).toBe(
|
||||
'yt-dlp.exe -f best -- https://x.test/v'
|
||||
)
|
||||
})
|
||||
|
||||
it('quotes an arg containing a space', () => {
|
||||
expect(formatCommandLine('yt-dlp.exe', ['--ppa', 'EmbedThumbnail+ffmpeg_o:-c:v mjpeg'])).toBe(
|
||||
'yt-dlp.exe --ppa "EmbedThumbnail+ffmpeg_o:-c:v mjpeg"'
|
||||
)
|
||||
})
|
||||
|
||||
it('escapes embedded double quotes', () => {
|
||||
expect(formatCommandLine('yt-dlp.exe', ['say "hi"'])).toBe('yt-dlp.exe "say \\"hi\\""')
|
||||
})
|
||||
|
||||
it('renders an empty-string arg as ""', () => {
|
||||
expect(formatCommandLine('yt-dlp.exe', [''])).toBe('yt-dlp.exe ""')
|
||||
})
|
||||
})
|
||||
|
||||
describe('buildArgs extraArgs', () => {
|
||||
it('appends extraArgs after every other option, still before -- url', () => {
|
||||
const argv = build(opts(), dlo(), NO_ACCESS, ['--write-thumbnail', '--no-mtime'])
|
||||
expect(hasSeq(argv, '--write-thumbnail', '--no-mtime', '--', URL)).toBe(true)
|
||||
})
|
||||
|
||||
it('omits nothing extra when extraArgs is empty', () => {
|
||||
const withEmpty = build(opts(), dlo(), NO_ACCESS, [])
|
||||
const withoutParam = build(opts(), dlo())
|
||||
expect(withEmpty).toEqual(withoutParam)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user