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