import { describe, it, expect, beforeEach } from 'vitest'; import { get } from 'svelte/store'; // Import the store module directly: the feature barrel re-exports .svelte // components, which the unit runner (no Svelte plugin) cannot transform. import { timelineStore, positionAt } from '$features/timeline/store'; beforeEach(() => { timelineStore.pause(); timelineStore.setRange(0, 0); timelineStore.setSpeed(1); timelineStore.setMarkers([]); }); describe('timelineStore', () => { it('clamps seek to the current range', () => { timelineStore.setRange(0, 10_000); timelineStore.seek(99_999); expect(get(timelineStore).time).toBe(10_000); timelineStore.seek(-5); expect(get(timelineStore).time).toBe(0); }); it('clamps the current time when the range shrinks', () => { timelineStore.setRange(0, 10_000); timelineStore.seek(9_000); timelineStore.setRange(0, 4_000); expect(get(timelineStore).time).toBe(4_000); }); it('ignores play when there is no duration', () => { timelineStore.play(); expect(get(timelineStore).playing).toBe(false); }); it('plays and pauses when a duration exists', () => { timelineStore.setRange(0, 10_000); timelineStore.play(); expect(get(timelineStore).playing).toBe(true); timelineStore.pause(); expect(get(timelineStore).playing).toBe(false); }); it('rewinds to min on reset', () => { timelineStore.setRange(0, 10_000); timelineStore.seek(7_000); timelineStore.reset(); expect(get(timelineStore).time).toBe(0); expect(get(timelineStore).playing).toBe(false); }); it('stores the playback speed', () => { timelineStore.setSpeed(5); expect(get(timelineStore).speed).toBe(5); }); }); describe('positionAt', () => { const path: [number, number, ...number[]][] = [ [60, 100], [62, 102], [64, 104] ]; it('returns the first point at t=0 and the last at t=duration', () => { expect(positionAt(path, 0, 1000)).toEqual([60, 100]); expect(positionAt(path, 1000, 1000)).toEqual([64, 104]); }); it('interpolates linearly between samples', () => { // Quarter of the way = halfway along the first segment. const p = positionAt(path, 250, 1000)!; expect(p[0]).toBeCloseTo(61, 6); expect(p[1]).toBeCloseTo(101, 6); }); it('clamps outside the flight window and handles an empty path', () => { expect(positionAt(path, -100, 1000)).toEqual([60, 100]); expect(positionAt(path, 99_999, 1000)).toEqual([64, 104]); expect(positionAt([], 0, 1000)).toBeNull(); }); });