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
This commit is contained in:
2026-08-05 22:56:40 -07:00
parent c305bf34c2
commit fb8d5bf7ba
2 changed files with 14 additions and 6 deletions
+8 -6
View File
@@ -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);
});
+6
View File
@@ -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,