MotorSignal-App/website/tests/telemetry-playback-sync.tes...

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