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

1,155 lines (986 loc) 43.3 kB
import fs from 'fs'; import { SportsLib } from '../src'; import { EventInterface } from '../src/events/event.interface'; import * as strava_3156040843 from './fixtures/strava-streams-compliance/suunto_export/strava_3156040843.json'; import * as strava_3182900697 from './fixtures/strava-streams-compliance/suunto_export/strava_3182900697.json'; import * as strava_343080886 from './fixtures/strava-streams-compliance/garmin_export/strava_343080886.json'; import * as strava_3171438371_gpx from './fixtures/strava-streams-compliance/garmin_export/strava_3171472783_gpx.json'; import * as strava_3171487458_tcx from './fixtures/strava-streams-compliance/garmin_export/strava_3171487458_tcx.json'; import * as strava_3183465494 from './fixtures/strava-streams-compliance/COROS_export/strava_3183465494.json'; import * as strava_3183490558 from './fixtures/strava-streams-compliance/COROS_export/strava_3183490558.json'; import * as strava_2709634581 from './fixtures/strava-streams-compliance/suunto_export/strava_2709634581.json'; import { DataAltitude } from '../src/data/data.altitude'; import { DataHeartRate } from '../src/data/data.heart-rate'; import { DataCadence } from '../src/data/data.cadence'; import { DataTemperature } from '../src/data/data.temperature'; import { DataPower } from '../src/data/data.power'; import { DataDistance } from '../src/data/data.distance'; import { DataGrade } from '../src/data/data.grade'; export const GRADE_TOLERANCE = 1.5; function clone(obj: any) { return JSON.parse(JSON.stringify(obj)); } describe('Strava data compliance', () => { const averageDeltaBetweenStreams = (actualStream: number[], expectedStream: number[]) => { let deltaSum = 0; actualStream.forEach((value, index) => { deltaSum += Math.abs(value - expectedStream[index]); }); return deltaSum / actualStream.length; }; describe('Compliance with suunto export (flat activity)', () => { const path = __dirname + '/fixtures/strava-streams-compliance/suunto_export/5e5fde38c2de24635a30d383.fit'; const buffer = fs.readFileSync(path); it(`should match time`, done => { // Given const stravaCadenceStream = clone(strava_3156040843.time); // When const eventInterfacePromise = SportsLib.importFromFit(buffer); // Then eventInterfacePromise.then((event: EventInterface) => { expect(stravaCadenceStream.length).toEqual( event.getFirstActivity().generateTimeStream([DataDistance.type, DataAltitude.type]).getData(true).length ); expect(stravaCadenceStream).toEqual( event.getFirstActivity().generateTimeStream([DataDistance.type, DataAltitude.type]).getData(true) ); done(); }); }); it(`should match altitude`, done => { // Given const stravaAltitudeStream = clone(strava_3156040843.altitude); // When const eventInterfacePromise = SportsLib.importFromFit(buffer); // Then eventInterfacePromise.then((event: EventInterface) => { expect(stravaAltitudeStream.length).toEqual(event.getFirstActivity().getStreamData(DataAltitude.type).length); expect(stravaAltitudeStream).toEqual( event .getFirstActivity() .getStreamData(DataAltitude.type) .map(value => (value === null ? null : Math.round(value * 10) / 10)) ); done(); }); }); it(`should match heart rate`, done => { // Given const stravaHeartRateStream = clone(strava_3156040843.heartrate); // When const eventInterfacePromise = SportsLib.importFromFit(buffer); // Then eventInterfacePromise.then((event: EventInterface) => { expect(stravaHeartRateStream.length).toEqual(event.getFirstActivity().getStreamData(DataHeartRate.type).length); expect(stravaHeartRateStream).toEqual( event .getFirstActivity() .getStreamData(DataHeartRate.type) .map(value => (value === null ? null : Math.round(value * 10) / 10)) ); done(); }); }); it(`should match cadence`, done => { // Given const stravaCadenceStream = clone(strava_3156040843.cadence); // When const eventInterfacePromise = SportsLib.importFromFit(buffer); // Then eventInterfacePromise.then((event: EventInterface) => { expect(stravaCadenceStream.length).toEqual(event.getFirstActivity().getStreamData(DataCadence.type).length); expect(stravaCadenceStream).toEqual( event .getFirstActivity() .getStreamData(DataCadence.type) .map(value => (value === null ? null : Math.round(value * 10) / 10)) ); done(); }); }); it(`should match temperature`, done => { // Given const stravaTemperatureStream = clone(strava_3156040843.temp); // When const eventInterfacePromise = SportsLib.importFromFit(buffer); // Then eventInterfacePromise.then((event: EventInterface) => { expect(stravaTemperatureStream.length).toEqual( event.getFirstActivity().getStreamData(DataTemperature.type).length ); expect(stravaTemperatureStream).toEqual( event .getFirstActivity() .getStreamData(DataTemperature.type) .map(value => (value === null ? null : Math.round(value * 10) / 10)) ); done(); }); }); it(`should match power`, done => { // Given const stravaPowerStream = clone(strava_3156040843.watts); // When const eventInterfacePromise = SportsLib.importFromFit(buffer); // Then eventInterfacePromise.then((event: EventInterface) => { expect(stravaPowerStream.length).toEqual(event.getFirstActivity().getStreamData(DataPower.type).length); expect(stravaPowerStream).toEqual( event .getFirstActivity() .getStreamData(DataPower.type) .map(value => (value === null ? null : Math.round(value * 10) / 10)) ); done(); }); }); it(`should match distance with x% error max`, done => { // Given const tolerance = 0.6; // percent const stravaDistanceStream = clone(strava_3156040843.distance); // When const eventInterfacePromise = SportsLib.importFromFit(buffer); // Then eventInterfacePromise.then((event: EventInterface) => { const streamData = event .getFirstActivity() .getStreamData(DataDistance.type) .map(value => (value === null ? null : Math.round(value * 10) / 10)); expect(stravaDistanceStream.length).toEqual(streamData.length); const commonCount = stravaDistanceStream.filter( (value: number | null) => streamData.indexOf(value) !== -1 ).length; expect(commonCount + Math.ceil((stravaDistanceStream.length * tolerance) / 100)).toBeGreaterThanOrEqual( stravaDistanceStream.length ); // expect(stravaDistanceStream).toEqual(streamData); done(); }); }); it(`should have an average grade diff lower than ${GRADE_TOLERANCE}%`, done => { // Given const toleranceAvgGradeDelta = GRADE_TOLERANCE; const stravaGradeStream = clone(strava_3156040843.grade_smooth); // When const eventInterfacePromise = SportsLib.importFromFit(buffer); // Then eventInterfacePromise.then((event: EventInterface) => { const streamData = <number[]>event .getFirstActivity() .getStreamData(DataGrade.type) .map(value => (value === null ? null : Math.round(value * 10) / 10)); expect(stravaGradeStream.length).toEqual(streamData.length); const deltaBetweenStreams = averageDeltaBetweenStreams(streamData, stravaGradeStream); console.log(`Delta is ${deltaBetweenStreams}`); expect(deltaBetweenStreams).toBeLessThan(toleranceAvgGradeDelta); done(); }); }); }); describe('Compliance with suunto export (flat activity) without paused records', () => { const path = __dirname + '/fixtures/strava-streams-compliance/suunto_export/5e5fde38c2de24635a30d383-2.fit'; const buffer = fs.readFileSync(path); it(`should match time`, done => { // Given const stravaCadenceStream = clone(strava_3182900697.time); // When const eventInterfacePromise = SportsLib.importFromFit(buffer); // Then eventInterfacePromise.then((event: EventInterface) => { expect(stravaCadenceStream.length).toEqual( event.getFirstActivity().generateTimeStream([DataDistance.type, DataAltitude.type]).getData(true).length ); expect(stravaCadenceStream).toEqual( event.getFirstActivity().generateTimeStream([DataDistance.type, DataAltitude.type]).getData(true) ); done(); }); }); it(`should match altitude`, done => { // Given const stravaAltitudeStream = clone(strava_3182900697.altitude); // When const eventInterfacePromise = SportsLib.importFromFit(buffer); // Then eventInterfacePromise.then((event: EventInterface) => { expect(stravaAltitudeStream.length).toEqual( event.getFirstActivity().getSquashedStreamData(DataAltitude.type).length ); expect(stravaAltitudeStream).toEqual( event .getFirstActivity() .getSquashedStreamData(DataAltitude.type) .map(value => (value === null ? null : Math.round(value * 10) / 10)) ); done(); }); }); it(`should match heart rate`, done => { // Given const stravaHeartRateStream = clone(strava_3182900697.heartrate); // When const eventInterfacePromise = SportsLib.importFromFit(buffer); // Then eventInterfacePromise.then((event: EventInterface) => { expect(stravaHeartRateStream.length).toEqual( event.getFirstActivity().getSquashedStreamData(DataHeartRate.type).length ); expect(stravaHeartRateStream).toEqual( event .getFirstActivity() .getSquashedStreamData(DataHeartRate.type) .map(value => (value === null ? null : Math.round(value * 10) / 10)) ); done(); }); }); it(`should match cadence`, done => { // Given const stravaCadenceStream = clone(strava_3182900697.cadence); // When const eventInterfacePromise = SportsLib.importFromFit(buffer); // Then eventInterfacePromise.then((event: EventInterface) => { expect(stravaCadenceStream.length).toEqual( event.getFirstActivity().getSquashedStreamData(DataCadence.type).length ); expect(stravaCadenceStream).toEqual( event .getFirstActivity() .getSquashedStreamData(DataCadence.type) .map(value => (value === null ? null : Math.round(value * 10) / 10)) ); done(); }); }); it(`should match distance with x% error max`, done => { // Given const tolerance = 0.6; // percent const stravaDistanceStream = clone(strava_3182900697.distance); // When const eventInterfacePromise = SportsLib.importFromFit(buffer); // Then eventInterfacePromise.then((event: EventInterface) => { const streamData = event .getFirstActivity() .getSquashedStreamData(DataDistance.type) .map(value => (value === null ? null : Math.round(value * 10) / 10)); expect(stravaDistanceStream.length).toEqual(streamData.length); const commonCount = stravaDistanceStream.filter( (value: number | null) => streamData.indexOf(value) !== -1 ).length; expect(commonCount + Math.ceil((stravaDistanceStream.length * tolerance) / 100)).toBeGreaterThanOrEqual( stravaDistanceStream.length ); // expect(stravaDistanceStream).toEqual(streamData); done(); }); }); it(`should have an average grade diff lower than ${GRADE_TOLERANCE}%`, done => { // Given const toleranceAvgGradeDelta = GRADE_TOLERANCE; const stravaGradeStream = clone(strava_3182900697.grade_smooth); // When const eventInterfacePromise = SportsLib.importFromFit(buffer); // Then eventInterfacePromise.then((event: EventInterface) => { const streamData = <number[]>event .getFirstActivity() .getSquashedStreamData(DataGrade.type) .map(value => (value === null ? null : Math.round(value * 10) / 10)); expect(stravaGradeStream.length).toEqual(streamData.length); const deltaBetweenStreams = averageDeltaBetweenStreams(streamData, stravaGradeStream); console.log(`Delta is ${deltaBetweenStreams}`); expect(deltaBetweenStreams).toBeLessThan(toleranceAvgGradeDelta); done(); }); }); }); describe('Compliance with suunto export (uphill 1800 activity) without paused records ', () => { const path = __dirname + '/fixtures/strava-streams-compliance/suunto_export/suunto-uphill.fit'; const buffer = fs.readFileSync(path); it(`should match time`, done => { // Given const stravaCadenceStream = clone(strava_2709634581.time); // When const eventInterfacePromise = SportsLib.importFromFit(buffer); // Then eventInterfacePromise.then((event: EventInterface) => { expect(stravaCadenceStream.length).toEqual(event.getFirstActivity().generateTimeStream().getData(true).length); expect(stravaCadenceStream).toEqual( event.getFirstActivity().generateTimeStream([DataDistance.type, DataAltitude.type]).getData(true) ); done(); }); }); it(`should match altitude`, done => { // Given const stravaAltitudeStream = clone(strava_2709634581.altitude); // When const eventInterfacePromise = SportsLib.importFromFit(buffer); // Then eventInterfacePromise.then((event: EventInterface) => { expect(stravaAltitudeStream.length).toEqual( event.getFirstActivity().getSquashedStreamData(DataAltitude.type).length ); expect(stravaAltitudeStream).toEqual( event .getFirstActivity() .getSquashedStreamData(DataAltitude.type) .map(value => (value === null ? null : Math.round(value * 10) / 10)) ); done(); }); }); it(`should match heart rate`, done => { // Given const stravaHeartRateStream = clone(strava_2709634581.heartrate); // When const eventInterfacePromise = SportsLib.importFromFit(buffer); // Then eventInterfacePromise.then((event: EventInterface) => { expect(stravaHeartRateStream.length).toEqual( event.getFirstActivity().getSquashedStreamData(DataHeartRate.type).length ); expect(stravaHeartRateStream).toEqual( event .getFirstActivity() .getSquashedStreamData(DataHeartRate.type) .map(value => (value === null ? null : Math.round(value * 10) / 10)) ); done(); }); }); it(`should match cadence`, done => { // Given const stravaCadenceStream = clone(strava_2709634581.cadence); // When const eventInterfacePromise = SportsLib.importFromFit(buffer); // Then eventInterfacePromise.then((event: EventInterface) => { expect(stravaCadenceStream.length).toEqual( event.getFirstActivity().getSquashedStreamData(DataCadence.type).length ); expect(stravaCadenceStream).toEqual( event .getFirstActivity() .getSquashedStreamData(DataCadence.type) .map(value => (value === null ? null : Math.round(value * 10) / 10)) ); done(); }); }); it.skip(`should match distance with x% error max`, done => { // Given const tolerance = 0.6; // percent const stravaDistanceStream = clone(strava_2709634581.distance); // When const eventInterfacePromise = SportsLib.importFromFit(buffer); // Then eventInterfacePromise.then((event: EventInterface) => { const streamData = event .getFirstActivity() .getSquashedStreamData(DataDistance.type) .map(value => (value === null ? null : Math.round(value * 10) / 10)); // expect(stravaDistanceStream.length).toEqual(streamData.length); const commonCount = stravaDistanceStream.filter( (value: number | null) => streamData.indexOf(value) !== -1 ).length; // expect(commonCount + Math.ceil((stravaDistanceStream.length * tolerance) / 100)).toBeGreaterThanOrEqual(stravaDistanceStream.length); expect(stravaDistanceStream).toEqual(streamData); done(); }); }); it.skip(`should have an average grade diff lower than ${GRADE_TOLERANCE}%`, done => { // Given const toleranceAvgGradeDelta = GRADE_TOLERANCE; const stravaGradeStream = clone(strava_3182900697.grade_smooth); // When const eventInterfacePromise = SportsLib.importFromFit(buffer); // Then eventInterfacePromise.then((event: EventInterface) => { const streamData = <number[]>event .getFirstActivity() .getSquashedStreamData(DataGrade.type) .map(value => (value === null ? null : Math.round(value * 10) / 10)); expect(stravaGradeStream.length).toEqual(streamData.length); const deltaBetweenStreams = averageDeltaBetweenStreams(streamData, stravaGradeStream); console.log(`Delta is ${deltaBetweenStreams}`); expect(deltaBetweenStreams).toBeLessThan(toleranceAvgGradeDelta); done(); }); }); }); describe('Compliance with COROS export', () => { const path = __dirname + '/fixtures/strava-streams-compliance/COROS_export/OutdoorRun20200314145229.fit'; const buffer = fs.readFileSync(path); it(`should match time`, done => { // Given const stravaCadenceStream = clone(strava_3183465494.time); // When const eventInterfacePromise = SportsLib.importFromFit(buffer); // Then eventInterfacePromise.then((event: EventInterface) => { expect(stravaCadenceStream.length).toEqual( event.getFirstActivity().generateTimeStream([DataDistance.type, DataAltitude.type]).getData(true).length ); expect(stravaCadenceStream).toEqual( event.getFirstActivity().generateTimeStream([DataDistance.type, DataAltitude.type]).getData(true) ); done(); }); }); it(`should match altitude`, done => { // Given const stravaAltitudeStream = clone(strava_3183465494.altitude); // When const eventInterfacePromise = SportsLib.importFromFit(buffer); // Then eventInterfacePromise.then((event: EventInterface) => { expect(stravaAltitudeStream.length).toEqual( event.getFirstActivity().getSquashedStreamData(DataAltitude.type).length ); expect(stravaAltitudeStream).toEqual( event .getFirstActivity() .getSquashedStreamData(DataAltitude.type) .map(value => (value === null ? null : Math.round(value * 10) / 10)) ); done(); }); }); it(`should match heart rate`, done => { // Given const stravaHeartRateStream = clone(strava_3183465494.heartrate); // When const eventInterfacePromise = SportsLib.importFromFit(buffer); // Then eventInterfacePromise.then((event: EventInterface) => { expect(stravaHeartRateStream.length).toEqual( event.getFirstActivity().getSquashedStreamData(DataHeartRate.type).length ); expect(stravaHeartRateStream).toEqual( event .getFirstActivity() .getSquashedStreamData(DataHeartRate.type) .map(value => (value === null ? null : Math.round(value * 10) / 10)) ); done(); }); }); it(`should match cadence`, done => { // Given const stravaCadenceStream = clone(strava_3183465494.cadence); // When const eventInterfacePromise = SportsLib.importFromFit(buffer); // Then eventInterfacePromise.then((event: EventInterface) => { expect(stravaCadenceStream.length).toEqual( event.getFirstActivity().getSquashedStreamData(DataCadence.type).length ); expect(stravaCadenceStream).toEqual( event .getFirstActivity() .getSquashedStreamData(DataCadence.type) .map(value => (value === null ? null : Math.round(value * 10) / 10)) ); done(); }); }); it(`should match distance`, done => { // Given const tolerance = 0.6; // percent const stravaDistanceStream = clone(strava_3183465494.distance); // When const eventInterfacePromise = SportsLib.importFromFit(buffer); // Then eventInterfacePromise.then((event: EventInterface) => { const streamData = event .getFirstActivity() .getSquashedStreamData(DataDistance.type) .map(value => (value === null ? null : Math.round(value * 10) / 10)); expect(stravaDistanceStream.length).toEqual(streamData.length); expect(stravaDistanceStream).toEqual(streamData); done(); }); }); it(`should have an average grade diff lower than ${GRADE_TOLERANCE}%`, done => { // Given const toleranceAvgGradeDelta = GRADE_TOLERANCE; const stravaGradeStream = clone(strava_3183465494.grade_smooth); // When const eventInterfacePromise = SportsLib.importFromFit(buffer); // Then eventInterfacePromise.then((event: EventInterface) => { const streamData = <number[]>event .getFirstActivity() .getSquashedStreamData(DataGrade.type) .map(value => (value === null ? null : Math.round(value * 10) / 10)); expect(stravaGradeStream.length).toEqual(streamData.length); const deltaBetweenStreams = averageDeltaBetweenStreams(streamData, stravaGradeStream); console.log(`Delta is ${deltaBetweenStreams}`); expect(deltaBetweenStreams).toBeLessThan(toleranceAvgGradeDelta); done(); }); }); }); describe('Compliance with COROS export trail', () => { const path = __dirname + '/fixtures/strava-streams-compliance/COROS_export/TrailRun20200215111630.fit'; const buffer = fs.readFileSync(path); it(`should match time`, done => { // Given const stravaCadenceStream = clone(strava_3183490558.time); // When const eventInterfacePromise = SportsLib.importFromFit(buffer); // Then eventInterfacePromise.then((event: EventInterface) => { expect(stravaCadenceStream.length).toEqual( event.getFirstActivity().generateTimeStream([DataDistance.type, DataAltitude.type]).getData(true).length ); expect(stravaCadenceStream).toEqual( event.getFirstActivity().generateTimeStream([DataDistance.type, DataAltitude.type]).getData(true) ); done(); }); }); it(`should match altitude`, done => { // Given const stravaAltitudeStream = clone(strava_3183490558.altitude); // When const eventInterfacePromise = SportsLib.importFromFit(buffer); // Then eventInterfacePromise.then((event: EventInterface) => { expect(stravaAltitudeStream.length).toEqual( event.getFirstActivity().getSquashedStreamData(DataAltitude.type).length ); expect(stravaAltitudeStream).toEqual( event .getFirstActivity() .getSquashedStreamData(DataAltitude.type) .map(value => (value === null ? null : Math.round(value * 10) / 10)) ); done(); }); }); it(`should match heart rate`, done => { // Given const stravaHeartRateStream = clone(strava_3183490558.heartrate); // When const eventInterfacePromise = SportsLib.importFromFit(buffer); // Then eventInterfacePromise.then((event: EventInterface) => { expect(stravaHeartRateStream.length).toEqual( event.getFirstActivity().getSquashedStreamData(DataHeartRate.type).length ); expect(stravaHeartRateStream).toEqual( event .getFirstActivity() .getSquashedStreamData(DataHeartRate.type) .map(value => (value === null ? null : Math.round(value * 10) / 10)) ); done(); }); }); it(`should match cadence`, done => { // Given const stravaCadenceStream = clone(strava_3183490558.cadence); // When const eventInterfacePromise = SportsLib.importFromFit(buffer); // Then eventInterfacePromise.then((event: EventInterface) => { expect(stravaCadenceStream.length).toEqual( event.getFirstActivity().getSquashedStreamData(DataCadence.type).length ); // @todo leaving this to fail to investigate how to fill linear as seen with Suunto expect(stravaCadenceStream).toEqual( event .getFirstActivity() .getSquashedStreamData(DataCadence.type) .map(value => (value === null ? null : Math.round(value * 10) / 10)) ); done(); }); }); it.skip(`should match distance`, done => { // Given const tolerance = 0.6; // percent const stravaDistanceStream = clone(strava_3183490558.distance); // When const eventInterfacePromise = SportsLib.importFromFit(buffer); // Then eventInterfacePromise.then((event: EventInterface) => { const streamData = event .getFirstActivity() .getSquashedStreamData(DataDistance.type) .map(value => (value === null ? null : Math.round(value * 10) / 10)); expect(stravaDistanceStream.length).toEqual(streamData.length); expect(stravaDistanceStream).toEqual(streamData); done(); }); }); it.skip(`should have an average grade diff lower than ${GRADE_TOLERANCE}%`, done => { // Given const toleranceAvgGradeDelta = GRADE_TOLERANCE; const stravaGradeStream = clone(strava_3183490558.grade_smooth); // When const eventInterfacePromise = SportsLib.importFromFit(buffer); // Then eventInterfacePromise.then((event: EventInterface) => { const streamData = <number[]>event .getFirstActivity() .getSquashedStreamData(DataGrade.type) .map(value => (value === null ? null : Math.round(value * 10) / 10)); expect(stravaGradeStream.length).toEqual(streamData.length); const deltaBetweenStreams = averageDeltaBetweenStreams(streamData, stravaGradeStream); console.log(`Delta is ${deltaBetweenStreams}`); expect(deltaBetweenStreams).toBeLessThan(toleranceAvgGradeDelta); done(); }); }); }); describe('Compliance with garmin export (hilly activity: https://connect.garmin.com/modern/activity/828989227 / https://www.strava.com/activities/343080886)', () => { describe('Fit file version', () => { const path = __dirname + '/fixtures/strava-streams-compliance/garmin_export/garmin_828989227.fit'; const buffer = fs.readFileSync(path); it(`should match time`, done => { // Given const stravaCadenceStream = clone(strava_343080886.time); // When const eventInterfacePromise = SportsLib.importFromFit(buffer); // Then eventInterfacePromise.then((event: EventInterface) => { expect(stravaCadenceStream.length).toEqual( event.getFirstActivity().generateTimeStream([DataDistance.type, DataAltitude.type]).getData(true).length ); expect(stravaCadenceStream).toEqual( event.getFirstActivity().generateTimeStream([DataDistance.type, DataAltitude.type]).getData(true) ); done(); }); }); it(`should match altitude`, done => { // Given const stravaAltitudeStream = clone(strava_343080886.altitude); // When const eventInterfacePromise = SportsLib.importFromFit(buffer); // Then eventInterfacePromise.then((event: EventInterface) => { expect(stravaAltitudeStream.length).toEqual( event.getFirstActivity().getSquashedStreamData(DataAltitude.type).length ); expect(stravaAltitudeStream).toEqual( event .getFirstActivity() .getSquashedStreamData(DataAltitude.type) .map(value => (value === null ? null : Math.round(value * 10) / 10)) ); done(); }); }); it(`should match heart rate`, done => { // Given const stravaHeartRateStream = clone(strava_343080886.heartrate); // When const eventInterfacePromise = SportsLib.importFromFit(buffer); // Then eventInterfacePromise.then((event: EventInterface) => { expect(stravaHeartRateStream.length).toEqual( event.getFirstActivity().getSquashedStreamData(DataHeartRate.type).length ); expect(stravaHeartRateStream).toEqual( event .getFirstActivity() .getSquashedStreamData(DataHeartRate.type) .map(value => (value === null ? null : Math.round(value * 10) / 10)) ); done(); }); }); it(`should match cadence`, done => { // Given const stravaCadenceStream = clone(strava_343080886.cadence); // When const eventInterfacePromise = SportsLib.importFromFit(buffer); // Then eventInterfacePromise.then((event: EventInterface) => { expect(stravaCadenceStream.length).toEqual( event.getFirstActivity().getSquashedStreamData(DataCadence.type).length ); expect(stravaCadenceStream).toEqual( event .getFirstActivity() .getSquashedStreamData(DataCadence.type) .map(value => (value === null ? null : Math.round(value * 10) / 10)) ); done(); }); }); it(`should match distance with x% error max`, done => { // Given const tolerance = 0.0; // percent const stravaDistanceStream = clone(strava_343080886.distance); // When const eventInterfacePromise = SportsLib.importFromFit(buffer); // Then eventInterfacePromise.then((event: EventInterface) => { expect(stravaDistanceStream.length).toEqual( event.getFirstActivity().getSquashedStreamData(DataDistance.type).length ); const streamData = event .getFirstActivity() .getSquashedStreamData(DataDistance.type) .map(value => (value === null ? null : Math.round(value * 10) / 10)); expect(stravaDistanceStream).toEqual(streamData); done(); }); }); it(`should have an average grade diff lower than ${GRADE_TOLERANCE}%`, done => { // Given const toleranceAvgGradeDelta = GRADE_TOLERANCE; const stravaGradeStream = clone(strava_343080886.grade_smooth); // When const eventInterfacePromise = SportsLib.importFromFit(buffer); // Then eventInterfacePromise.then((event: EventInterface) => { const streamData = <number[]>event .getFirstActivity() .getSquashedStreamData(DataGrade.type) .map(value => (value === null ? null : Math.round(value * 10) / 10)); expect(stravaGradeStream.length).toEqual(streamData.length); const deltaBetweenStreams = averageDeltaBetweenStreams(streamData, stravaGradeStream); console.log(`Delta is ${deltaBetweenStreams}`); expect(deltaBetweenStreams).toBeLessThan(toleranceAvgGradeDelta); done(); }); }); }); describe('Tcx file version', () => { const path = __dirname + '/fixtures/strava-streams-compliance/garmin_export/garmin_828989227.tcx'; const doc = new DOMParser().parseFromString(fs.readFileSync(path).toString(), 'application/xml'); it(`should match time`, done => { // Given const stravaCadenceStream = clone(strava_3171487458_tcx.time); // When const eventInterfacePromise = SportsLib.importFromTCX(doc); // Then eventInterfacePromise.then((event: EventInterface) => { expect(stravaCadenceStream.length).toEqual( event.getFirstActivity().generateTimeStream([DataDistance.type, DataAltitude.type]).getData(true).length ); expect(stravaCadenceStream).toEqual( event.getFirstActivity().generateTimeStream([DataDistance.type, DataAltitude.type]).getData(true) ); done(); }); }); it(`should match altitude`, done => { // Given const stravaAltitudeStream = clone(strava_3171487458_tcx.altitude); // When const eventInterfacePromise = SportsLib.importFromTCX(doc); // Then eventInterfacePromise.then((event: EventInterface) => { expect(stravaAltitudeStream.length).toEqual( event.getFirstActivity().getSquashedStreamData(DataAltitude.type).length ); expect(stravaAltitudeStream).toEqual( event .getFirstActivity() .getSquashedStreamData(DataAltitude.type) .map(value => (value === null ? null : Math.round(value * 10) / 10)) ); done(); }); }); it(`should match heart rate`, done => { // Given const stravaHeartRateStream = clone(strava_3171487458_tcx.heartrate); // When const eventInterfacePromise = SportsLib.importFromTCX(doc); // Then eventInterfacePromise.then((event: EventInterface) => { expect(stravaHeartRateStream.length).toEqual( event.getFirstActivity().getSquashedStreamData(DataHeartRate.type).length ); expect(stravaHeartRateStream).toEqual( event .getFirstActivity() .getSquashedStreamData(DataHeartRate.type) .map(value => (value === null ? null : Math.round(value * 10) / 10)) ); done(); }); }); it(`should match cadence`, done => { // Given const stravaCadenceStream = clone(strava_3171487458_tcx.cadence); // When const eventInterfacePromise = SportsLib.importFromTCX(doc); // Then eventInterfacePromise.then((event: EventInterface) => { expect(stravaCadenceStream.length).toEqual( event.getFirstActivity().getSquashedStreamData(DataCadence.type).length ); expect(stravaCadenceStream).toEqual( event .getFirstActivity() .getSquashedStreamData(DataCadence.type) .map(value => (value === null ? null : Math.round(value * 10) / 10)) ); done(); }); }); it(`should match distance with x% error max`, done => { // Given const tolerance = 2.6; // percent const toleranceDelta = 0.1; const stravaDistanceStream = clone(strava_3171487458_tcx.distance); // When const eventInterfacePromise = SportsLib.importFromTCX(doc); // Then eventInterfacePromise.then((event: EventInterface) => { const streamData = <number[]>event .getFirstActivity() .getSquashedStreamData(DataDistance.type) .map(value => (value === null ? null : Math.round(value * 10) / 10)); expect(stravaDistanceStream.length).toEqual(streamData.length); // const commonCount = stravaDistanceStream // .filter((value: number) => streamData.indexOf(value) !== -1).length; // We find the common then add the % tolerance and we check if its more than equal to the "strava" stream // expect(commonCount + Math.ceil((stravaDistanceStream.length * tolerance) / 100)).toBeGreaterThanOrEqual(stravaDistanceStream.length); expect(stravaDistanceStream).toMatchObject(streamData); // const deltaBetweenStreams = averageDeltaBetweenStreams(streamData, stravaDistanceStream); // expect(deltaBetweenStreams).toBeLessThan(toleranceDelta); done(); }); }); it(`should have an average grade diff lower than ${GRADE_TOLERANCE}%`, done => { // Given const toleranceAvgGradeDelta = GRADE_TOLERANCE; const stravaGradeStream = clone(strava_343080886.grade_smooth); // When const eventInterfacePromise = SportsLib.importFromTCX(doc); // Then eventInterfacePromise.then((event: EventInterface) => { const streamData = <number[]>event .getFirstActivity() .getSquashedStreamData(DataGrade.type) .map(value => (value === null ? null : Math.round(value * 10) / 10)); expect(stravaGradeStream.length).toEqual(streamData.length); const deltaBetweenStreams = averageDeltaBetweenStreams(streamData, stravaGradeStream); console.log(`Delta is ${deltaBetweenStreams}`); expect(deltaBetweenStreams).toBeLessThan(toleranceAvgGradeDelta); done(); }); }); }); describe('GPX file version', () => { const path = __dirname + '/fixtures/strava-streams-compliance/garmin_export/garmin_828989227.gpx'; const gpxString = fs.readFileSync(path).toString(); it(`should match time`, done => { // Given const stravaTimeStream = clone(strava_3171438371_gpx.time); // When const eventInterfacePromise = SportsLib.importFromGPX(gpxString); // Then eventInterfacePromise.then((event: EventInterface) => { expect(stravaTimeStream.length).toEqual( event.getFirstActivity().generateTimeStream([DataDistance.type, DataAltitude.type]).getData(true).length ); expect(stravaTimeStream).toEqual( event.getFirstActivity().generateTimeStream([DataDistance.type, DataAltitude.type]).getData(true) ); done(); }); }); // Strava adds their own altitude so no go from here // it(`should match altitude`, done => { // // // Given // const stravaAltitudeStream = clone(strava_3171438371_gpx.altitude); // // // When // const eventInterfacePromise = SportsLib.importFromGPX(gpxString); // // // Then // eventInterfacePromise.then((event: EventInterface) => { // expect(stravaAltitudeStream.length).toEqual(event.getFirstActivity().getSquashedStreamData(DataAltitude.type).length); // expect(stravaAltitudeStream).toEqual(event.getFirstActivity().getSquashedStreamData(DataAltitude.type).map(value => value === null ? null : Math.round(value * 10) / 10)); // done(); // }); // }); // we skip this because https://support.strava.com/hc/en-us/community/posts/360054808092-Not-getting-Garmin-heart-rate-data-into-Strava // it(`should match heart rate`, done => { // // // Given // const stravaHeartRateStream = clone(strava_3171438371.heartrate); // // // When // const eventInterfacePromise = SportsLib.importFromGPX(gpxString); // // // Then // eventInterfacePromise.then((event: EventInterface) => { // expect(stravaHeartRateStream.length).toEqual(event.getFirstActivity().getSquashedStreamData(DataHeartRate.type).length); // expect(stravaHeartRateStream).toEqual(event.getFirstActivity().getSquashedStreamData(DataHeartRate.type).map(value => value === null ? null : Math.round(value * 10) / 10)); // done(); // }); // }); // we skip this because https://support.strava.com/hc/en-us/community/posts/360054808092-Not-getting-Garmin-heart-rate-data-into-Strava // it(`should match cadence`, done => { // // // Given // const stravaCadenceStream = clone(strava_3171438371.cadence); // // // When // const eventInterfacePromise = SportsLib.importFromGPX(gpxString); // // // Then // eventInterfacePromise.then((event: EventInterface) => { // expect(stravaCadenceStream.length).toEqual(event.getFirstActivity().getSquashedStreamData(DataCadence.type).length); // expect(stravaCadenceStream).toEqual(event.getFirstActivity().getSquashedStreamData(DataCadence.type).map(value => value === null ? null : Math.round(value * 10) / 10)); // done(); // }); // }); // @todo try different geolib adapters etc to make this diff smaller or investigate more on how strava // Bumps the distance it(`should match distance with x% error max`, done => { // Given const deltaTolerance = 15; // percent const stravaDistanceStream = clone(strava_3171438371_gpx.distance); // When const eventInterfacePromise = SportsLib.importFromGPX(gpxString); // Then eventInterfacePromise.then((event: EventInterface) => { const streamData = <number[]>event .getFirstActivity() .getSquashedStreamData(DataDistance.type) .map(value => (value === null ? null : Math.round(value * 10) / 10)); expect(stravaDistanceStream.length).toEqual(streamData.length); const deltaBetweenStreams = averageDeltaBetweenStreams(streamData, stravaDistanceStream); expect(deltaBetweenStreams).toBeLessThan(deltaTolerance); done(); }); }); // Tolerance is higher since distance is different unfortunately (see above test) it(`should have an average grade diff lower than 3.2%`, done => { // Given const toleranceAvgGradeDelta = 3.2; const stravaGradeStream = clone(strava_3171438371_gpx.grade_smooth); // When const eventInterfacePromise = SportsLib.importFromGPX(gpxString); // Then eventInterfacePromise.then((event: EventInterface) => { const streamData = <number[]>event .getFirstActivity() .getSquashedStreamData(DataGrade.type) .map(value => (value === null ? null : Math.round(value * 10) / 10)); expect(stravaGradeStream.length).toEqual(streamData.length); const deltaBetweenStreams = averageDeltaBetweenStreams(streamData, stravaGradeStream); console.log(`Delta is ${deltaBetweenStreams}`); expect(deltaBetweenStreams).toBeLessThan(toleranceAvgGradeDelta); done(); }); }); }); }); });