70 lines
2.3 KiB
TypeScript
70 lines
2.3 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { advancePlaybackIndex } from '../lib/telemetry-track';
|
|
import type { TelemetryPoint } from '../lib/telemetry-export';
|
|
|
|
const BASE = Date.parse('2026-09-28T14:30:00.000Z');
|
|
|
|
// Minimal TelemetryPoint builder — advancePlaybackIndex only reads timestamp + segmentId.
|
|
function mk(offsetMs: number, segmentId = 0): TelemetryPoint {
|
|
return {
|
|
timestamp: new Date(BASE + offsetMs).toISOString(),
|
|
latitude: 40,
|
|
longitude: -74,
|
|
altitude: 0,
|
|
speed: 0,
|
|
heading: 0,
|
|
gpsAccuracy: 0,
|
|
leanAngle: 0,
|
|
pitchAngle: 0,
|
|
accelX: 0,
|
|
accelY: 0,
|
|
accelZ: 0,
|
|
longitudinalG: 0,
|
|
lateralG: 0,
|
|
segmentId,
|
|
};
|
|
}
|
|
|
|
describe('advancePlaybackIndex (replay playback synchronization)', () => {
|
|
const uniform = Array.from({ length: 10 }, (_, i) => mk(i * 50)); // 20 Hz, 50 ms apart
|
|
|
|
it('advances in step with real recorded time (1x)', () => {
|
|
// 200 ms of budget over 50 ms samples = 4 steps.
|
|
const r = advancePlaybackIndex(uniform, 0, 200);
|
|
expect(r.index).toBe(4);
|
|
expect(r.remainingMs).toBe(0);
|
|
});
|
|
|
|
it('carries leftover budget when it does not cover a full step', () => {
|
|
// 120 ms consumes two 50 ms steps (100 ms), leaving 20 ms.
|
|
const r = advancePlaybackIndex(uniform, 0, 120);
|
|
expect(r.index).toBe(2);
|
|
expect(r.remainingMs).toBe(20);
|
|
});
|
|
|
|
it('does not advance when the budget is below one step', () => {
|
|
const r = advancePlaybackIndex(uniform, 3, 10);
|
|
expect(r.index).toBe(3);
|
|
expect(r.remainingMs).toBe(10);
|
|
});
|
|
|
|
it('compresses a multi-second gap to a single 50 ms step so playback never stalls', () => {
|
|
// Samples 0..2 uniform; sample 3 is 10 s later (disconnected). A 60 ms budget from
|
|
// index 2 must still cross the gap (treated as a 50 ms step), not wait 10 s.
|
|
const gapped = [mk(0), mk(50), mk(100), mk(10_100)];
|
|
const r = advancePlaybackIndex(gapped, 2, 60);
|
|
expect(r.index).toBe(3);
|
|
expect(r.remainingMs).toBe(0); // reached the end
|
|
});
|
|
|
|
it('clamps at the final index with no remaining budget', () => {
|
|
const r = advancePlaybackIndex(uniform, 8, 100_000);
|
|
expect(r.index).toBe(9);
|
|
expect(r.remainingMs).toBe(0);
|
|
});
|
|
|
|
it('is a no-op on empty input', () => {
|
|
expect(advancePlaybackIndex([], 0, 1000)).toEqual({ index: 0, remainingMs: 0 });
|
|
});
|
|
});
|