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

165 lines (164 loc) 7.51 kB
"use strict"; 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()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const fs_1 = __importDefault(require("fs")); const index_1 = require("../index"); const data_ftp_1 = require("../data/data.ftp"); const data_critical_power_1 = require("../data/data.critical-power"); const data_w_prime_1 = require("../data/data.w-prime"); const data_power_curve_1 = require("../data/data.power-curve"); describe('FTP and CP on real FIT files', () => { const fixtureCases = [ { label: 'Garmin Edge 1000 long ride', fixturePath: __dirname + '/fixtures/rides/fit/7432332116.fit', ftp: 180, cp: 199, wPrime: 7059 }, { label: 'Garmin ride with high variability', fixturePath: __dirname + '/fixtures/rides/fit/7386755164.fit', ftp: 228, cp: 197, wPrime: 13417 }, { label: 'Garmin ride with stable sustained effort', fixturePath: __dirname + '/fixtures/rides/fit/971150603.fit', ftp: 157, cp: 157, wPrime: 7575 }, { label: 'Garmin ride with moderate variability', fixturePath: __dirname + '/fixtures/rides/fit/7445393868.fit', ftp: 201, cp: 149, wPrime: 8395 } ]; const withPowerSampleCases = [ { label: 'withpower sample 2025-11-02_10-30', fixturePath: __dirname + '/fixtures/rides/fit/withpower/2025-11-02_10-30.fit', ftp: 200, cp: 175, wPrime: 17016 }, { label: 'withpower sample 2025-11-11_15-47', fixturePath: __dirname + '/fixtures/rides/fit/withpower/2025-11-11_15-47.fit', ftp: 210, cp: 241, wPrime: 8989 }, { label: 'withpower sample 2025-12-29_14-12', fixturePath: __dirname + '/fixtures/rides/fit/withpower/2025-12-29_14-12.fit', ftp: 234, cp: 155, wPrime: 2080 }, { label: 'withpower sample 2026-01-02_14-24', fixturePath: __dirname + '/fixtures/rides/fit/withpower/2026-01-02_14-24.fit', ftp: 234, cp: 202, wPrime: 5909 }, { label: 'withpower sample 2026-01-05_14-41', fixturePath: __dirname + '/fixtures/rides/fit/withpower/2026-01-05_14-41.fit', ftp: 234, cp: 169, wPrime: 6307 }, { label: 'withpower sample 2026-01-16_15-01', fixturePath: __dirname + '/fixtures/rides/fit/withpower/2026-01-16_15-01.fit', ftp: 234, cp: 176, wPrime: 6908 }, { label: 'withpower sample 2026-01-19_14-57', fixturePath: __dirname + '/fixtures/rides/fit/withpower/2026-01-19_14-57.fit', ftp: 234, cp: 141, wPrime: 7224 }, { label: 'withpower sample 2026-01-27_14-37', fixturePath: __dirname + '/fixtures/rides/fit/withpower/2026-01-27_14-37.fit', ftp: 234, cp: 149, wPrime: 4331 }, { label: 'withpower sample 2026-02-02_14-32', fixturePath: __dirname + '/fixtures/rides/fit/withpower/2026-02-02_14-32.fit', ftp: 234, cp: 195, wPrime: 11461 } ]; const assertPowerMetrics = (testCase) => { it(`should calculate FTP, CP and W' for ${testCase.label}`, () => __awaiter(void 0, void 0, void 0, function* () { const buffer = fs_1.default.readFileSync(testCase.fixturePath); const event = yield index_1.SportsLib.importFromFit(buffer); const activity = event.getFirstActivity(); const ftp = activity.getStat(data_ftp_1.DataFTP.type); const cp = activity.getStat(data_critical_power_1.DataCriticalPower.type); const wPrime = activity.getStat(data_w_prime_1.DataWPrime.type); expect(ftp).toBeDefined(); expect(cp).toBeDefined(); expect(wPrime).toBeDefined(); expect(ftp.getValue()).toBe(testCase.ftp); expect(cp.getValue()).toBe(testCase.cp); expect(wPrime.getValue()).toBe(testCase.wPrime); })); }; fixtureCases.forEach(assertPowerMetrics); withPowerSampleCases.forEach(assertPowerMetrics); it('should show that FTP and CP are not always close', () => __awaiter(void 0, void 0, void 0, function* () { const variableRide = fs_1.default.readFileSync(__dirname + '/fixtures/rides/fit/7386755164.fit'); const steadyRide = fs_1.default.readFileSync(__dirname + '/fixtures/rides/fit/971150603.fit'); const [variableEvent, steadyEvent] = yield Promise.all([ index_1.SportsLib.importFromFit(variableRide), index_1.SportsLib.importFromFit(steadyRide) ]); const variableActivity = variableEvent.getFirstActivity(); const steadyActivity = steadyEvent.getFirstActivity(); const variableFTP = variableActivity.getStat(data_ftp_1.DataFTP.type).getValue(); const variableCP = variableActivity.getStat(data_critical_power_1.DataCriticalPower.type).getValue(); const steadyFTP = steadyActivity.getStat(data_ftp_1.DataFTP.type).getValue(); const steadyCP = steadyActivity.getStat(data_critical_power_1.DataCriticalPower.type).getValue(); expect(Math.abs(variableCP - variableFTP)).toBeGreaterThanOrEqual(20); expect(Math.abs(steadyCP - steadyFTP)).toBeLessThanOrEqual(2); })); it('should prefer imported Garmin FTP over computed 20-minute FTP when available', () => __awaiter(void 0, void 0, void 0, function* () { var _a; const buffer = fs_1.default.readFileSync(__dirname + '/fixtures/rides/fit/7432332116.fit'); const event = yield index_1.SportsLib.importFromFit(buffer); const activity = event.getFirstActivity(); const importedFTP = activity.getStat(data_ftp_1.DataFTP.type).getValue(); const curvePoints = (_a = activity.getStat(data_power_curve_1.DataPowerCurve.type)) === null || _a === void 0 ? void 0 : _a.getValue(); const twentyMinutePoint = curvePoints.find(point => point.duration.getValue() === 1200); const computedFtpFromCurve = Math.round(twentyMinutePoint.power.getValue() * 0.95); expect(importedFTP).toBe(180); expect(computedFtpFromCurve).toBe(191); expect(importedFTP).not.toBe(computedFtpFromCurve); })); });