UNPKG

@sports-alliance/sports-lib

Version:

A Library to for importing / exporting and processing GPX, TCX, FIT and JSON files from services such as Strava, Movescount, Garmin, Polar etc

181 lines (180 loc) 13 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const fs = __importStar(require("fs")); const path = __importStar(require("path")); const importer_fit_mapper_1 = require("./importer.fit.mapper"); const data_ground_time_1 = require("../../../../data/data.ground-time"); const data_ground_contact_time_1 = require("../../../../data/data.ground-contact-time"); const data_ground_contact_time_balance_left_1 = require("../../../../data/data-ground-contact-time-balance-left"); const data_ground_contact_time_balance_right_1 = require("../../../../data/data-ground-contact-time-balance-right"); const data_stance_time_balance_left_1 = require("../../../../data/data-stance-time-balance-left"); const data_effort_pace_1 = require("../../../../data/data.effort-pace"); const data_power_balance_left_1 = require("../../../../data/data.power-balance-left"); const data_power_balance_right_1 = require("../../../../data/data.power-balance-right"); const data_stamina_1 = require("../../../../data/data.stamina"); const importer_fit_1 = require("./importer.fit"); const helpers_1 = require("../../../utilities/helpers"); const data_running_dynamics_balance_1 = require("../../../../data/data.running-dynamics-balance"); describe('FITSampleMapper', () => { it('should map Ground Time as milliseconds without scaling', () => { const mapper = importer_fit_mapper_1.FITSampleMapper.find(m => m.dataType === data_ground_time_1.DataGroundTime.type); expect(mapper).toBeDefined(); const mapped = mapper.getSampleValue({ 'Ground Time': 1216 }); expect(mapped).toBe(1216); }); it('should map Ground Contact Time from stance_time', () => { const mapper = importer_fit_mapper_1.FITSampleMapper.find(m => m.dataType === data_ground_contact_time_1.DataGroundContactTime.type); expect(mapper).toBeDefined(); const mapped = mapper.getSampleValue({ stance_time: 296 }); expect(mapped).toBe(296); }); it('should map Ground Contact Time from Ground Time as fallback when stance_time is missing', () => { const mapper = importer_fit_mapper_1.FITSampleMapper.find(m => m.dataType === data_ground_contact_time_1.DataGroundContactTime.type); expect(mapper).toBeDefined(); const mapped = mapper.getSampleValue({ 'Ground Time': 1216 }); expect(mapped).toBe(1216); }); it('should prefer stance_time over Ground Time when both are present', () => { const mapper = importer_fit_mapper_1.FITSampleMapper.find(m => m.dataType === data_ground_contact_time_1.DataGroundContactTime.type); expect(mapper).toBeDefined(); const mapped = mapper.getSampleValue({ stance_time: 296, 'Ground Time': 1216 }); expect(mapped).toBe(296); }); it('should treat zero Ground Contact Time balance samples as missing', () => { const leftMapper = importer_fit_mapper_1.FITSampleMapper.find(m => m.dataType === data_ground_contact_time_balance_left_1.DataGroundContactTimeBalanceLeft.type); const rightMapper = importer_fit_mapper_1.FITSampleMapper.find(m => m.dataType === data_ground_contact_time_balance_right_1.DataGroundContactTimeBalanceRight.type); const legacyLeftMapper = importer_fit_mapper_1.FITSampleMapper.find(m => m.dataType === data_stance_time_balance_left_1.DataStanceTimeBalanceLeft.type); expect(leftMapper).toBeDefined(); expect(rightMapper).toBeDefined(); expect(legacyLeftMapper).toBeDefined(); expect(leftMapper.getSampleValue({ stance_time_balance: 51.25 })).toBe(51.25); expect(rightMapper.getSampleValue({ stance_time_balance: 51.25 })).toBe(48.75); expect(legacyLeftMapper.getSampleValue({ stance_time_balance: 51.25 })).toBe(51.25); expect(leftMapper.getSampleValue({ stance_time_balance: 0 })).toBeNull(); expect(rightMapper.getSampleValue({ stance_time_balance: 0 })).toBeNull(); expect(legacyLeftMapper.getSampleValue({ stance_time_balance: 0 })).toBeNull(); }); it('should map FIT left_right_balance to canonical power balance fields', () => { const leftMapper = importer_fit_mapper_1.FITSampleMapper.find(m => m.dataType === data_power_balance_left_1.DataPowerBalanceLeft.type); const rightMapper = importer_fit_mapper_1.FITSampleMapper.find(m => m.dataType === data_power_balance_right_1.DataPowerBalanceRight.type); expect(leftMapper).toBeDefined(); expect(rightMapper).toBeDefined(); expect(leftMapper.getSampleValue({ left_right_balance: { right: false, value: 51.25 } })).toBe(51.25); expect(rightMapper.getSampleValue({ left_right_balance: { right: false, value: 51.25 } })).toBe(48.75); expect(leftMapper.getSampleValue({ left_right_balance: { right: true, value: 48.75 } })).toBe(51.25); expect(rightMapper.getSampleValue({ left_right_balance: { right: true, value: 48.75 } })).toBe(48.75); }); it('should map Stryd Duo balance fields as left/right pairs', () => { const sample = { 'Vertical Oscillation Balance': 48.75, 'Leg Spring Stiffness Balance': 47.5, 'Impact Loading Rate Balance': 50.25 }; expect(importer_fit_mapper_1.FITSampleMapper.find(m => m.dataType === data_running_dynamics_balance_1.DataVerticalOscillationBalanceLeft.type).getSampleValue(sample)).toBe(48.75); expect(importer_fit_mapper_1.FITSampleMapper.find(m => m.dataType === data_running_dynamics_balance_1.DataVerticalOscillationBalanceRight.type).getSampleValue(sample)).toBe(51.25); expect(importer_fit_mapper_1.FITSampleMapper.find(m => m.dataType === data_running_dynamics_balance_1.DataLegSpringStiffnessBalanceLeft.type).getSampleValue(sample)).toBe(47.5); expect(importer_fit_mapper_1.FITSampleMapper.find(m => m.dataType === data_running_dynamics_balance_1.DataLegSpringStiffnessBalanceRight.type).getSampleValue(sample)).toBe(52.5); expect(importer_fit_mapper_1.FITSampleMapper.find(m => m.dataType === data_running_dynamics_balance_1.DataImpactLoadingRateBalanceLeft.type).getSampleValue(sample)).toBe(50.25); expect(importer_fit_mapper_1.FITSampleMapper.find(m => m.dataType === data_running_dynamics_balance_1.DataImpactLoadingRateBalanceRight.type).getSampleValue(sample)).toBe(49.75); }); it('should treat zero Stryd Duo balance samples as missing', () => { const sample = { 'Vertical Oscillation Balance': 0, 'Leg Spring Stiffness Balance': 0, 'Impact Loading Rate Balance': 0 }; expect(importer_fit_mapper_1.FITSampleMapper.find(m => m.dataType === data_running_dynamics_balance_1.DataVerticalOscillationBalanceLeft.type).getSampleValue(sample)).toBeNull(); expect(importer_fit_mapper_1.FITSampleMapper.find(m => m.dataType === data_running_dynamics_balance_1.DataVerticalOscillationBalanceRight.type).getSampleValue(sample)).toBeNull(); expect(importer_fit_mapper_1.FITSampleMapper.find(m => m.dataType === data_running_dynamics_balance_1.DataLegSpringStiffnessBalanceLeft.type).getSampleValue(sample)).toBeNull(); expect(importer_fit_mapper_1.FITSampleMapper.find(m => m.dataType === data_running_dynamics_balance_1.DataLegSpringStiffnessBalanceRight.type).getSampleValue(sample)).toBeNull(); expect(importer_fit_mapper_1.FITSampleMapper.find(m => m.dataType === data_running_dynamics_balance_1.DataImpactLoadingRateBalanceLeft.type).getSampleValue(sample)).toBeNull(); expect(importer_fit_mapper_1.FITSampleMapper.find(m => m.dataType === data_running_dynamics_balance_1.DataImpactLoadingRateBalanceRight.type).getSampleValue(sample)).toBeNull(); }); it('should keep Ground Time stream values in ms when importing a FIT file', () => __awaiter(void 0, void 0, void 0, function* () { const fixturePath = path.resolve(__dirname, '../../../../specs/fixtures/runs/fit/6782987395.fit'); const fileBuffer = fs.readFileSync(fixturePath); const arrayBuffer = fileBuffer.buffer.slice(fileBuffer.byteOffset, fileBuffer.byteOffset + fileBuffer.byteLength); const event = yield importer_fit_1.EventImporterFIT.getFromArrayBuffer(arrayBuffer); const activity = event.getFirstActivity(); const values = activity.getStreamData(data_ground_time_1.DataGroundTime.type).filter(value => Number.isFinite(value)); expect(values.length).toBeGreaterThan(0); expect(values[0]).toBe(1216); expect(values[0]).toBeGreaterThan(100); })); it('should populate Ground Contact Time stream from Ground Time fallback when stance_time is absent', () => __awaiter(void 0, void 0, void 0, function* () { const fixturePath = path.resolve(__dirname, '../../../../specs/fixtures/runs/fit/6782987395.fit'); const fileBuffer = fs.readFileSync(fixturePath); const arrayBuffer = fileBuffer.buffer.slice(fileBuffer.byteOffset, fileBuffer.byteOffset + fileBuffer.byteLength); const event = yield importer_fit_1.EventImporterFIT.getFromArrayBuffer(arrayBuffer); const activity = event.getFirstActivity(); const values = activity .getStreamData(data_ground_contact_time_1.DataGroundContactTime.type) .filter(value => Number.isFinite(value)); expect(values.length).toBeGreaterThan(0); expect(values[0]).toBe(1216); expect(values[0]).toBeGreaterThan(100); })); it('should map Effort Pace from speed (m/s) to pace (min/km)', () => { const mapper = importer_fit_mapper_1.FITSampleMapper.find(m => m.dataType === data_effort_pace_1.DataEffortPace.type); expect(mapper).toBeDefined(); const mapped = mapper.getSampleValue({ 'Effort Pace': 3.412 }); expect(mapped).toBeCloseTo((0, helpers_1.convertSpeedToPace)(3.412), 6); }); it('should map non-positive Effort Pace speed values to null', () => { const mapper = importer_fit_mapper_1.FITSampleMapper.find(m => m.dataType === data_effort_pace_1.DataEffortPace.type); expect(mapper).toBeDefined(); expect(mapper.getSampleValue({ 'Effort Pace': 0 })).toBeNull(); expect(mapper.getSampleValue({ 'Effort Pace': -1 })).toBeNull(); }); it('should map Garmin stamina streams from record fields', () => { const staminaMapper = importer_fit_mapper_1.FITSampleMapper.find(m => m.dataType === data_stamina_1.DataStamina.type); const potentialStaminaMapper = importer_fit_mapper_1.FITSampleMapper.find(m => m.dataType === data_stamina_1.DataPotentialStamina.type); expect(staminaMapper).toBeDefined(); expect(potentialStaminaMapper).toBeDefined(); expect(staminaMapper.getSampleValue({ stamina: 34, potential_stamina: 83 })).toBe(34); expect(potentialStaminaMapper.getSampleValue({ stamina: 34, potential_stamina: 83 })).toBe(83); expect(staminaMapper.getSampleValue({ stamina: 0 })).toBe(0); }); });