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

761 lines (760 loc) 42.6 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_1 = require("./importer.fit"); const activity_parsing_options_1 = require("../../../../activities/activity-parsing-options"); const activity_types_1 = require("../../../../activities/activity.types"); const data_cadence_1 = require("../../../../data/data.cadence"); const data_distance_1 = require("../../../../data/data.distance"); const data_duration_1 = require("../../../../data/data.duration"); const data_heart_rate_1 = require("../../../../data/data.heart-rate"); const data_pace_1 = require("../../../../data/data.pace"); const data_speed_1 = require("../../../../data/data.speed"); const importer_json_1 = require("../json/importer.json"); const activity_1 = require("../../../../activities/activity"); const creator_1 = require("../../../../creators/creator"); describe('EventImporterFIT', () => { describe('Session normalization', () => { it('should synthesize a fallback session when sessions are missing but top-level messages exist', () => { const startDate = new Date('2026-03-06T08:14:19.000Z'); const midDate = new Date('2026-03-06T08:24:19.000Z'); const endDate = new Date('2026-03-06T08:44:19.000Z'); const fitDataObject = { sessions: [], laps: [ { start_time: startDate, timestamp: midDate, total_elapsed_time: 600, total_timer_time: 550, total_distance: 1000, total_ascent: 10, total_descent: 5, avg_grade: 1, max_pos_grade: 4, max_neg_grade: -2, sport: 'running', sub_sport: 'road', records: [{ timestamp: new Date('2026-03-06T08:14:20.000Z') }] }, { start_time: midDate, timestamp: endDate, total_elapsed_time: 1200, total_timer_time: 1150, total_distance: 3000, total_ascent: 20, total_descent: 15, avg_grade: 5, max_pos_grade: 9, max_neg_grade: -6, sport: 'running', sub_sport: 'road', records: [{ timestamp: new Date('2026-03-06T08:24:20.000Z') }] } ], records: [ { timestamp: startDate, distance: 0 }, { timestamp: endDate, distance: 4000 } ], events: [{ timestamp: new Date('2026-03-06T08:14:25.000Z'), event: 'timer', event_type: 'start' }] }; importer_fit_1.EventImporterFIT.normalizeFitDataObjectForActivities(fitDataObject); expect(fitDataObject.sessions).toHaveLength(1); const session = fitDataObject.sessions[0]; expect(session.laps).toHaveLength(2); expect(session.start_time.toISOString()).toBe(startDate.toISOString()); expect(session.timestamp.toISOString()).toBe(endDate.toISOString()); expect(session.total_elapsed_time).toBe(1800); expect(session.total_timer_time).toBe(1700); expect(session.total_distance).toBe(4000); expect(session.total_ascent).toBe(30); expect(session.total_descent).toBe(20); expect(session.avg_grade).toBe(4); expect(session.max_pos_grade).toBe(9); expect(session.max_neg_grade).toBe(-6); expect(session.sport).toBe('running'); expect(session.sub_sport).toBe('road'); }); it('should use final record distance when sessionless lap summary distance coverage is incomplete', () => { const startDate = new Date('2026-03-06T08:14:19.000Z'); const midDate = new Date('2026-03-06T08:24:19.000Z'); const endDate = new Date('2026-03-06T08:44:19.000Z'); const fitDataObject = { sessions: [], laps: [ { start_time: startDate, timestamp: midDate, total_elapsed_time: 600, total_distance: 1000, total_ascent: 10 }, { start_time: midDate, timestamp: endDate, total_elapsed_time: 1200 } ], records: [{ timestamp: startDate, distance: 0 }, { timestamp: midDate, distance: 4000 }, { timestamp: endDate }] }; importer_fit_1.EventImporterFIT.normalizeFitDataObjectForActivities(fitDataObject); const session = fitDataObject.sessions[0]; expect(session.total_distance).toBe(4000); expect(session.total_ascent).toBeUndefined(); }); it('should keep sessions empty when no valid time boundaries exist', () => { const fitDataObject = { sessions: [], laps: [{ total_elapsed_time: 120 }], records: [{ timestamp: null }], events: [{ timestamp: 'not-a-date' }] }; importer_fit_1.EventImporterFIT.normalizeFitDataObjectForActivities(fitDataObject); expect(fitDataObject.sessions).toEqual([]); }); it('should normalize existing sessions that miss laps arrays', () => { const fitDataObject = { sessions: [ { start_time: new Date('2026-03-06T08:14:19.000Z'), timestamp: new Date('2026-03-06T08:44:19.000Z') } ] }; importer_fit_1.EventImporterFIT.normalizeFitDataObjectForActivities(fitDataObject); expect(Array.isArray(fitDataObject.records)).toBe(true); expect(Array.isArray(fitDataObject.events)).toBe(true); expect(Array.isArray(fitDataObject.laps)).toBe(true); expect(Array.isArray(fitDataObject.sessions[0].laps)).toBe(true); expect(fitDataObject.sessions[0].laps).toHaveLength(0); }); }); describe('HRV handling', () => { it('should preserve elapsed time after invalid HRV sentinel values', () => { const ibiData = importer_fit_1.EventImporterFIT.getIBIDataForActivity([{ time: [1, 65.535, 1] }], new Date(0), { startDate: new Date(50000), endDate: new Date(70000) }); expect(ibiData).toEqual([1000]); }); it('should preserve elapsed time across consecutive invalid HRV sentinel values from multiple records', () => { const ibiData = importer_fit_1.EventImporterFIT.getIBIDataForActivity([{ time: [1, 65.535] }, { time: [65.535, 1] }], new Date(0), { startDate: new Date(130000), endDate: new Date(140000) }); expect(ibiData).toEqual([1000]); }); it('should drop trailing invalid HRV sentinel values without emitting synthetic intervals', () => { const ibiData = importer_fit_1.EventImporterFIT.getIBIDataForActivity([{ time: [1, 1, 65.535] }], new Date(0), { startDate: new Date(0), endDate: new Date(100000) }); expect(ibiData).toEqual([1000, 1000]); }); }); describe('Swim lengths', () => { it('should expose FIT pool swim length rows and preserve them through JSON import', () => __awaiter(void 0, void 0, void 0, function* () { var _a, _b; const fitFilePath = path.resolve(__dirname, '../../../../specs/fixtures/swim/fit/6860712481.fit'); const fileBuffer = fs.readFileSync(fitFilePath); const arrayBuffer = fileBuffer.buffer.slice(fileBuffer.byteOffset, fileBuffer.byteOffset + fileBuffer.byteLength); const event = yield importer_fit_1.EventImporterFIT.getFromArrayBuffer(arrayBuffer, activity_parsing_options_1.ActivityParsingOptions.DEFAULT, 'swim-lengths.fit'); const activity = event.getActivities()[0]; const swimLengths = activity.getSwimLengths(); expect(swimLengths).toHaveLength(94); expect(swimLengths.filter(length => length.type === 'active')).toHaveLength(80); expect(swimLengths.filter(length => length.type === 'idle')).toHaveLength(14); expect((_a = swimLengths[0].poolLength) === null || _a === void 0 ? void 0 : _a.getValue()).toBe(25); const firstActiveLength = swimLengths.find(length => length.type === 'active'); expect((_b = firstActiveLength === null || firstActiveLength === void 0 ? void 0 : firstActiveLength.distance) === null || _b === void 0 ? void 0 : _b.getValue()).toBe(25); expect(firstActiveLength === null || firstActiveLength === void 0 ? void 0 : firstActiveLength.stroke).toBeTruthy(); const firstIdleLength = swimLengths.find(length => length.type === 'idle'); expect(firstIdleLength === null || firstIdleLength === void 0 ? void 0 : firstIdleLength.distance).toBeNull(); const roundTrippedActivity = importer_json_1.EventImporterJSON.getActivityFromJSON(activity.toJSON()); expect(roundTrippedActivity.getSwimLengths().map(length => length.toJSON())).toEqual(swimLengths.map(length => length.toJSON())); })); it('should expose swim lengths from original event 2e7c563f3145828e12ac2eff38ede2d9c4d3ff9f19d4c55aae9c9d6760ab4f7c', () => __awaiter(void 0, void 0, void 0, function* () { const fitFilePath = path.resolve(__dirname, '../../../../specs/fixtures/swim/fit/original-event-2e7c563f3145828e12ac2eff38ede2d9c4d3ff9f19d4c55aae9c9d6760ab4f7c.fit'); const fileBuffer = fs.readFileSync(fitFilePath); const arrayBuffer = fileBuffer.buffer.slice(fileBuffer.byteOffset, fileBuffer.byteOffset + fileBuffer.byteLength); const event = yield importer_fit_1.EventImporterFIT.getFromArrayBuffer(arrayBuffer, activity_parsing_options_1.ActivityParsingOptions.DEFAULT, 'original-event-2e7c563f3145828e12ac2eff38ede2d9c4d3ff9f19d4c55aae9c9d6760ab4f7c.fit'); const activity = event.getActivities()[0]; const swimLengths = activity.getSwimLengths(); expect(event.getActivities()).toHaveLength(1); expect(activity.type).toBe(activity_types_1.ActivityTypes.Swimming); expect(activity.getLaps()).toHaveLength(2); expect(swimLengths).toHaveLength(67); expect(swimLengths.filter(length => length.type === 'active')).toHaveLength(64); expect(swimLengths.filter(length => length.type === 'idle')).toHaveLength(3); expect(swimLengths.reduce((totalDistance, length) => { var _a; return totalDistance + (((_a = length.distance) === null || _a === void 0 ? void 0 : _a.getValue()) || 0); }, 0)).toBe(1600); expect(swimLengths[0].toJSON()).toEqual(expect.objectContaining({ index: 1, lapIndex: 1, type: 'idle', distance: null, poolLength: 25 })); const firstActiveLength = swimLengths.find(length => length.type === 'active'); expect(firstActiveLength === null || firstActiveLength === void 0 ? void 0 : firstActiveLength.toJSON()).toEqual(expect.objectContaining({ lapIndex: 1, stroke: 'freestyle', distance: 25, poolLength: 25 })); const roundTrippedActivity = importer_json_1.EventImporterJSON.getActivityFromJSON(activity.toJSON()); expect(roundTrippedActivity.getSwimLengths().map(length => length.toJSON())).toEqual(swimLengths.map(length => length.toJSON())); })); it('should prefer session-scoped lengths and normalize numeric swim enum fields', () => { const startDate = new Date('2026-05-16T15:27:09.000Z'); const endDate = new Date('2026-05-16T15:27:34.000Z'); const activity = new activity_1.Activity(startDate, endDate, activity_types_1.ActivityTypes.Swimming, new creator_1.Creator('Test')); importer_fit_1.EventImporterFIT.addSwimLengthsFromSessionObject(activity, { start_time: startDate, timestamp: endDate, total_elapsed_time: 25, pool_length: 25, pool_length_unit: 0, laps: [ { start_time: startDate, timestamp: endDate, total_elapsed_time: 25, total_distance: 25 } ], lengths: [ { start_time: startDate, timestamp: endDate, total_elapsed_time: 25, total_timer_time: 25, length_type: 1, swim_stroke: 0, total_strokes: 8, avg_speed: 1, avg_swimming_cadence: 20 } ] }, { lengths: [ { start_time: startDate, timestamp: endDate, total_elapsed_time: 25, total_timer_time: 25, length_type: 0 } ] }); const swimLength = activity.getSwimLengths()[0]; expect(swimLength.elapsedTime).toBeInstanceOf(data_duration_1.DataDuration); expect(swimLength.distance).toBeInstanceOf(data_distance_1.DataDistance); expect(swimLength.poolLength).toBeInstanceOf(data_distance_1.DataDistance); expect(swimLength.avgSpeed).toBeInstanceOf(data_speed_1.DataSpeed); expect(swimLength.avgCadence).toBeInstanceOf(data_cadence_1.DataCadence); expect(activity.getSwimLengths().map(length => length.toJSON())).toEqual([ expect.objectContaining({ index: 1, lapIndex: 1, type: 'active', stroke: 'freestyle', distance: 25, poolLength: 25, strokes: 8, avgSpeed: 1 }) ]); }); }); describe('Activity type resolution', () => { it('should map Garmin snorkeling sport id to canonical Snorkeling activity type', () => { const activityType = importer_fit_1.EventImporterFIT.getActivityTypeFromSessionObject({ sport: 82 }); expect(activityType).toEqual(activity_types_1.ActivityTypes.Snorkeling); }); it('should map Garmin HIIT sport id 62 to canonical HIIT when sport is numeric', () => { const activityType = importer_fit_1.EventImporterFIT.getActivityTypeFromSessionObject({ sport: 62, sub_sport: 0 }); expect(activityType).toEqual(activity_types_1.ActivityTypes.HIIT); }); it('should not leak unresolved numeric sport ids when generic sub sport is present', () => { const activityType = importer_fit_1.EventImporterFIT.getActivityTypeFromSessionObject({ sport: 999999, sub_sport: 0 }); expect(activityType).toEqual(activity_types_1.ActivityTypes.Generic); }); it('should fall back to Unknown Sport when sport id is unresolved and no mapped fallback exists', () => { const activityType = importer_fit_1.EventImporterFIT.getActivityTypeFromSessionObject({ sport: 999999 }); expect(activityType).toEqual(activity_types_1.ActivityTypes.unknown); }); it('should map sport profile name ENDURO MTB to canonical Enduro MTB activity type', () => { const activityType = importer_fit_1.EventImporterFIT.getActivityTypeFromSessionObject({ sport: 'cycling', sub_sport: 123, sport_profile_name: 'ENDURO MTB' }); expect(activityType).toEqual(activity_types_1.ActivityTypes['Enduro MTB']); }); it('should map rock_climbing + sub_sport id 68 to Indoor Climbing', () => { const activityType = importer_fit_1.EventImporterFIT.getActivityTypeFromSessionObject({ sport: 'rock_climbing', sub_sport: 68 }); expect(activityType).toEqual(activity_types_1.ActivityTypes['Indoor Climbing']); }); it('should map numeric string sport/sub_sport ids to Indoor Climbing', () => { const activityType = importer_fit_1.EventImporterFIT.getActivityTypeFromSessionObject({ sport: '31', sub_sport: '68' }); expect(activityType).toEqual(activity_types_1.ActivityTypes['Indoor Climbing']); }); it('should map rock_climbing + sub_sport id 69 to Bouldering', () => { const activityType = importer_fit_1.EventImporterFIT.getActivityTypeFromSessionObject({ sport: 31, sub_sport: 69 }); expect(activityType).toEqual(activity_types_1.ActivityTypes.Bouldering); }); }); describe('Handle device creator', () => { function generateFitDeviceDataObject(manufacturer = null, productId = -1, productName = null) { const data = {}; if (manufacturer !== null) { data.manufacturer = manufacturer; } if (productId !== -1) { data.product = productId; } if (productName) { data.product_name = productName; } return { file_ids: [data] }; } describe('Recognized', () => { it('should recognize a known Suunto device', done => { const manufacturer = 'suunto'; const expectedName = 'Suunto Ambit3 Peak'; const productId = 22; const fitDataObject = generateFitDeviceDataObject(manufacturer, productId); // When const creator = importer_fit_1.EventImporterFIT.getCreatorFromFitDataObject(fitDataObject); // Then expect(creator.isRecognized).toBeTruthy(); expect(creator.name).toEqual(expectedName); expect(creator.manufacturer).toEqual(manufacturer); done(); }); it('should recognize a known Coros device', done => { const manufacturer = 'coros'; const expectedName = 'Coros APEX Pro'; const productId = 841; const fitDataObject = generateFitDeviceDataObject(manufacturer, productId); // When const creator = importer_fit_1.EventImporterFIT.getCreatorFromFitDataObject(fitDataObject); // Then expect(creator.isRecognized).toBeTruthy(); expect(creator.name).toEqual(expectedName); expect(creator.manufacturer).toEqual(manufacturer); done(); }); it('should recognize a known Garmin device', done => { const manufacturer = 'garmin'; const expectedName = 'Garmin Edge 1000'; const productId = 1836; const fitDataObject = generateFitDeviceDataObject(manufacturer, productId); // When const creator = importer_fit_1.EventImporterFIT.getCreatorFromFitDataObject(fitDataObject); // Then expect(creator.isRecognized).toBeTruthy(); expect(creator.name).toEqual(expectedName); expect(creator.manufacturer).toEqual(manufacturer); done(); }); it('should recognize a known Wahoo device', done => { const manufacturer = 'wahoo_fitness'; const expectedName = 'Wahoo ELEMNT BOLT'; const productId = 31; const fitDataObject = generateFitDeviceDataObject(manufacturer, productId); // When const creator = importer_fit_1.EventImporterFIT.getCreatorFromFitDataObject(fitDataObject); // Then expect(creator.isRecognized).toBeTruthy(); expect(creator.name).toEqual(expectedName); expect(creator.manufacturer).toEqual(manufacturer); done(); }); it('should recognize a known hammerhead device', done => { const manufacturer = 'hammerhead'; const expectedName = 'Hammerhead Karoo 2'; const productId = 2; const fitDataObject = generateFitDeviceDataObject(manufacturer, productId); // When const creator = importer_fit_1.EventImporterFIT.getCreatorFromFitDataObject(fitDataObject); // Then expect(creator.isRecognized).toBeTruthy(); expect(creator.name).toEqual(expectedName); expect(creator.manufacturer).toEqual(manufacturer); done(); }); it('should recognize a known SRM device', done => { const manufacturer = 'srm'; const expectedName = 'Srm PC8'; const productId = 8; const fitDataObject = generateFitDeviceDataObject(manufacturer, productId); // When const creator = importer_fit_1.EventImporterFIT.getCreatorFromFitDataObject(fitDataObject); // Then expect(creator.isRecognized).toBeTruthy(); expect(creator.name).toEqual(expectedName); expect(creator.manufacturer).toEqual(manufacturer); done(); }); it('should recognize a known zwift virtual device', done => { const manufacturer = 'zwift'; const expectedName = 'Zwift'; const fitDataObject = generateFitDeviceDataObject(manufacturer); // When const creator = importer_fit_1.EventImporterFIT.getCreatorFromFitDataObject(fitDataObject); // Then expect(creator.isRecognized).toBeTruthy(); expect(creator.name).toEqual(expectedName); expect(creator.manufacturer).toEqual(manufacturer); done(); }); it('should recognize a known Rouvy virtual device', done => { const manufacturer = 'virtualtraining'; const expectedName = 'Rouvy'; const fitDataObject = generateFitDeviceDataObject(manufacturer, 1); // When const creator = importer_fit_1.EventImporterFIT.getCreatorFromFitDataObject(fitDataObject); // Then expect(creator.isRecognized).toBeTruthy(); expect(creator.name).toEqual(expectedName); expect(creator.manufacturer).toEqual(manufacturer); done(); }); it('should recognize a known Wahoo SYSTM virtual device', done => { const manufacturer = 'the_sufferfest'; const expectedName = 'Wahoo SYSTM'; const productId = 1231; const fitDataObject = generateFitDeviceDataObject(manufacturer, productId); // When const creator = importer_fit_1.EventImporterFIT.getCreatorFromFitDataObject(fitDataObject); // Then expect(creator.isRecognized).toBeTruthy(); expect(creator.name).toEqual(expectedName); expect(creator.manufacturer).toEqual(manufacturer); done(); }); it('should recognize a known development device', done => { const manufacturer = 'development'; const expectedName = 'Zwift'; const productId = 15706; const fitDataObject = generateFitDeviceDataObject(manufacturer, productId); // When const creator = importer_fit_1.EventImporterFIT.getCreatorFromFitDataObject(fitDataObject); // Then expect(creator.isRecognized).toBeTruthy(); expect(creator.name).toEqual(expectedName); expect(creator.manufacturer).toEqual(manufacturer); done(); }); }); describe('Non recognized', () => { describe('Known manufacturer with unknown productId & known product name', () => { it('should format a non-recognized development device with known productId & unknown product name', done => { const manufacturer = 'development'; const productId = 42; const expectedName = 'Unknown'; const fitDataObject = generateFitDeviceDataObject(manufacturer, productId); // When const creator = importer_fit_1.EventImporterFIT.getCreatorFromFitDataObject(fitDataObject); // Then expect(creator.isRecognized).toBeTruthy(); expect(creator.name).toEqual(expectedName); expect(creator.manufacturer).toEqual(manufacturer); done(); }); }); describe('Known manufacturer with unknown productId & known product name', () => { it('should format a non-recognized Coros device with unknown productId & known product name', done => { const manufacturer = 'coros'; const productId = Math.random(); const productName = 'COROS REFLEX Fake'; const expectedName = 'Coros REFLEX Fake'; const fitDataObject = generateFitDeviceDataObject(manufacturer, productId, productName); // When const creator = importer_fit_1.EventImporterFIT.getCreatorFromFitDataObject(fitDataObject); // Then expect(creator.isRecognized).toBeFalsy(); expect(creator.name).toEqual(expectedName); expect(creator.manufacturer).toEqual(manufacturer); done(); }); it('should format a non-recognized Wahoo device with unknown productId & known product name', done => { const manufacturer = 'wahoo_fitness'; const productId = Math.random(); const productName = 'Fake'; const expectedName = 'Wahoo Fake'; const fitDataObject = generateFitDeviceDataObject(manufacturer, productId, productName); // When const creator = importer_fit_1.EventImporterFIT.getCreatorFromFitDataObject(fitDataObject); // Then expect(creator.isRecognized).toBeFalsy(); expect(creator.name).toEqual(expectedName); expect(creator.manufacturer).toEqual(manufacturer); done(); }); }); describe('Known manufacturer with unknown productId & missing product name', () => { it('should format a non-recognized Suunto device with unknown productId & missing product name', done => { const manufacturer = 'suunto'; const expectedName = 'Suunto'; const productId = Math.random(); const fitDataObject = generateFitDeviceDataObject(manufacturer, productId); // When const creator = importer_fit_1.EventImporterFIT.getCreatorFromFitDataObject(fitDataObject); // Then expect(creator.isRecognized).toBeFalsy(); expect(creator.name).toEqual(expectedName); expect(creator.manufacturer).toEqual(manufacturer); done(); }); it('should format a non-recognized Garmin device with unknown productId & missing product name', done => { const manufacturer = 'garmin'; const expectedName = 'Garmin'; const productId = Math.random(); const fitDataObject = generateFitDeviceDataObject(manufacturer, productId); // When const creator = importer_fit_1.EventImporterFIT.getCreatorFromFitDataObject(fitDataObject); // Then expect(creator.isRecognized).toBeFalsy(); expect(creator.name).toEqual(expectedName); expect(creator.manufacturer).toEqual(manufacturer); done(); }); it('should format a non-recognized Wahoo device with unknown productId & missing product name', done => { const manufacturer = 'wahoo_fitness'; const expectedName = 'Wahoo'; const productId = Math.random(); const fitDataObject = generateFitDeviceDataObject(manufacturer, productId); // When const creator = importer_fit_1.EventImporterFIT.getCreatorFromFitDataObject(fitDataObject); // Then expect(creator.isRecognized).toBeFalsy(); expect(creator.name).toEqual(expectedName); expect(creator.manufacturer).toEqual(manufacturer); done(); }); }); describe('Known manufacturer with missing productId & missing product name', () => { it('should format a non-recognized Suunto device without productId && product name', done => { const manufacturer = 'suunto'; const expectedName = 'Suunto'; const fitDataObject = generateFitDeviceDataObject(manufacturer); // When const creator = importer_fit_1.EventImporterFIT.getCreatorFromFitDataObject(fitDataObject); // Then expect(creator.isRecognized).toBeFalsy(); expect(creator.name).toEqual(expectedName); expect(creator.manufacturer).toEqual(manufacturer); done(); }); it('should format a non-recognized Garmin device without productId && product name', done => { const manufacturer = 'garmin'; const expectedName = 'Garmin'; const fitDataObject = generateFitDeviceDataObject(manufacturer); // When const creator = importer_fit_1.EventImporterFIT.getCreatorFromFitDataObject(fitDataObject); // Then expect(creator.isRecognized).toBeFalsy(); expect(creator.name).toEqual(expectedName); expect(creator.manufacturer).toEqual(manufacturer); done(); }); it('should format a non-recognized Garmin device without productId && product name', done => { const manufacturer = 'wahoo_fitness'; const expectedName = 'Wahoo'; const fitDataObject = generateFitDeviceDataObject(manufacturer); // When const creator = importer_fit_1.EventImporterFIT.getCreatorFromFitDataObject(fitDataObject); // Then expect(creator.isRecognized).toBeFalsy(); expect(creator.name).toEqual(expectedName); expect(creator.manufacturer).toEqual(manufacturer); done(); }); }); describe('Unknown manufacturer with missing productId & missing product name', () => { it('should format a non-recognized Suunto device without productId && product name', done => { const manufacturer = 'reactive'; const expectedName = 'Reactive'; const fitDataObject = generateFitDeviceDataObject(manufacturer); // When const creator = importer_fit_1.EventImporterFIT.getCreatorFromFitDataObject(fitDataObject); // Then expect(creator.isRecognized).toBeFalsy(); expect(creator.name).toEqual(expectedName); expect(creator.manufacturer).toEqual(manufacturer); done(); }); it('should format a non-recognized Garmin device without productId && product name', done => { const manufacturer = 'high_cadence_fitness'; const expectedName = 'High Cadence Fitness'; const fitDataObject = generateFitDeviceDataObject(manufacturer); // When const creator = importer_fit_1.EventImporterFIT.getCreatorFromFitDataObject(fitDataObject); // Then expect(creator.isRecognized).toBeFalsy(); expect(creator.name).toEqual(expectedName); expect(creator.manufacturer).toEqual(manufacturer); done(); }); }); describe('Empty manufacturer with missing productId & missing product name', () => { it('should format a non-recognized device without manufacturer, productId && product name (1)', done => { const manufacturer = 0; const expectedName = 'Unknown'; const fitDataObject = generateFitDeviceDataObject(manufacturer); // When const creator = importer_fit_1.EventImporterFIT.getCreatorFromFitDataObject(fitDataObject); // Then expect(creator.isRecognized).toBeFalsy(); expect(creator.name).toEqual(expectedName); expect(creator.manufacturer).toEqual(manufacturer); done(); }); it('should format a non-recognized device without manufacturer, productId && product name (2)', done => { const expectedName = 'Unknown'; const fitDataObject = generateFitDeviceDataObject(); // When const creator = importer_fit_1.EventImporterFIT.getCreatorFromFitDataObject(fitDataObject); // Then expect(creator.isRecognized).toBeFalsy(); expect(creator.name).toEqual(expectedName); done(); }); it('should not throw for non-zero numeric manufacturer values', done => { const manufacturer = 99999; const expectedName = 'Unknown'; const fitDataObject = generateFitDeviceDataObject(manufacturer); const creator = importer_fit_1.EventImporterFIT.getCreatorFromFitDataObject(fitDataObject); expect(creator.isRecognized).toBeFalsy(); expect(creator.name).toEqual(expectedName); expect(creator.manufacturer).toEqual(manufacturer); done(); }); }); }); }); describe('Stream includeTypes filtering', () => { const sampleFitPath = path.resolve(__dirname, '../../../../../samples/fit/garmin.fit'); function toArrayBuffer(fileBuffer) { return fileBuffer.buffer.slice(fileBuffer.byteOffset, fileBuffer.byteOffset + fileBuffer.byteLength); } function parseSample(options) { return __awaiter(this, void 0, void 0, function* () { const fileBuffer = fs.readFileSync(sampleFitPath); const event = yield importer_fit_1.EventImporterFIT.getFromArrayBuffer(toArrayBuffer(fileBuffer), options, 'fit-stream-filter'); return event .getActivities()[0] .getAllStreams() .map(stream => stream.type); }); } it('should keep baseline behavior when includeTypes is empty', () => __awaiter(void 0, void 0, void 0, function* () { const baseOptions = new activity_parsing_options_1.ActivityParsingOptions({ generateUnitStreams: false }); const emptyFilterOptions = new activity_parsing_options_1.ActivityParsingOptions({ generateUnitStreams: false, streams: { includeTypes: [] } }); const baselineTypes = new Set(yield parseSample(baseOptions)); const emptyFilterTypes = new Set(yield parseSample(emptyFilterOptions)); expect(emptyFilterTypes).toEqual(baselineTypes); })); it('should return only requested raw streams', () => __awaiter(void 0, void 0, void 0, function* () { const streamTypes = new Set(yield parseSample(new activity_parsing_options_1.ActivityParsingOptions({ streams: { includeTypes: [data_distance_1.DataDistance.type, data_heart_rate_1.DataHeartRate.type] } }))); expect(streamTypes).toEqual(new Set([data_distance_1.DataDistance.type, data_heart_rate_1.DataHeartRate.type])); })); it('should return only requested derived streams', () => __awaiter(void 0, void 0, void 0, function* () { const streamTypes = new Set(yield parseSample(new activity_parsing_options_1.ActivityParsingOptions({ streams: { includeTypes: [data_pace_1.DataPace.type] } }))); expect(streamTypes).toEqual(new Set([data_pace_1.DataPace.type])); })); it('should return only requested mixed raw and derived streams', () => __awaiter(void 0, void 0, void 0, function* () { const streamTypes = new Set(yield parseSample(new activity_parsing_options_1.ActivityParsingOptions({ streams: { includeTypes: [data_distance_1.DataDistance.type, data_pace_1.DataPace.type] } }))); expect(streamTypes).toEqual(new Set([data_distance_1.DataDistance.type, data_pace_1.DataPace.type])); })); it('should preserve stream order after includeTypes pruning', () => __awaiter(void 0, void 0, void 0, function* () { const includeTypes = [data_distance_1.DataDistance.type, data_pace_1.DataPace.type]; const baselineStreamTypes = yield parseSample(new activity_parsing_options_1.ActivityParsingOptions({ generateUnitStreams: false })); const filteredStreamTypes = yield parseSample(new activity_parsing_options_1.ActivityParsingOptions({ generateUnitStreams: false, streams: { includeTypes } })); const expectedOrder = baselineStreamTypes.filter(type => includeTypes.includes(type)); expect(filteredStreamTypes).toEqual(expectedOrder); expect(filteredStreamTypes).toEqual(includeTypes); })); it('should throw when includeTypes contains unknown stream types', () => __awaiter(void 0, void 0, void 0, function* () { yield expect(parseSample(new activity_parsing_options_1.ActivityParsingOptions({ streams: { includeTypes: ['Not A Stream Type'] } }))).rejects.toThrow('Unknown stream includeTypes'); })); }); });