From fb8d5bf7ba22cac3a4fb224031f6d03d36d4a583 Mon Sep 17 00:00:00 2001 From: sudacode Date: Wed, 5 Aug 2026 22:56:40 -0700 Subject: [PATCH] test(subtitles): verify \t nesting cap and document dedup sort order - Assert collectAssOverrideCommands stops at the recursion cap (exact command count) instead of asserting only the outer tag, using a realistic nesting depth instead of 200k - Document mergeDuplicateCues' precondition that cues are pre-sorted by startTime/endTime/order, since burst detection silently breaks on unsorted input --- src/core/services/ass-text.test.ts | 14 ++++++++------ src/core/services/subtitle-cue-dedup.ts | 6 ++++++ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/core/services/ass-text.test.ts b/src/core/services/ass-text.test.ts index fc51f6e5..216c23f8 100644 --- a/src/core/services/ass-text.test.ts +++ b/src/core/services/ass-text.test.ts @@ -127,15 +127,17 @@ test('collectAssOverrideCommands marks tags animated by a wrapping \\t', () => { assert.equal(hasAssTemporalOverride(commands), true); }); -test('collectAssOverrideCommands survives pathologically nested \\t tags', () => { - const depth = 200000; - const block = `{${'\\t(0,500,'.repeat(depth)}\\frz30${')'.repeat(depth)}}文字`; +test('collectAssOverrideCommands stops descending into deeply nested \\t tags', () => { + // Nested far past the recursion cap. Uncapped, this recurses once per level, and a + // pathological line (real files reach one or two levels) overflows the stack. + const nesting = 32; + const block = `{${'\\t(0,500,'.repeat(nesting)}\\frz30${')'.repeat(nesting)}}文字`; const commands = collectAssOverrideCommands(block); - // Recursion stops at the nesting cap; the outer tags are still reported, and nothing - // blows the call stack. - assert.equal(commands[0]!.name, 't'); + // The outer `\t` plus one per allowed recursion level, and nothing from below the cap. + assert.equal(commands.length, 9); + assert.deepEqual(new Set(commands.map((command) => command.name)), new Set(['t'])); assert.equal(hasAssTemporalOverride(commands), true); }); diff --git a/src/core/services/subtitle-cue-dedup.ts b/src/core/services/subtitle-cue-dedup.ts index ff8f70ca..0b0d9dcb 100644 --- a/src/core/services/subtitle-cue-dedup.ts +++ b/src/core/services/subtitle-cue-dedup.ts @@ -177,6 +177,12 @@ function collapseAnimationBursts( return merged; } +/** + * Collapse redundant cues. Input must already be sorted by non-decreasing `startTime`, + * ties broken by `endTime` then source `order` -- burst detection chains events by + * comparing each one against the running end of the events before it, so an unsorted + * list breaks runs apart and leaves the frames behind. + */ export function mergeDuplicateCues( cues: AnnotatedSubtitleCue[], format: SubtitleSourceFormat,