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

446 lines (445 loc) 22 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const activity_1 = require("./activity"); const data_heart_rate_1 = require("../data/data.heart-rate"); const data_altitude_1 = require("../data/data.altitude"); const activity_types_1 = require("./activity.types"); const creator_1 = require("../creators/creator"); const stream_1 = require("../streams/stream"); const data_distance_1 = require("../data/data.distance"); const data_stop_event_1 = require("../data/data.stop-event"); const data_start_event_1 = require("../data/data.start-event"); const data_stop_all_event_1 = require("../data/data.stop-all-event"); const data_ibi_1 = require("../data/data.ibi"); const ibi_stream_1 = require("../streams/ibi-stream"); describe('Activity', () => { let activity; beforeEach(() => { // New activity that ends +6m and is 10s duration from new Date(0) activity = new activity_1.Activity(new Date(0), new Date(new Date(0).getTime() + 10000), activity_types_1.ActivityTypes.Running, new creator_1.Creator('Test')); activity.setID('123'); }); it('should serialize swim lengths as an activity JSON array like laps', () => { expect(activity.toJSON().laps).toEqual([]); expect(activity.toJSON().swimLengths).toEqual([]); }); it('should get streams based on time', () => { activity.addStream(new stream_1.Stream(data_altitude_1.DataAltitude.type, [200, 500, null, 502, null, 600, 700])); activity.addStream(new stream_1.Stream(data_distance_1.DataDistance.type, [0, 10, 20, 30, 40, 50, 60])); expect(activity.getStreamDataTypesBasedOnTime([data_altitude_1.DataAltitude.type])).toEqual({ 0: { Altitude: 200 }, 1000: { Altitude: 500 }, 3000: { Altitude: 502 }, 5000: { Altitude: 600 }, 6000: { Altitude: 700 } }); expect(activity.getStreamDataTypesBasedOnTime([data_distance_1.DataDistance.type])).toEqual({ 0: { Distance: 0 }, 1000: { Distance: 10 }, 2000: { Distance: 20 }, 3000: { Distance: 30 }, 4000: { Distance: 40 }, 5000: { Distance: 50 }, 6000: { Distance: 60 } }); expect(activity.getStreamDataTypesBasedOnTime([data_altitude_1.DataAltitude.type, data_distance_1.DataDistance.type])).toEqual({ 0: { Altitude: 200, Distance: 0 }, 1000: { Altitude: 500, Distance: 10 }, 2000: { Distance: 20 }, 3000: { Altitude: 502, Distance: 30 }, 4000: { Distance: 40 }, 5000: { Altitude: 600, Distance: 50 }, 6000: { Altitude: 700, Distance: 60 } }); }); it('should get streams based on another stream', () => { activity.addStream(new stream_1.Stream(data_altitude_1.DataAltitude.type, [200, 500, null, 502, null, 500, 700])); activity.addStream(new stream_1.Stream(data_heart_rate_1.DataHeartRate.type, [60, 70, 80, null, null, null, 120])); activity.addStream(new stream_1.Stream(data_distance_1.DataDistance.type, [0, 10, 20, 30, 40, 50, 60])); expect(activity.getStreamDataTypesBasedOnDataType(data_distance_1.DataDistance.type, [data_altitude_1.DataAltitude.type])).toEqual([ { Altitude: 200, Distance: 0 }, { Altitude: 500, Distance: 10 }, { Altitude: null, Distance: 20 }, { Altitude: 502, Distance: 30 }, { Altitude: null, Distance: 40 }, { Altitude: 500, Distance: 50 }, { Altitude: 700, Distance: 60 } ]); expect(activity.getStreamDataTypesBasedOnDataType(data_distance_1.DataDistance.type, [data_heart_rate_1.DataHeartRate.type, data_altitude_1.DataAltitude.type])).toEqual([ { Altitude: 200, Distance: 0, 'Heart Rate': 60 }, { Altitude: 500, Distance: 10, 'Heart Rate': 70 }, { Altitude: null, Distance: 20, 'Heart Rate': 80 }, { Altitude: 502, Distance: 30, 'Heart Rate': null }, { Altitude: null, Distance: 40, 'Heart Rate': null }, { Altitude: 500, Distance: 50, 'Heart Rate': null }, { Altitude: 700, Distance: 60, 'Heart Rate': 120 } ]); expect(activity.getStreamDataTypesBasedOnDataType(data_altitude_1.DataAltitude.type, [data_distance_1.DataDistance.type, data_heart_rate_1.DataHeartRate.type])).toEqual([ { Altitude: 200, Distance: 0, 'Heart Rate': 60 }, { Altitude: 500, Distance: 10, 'Heart Rate': 70 }, { Altitude: 502, Distance: 30, 'Heart Rate': null }, { Altitude: 500, Distance: 50, 'Heart Rate': null }, { Altitude: 700, Distance: 60, 'Heart Rate': 120 } ]); }); describe('Trainer flagging', () => { it('should flag activities to be performed on a trainer', () => { // Given const types = [ activity_types_1.ActivityTypes.VirtualRun, activity_types_1.ActivityTypes.VirtualCycling, activity_types_1.ActivityTypes.Treadmill, activity_types_1.ActivityTypes.IndoorCycling, activity_types_1.ActivityTypes.IndoorRunning, activity_types_1.ActivityTypes.IndoorRowing, activity_types_1.ActivityTypes.Crosstrainer, activity_types_1.ActivityTypes.EllipticalTrainer, activity_types_1.ActivityTypes.FitnessEquipment, activity_types_1.ActivityTypes.StairStepper ]; // When, Then types.forEach(type => { const fakeActivity = new activity_1.Activity(new Date(), new Date(), type, new creator_1.Creator('John doo')); expect(fakeActivity.isTrainer()).toBeTruthy(); }); }); it('should NOT flag activities to be performed on a trainer', () => { // Given const types = [ activity_types_1.ActivityTypes.Aerobics, activity_types_1.ActivityTypes.AlpineSkiing, activity_types_1.ActivityTypes.AmericanFootball, activity_types_1.ActivityTypes.Aquathlon, activity_types_1.ActivityTypes.BackcountrySkiing, activity_types_1.ActivityTypes.Badminton, activity_types_1.ActivityTypes.Baseball, activity_types_1.ActivityTypes.Basketball, activity_types_1.ActivityTypes.Boxing, activity_types_1.ActivityTypes.Canoeing, activity_types_1.ActivityTypes.CardioTraining, activity_types_1.ActivityTypes.Climbing, activity_types_1.ActivityTypes.Combat, activity_types_1.ActivityTypes.Cricket, activity_types_1.ActivityTypes.Crossfit, activity_types_1.ActivityTypes.CrosscountrySkiing, activity_types_1.ActivityTypes.Cycling, activity_types_1.ActivityTypes.Dancing, activity_types_1.ActivityTypes.Diving, activity_types_1.ActivityTypes.DownhillSkiing, activity_types_1.ActivityTypes.Driving, activity_types_1.ActivityTypes.Duathlon, activity_types_1.ActivityTypes.EBikeRide, activity_types_1.ActivityTypes.Fishing, activity_types_1.ActivityTypes.FlexibilityTraining, activity_types_1.ActivityTypes.FloorClimbing, activity_types_1.ActivityTypes.Floorball, activity_types_1.ActivityTypes.Flying, activity_types_1.ActivityTypes.Football, activity_types_1.ActivityTypes.FreeDiving, activity_types_1.ActivityTypes.Frisbee, activity_types_1.ActivityTypes.Generic, activity_types_1.ActivityTypes.Golf, activity_types_1.ActivityTypes.Gymnastics, activity_types_1.ActivityTypes.Handcycle, activity_types_1.ActivityTypes.Handball, activity_types_1.ActivityTypes.HangGliding, activity_types_1.ActivityTypes.Hiking, activity_types_1.ActivityTypes.HorsebackRiding, activity_types_1.ActivityTypes.IceHockey, activity_types_1.ActivityTypes.IceSkating, activity_types_1.ActivityTypes.IndoorTraining, activity_types_1.ActivityTypes.InlineSkating, activity_types_1.ActivityTypes.Kayaking, activity_types_1.ActivityTypes.Kettlebell, activity_types_1.ActivityTypes.Kitesurfing, activity_types_1.ActivityTypes.Motorcycling, activity_types_1.ActivityTypes.Motorsports, activity_types_1.ActivityTypes.MountainBiking, activity_types_1.ActivityTypes.Mountaineering, activity_types_1.ActivityTypes.NordicWalking, activity_types_1.ActivityTypes.OpenWaterSwimming, activity_types_1.ActivityTypes.Orienteering, activity_types_1.ActivityTypes.Paddling, activity_types_1.ActivityTypes.Paragliding, activity_types_1.ActivityTypes.Rafting, activity_types_1.ActivityTypes.RockClimbing, activity_types_1.ActivityTypes.RollerSki, activity_types_1.ActivityTypes.Rowing, activity_types_1.ActivityTypes.Rugby, activity_types_1.ActivityTypes.Running, activity_types_1.ActivityTypes.Sailing, activity_types_1.ActivityTypes.ScubaDiving, activity_types_1.ActivityTypes.Skating, activity_types_1.ActivityTypes.SkiTouring, activity_types_1.ActivityTypes.SkyDiving, activity_types_1.ActivityTypes.Snorkeling, activity_types_1.ActivityTypes.Snowboarding, activity_types_1.ActivityTypes.Snowmobiling, activity_types_1.ActivityTypes.Snowshoeing, activity_types_1.ActivityTypes.Soccer, activity_types_1.ActivityTypes.Softball, activity_types_1.ActivityTypes.Squash, activity_types_1.ActivityTypes.StandUpPaddling, activity_types_1.ActivityTypes.StrengthTraining, activity_types_1.ActivityTypes.Stretching, activity_types_1.ActivityTypes.Surfing, activity_types_1.ActivityTypes.Swimming, activity_types_1.ActivityTypes.Swimrun, activity_types_1.ActivityTypes.TableTennis, activity_types_1.ActivityTypes.Tactical, activity_types_1.ActivityTypes.TelemarkSkiing, activity_types_1.ActivityTypes.Tennis, activity_types_1.ActivityTypes.TrackAndField, activity_types_1.ActivityTypes.TrailRunning, activity_types_1.ActivityTypes.Training, activity_types_1.ActivityTypes.Trekking, activity_types_1.ActivityTypes.Triathlon, activity_types_1.ActivityTypes.UnknownSport, activity_types_1.ActivityTypes.Velomobile, activity_types_1.ActivityTypes.Volleyball, activity_types_1.ActivityTypes.Wakeboarding, activity_types_1.ActivityTypes.Walking, activity_types_1.ActivityTypes.WaterSkiing, activity_types_1.ActivityTypes.WeightTraining, activity_types_1.ActivityTypes.Wheelchair, activity_types_1.ActivityTypes.Windsurfing, activity_types_1.ActivityTypes.Workout, activity_types_1.ActivityTypes.Yoga, activity_types_1.ActivityTypes.YogaPilates ]; // When, Then types.forEach(type => { const fakeActivity = new activity_1.Activity(new Date(), new Date(), type, new creator_1.Creator('John doo')); expect(fakeActivity.isTrainer()).toBeFalsy(); }); }); }); it('should get the time stream', () => { activity.addStreams([ // 0 1 2 3 4 5 6 7 8 9 and 10 are not set new stream_1.Stream(data_altitude_1.DataAltitude.type, [200, null, 502, Infinity, -Infinity, NaN, 0]), new stream_1.Stream(data_distance_1.DataDistance.type, [0, null, 600, 700, 800, null, NaN, 900, Infinity]) ]); expect(activity.generateTimeStream().getData()).toEqual([0, null, 2, 3, 4, null, 6, 7, 8, null, null]); expect(activity.generateTimeStream().getData(true)).toEqual([0, 2, 3, 4, 6, 7, 8]); expect(activity.generateTimeStream().getData(false)).toEqual([0, null, 2, 3, 4, null, 6, 7, 8, null, null]); }); it('should ignore duplicate stream types when adding multiple streams', () => { activity.addStream(new stream_1.Stream(data_distance_1.DataDistance.type, [0, 10, 20])); activity.addStreams([new stream_1.Stream(data_distance_1.DataDistance.type, [0, 20, 40]), new stream_1.Stream(data_altitude_1.DataAltitude.type, [100, 110, 120])]); const allTypes = activity.getAllStreams().map(stream => stream.type); expect(allTypes.filter(type => type === data_distance_1.DataDistance.type).length).toBe(1); expect(allTypes.filter(type => type === data_altitude_1.DataAltitude.type).length).toBe(1); }); it('should map IBI timestamps into valid second slots when generating a time stream', () => { activity.addStream(new ibi_stream_1.IBIStream([823, 823, 823])); expect(activity.generateTimeStream([data_ibi_1.DataIBI.type]).getData()).toEqual([ null, 1, 2, null, null, null, null, null, null, null, null ]); expect(activity.generateTimeStream([data_ibi_1.DataIBI.type]).getData(true)).toEqual([1, 2]); }); it('should collapse multiple IBI beats that round into the same second slot', () => { activity.addStream(new ibi_stream_1.IBIStream([1100, 100, 100])); expect(activity.generateTimeStream([data_ibi_1.DataIBI.type]).getData()).toEqual([ null, 1, null, null, null, null, null, null, null, null, null ]); expect(activity.generateTimeStream([data_ibi_1.DataIBI.type]).getData(true)).toEqual([1]); }); it('should round IBI timestamps on the 499 500 501ms boundary as expected', () => { activity.addStream(new ibi_stream_1.IBIStream([499, 1, 1])); expect(activity.generateTimeStream([data_ibi_1.DataIBI.type]).getData()).toEqual([ 0, 1, null, null, null, null, null, null, null, null, null ]); expect(activity.generateTimeStream([data_ibi_1.DataIBI.type]).getData(true)).toEqual([0, 1]); }); it('should keep the default time stream empty when IBI is the only stream', () => { activity.addStream(new ibi_stream_1.IBIStream([823, 823, 823])); expect(activity.generateTimeStream().getData(true)).toEqual([]); }); it('should ignore IBI when generating the default time stream', () => { activity.addStream(new stream_1.Stream(data_distance_1.DataDistance.type, [0, null, null, 30, null, null, 60])); activity.addStream(new ibi_stream_1.IBIStream([823, 823, 823])); expect(activity.generateTimeStream().getData()).toEqual([0, null, null, 3, null, null, 6, null, null, null, null]); expect(activity.generateTimeStream().getData(true)).toEqual([0, 3, 6]); }); it('should size explicit IBI time streams from projected beat timestamps when they exceed end date', () => { const shortActivity = new activity_1.Activity(new Date(0), new Date(new Date(0).getTime() + 1000), activity_types_1.ActivityTypes.Running, new creator_1.Creator('Test')); shortActivity.addStream(new ibi_stream_1.IBIStream([823, 823, 823])); expect(shortActivity.generateTimeStream([data_ibi_1.DataIBI.type]).getData()).toEqual([null, 1, 2]); }); it('should size generated time streams to the selected stream length when it exceeds end date', () => { const shortActivity = new activity_1.Activity(new Date(0), new Date(new Date(0).getTime() + 5000), activity_types_1.ActivityTypes.Running, new creator_1.Creator('Test')); shortActivity.addStream(new stream_1.Stream(data_distance_1.DataDistance.type, [0, 10, 20, 30, 40, 50, null, 70])); expect(shortActivity.generateTimeStream([data_distance_1.DataDistance.type]).getData(true)).toEqual([0, 1, 2, 3, 4, 5, 7]); }); it('should generate duration streams using the same explicit IBI projection rules', () => { activity.addStream(new stream_1.Stream(data_distance_1.DataDistance.type, [0, null, null, 30])); activity.addStream(new ibi_stream_1.IBIStream([823, 823, 823])); expect(activity.generateDurationStream().getData(true)).toEqual([0, 3]); expect(activity.generateDurationStream([data_ibi_1.DataIBI.type]).getData(true)).toEqual([1, 2]); }); it('should round date indexes onto the same second grid used for IBI projection', () => { expect(activity.getDateIndex(new Date(499))).toBe(0); expect(activity.getDateIndex(new Date(500))).toBe(1); expect(activity.getDateIndex(new Date(501))).toBe(1); }); it('should set the correct sample sizes', () => { const stream = activity.createStream(data_altitude_1.DataAltitude.type); expect(stream.getData().length).toBe(11); }); it('should get and set the correct sample keys and ', () => { const stream = activity.createStream(data_altitude_1.DataAltitude.type); activity.addStream(stream); activity.addDataToStream(data_altitude_1.DataAltitude.type, new Date(0), 0); activity.addDataToStream(data_altitude_1.DataAltitude.type, new Date(500), 5); activity.addDataToStream(data_altitude_1.DataAltitude.type, new Date(1040), 10); activity.addDataToStream(data_altitude_1.DataAltitude.type, new Date(2010), 20); activity.addDataToStream(data_altitude_1.DataAltitude.type, new Date(2060), 30); activity.addDataToStream(data_altitude_1.DataAltitude.type, new Date(2080), 35); activity.addDataToStream(data_altitude_1.DataAltitude.type, new Date(3000), 40); activity.addDataToStream(data_altitude_1.DataAltitude.type, new Date(4000), 50); activity.addDataToStream(data_altitude_1.DataAltitude.type, new Date(5000), 60); activity.addDataToStream(data_altitude_1.DataAltitude.type, new Date(6300), 70); activity.addDataToStream(data_altitude_1.DataAltitude.type, new Date(7000), 80); activity.addDataToStream(data_altitude_1.DataAltitude.type, new Date(8000), 90); activity.addDataToStream(data_altitude_1.DataAltitude.type, new Date(9000), 100); activity.addDataToStream(data_altitude_1.DataAltitude.type, new Date(10000), 110); expect(stream.getData()).toEqual([0, 10, 35, 40, 50, 60, 70, 80, 90, 100, 110]); }); it('should get events correctly', () => { activity.addEvent(new data_stop_event_1.DataStopEvent(1)); activity.addEvent(new data_stop_event_1.DataStopEvent(1)); activity.addEvent(new data_start_event_1.DataStartEvent(1)); activity.addEvent(new data_start_event_1.DataStartEvent(1)); activity.addEvent(new data_stop_all_event_1.DataStopAllEvent(1)); expect(activity.getAllEvents().length).toBe(5); expect(activity.getStartEvents().length).toBe(2); expect(activity.getStopEvents().length).toBe(2); expect(activity.getStopAllEvents().length).toBe(1); }); it('should set all events', () => { activity.setAllEvents([ new data_stop_event_1.DataStopEvent(1), new data_stop_event_1.DataStopEvent(1), new data_start_event_1.DataStartEvent(1), new data_start_event_1.DataStartEvent(1), new data_stop_all_event_1.DataStopAllEvent(1) ]); expect(activity.getAllEvents().length).toBe(5); expect(activity.getStartEvents().length).toBe(2); expect(activity.getStopEvents().length).toBe(2); expect(activity.getStopAllEvents().length).toBe(1); }); });