diff --git a/src/renderer/src/components/wizard/DownloadProfileEditor.tsx b/src/renderer/src/components/wizard/DownloadProfileEditor.tsx index af2fda02..100903ec 100644 --- a/src/renderer/src/components/wizard/DownloadProfileEditor.tsx +++ b/src/renderer/src/components/wizard/DownloadProfileEditor.tsx @@ -4,17 +4,7 @@ import {DOWNLOAD_PROFILE_ICONS} from '@shared/schemas.js' import type {CommonSettings, DownloadProfile, DownloadProfileAudioFormat, DownloadProfileIcon, DownloadProfileSubtitleSource, PlaylistVideoCodec, PlaylistVideoTier, SponsorBlockMode, SubtitleFormat, SubtitleMode} from '@shared/types.js' import {effectiveOutputDir} from '@shared/subfolder.js' import {cn, formatHomeRelativePath} from '@renderer/lib/utils.js' -import { - createDownloadProfileDraft, - defaultProfileSubfolderName, - downloadProfileFromDraft, - type DownloadProfileAudioQuality, - type DownloadProfileDraftAction, - type DownloadProfileMediaMode, - type DownloadProfilePlaylistCap, - updateDownloadProfileDraft, - validateDownloadProfileDraft -} from '../../store/wizard/downloadProfileDraft.js' +import {createDownloadProfileDraft, defaultProfileSubfolderName, downloadProfileFromDraft, type DownloadProfileAudioQuality, type DownloadProfileDraftAction, type DownloadProfileMediaMode, updateDownloadProfileDraft, validateDownloadProfileDraft} from '../../store/wizard/downloadProfileDraft.js' import {Alert, AlertDescription} from '../ui/alert.js' import {Badge} from '../ui/badge.js' import {Button} from '../ui/button.js' @@ -157,14 +147,6 @@ const SPONSOR_BLOCK_OPTIONS: {value: SponsorBlockMode; label: string}[] = [ const SPONSOR_BLOCK_HINTS: Record = {off: 'No SponsorBlock — video plays as uploaded.', mark: 'Marks sponsor segments as chapters (non-destructive).', remove: 'Cuts sponsor segments from the video using FFmpeg.'} -const PLAYLIST_CAP_OPTIONS: SelectOption[] = [ - {value: 'confirm', label: 'Confirm when capped'}, - {value: '100', label: 'Load 100 items'}, - {value: '250', label: 'Load 250 items'}, - {value: '500', label: 'Load 500 items'}, - {value: '1000', label: 'Load 1000 items'} -] - const SELECTABLE_TOGGLE_CLASS = 'flex-1 data-[state=on]:border-[var(--brand)] data-[state=on]:bg-[var(--brand-dim)] data-[state=on]:text-[var(--brand)] aria-pressed:border-[var(--brand)] aria-pressed:bg-[var(--brand-dim)] aria-pressed:text-[var(--brand)]' const OUTPUT_MODE_CARD_CLASS = 'h-auto min-h-[4.35rem] flex-col gap-1.5 whitespace-normal rounded-lg border border-[var(--border-strong)] px-2 py-2.5 text-center data-[state=on]:border-[var(--brand)] data-[state=on]:bg-[var(--brand-dim)] data-[state=on]:text-[var(--brand)] aria-pressed:border-[var(--brand)] aria-pressed:bg-[var(--brand-dim)] aria-pressed:text-[var(--brand)]' @@ -258,8 +240,7 @@ export function DownloadProfileEditor({commonPaths, globalDestination = '', init embedChapters, saveDescription, saveThumbnail, - sponsorBlockMode, - playlistCap + sponsorBlockMode } = draft const showVideo = mediaMode === 'video-audio' || mediaMode === 'video-only' const showAudio = mediaMode === 'video-audio' || mediaMode === 'audio-only' @@ -757,8 +738,6 @@ export function DownloadProfileEditor({commonPaths, globalDestination = '', init )} - - updateDraft({type: 'set-playlist-cap', playlistCap: next})} testId="profiles-editor-playlist-cap" /> diff --git a/src/renderer/src/store/wizard/downloadProfileDraft.ts b/src/renderer/src/store/wizard/downloadProfileDraft.ts index 8e45b1dd..56c943dc 100644 --- a/src/renderer/src/store/wizard/downloadProfileDraft.ts +++ b/src/renderer/src/store/wizard/downloadProfileDraft.ts @@ -5,7 +5,6 @@ import {isValidSubfolder, safeFolderName} from '@shared/subfolder.js' export type DownloadProfileMediaMode = DownloadProfile['media']['kind'] export type DownloadProfileAudioQuality = 'best' | '320' | '192' | '128' -export type DownloadProfilePlaylistCap = 'confirm' | '100' | '250' | '500' | '1000' export interface DownloadProfileDraft { profileId: string | null @@ -31,7 +30,6 @@ export interface DownloadProfileDraft { saveDescription: boolean saveThumbnail: boolean sponsorBlockMode: SponsorBlockMode - playlistCap: DownloadProfilePlaylistCap } export type DownloadProfileDraftAction = @@ -58,7 +56,6 @@ export type DownloadProfileDraftAction = | {type: 'set-save-description'; saveDescription: boolean} | {type: 'set-save-thumbnail'; saveThumbnail: boolean} | {type: 'set-sponsor-block-mode'; sponsorBlockMode: SponsorBlockMode} - | {type: 'set-playlist-cap'; playlistCap: DownloadProfilePlaylistCap} export interface DownloadProfileDraftValidation { subfolderInvalid: boolean @@ -88,11 +85,6 @@ function bitrateToQuality(bitrateKbps: number | undefined): DownloadProfileAudio return bitrateKbps === undefined ? 'best' : '192' } -function playlistCapToControlValue(cap: DownloadProfile['playlistProbeCap'] | undefined): DownloadProfilePlaylistCap { - if (cap === 100 || cap === 250 || cap === 500 || cap === 1000) return String(cap) as DownloadProfilePlaylistCap - return 'confirm' -} - function initialCodec(profile: DownloadProfile | null): PlaylistVideoCodec { const media = profile?.media return media?.kind === 'video-audio' || media?.kind === 'video-only' ? media.codec : 'mp4' @@ -141,8 +133,7 @@ export function createDownloadProfileDraft(initialProfile: DownloadProfile | nul embedChapters: initialProfile?.embed.chapters ?? true, saveDescription: initialProfile?.embed.description ?? true, saveThumbnail: initialProfile?.embed.thumbnailSidecar ?? true, - sponsorBlockMode: initialProfile?.sponsorBlock.mode ?? 'off', - playlistCap: playlistCapToControlValue(initialProfile?.playlistProbeCap) + sponsorBlockMode: initialProfile?.sponsorBlock.mode ?? 'off' } } @@ -201,8 +192,6 @@ export function updateDownloadProfileDraft(draft: DownloadProfileDraft, action: return {...draft, saveThumbnail: action.saveThumbnail} case 'set-sponsor-block-mode': return {...draft, sponsorBlockMode: action.sponsorBlockMode} - case 'set-playlist-cap': - return {...draft, playlistCap: action.playlistCap} } } @@ -242,7 +231,6 @@ export function downloadProfileFromDraft(draft: DownloadProfileDraft, now: strin subfolder: {enabled: draft.saveInsideSubfolder, name: draft.saveInsideSubfolder ? draft.subfolderName.trim() || defaultProfileSubfolderName(draft.profileName) : ''}, sponsorBlock: {mode: showVideo ? draft.sponsorBlockMode : 'off', categories: showVideo && draft.sponsorBlockMode !== 'off' ? [...DEFAULTS.sponsorBlockCategories] : []}, embed: {chapters: showVideo && draft.embedChapters, metadata: draft.embedMetadata, thumbnail: false, description: draft.saveDescription, thumbnailSidecar: draft.saveThumbnail}, - playlistProbeCap: draft.playlistCap === 'confirm' ? 'confirm' : Number(draft.playlistCap), createdAt: draft.createdAt ?? now, updatedAt: now } diff --git a/src/renderer/src/store/wizard/queueSubmission.ts b/src/renderer/src/store/wizard/queueSubmission.ts index b74bdf88..9012543d 100644 --- a/src/renderer/src/store/wizard/queueSubmission.ts +++ b/src/renderer/src/store/wizard/queueSubmission.ts @@ -188,7 +188,7 @@ export function prepareActiveProfileQueueSubmission(probe: ProbeResult, state: A if (probe.kind === 'video') { const outputTemplate = singleOutputTemplate(state.settings?.common?.includeIdInSingleFilenames ?? DEFAULTS.includeIdInSingleFilenames) - const item = buildProfileEntryQueueItem({entry: {url: state.wizardUrl || probe.webpageUrl, title: probe.title, thumbnail: probe.thumbnail}, outputDir: singleOutputDir, extractor: probe.extractor, extractorKey: probe.extractorKey, resolved, profile, outputTemplate, writeM3u: false, lane}) + const item = buildProfileEntryQueueItem({entry: {url: probe.webpageUrl || state.wizardUrl, title: probe.title, thumbnail: probe.thumbnail}, outputDir: singleOutputDir, extractor: probe.extractor, extractorKey: probe.extractorKey, resolved, profile, outputTemplate, writeM3u: false, lane}) return {items: [item]} } diff --git a/src/shared/downloadProfiles.ts b/src/shared/downloadProfiles.ts index f0bf612a..e4de71f0 100644 --- a/src/shared/downloadProfiles.ts +++ b/src/shared/downloadProfiles.ts @@ -23,7 +23,6 @@ function baseProfile(id: string, name: string, media: DownloadProfile['media'], subtitles: {enabled: false, languages: [], source: 'manual-first', mode: DEFAULTS.subtitleMode, format: DEFAULTS.subtitleFormat}, sponsorBlock: {mode: DEFAULTS.sponsorBlockMode, categories: [...DEFAULTS.sponsorBlockCategories]}, embed: {...BUILTIN_PROFILE_EMBED}, - playlistProbeCap: 'confirm', createdAt: BUILTIN_TIMESTAMP, updatedAt: BUILTIN_TIMESTAMP, output: {kind: 'default'}, diff --git a/src/shared/schemas.ts b/src/shared/schemas.ts index 62711249..b376f6c3 100644 --- a/src/shared/schemas.ts +++ b/src/shared/schemas.ts @@ -117,7 +117,6 @@ export const downloadProfileSchema = z.object({ subfolder: z.object({enabled: z.boolean(), name: subfolderNameSchema}), sponsorBlock: z.object({mode: sponsorBlockModeSchema, categories: z.array(sponsorBlockCategorySchema)}), embed: z.object({chapters: z.boolean(), metadata: z.boolean(), thumbnail: z.boolean(), description: z.boolean(), thumbnailSidecar: z.boolean()}), - playlistProbeCap: z.union([z.literal('confirm'), playlistProbeLimitSchema]), createdAt: z.string(), updatedAt: z.string() }) diff --git a/tests/e2e/fixture-workflows.spec.ts b/tests/e2e/fixture-workflows.spec.ts index 4af5210d..6880c762 100644 --- a/tests/e2e/fixture-workflows.spec.ts +++ b/tests/e2e/fixture-workflows.spec.ts @@ -2,12 +2,35 @@ import fs from 'node:fs' import path from 'node:path' import {expect, test} from '@playwright/test' import {BUILTIN_DOWNLOAD_PROFILES} from '../../src/shared/downloadProfiles.js' +import type {AppSettings} from '../../src/shared/types.js' import {FIXTURE_PLAYLIST_VIDEO_IDS, FIXTURE_VIDEO_IDS} from './fixtureHarness.js' import {withFixtureProductApp} from './fixtureProductE2E.js' import {clickContinue, preparePlaylistConfirm, prepareSingleConfirm, startBulkFromClipboard} from './fixtureWorkflow.js' test.describe.configure({mode: 'serial'}) +const FIXTURE_M3U_VIDEO_ID_PATTERN = /\[(ARX[0-9A-Z]{8})\]\.mp4/g + +function configureSmallFileQuickProfile(settings: AppSettings): void { + const smallFileProfile = BUILTIN_DOWNLOAD_PROFILES.find(profile => profile.id === 'small-file') + if (!smallFileProfile) throw new Error('small-file built-in profile missing') + settings.profiles.active = {kind: 'builtin', id: 'small-file'} + settings.profiles.overrides = [{...smallFileProfile, embed: {chapters: false, metadata: false, thumbnail: false, description: false, thumbnailSidecar: false}, sponsorBlock: {mode: 'off', categories: []}}] +} + +function smallFileProfileDir(outputDir: string): string { + return path.join(outputDir, 'Small file 480p') +} + +async function expectOrderedM3u(dir: string, playlistTitle: string, expectedIds: readonly string[]): Promise { + const m3uPath = path.join(dir, `${playlistTitle}.m3u`) + await expect.poll(() => fs.existsSync(m3uPath), {timeout: 20_000}).toBe(true) + const m3u = fs.readFileSync(m3uPath, 'utf8') + expect(m3u.startsWith('#EXTM3U\n')).toBe(true) + const orderedIds = [...m3u.matchAll(FIXTURE_M3U_VIDEO_ID_PATTERN)].map(match => match[1]) + expect(orderedIds).toEqual([...expectedIds]) +} + test('Electron quick download applies the active Download Profile to a fixture video', async () => { test.setTimeout(140_000) const videoId = FIXTURE_VIDEO_IDS[7] @@ -17,10 +40,7 @@ test('Electron quick download applies the active Download Profile to a fixture v userDataPrefix: 'arroxy-fixture-profile-quick-user-', outputPrefix: 'arroxy-fixture-profile-quick-out-', settings: settings => { - const smallFileProfile = BUILTIN_DOWNLOAD_PROFILES.find(profile => profile.id === 'small-file') - if (!smallFileProfile) throw new Error('small-file built-in profile missing') - settings.profiles.active = {kind: 'builtin', id: 'small-file'} - settings.profiles.overrides = [{...smallFileProfile, embed: {chapters: false, metadata: false, thumbnail: false, description: false, thumbnailSidecar: false}, sponsorBlock: {mode: 'off', categories: []}}] + configureSmallFileQuickProfile(settings) } }, async ({page, outputDir, fixtureServer, urls, queue, files}) => { @@ -30,7 +50,7 @@ test('Electron quick download applies the active Download Profile to a fixture v await expect(page.locator('[data-testid="profiles-mascot-help"]')).toContainText(/queued/i, {timeout: 60_000}) await queue.expectStatus('Fixture Video 8', 'done', 120_000) - const profileOutputDir = path.join(outputDir, 'Small file 480p') + const profileOutputDir = smallFileProfileDir(outputDir) files.expectMp4Count(1, profileOutputDir) const mediaRequest = fixtureServer.telemetry().requests.find(request => request.kind === 'media' && request.videoId === videoId && request.status === 200) expect(mediaRequest).toMatchObject({kind: 'media', videoId, formatId: '18', status: 200}) @@ -38,6 +58,88 @@ test('Electron quick download applies the active Download Profile to a fixture v ) }) +test('Electron Quick Download playlist queues entries and writes an ordered M3U', async () => { + test.setTimeout(220_000) + + await withFixtureProductApp({userDataPrefix: 'arroxy-fixture-quick-playlist-user-', outputPrefix: 'arroxy-fixture-quick-playlist-out-', settings: configureSmallFileQuickProfile}, async ({page, outputDir, urls, files}) => { + await page.locator('[data-testid="profiles-main-input"]').fill(urls.playlist()) + await page.locator('[data-testid="profiles-quick-download"]').click() + + await expect(page.locator('[data-testid^="queue-card-"]')).toHaveCount(FIXTURE_PLAYLIST_VIDEO_IDS.length, {timeout: 60_000}) + await expect(page.locator('[data-testid^="queue-card-"][data-status="done"]')).toHaveCount(FIXTURE_PLAYLIST_VIDEO_IDS.length, {timeout: 160_000}) + + const profileDir = smallFileProfileDir(outputDir) + files.expectMp4Count(FIXTURE_PLAYLIST_VIDEO_IDS.length, profileDir) + await expectOrderedM3u(profileDir, 'Fixture Playlist', FIXTURE_PLAYLIST_VIDEO_IDS) + }) +}) + +test('Electron Quick Download capped playlist can queue the globally loaded slice', async () => { + test.setTimeout(180_000) + const expectedIds = FIXTURE_PLAYLIST_VIDEO_IDS.slice(0, 2) + const skippedId = FIXTURE_PLAYLIST_VIDEO_IDS[2] + if (!skippedId) throw new Error('fixture playlist needs a skipped third item') + + await withFixtureProductApp( + { + userDataPrefix: 'arroxy-fixture-quick-playlist-cap-queue-user-', + outputPrefix: 'arroxy-fixture-quick-playlist-cap-queue-out-', + settings: settings => { + configureSmallFileQuickProfile(settings) + settings.common.playlistProbeLimit = 2 + } + }, + async ({page, outputDir, urls, files}) => { + await page.locator('[data-testid="profiles-main-input"]').fill(urls.playlist()) + await page.locator('[data-testid="profiles-quick-download"]').click() + + await expect(page.locator('[data-testid="quick-playlist-cap-dialog"]')).toBeVisible({timeout: 60_000}) + await expect(page.locator('[data-testid="quick-playlist-cap-dialog"]')).toContainText('Arroxy loaded 2 items using the current limit of 2') + await page.locator('[data-testid="quick-playlist-cap-queue-loaded"]').click() + + await expect(page.locator('[data-testid="quick-playlist-cap-dialog"]')).toBeHidden({timeout: 20_000}) + await expect(page.locator('[data-testid^="queue-card-"]')).toHaveCount(expectedIds.length, {timeout: 20_000}) + await expect(page.locator('[data-testid^="queue-card-"][data-status="done"]')).toHaveCount(expectedIds.length, {timeout: 140_000}) + + const profileDir = smallFileProfileDir(outputDir) + files.expectMp4Count(expectedIds.length, profileDir) + files.expectNoMp4For(skippedId, profileDir) + await expectOrderedM3u(profileDir, 'Fixture Playlist', expectedIds) + } + ) +}) + +test('Electron Quick Download capped playlist can increase the global cap and retry', async () => { + test.setTimeout(220_000) + + await withFixtureProductApp( + { + userDataPrefix: 'arroxy-fixture-quick-playlist-cap-retry-user-', + outputPrefix: 'arroxy-fixture-quick-playlist-cap-retry-out-', + settings: settings => { + configureSmallFileQuickProfile(settings) + settings.common.playlistProbeLimit = 2 + } + }, + async ({page, outputDir, urls, files}) => { + await page.locator('[data-testid="profiles-main-input"]').fill(urls.playlist()) + await page.locator('[data-testid="profiles-quick-download"]').click() + + await expect(page.locator('[data-testid="quick-playlist-cap-dialog"]')).toBeVisible({timeout: 60_000}) + await page.locator('[data-testid="quick-playlist-cap-probe-limit-trigger"]').click() + await page.locator('[data-testid="quick-playlist-cap-probe-limit-option-100"]').click() + + await expect(page.locator('[data-testid="quick-playlist-cap-dialog"]')).toBeHidden({timeout: 20_000}) + await expect(page.locator('[data-testid^="queue-card-"]')).toHaveCount(FIXTURE_PLAYLIST_VIDEO_IDS.length, {timeout: 60_000}) + await expect(page.locator('[data-testid^="queue-card-"][data-status="done"]')).toHaveCount(FIXTURE_PLAYLIST_VIDEO_IDS.length, {timeout: 160_000}) + + const profileDir = smallFileProfileDir(outputDir) + files.expectMp4Count(FIXTURE_PLAYLIST_VIDEO_IDS.length, profileDir) + await expectOrderedM3u(profileDir, 'Fixture Playlist', FIXTURE_PLAYLIST_VIDEO_IDS) + } + ) +}) + test('Electron full single-video wizard completes media and sidecar subtitles', async () => { test.setTimeout(180_000) const videoId = FIXTURE_VIDEO_IDS[8] @@ -81,15 +183,7 @@ test('Electron true playlist URL queues entries and writes an ordered M3U', asyn const playlistDir = path.join(outputDir, 'Fixture Playlist') files.expectMp4Count(FIXTURE_PLAYLIST_VIDEO_IDS.length, playlistDir) - const m3uPath = path.join(playlistDir, 'Fixture Playlist.m3u') - await expect.poll(() => fs.existsSync(m3uPath), {timeout: 20_000}).toBe(true) - const m3u = fs.readFileSync(m3uPath, 'utf8') - expect(m3u.startsWith('#EXTM3U\n')).toBe(true) - for (const videoId of FIXTURE_PLAYLIST_VIDEO_IDS) { - expect(m3u).toContain(`[${videoId}].mp4`) - } - const orderedIds = [...m3u.matchAll(/\[(ARX[0-9A-Z]{8})\]\.mp4/g)].map(match => match[1]) - expect(orderedIds).toEqual([...FIXTURE_PLAYLIST_VIDEO_IDS]) + await expectOrderedM3u(playlistDir, 'Fixture Playlist', FIXTURE_PLAYLIST_VIDEO_IDS) }) }) @@ -143,13 +237,10 @@ test('Electron bulk Quick Download shows preparation progress and queues fixture userDataPrefix: 'arroxy-fixture-bulk-quick-user-', outputPrefix: 'arroxy-fixture-bulk-quick-out-', settings: settings => { - const smallFileProfile = BUILTIN_DOWNLOAD_PROFILES.find(profile => profile.id === 'small-file') - if (!smallFileProfile) throw new Error('small-file built-in profile missing') - settings.profiles.active = {kind: 'builtin', id: 'small-file'} - settings.profiles.overrides = [{...smallFileProfile, embed: {chapters: false, metadata: false, thumbnail: false, description: false, thumbnailSidecar: false}, sponsorBlock: {mode: 'off', categories: []}}] + configureSmallFileQuickProfile(settings) } }, - async ({app, page, urls, files}) => { + async ({app, page, outputDir, urls, files}) => { const rawBulkText = urls.videos([FIXTURE_VIDEO_IDS[0], FIXTURE_VIDEO_IDS[1]]).join('\n') await startBulkFromClipboard(page, app, rawBulkText) await expect(page.locator('[data-testid="bulk-url-valid-count"]')).toContainText('2') @@ -165,7 +256,9 @@ test('Electron bulk Quick Download shows preparation progress and queues fixture await expect .poll(async () => page.locator('[data-testid^="queue-card-"]').evaluateAll(cards => cards.map(card => ({status: card.getAttribute('data-status'), text: card.textContent?.replace(/\s+/g, ' ').trim()}))), {timeout: 140_000}) .toEqual([expect.objectContaining({status: 'done'}), expect.objectContaining({status: 'done'})]) - files.expectMp4Count(2) + const profileDir = smallFileProfileDir(outputDir) + await expect.poll(() => files.mediaFiles('.mp4', profileDir).length, {timeout: 20_000}).toBe(2) + files.expectMp4Count(2, profileDir) } ) }) diff --git a/tests/renderer/download-profile-editor.test.tsx b/tests/renderer/download-profile-editor.test.tsx index 7ded74a2..d6f00f1f 100644 --- a/tests/renderer/download-profile-editor.test.tsx +++ b/tests/renderer/download-profile-editor.test.tsx @@ -22,6 +22,17 @@ describe('DownloadProfileEditor', () => { expect(screen.queryByTestId('profiles-editor-audio-quality')).not.toBeInTheDocument() }) + it('does not render profile-specific playlist cap controls', async () => { + const profile = BUILTIN_DOWNLOAD_PROFILES.find(item => item.id === 'balanced') + expect(profile).toBeDefined() + + render( undefined} />) + + expect(await screen.findByTestId('profiles-editor-video-codec')).toBeInTheDocument() + expect(screen.queryByTestId('profiles-editor-playlist-cap')).not.toBeInTheDocument() + expect(screen.queryByText('Playlist probe cap')).not.toBeInTheDocument() + }) + it('shows M4A/AAC audio preference for Smart TV H.264 MP4 video profiles', async () => { const profile = BUILTIN_DOWNLOAD_PROFILES.find(item => item.id === 'mp4-1080') expect(profile).toBeDefined() diff --git a/tests/renderer/probe-orchestrator.test.tsx b/tests/renderer/probe-orchestrator.test.tsx index d1ce3b50..ddb98a57 100644 --- a/tests/renderer/probe-orchestrator.test.tsx +++ b/tests/renderer/probe-orchestrator.test.tsx @@ -205,7 +205,7 @@ describe('quickDownload', () => { expect(api.downloads.probe).toHaveBeenCalledWith({url: mixedUrl, playlistMode: 'video'}) const queued = vi.mocked(api.queue.cmd.add).mock.calls[0]?.[0]?.[0] expect(queued).toMatchObject({ - url: mixedUrl, + url: VIDEO_PROBE.webpageUrl, title: 'Test Video', outputDir: '/tmp/downloads/Balanced 720p', status: 'pending', diff --git a/tests/unit/download-profile-draft.test.ts b/tests/unit/download-profile-draft.test.ts index 0ed23a16..7c68b060 100644 --- a/tests/unit/download-profile-draft.test.ts +++ b/tests/unit/download-profile-draft.test.ts @@ -100,5 +100,6 @@ describe('DownloadProfileDraft', () => { const profile = downloadProfileFromDraft(draft, NOW, () => 'profile-id') expect(profile).toMatchObject({id: 'profile-id', name: 'Study Captions', media: {kind: 'subtitles-only'}, subtitles: {enabled: true, languages: ['en', 'uk']}, output: {kind: 'fixed', dir: '/courses'}, subfolder: {enabled: true, name: 'Study Captions'}, createdAt: NOW, updatedAt: NOW}) + expect('playlistProbeCap' in profile).toBe(false) }) }) diff --git a/tests/unit/queue-submission.test.ts b/tests/unit/queue-submission.test.ts index 0d1e788f..8181479e 100644 --- a/tests/unit/queue-submission.test.ts +++ b/tests/unit/queue-submission.test.ts @@ -85,7 +85,6 @@ function subtitleProfile(): DownloadProfile { subfolder: {enabled: true, name: 'Subs'}, sponsorBlock: {mode: 'remove', categories: ['sponsor']}, embed: {chapters: true, metadata: true, thumbnail: false, description: true, thumbnailSidecar: true}, - playlistProbeCap: 'confirm', createdAt: '2026-06-07T00:00:00.000Z', updatedAt: '2026-06-07T00:00:00.000Z' } @@ -134,6 +133,12 @@ describe('QueueSubmission', () => { expect(prepared?.items[0]).toMatchObject({url: 'https://www.youtube.com/watch?v=abc', title: 'Video', outputDir: '/profile-downloads/Balanced 720p', formatLabel: 'Video + audio · Best native · up to 720p · best native audio', job: {kind: 'ranged-format'}}) }) + it('uses the probe webpage URL for active-profile video items when wizard state is stale', () => { + const prepared = prepareActiveProfileQueueSubmission(VIDEO_PROBE, state({wizardUrl: 'https://www.youtube.com/watch?v=stale'}), 'normal') + + expect(prepared?.items[0]?.url).toBe('https://www.youtube.com/watch?v=abc') + }) + it('uses the default downloads folder and profile subfolder with native separators', () => { const settings = defaultAppSettings('C:\\Users\\User\\Downloads') const prepared = prepareActiveProfileQueueSubmission(VIDEO_PROBE, state({settings, wizardOutputDir: ''}), 'normal')