87 lines
2.6 KiB
TypeScript
87 lines
2.6 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import {
|
|
addDurations,
|
|
clampTime,
|
|
roundSeconds,
|
|
validateSourceRange,
|
|
} from '../../../src/media/duration';
|
|
import {
|
|
formatFrameTimecode,
|
|
formatTimecode,
|
|
parseTimecode,
|
|
parseTimecodeDetailed,
|
|
} from '../../../src/media/timecode';
|
|
|
|
describe('duration arithmetic', () => {
|
|
it('adds durations without exposing common binary drift', () => {
|
|
expect(addDurations([0.1, 0.2, 1.0000004])).toBe(1.3);
|
|
expect(roundSeconds(-0.0000001, 3)).toBe(0);
|
|
});
|
|
|
|
it('rejects invalid durations and clamps preview times', () => {
|
|
expect(() => addDurations([1, Number.NaN])).toThrow();
|
|
expect(() => addDurations([-1])).toThrow();
|
|
expect(clampTime(-2, 10)).toBe(0);
|
|
expect(clampTime(12, 10)).toBe(10);
|
|
});
|
|
|
|
it('validates source in/out ranges and source bounds', () => {
|
|
expect(validateSourceRange(1, 3, 5)).toEqual({
|
|
valid: true,
|
|
durationSeconds: 2,
|
|
issues: [],
|
|
});
|
|
expect(validateSourceRange(3, 1, 5).issues[0]?.code).toBe(
|
|
'source-out-before-in'
|
|
);
|
|
expect(validateSourceRange(1, 1, 5).issues[0]?.code).toBe('clip-too-short');
|
|
expect(
|
|
validateSourceRange(1, 6, 5).issues.some(
|
|
(issue) => issue.code === 'source-out-after-duration'
|
|
)
|
|
).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('timecode parsing and formatting', () => {
|
|
it.each([
|
|
['12.5', 12.5, undefined],
|
|
['01:02.500', 62.5, undefined],
|
|
['02:03:04.125', 7_384.125, undefined],
|
|
['00:00:10:12', 10.5, { frameRate: 24 }],
|
|
])('parses %s', (text, expected, options) => {
|
|
expect(parseTimecode(text, options)).toBe(expected);
|
|
});
|
|
|
|
it('reports malformed components', () => {
|
|
expect(parseTimecodeDetailed('01:60')).toEqual({
|
|
ok: false,
|
|
reason: 'seconds-out-of-range',
|
|
});
|
|
expect(parseTimecodeDetailed('00:00:01:10')).toEqual({
|
|
ok: false,
|
|
reason: 'frame-rate-required',
|
|
});
|
|
expect(parseTimecodeDetailed('-00:01', { allowNegative: false })).toEqual({
|
|
ok: false,
|
|
reason: 'negative-not-allowed',
|
|
});
|
|
expect(parseTimecode('anything')).toBeUndefined();
|
|
expect(parseTimecode('1.5:02:03')).toBeUndefined();
|
|
});
|
|
|
|
it('formats carry, precision, negative values and nominal frames', () => {
|
|
expect(formatTimecode(59.9996)).toBe('00:01:00.000');
|
|
expect(
|
|
formatTimecode(62.5, {
|
|
decimalPlaces: 2,
|
|
alwaysShowHours: false,
|
|
})
|
|
).toBe('01:02.50');
|
|
expect(formatTimecode(-1.25, { decimalPlaces: 2 })).toBe('-00:00:01.25');
|
|
expect(formatFrameTimecode(10.5, 24)).toBe('00:00:10:12');
|
|
expect(() => formatFrameTimecode(5, 0.5)).toThrow();
|
|
});
|
|
});
|