MotorSignal-App/website/tests/telemetry-replay-export.tes...

126 lines
4.6 KiB
TypeScript

import { describe, it, expect, vi } from 'vitest';
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import {
generateRideGpx,
generateRideCsv,
synthesizeTelemetryTrack,
analyzeCorners,
} from '../lib/telemetry-export';
import { WebTelemetryReplayModal } from '../components/web-telemetry-replay-modal';
import { Ride } from '../components/app-cloud-data';
const mockRide: Ride = {
id: 'ride_unit_test_123',
startedAt: new Date('2026-09-28T14:30:00Z'),
endedAt: new Date('2026-09-28T15:15:00Z'),
distanceMiles: 24.8,
topSpeedMph: 86.4,
averageSpeed: 48.2,
maxLeanLeft: 42.1,
maxLeanRight: 39.5,
maxAccelG: 0.72,
maxBrakingG: 0.95,
movingTime: 1850,
elevationGain: 1240,
qualityScore: 98,
motorcycle: '2024 Yamaha YZF-R6',
};
describe('Web Telemetry Replay & Export (WEB-23)', () => {
it('synthesizes realistic 20 Hz telemetry points matching ride bounds', () => {
const points = synthesizeTelemetryTrack(mockRide);
expect(points.length).toBeGreaterThan(50);
// Verify properties exist and have valid numerical ranges
const first = points[0];
expect(first.timestamp).toBeDefined();
expect(first.latitude).toBeGreaterThan(30);
expect(first.longitude).toBeLessThan(-80);
expect(first.speed).toBeGreaterThanOrEqual(0);
expect(first.leanAngle).toBeDefined();
expect(first.longitudinalG).toBeDefined();
expect(first.lateralG).toBeDefined();
});
it('generates valid GPX 1.1 with trackpoints, elevation, speed, and lean extensions', () => {
const points = synthesizeTelemetryTrack(mockRide);
const gpx = generateRideGpx(mockRide, points);
expect(gpx).toContain('<?xml version="1.0" encoding="UTF-8"?>');
expect(gpx).toContain('<gpx version="1.1" creator="MotorSignal"');
expect(gpx).toContain('<trk>');
expect(gpx).toContain('<trkseg>');
expect(gpx).toContain('<trkpt lat=');
expect(gpx).toContain('<ele>');
expect(gpx).toContain('<extensions>');
expect(gpx).toContain('<speed>');
expect(gpx).toContain('<lean>');
});
it('generates compliant 20 Hz CSV matching ExportService format', () => {
const points = synthesizeTelemetryTrack(mockRide);
const csv = generateRideCsv(mockRide, points);
const lines = csv.split('\n');
expect(lines[0]).toBe(
'timestamp,latitude,longitude,altitude,speed,heading,gpsAccuracy,leanAngle,pitchAngle,accelX,accelY,accelZ,longitudinalG,lateralG'
);
expect(lines.length).toBe(points.length + 1);
// Inspect first sample row
const firstRowCols = lines[1].split(',');
expect(firstRowCols.length).toBe(14);
});
it('analyzes corners and grades apex performance correctly', () => {
const points = synthesizeTelemetryTrack(mockRide);
const corners = analyzeCorners(points);
expect(Array.isArray(corners)).toBe(true);
if (corners.length > 0) {
const c = corners[0];
expect(c.direction).toMatch(/Left|Right/);
expect(c.entrySpeed).toBeGreaterThan(0);
expect(c.apexSpeed).toBeGreaterThan(0);
expect(c.apexLeanAngle).toBeGreaterThan(15);
expect(['A+', 'A', 'B', 'C', 'D']).toContain(c.grade);
}
});
it('renders WebTelemetryReplayModal with HUD gauges, transport controls, and export buttons', () => {
const onClose = vi.fn();
render(<WebTelemetryReplayModal ride={mockRide} onClose={onClose} />);
// Check title and metrics
expect(screen.getByText('TELEMETRY FORENSIC REPLAY')).toBeDefined();
expect(screen.getByText(/2024 Yamaha YZF-R6/)).toBeDefined();
// Check HUD stats
expect(screen.getByText('VEHICLE SPEED')).toBeDefined();
expect(screen.getByText('LEAN ANGLE')).toBeDefined();
expect(screen.getByText('G-FORCE DYNAMICS')).toBeDefined();
expect(screen.getByText('ALTITUDE & TIME')).toBeDefined();
// Check buttons
expect(screen.getByTestId('export-gpx-btn')).toBeDefined();
expect(screen.getByTestId('export-csv-btn')).toBeDefined();
expect(screen.getByTestId('play-pause-btn')).toBeDefined();
expect(screen.getByTestId('replay-timeline-slider')).toBeDefined();
// Check tabs
expect(screen.getByTestId('tab-map-replay')).toBeDefined();
expect(screen.getByTestId('tab-corner-grading')).toBeDefined();
// Switch to Corner Apex Grading tab
fireEvent.click(screen.getByTestId('tab-corner-grading'));
expect(screen.getByText('Corner')).toBeDefined();
expect(screen.getByText('Apex Lean')).toBeDefined();
expect(screen.getByText('Grade')).toBeDefined();
// Close modal
fireEvent.click(screen.getByTestId('close-replay-modal'));
expect(onClose).toHaveBeenCalled();
});
});