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