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(); }); });