@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
691 lines • 57.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const event_1 = require("../event");
const data_speed_max_1 = require("../../data/data.speed-max");
const activity_1 = require("../../activities/activity");
const activity_parsing_options_1 = require("../../activities/activity-parsing-options");
const data_heart_rate_1 = require("../../data/data.heart-rate");
const data_altitude_1 = require("../../data/data.altitude");
const data_power_1 = require("../../data/data.power");
const data_distance_1 = require("../../data/data.distance");
const data_duration_1 = require("../../data/data.duration");
const creator_1 = require("../../creators/creator");
const activity_types_1 = require("../../activities/activity.types");
const stream_1 = require("../../streams/stream");
const activity_utilities_1 = require("./activity.utilities");
const data_speed_1 = require("../../data/data.speed");
const lap_1 = require("../../laps/lap");
const data_speed_avg_1 = require("../../data/data.speed-avg");
const lap_types_1 = require("../../laps/lap.types");
const data_time_1 = require("../../data/data.time");
const file_type_enum_1 = require("../adapters/file-type.enum");
const importer_json_1 = require("../adapters/importers/json/importer.json");
const data_pace_1 = require("../../data/data.pace");
const data_pace_max_1 = require("../../data/data.pace-max");
const data_pace_min_1 = require("../../data/data.pace-min");
const data_speed_2 = require("../../data/data.speed");
const data_swim_pace_1 = require("../../data/data.swim-pace");
const data_swim_pace_max_1 = require("../../data/data.swim-pace-max");
const data_swim_pace_min_1 = require("../../data/data.swim-pace-min");
const data_grade_adjusted_speed_1 = require("../../data/data.grade-adjusted-speed");
const data_grade_adjusted_pace_min_1 = require("../../data/data.grade-adjusted-pace-min");
const data_step_length_1 = require("../../data/data.step-length");
const data_gnss_distance_1 = require("../../data/data.gnss-distance");
const data_gnss_distance_miles_1 = require("../../data/data.gnss-distance-miles");
const data_ascent_1 = require("../../data/data.ascent");
const data_descent_1 = require("../../data/data.descent");
const data_absolute_pressure_1 = require("../../data/data.absolute-pressure");
const data_absolute_pressure_avg_1 = require("../../data/data.absolute-pressure-avg");
const data_absolute_pressure_max_1 = require("../../data/data.absolute-pressure-max");
const data_absolute_pressure_min_1 = require("../../data/data.absolute-pressure-min");
const data_evpe_1 = require("../../data/data.evpe");
const data_evpe_min_1 = require("../../data/data.evpe-min");
const data_evpe_max_1 = require("../../data/data.evpe-max");
const data_evpe_avg_1 = require("../../data/data.evpe-avg");
const data_ehpe_1 = require("../../data/data.ehpe");
const data_ehpe_min_1 = require("../../data/data.ehpe-min");
const data_ehpe_max_1 = require("../../data/data.ehpe-max");
const data_ehpe_avg_1 = require("../../data/data.ehpe-avg");
const data_satellite_5_best_snr_1 = require("../../data/data.satellite-5-best-snr");
const data_satellite_5_best_snr_min_1 = require("../../data/data.satellite-5-best-snr-min");
const data_satellite_5_best_snr_max_1 = require("../../data/data.satellite-5-best-snr-max");
const data_satellite_5_best_snr_avg_1 = require("../../data/data.satellite-5-best-snr-avg");
const data_number_of_satellites_1 = require("../../data/data.number-of-satellites");
const data_number_of_satellites_min_1 = require("../../data/data.number-of-satellites-min");
const data_number_of_satellites_max_1 = require("../../data/data.number-of-satellites-max");
const data_number_of_satellites_avg_1 = require("../../data/data.number-of-satellites-avg");
const data_grade_1 = require("../../data/data.grade");
const data_grade_min_1 = require("../../data/data.grade-min");
const data_grade_max_1 = require("../../data/data.grade-max");
const data_grade_avg_1 = require("../../data/data.grade-avg");
const data_leg_stiffness_1 = require("../../data/data.leg-stiffness");
const data_leg_stiffness_min_1 = require("../../data/data.leg-stiffness-min");
const data_leg_stiffness_max_1 = require("../../data/data.leg-stiffness-max");
const data_leg_stiffness_avg_1 = require("../../data/data.leg-stiffness-avg");
const data_vertical_ratio_1 = require("../../data/data.vertical-ratio");
const data_vertical_ratio_min_1 = require("../../data/data.vertical-ratio-min");
const data_vertical_ratio_max_1 = require("../../data/data.vertical-ratio-max");
const data_vertical_ratio_avg_1 = require("../../data/data.vertical-ratio-avg");
const data_stamina_1 = require("../../data/data.stamina");
const data_power_avg_1 = require("../../data/data.power-avg");
const data_power_balance_left_1 = require("../../data/data.power-balance-left");
const data_power_balance_right_1 = require("../../data/data.power-balance-right");
const data_power_watts_per_kg_1 = require("../../data/data.power-watts-per-kg");
const data_weight_1 = require("../../data/data.weight");
const ibi_stream_1 = require("../../streams/ibi-stream");
const data_ibi_1 = require("../../data/data.ibi");
const data_running_dynamics_balance_1 = require("../../data/data.running-dynamics-balance");
describe('Activity Utilities', () => {
let event;
beforeEach(() => {
event = new event_1.Event('New name', new Date(0), new Date(200), file_type_enum_1.FileType.FIT);
const 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.setDuration(new data_duration_1.DataDuration(10));
activity.setDistance(new data_distance_1.DataDistance(10));
event.addActivity(activity);
});
it('should get the correct minimum for a DataType', () => {
event.getFirstActivity().addStream(new stream_1.Stream(data_heart_rate_1.DataHeartRate.type, [0, 50, 100]));
event.getFirstActivity().addStream(new stream_1.Stream(data_altitude_1.DataAltitude.type, [200, 300, 400]));
expect(activity_utilities_1.ActivityUtilities.getDataTypeMin(event.getFirstActivity(), data_heart_rate_1.DataHeartRate.type)).toBe(0);
expect(activity_utilities_1.ActivityUtilities.getDataTypeMin(event.getFirstActivity(), data_altitude_1.DataAltitude.type)).toBe(200);
});
it('should get the correct maximum for a DataType', () => {
event.getFirstActivity().addStream(new stream_1.Stream(data_heart_rate_1.DataHeartRate.type, [0, 50, 100]));
event.getFirstActivity().addStream(new stream_1.Stream(data_altitude_1.DataAltitude.type, [200, 300, 400]));
expect(activity_utilities_1.ActivityUtilities.getDataTypeMax(event.getFirstActivity(), data_heart_rate_1.DataHeartRate.type)).toBe(100);
expect(activity_utilities_1.ActivityUtilities.getDataTypeMax(event.getFirstActivity(), data_altitude_1.DataAltitude.type)).toBe(400);
});
it('should get the correct difference for a DataType', () => {
event.getFirstActivity().addStream(new stream_1.Stream(data_heart_rate_1.DataHeartRate.type, [0, 50, 100]));
event.getFirstActivity().addStream(new stream_1.Stream(data_altitude_1.DataAltitude.type, [200, 300, 400]));
expect(activity_utilities_1.ActivityUtilities.getDataTypeMinToMaxDifference(event.getFirstActivity(), data_heart_rate_1.DataHeartRate.type)).toBe(100);
expect(activity_utilities_1.ActivityUtilities.getDataTypeMinToMaxDifference(event.getFirstActivity(), data_altitude_1.DataAltitude.type)).toBe(200);
});
it('should get the correct average for a DataType', () => {
event.getFirstActivity().addStream(new stream_1.Stream(data_heart_rate_1.DataHeartRate.type, [0, 50, 100]));
event.getFirstActivity().addStream(new stream_1.Stream(data_altitude_1.DataAltitude.type, [200, 300, 400]));
expect(activity_utilities_1.ActivityUtilities.getDataTypeAvg(event.getFirstActivity(), data_heart_rate_1.DataHeartRate.type)).toBe(50);
expect(activity_utilities_1.ActivityUtilities.getDataTypeAvg(event.getFirstActivity(), data_altitude_1.DataAltitude.type)).toBe(300);
});
it('should get the correct gain for a DataType', () => {
event.getFirstActivity().addStream(new stream_1.Stream(data_altitude_1.DataAltitude.type, [200, 300, 400]));
expect(activity_utilities_1.ActivityUtilities.getActivityDataTypeGain(event.getFirstActivity(), data_altitude_1.DataAltitude.type)).toBe(200);
// Add more altitude data but this time descending so it would not affect the gain
event.getFirstActivity().getStreamData(data_altitude_1.DataAltitude.type).push(400);
event.getFirstActivity().getStreamData(data_altitude_1.DataAltitude.type).push(300);
event.getFirstActivity().getStreamData(data_altitude_1.DataAltitude.type).push(200);
expect(activity_utilities_1.ActivityUtilities.getActivityDataTypeGain(event.getFirstActivity(), data_altitude_1.DataAltitude.type)).toBe(200);
// Add more for gain
event.getFirstActivity().getStreamData(data_altitude_1.DataAltitude.type).push(400); // Gain 400 (from prev)
event.getFirstActivity().getStreamData(data_altitude_1.DataAltitude.type).push(300);
// Gain 400
event.getFirstActivity().getStreamData(data_altitude_1.DataAltitude.type).push(400); // Gain 500
expect(activity_utilities_1.ActivityUtilities.getActivityDataTypeGain(event.getFirstActivity(), data_altitude_1.DataAltitude.type)).toBe(500);
});
it('should get the correct gain for a DataType with a changed min difference', () => {
event.getFirstActivity().addStream(new stream_1.Stream(data_altitude_1.DataAltitude.type, [200, 300, 400]));
// With a diff of 100,200 the gain should be included
expect(activity_utilities_1.ActivityUtilities.getActivityDataTypeGain(event.getFirstActivity(), data_altitude_1.DataAltitude.type, void 0, void 0, 100)).toBe(200);
expect(activity_utilities_1.ActivityUtilities.getActivityDataTypeGain(event.getFirstActivity(), data_altitude_1.DataAltitude.type, void 0, void 0, 200)).toBe(200);
// with a diff of 201 it shouldn't
expect(activity_utilities_1.ActivityUtilities.getActivityDataTypeGain(event.getFirstActivity(), data_altitude_1.DataAltitude.type, void 0, void 0, 201)).toBe(0);
// Add more
event.getFirstActivity().getStreamData(data_altitude_1.DataAltitude.type).push(100);
event.getFirstActivity().getStreamData(data_altitude_1.DataAltitude.type).push(101);
event.getFirstActivity().getStreamData(data_altitude_1.DataAltitude.type).push(102);
// Up to now we have 200m, 300m, 400m, 100m, 101m, 102m
expect(activity_utilities_1.ActivityUtilities.getActivityDataTypeGain(event.getFirstActivity(), data_altitude_1.DataAltitude.type, void 0, void 0, 100)).toBe(200);
expect(activity_utilities_1.ActivityUtilities.getActivityDataTypeGain(event.getFirstActivity(), data_altitude_1.DataAltitude.type, void 0, void 0, 200)).toBe(200);
expect(activity_utilities_1.ActivityUtilities.getActivityDataTypeGain(event.getFirstActivity(), data_altitude_1.DataAltitude.type, void 0, void 0, 300)).toBe(0);
expect(activity_utilities_1.ActivityUtilities.getActivityDataTypeGain(event.getFirstActivity(), data_altitude_1.DataAltitude.type, void 0, void 0, 1)).toBe(202);
expect(activity_utilities_1.ActivityUtilities.getActivityDataTypeGain(event.getFirstActivity(), data_altitude_1.DataAltitude.type, void 0, void 0, 2)).toBe(202);
expect(activity_utilities_1.ActivityUtilities.getActivityDataTypeGain(event.getFirstActivity(), data_altitude_1.DataAltitude.type, void 0, void 0, 3)).toBe(200);
});
it('should get the correct gain for a DataType with a set of points of non data', () => {
event.getFirstActivity().addStream(new stream_1.Stream(data_altitude_1.DataAltitude.type, [100, 300, 200, 400]));
expect(activity_utilities_1.ActivityUtilities.getActivityDataTypeGain(event.getFirstActivity(), data_altitude_1.DataAltitude.type)).toBe(400);
});
it('should get the correct loss for a DataType', () => {
event.getFirstActivity().addStream(new stream_1.Stream(data_altitude_1.DataAltitude.type, [400, 300, 200]));
expect(activity_utilities_1.ActivityUtilities.getActivityDataTypeLoss(event.getFirstActivity(), data_altitude_1.DataAltitude.type)).toBe(200);
// Add more altitude data but this time ascenting so it would not affect the Loss
event.getFirstActivity().getStreamData(data_altitude_1.DataAltitude.type).push(200); // Loss 0
event.getFirstActivity().getStreamData(data_altitude_1.DataAltitude.type).push(300); // Loss 0
event.getFirstActivity().getStreamData(data_altitude_1.DataAltitude.type).push(400); // Loss 0
expect(activity_utilities_1.ActivityUtilities.getActivityDataTypeLoss(event.getFirstActivity(), data_altitude_1.DataAltitude.type)).toBe(200);
event.getFirstActivity().getStreamData(data_altitude_1.DataAltitude.type).push(200); // loss 200
event.getFirstActivity().getStreamData(data_altitude_1.DataAltitude.type).push(300); // loss 0
event.getFirstActivity().getStreamData(data_altitude_1.DataAltitude.type).push(200); // Gain 100 a total (see above of 500)
expect(activity_utilities_1.ActivityUtilities.getActivityDataTypeLoss(event.getFirstActivity(), data_altitude_1.DataAltitude.type)).toBe(500);
});
it('should get the correct loss for a DataType with a changed min difference', () => {
event.getFirstActivity().addStream(new stream_1.Stream(data_altitude_1.DataAltitude.type, [400, 300, 200]));
// With a diff of 100,200 the gain should be included
expect(activity_utilities_1.ActivityUtilities.getActivityDataTypeLoss(event.getFirstActivity(), data_altitude_1.DataAltitude.type, void 0, void 0, 100)).toBe(200);
expect(activity_utilities_1.ActivityUtilities.getActivityDataTypeLoss(event.getFirstActivity(), data_altitude_1.DataAltitude.type, void 0, void 0, 200)).toBe(200);
// with a diff of 201 it shouldn't
expect(activity_utilities_1.ActivityUtilities.getActivityDataTypeLoss(event.getFirstActivity(), data_altitude_1.DataAltitude.type, void 0, void 0, 201)).toBe(0);
// Add more
event.getFirstActivity().getStreamData(data_altitude_1.DataAltitude.type).push(500);
event.getFirstActivity().getStreamData(data_altitude_1.DataAltitude.type).push(499);
event.getFirstActivity().getStreamData(data_altitude_1.DataAltitude.type).push(498);
// 200m, 300m, 400m, 100m, 101m, 102m
// Up to now we have 400m, 300m, 200m, 500m, 499m, 498m
expect(activity_utilities_1.ActivityUtilities.getActivityDataTypeLoss(event.getFirstActivity(), data_altitude_1.DataAltitude.type, void 0, void 0, 100)).toBe(200);
expect(activity_utilities_1.ActivityUtilities.getActivityDataTypeLoss(event.getFirstActivity(), data_altitude_1.DataAltitude.type, void 0, void 0, 200)).toBe(200);
expect(activity_utilities_1.ActivityUtilities.getActivityDataTypeLoss(event.getFirstActivity(), data_altitude_1.DataAltitude.type, void 0, void 0, 300)).toBe(0);
expect(activity_utilities_1.ActivityUtilities.getActivityDataTypeLoss(event.getFirstActivity(), data_altitude_1.DataAltitude.type, void 0, void 0, 1)).toBe(202);
expect(activity_utilities_1.ActivityUtilities.getActivityDataTypeLoss(event.getFirstActivity(), data_altitude_1.DataAltitude.type, void 0, void 0, 2)).toBe(202);
expect(activity_utilities_1.ActivityUtilities.getActivityDataTypeLoss(event.getFirstActivity(), data_altitude_1.DataAltitude.type, void 0, void 0, 3)).toBe(200);
});
it('should get the correct loss for a DataType with a set of points of non data', () => {
event
.getFirstActivity()
.getAllStreams()
.push(new stream_1.Stream(data_altitude_1.DataAltitude.type, [400, 200, 300, 100]) // loos 0, 200, 0, 400
);
expect(activity_utilities_1.ActivityUtilities.getActivityDataTypeLoss(event.getFirstActivity(), data_altitude_1.DataAltitude.type)).toBe(400);
});
it('should get the correct data length', () => {
const activity = event.getFirstActivity(); // 10s
// 10 seconds = 11 slugs; from second 1 eg
// [1,2,3,4,5,6,7,8,9,10] ->
// [0, 1, 2,3,4,5,6,7,8,9,10]
expect(activity_utilities_1.ActivityUtilities.getDataLength(activity.startDate, activity.endDate)).toBe(11);
// Change start / end date to <1s
activity.startDate = new Date(0);
activity.endDate = new Date(50); // 50ms
// More than 0 = 1 slug
expect(activity_utilities_1.ActivityUtilities.getDataLength(activity.startDate, activity.endDate)).toBe(2);
// Change start / end date to >9999ms and <100000s
activity.startDate = new Date(0);
activity.endDate = new Date(9999); // 9.9 seconds
// more than 9 is 10 slugs
expect(activity_utilities_1.ActivityUtilities.getDataLength(activity.startDate, activity.endDate)).toBe(11);
});
it('should provide serialization/deserialization through toJSON', () => {
var _a, _b;
// Given
const activity = event.getFirstActivity();
activity.startDate = new Date();
activity.endDate = new Date(activity.startDate.getTime() + 3000);
event.getFirstActivity().addStream(new stream_1.Stream(data_distance_1.DataDistance.type, [0, 9, null, 30]));
event.getFirstActivity().addStream(new stream_1.Stream(data_speed_1.DataSpeed.type, [0, 10, null, 15]));
event.getFirstActivity().addStream(new stream_1.Stream(data_heart_rate_1.DataHeartRate.type, [0, 50, null, 100]));
event.getFirstActivity().addStream(new stream_1.Stream(data_altitude_1.DataAltitude.type, [200, 300, null, 400]));
const lap1 = new lap_1.Lap(activity.startDate, activity.endDate, 1, lap_types_1.LapTypes.Autolap);
lap1.addStat(new data_speed_avg_1.DataSpeedAvg(10));
activity.addLap(lap1);
const lap2 = new lap_1.Lap(activity.startDate, activity.endDate, 2, lap_types_1.LapTypes.Autolap);
lap2.addStat(new data_speed_avg_1.DataSpeedAvg(15));
activity.addLap(lap2);
// When serialize
const activitySerialized = activity.toJSON();
// Then
expect(activitySerialized.name).toBeNull();
expect(activitySerialized.startDate).toEqual(activity.startDate.getTime());
expect(activitySerialized.endDate).toEqual(activity.endDate.getTime());
expect(activitySerialized.powerMeter).toBeFalsy();
expect(activitySerialized.trainer).toBeFalsy();
expect(activitySerialized.laps.length).toEqual(activity.getLaps().length);
expect(activitySerialized.laps[0].startIndex).toEqual(0);
expect(activitySerialized.laps[0].endIndex).toEqual(3);
expect(activitySerialized.laps[0].stats[data_speed_avg_1.DataSpeedAvg.type]).toEqual(activity.getLaps()[0].getStat(data_speed_avg_1.DataSpeedAvg.type).getValue());
expect(activitySerialized.streams.length).toEqual(activity.getAllStreams().length + 1); // +1 because we add time stream
expect((_a = activitySerialized.streams.find(s => s.type == data_time_1.DataTime.type)) === null || _a === void 0 ? void 0 : _a.data.length).toEqual((_b = activitySerialized.streams.find(s => s.type == data_distance_1.DataDistance.type)) === null || _b === void 0 ? void 0 : _b.data.length);
// When deserialize
const activityDeserialized = importer_json_1.EventImporterJSON.getActivityFromJSON(activitySerialized);
// Then
expect(activityDeserialized.startDate).toEqual(activity.startDate);
expect(activityDeserialized.endDate).toEqual(activity.endDate);
expect(activityDeserialized.hasPowerMeter()).toEqual(activity.hasPowerMeter());
expect(activityDeserialized.getLaps().length).toEqual(activity.getLaps().length);
expect(activityDeserialized.getLaps()[0].getStat(data_speed_avg_1.DataSpeedAvg.type).getValue()).toEqual(activity.getLaps()[0].getStat(data_speed_avg_1.DataSpeedAvg.type).getValue());
expect(activityDeserialized.getStream(data_distance_1.DataDistance.type).getData().length).toEqual(activity.getStream(data_distance_1.DataDistance.type).getData().length);
expect(activityDeserialized.getStream(data_distance_1.DataDistance.type)).toEqual(activity.getStream(data_distance_1.DataDistance.type));
expect(activityDeserialized.getStream(data_speed_1.DataSpeed.type)).toEqual(activity.getStream(data_speed_1.DataSpeed.type));
expect(activityDeserialized.getStream(data_heart_rate_1.DataHeartRate.type)).toEqual(activity.getStream(data_heart_rate_1.DataHeartRate.type));
expect(activityDeserialized.getStream(data_altitude_1.DataAltitude.type)).toEqual(activity.getStream(data_altitude_1.DataAltitude.type));
expect(activityDeserialized.hasStreamData(data_time_1.DataTime.type)).toBeFalsy();
});
it('should serialize time from the primary regular stream even when IBI is also present', () => {
const activity = event.getFirstActivity();
activity.startDate = new Date();
activity.endDate = new Date(activity.startDate.getTime() + 3000);
activity.addStream(new ibi_stream_1.IBIStream([823, 823, 823]));
activity.addStream(new stream_1.Stream(data_distance_1.DataDistance.type, [0, 9, null, 30]));
const activitySerialized = activity.toJSON();
const timeStream = activitySerialized.streams.find(s => s.type === data_time_1.DataTime.type);
expect(timeStream === null || timeStream === void 0 ? void 0 : timeStream.data).toEqual([0, 1, null, 3]);
});
it('should serialize projected time data when IBI is the only stream', () => {
const activity = new activity_1.Activity(new Date(0), new Date(new Date(0).getTime() + 1000), activity_types_1.ActivityTypes.Running, new creator_1.Creator('Test'));
activity.addStream(new ibi_stream_1.IBIStream([823, 823, 823]));
const activitySerialized = activity.toJSON();
const timeStream = activitySerialized.streams.find(s => s.type === data_time_1.DataTime.type);
const activityDeserialized = importer_json_1.EventImporterJSON.getActivityFromJSON(activitySerialized);
expect(timeStream === null || timeStream === void 0 ? void 0 : timeStream.data).toEqual([null, 1, 2]);
expect(activityDeserialized.getStream(data_ibi_1.DataIBI.type)).toEqual(activity.getStream(data_ibi_1.DataIBI.type));
expect(activityDeserialized.hasStreamData(data_time_1.DataTime.type)).toBeFalsy();
});
describe('Fill streams', () => {
const createFakeActivityWithStreams = (lengthInSeconds, streams) => {
const startDate = new Date();
const endDate = new Date(startDate.getTime() + lengthInSeconds * 1000);
const activity = new activity_1.Activity(startDate, endDate, activity_types_1.ActivityTypes.Running, new creator_1.Creator('creator'));
streams.forEach(stream => {
activity.addStream(new stream_1.Stream(stream.type).setData(stream.data));
});
return activity;
};
it('should add missing data to streams (1)', done => {
// Given
const timeData = [0, 1, 2, 3, 4, 5, 6]; // 6 seconds
const seconds = timeData.length - 1;
const distanceData = [0, 10, 20, 25, 40, 45, 55];
const altitudeData = [null, 13, 10, null, 8, 7, null];
const heartRateData = [123, 135, null, null, null, null, null];
const expectedAltitudes = [13, 13, 10, 10, 8, 7, 7];
const expectedHeartRates = [123, 135, 135, 135, 135, 135, 135];
const activity = createFakeActivityWithStreams(seconds, [
{ type: data_distance_1.DataDistance.type, data: distanceData },
{ type: data_altitude_1.DataAltitude.type, data: altitudeData },
{ type: data_heart_rate_1.DataHeartRate.type, data: heartRateData }
]);
// When
activity_utilities_1.ActivityUtilities.addMissingDataToStreams(activity);
// Then
expect(activity.getStreamData(data_distance_1.DataDistance.type)).toEqual(distanceData);
expect(activity.getStreamData(data_altitude_1.DataAltitude.type)).toEqual(expectedAltitudes);
expect(activity.getStreamData(data_heart_rate_1.DataHeartRate.type)).toEqual(expectedHeartRates);
done();
});
it('should ignore IBI occupancy when backfilling distance and altitude streams', () => {
const activity = createFakeActivityWithStreams(6, [
{ type: data_distance_1.DataDistance.type, data: [0, null, null, null, null, null, 60] },
{ type: data_altitude_1.DataAltitude.type, data: [100, null, null, null, null, null, 160] }
]);
activity.addStream(new ibi_stream_1.IBIStream([1000, 1000, 1000, 1000, 1000]));
activity_utilities_1.ActivityUtilities.addMissingDataToStreams(activity);
expect(activity.getStreamData(data_distance_1.DataDistance.type)).toEqual([0, null, null, null, null, null, 60]);
expect(activity.getStreamData(data_altitude_1.DataAltitude.type)).toEqual([100, null, null, null, null, null, 160]);
});
});
describe('createUnitStreamsFromStreams', () => {
it('should include derived types and unit variants by default', () => {
const streams = [new stream_1.Stream(data_speed_1.DataSpeed.type, [10, 20])];
const result = activity_utilities_1.ActivityUtilities.createUnitStreamsFromStreams(streams, activity_types_1.ActivityTypes.Running);
const paceStream = result.find(s => s.type === data_pace_1.DataPaceMinutesPerMile.type);
const kmhStream = result.find(s => s.type === data_speed_2.DataSpeedKilometersPerHour.type);
expect(paceStream).toBeDefined();
expect(kmhStream).toBeDefined();
});
it('should include pace-derived streams for hiking activities', () => {
const streams = [new stream_1.Stream(data_speed_1.DataSpeed.type, [10, 20])];
const result = activity_utilities_1.ActivityUtilities.createUnitStreamsFromStreams(streams, activity_types_1.ActivityTypes.Hiking);
const paceStream = result.find(s => s.type === data_pace_1.DataPace.type);
const paceUnitStream = result.find(s => s.type === data_pace_1.DataPaceMinutesPerMile.type);
const kmhStream = result.find(s => s.type === data_speed_2.DataSpeedKilometersPerHour.type);
expect(paceStream).toBeDefined();
expect(paceUnitStream).toBeDefined();
expect(kmhStream).toBeDefined();
});
it('should exclude derived types when includeDerivedTypes is false', () => {
const streams = [new stream_1.Stream(data_speed_1.DataSpeed.type, [10, 20])];
const result = activity_utilities_1.ActivityUtilities.createUnitStreamsFromStreams(streams, activity_types_1.ActivityTypes.Running, undefined, {
includeDerivedTypes: false,
includeUnitVariants: true
});
const paceStream = result.find(s => s.type === data_pace_1.DataPaceMinutesPerMile.type);
const kmhStream = result.find(s => s.type === data_speed_2.DataSpeedKilometersPerHour.type);
expect(paceStream).toBeUndefined();
expect(kmhStream).toBeDefined();
});
it('should exclude unit variants when includeUnitVariants is false', () => {
const streams = [new stream_1.Stream(data_speed_1.DataSpeed.type, [10, 20])];
// We pass DataPace.type in unitStreamTypes so we can check if the derived base stream is present
const result = activity_utilities_1.ActivityUtilities.createUnitStreamsFromStreams(streams, activity_types_1.ActivityTypes.Running, [data_pace_1.DataPace.type], {
includeDerivedTypes: true,
includeUnitVariants: false
});
const paceStream = result.find(s => s.type === data_pace_1.DataPace.type);
const paceUnitStream = result.find(s => s.type === data_pace_1.DataPaceMinutesPerMile.type);
expect(paceStream).toBeDefined(); // Derived type should be there
expect(paceUnitStream).toBeUndefined(); // Unit variant should be gone
});
it('should exclude both derived types and unit variants', () => {
const streams = [new stream_1.Stream(data_speed_1.DataSpeed.type, [10, 20])];
const result = activity_utilities_1.ActivityUtilities.createUnitStreamsFromStreams(streams, activity_types_1.ActivityTypes.Running, [data_pace_1.DataPace.type], {
includeDerivedTypes: false,
includeUnitVariants: false
});
const paceStream = result.find(s => s.type === data_pace_1.DataPace.type);
const paceUnitStream = result.find(s => s.type === data_pace_1.DataPaceMinutesPerMile.type);
expect(paceStream).toBeUndefined();
expect(paceUnitStream).toBeUndefined();
});
it('should not generate derived pace when pace already exists', () => {
const streams = [new stream_1.Stream(data_speed_1.DataSpeed.type, [10, 20]), new stream_1.Stream(data_pace_1.DataPace.type, [100, 200])];
const result = activity_utilities_1.ActivityUtilities.createUnitStreamsFromStreams(streams, activity_types_1.ActivityTypes.Running, [data_pace_1.DataPace.type], {
includeDerivedTypes: true,
includeUnitVariants: false
});
const generatedPaceStreams = result.filter(stream => stream.type === data_pace_1.DataPace.type);
expect(generatedPaceStreams.length).toBe(0);
});
it('should generate distance miles only once when distance and step length both exist', () => {
const streams = [new stream_1.Stream(data_distance_1.DataDistance.type, [1000, 2000]), new stream_1.Stream(data_step_length_1.DataStepLength.type, [0.8, 0.9])];
const result = activity_utilities_1.ActivityUtilities.createUnitStreamsFromStreams(streams, activity_types_1.ActivityTypes.Running, [data_distance_1.DataDistanceMiles.type], {
includeDerivedTypes: true,
includeUnitVariants: true
});
const generatedDistanceMiles = result.filter(stream => stream.type === data_distance_1.DataDistanceMiles.type);
expect(generatedDistanceMiles.length).toBe(1);
});
it('should generate GNSS-specific miles stream for GNSS distance', () => {
const streams = [new stream_1.Stream(data_gnss_distance_1.DataGNSSDistance.type, [0, 1000, 2000])];
const result = activity_utilities_1.ActivityUtilities.createUnitStreamsFromStreams(streams, activity_types_1.ActivityTypes.Running);
const generatedDistanceMiles = result.filter(stream => stream.type === data_distance_1.DataDistanceMiles.type);
const generatedGNSSDistanceMiles = result.filter(stream => stream.type === data_gnss_distance_miles_1.DataGNSSDistanceMiles.type);
expect(generatedDistanceMiles.length).toBe(0);
expect(generatedGNSSDistanceMiles.length).toBe(1);
});
});
describe('generateMissingStreams', () => {
it('should generate unit streams when generateUnitStreams = true', () => {
const activity = new activity_1.Activity(new Date(), new Date(), activity_types_1.ActivityTypes.Running, new creator_1.Creator('test'), new activity_parsing_options_1.ActivityParsingOptions({ generateUnitStreams: true }));
// Add a speed stream
activity.addStream(new stream_1.Stream(data_speed_1.DataSpeed.type, [10, 20]));
activity_utilities_1.ActivityUtilities.generateMissingStreams(activity);
// Should have generated "sister" types (e.g. Pace) and unit variants (e.g. Speed km/h)
expect(activity.hasStreamData(data_speed_2.DataSpeedKilometersPerHour.type)).toBe(true);
expect(activity.hasStreamData(data_pace_1.DataPaceMinutesPerMile.type)).toBe(true);
});
it('should NOT generate unit streams when generateUnitStreams = false', () => {
const activity = new activity_1.Activity(new Date(), new Date(), activity_types_1.ActivityTypes.Running, new creator_1.Creator('test'));
// Mock parsing options
activity.parseOptions = {
streams: { smooth: {}, fixAbnormal: {} },
maxActivityDurationDays: 14,
generateUnitStreams: false,
deviceInfoMode: 'raw'
};
// Add a speed stream
activity.addStream(new stream_1.Stream(data_speed_1.DataSpeed.type, [10, 20]));
activity_utilities_1.ActivityUtilities.generateMissingStreams(activity);
// Should NOT have generated unit variants
expect(activity.hasStreamData(data_speed_2.DataSpeedKilometersPerHour.type)).toBe(false);
// It might still generate some "derived" streams depending on other flags, but our specific unit loop should be skipped
// The Pace stream comes from createUnitStreamsFromStreams too, so it should also be missing
expect(activity.hasStreamData(data_pace_1.DataPaceMinutesPerMile.type)).toBe(false);
});
});
describe('generateMissingStreamsAndStatsForActivity', () => {
it('should generate PowerWattsPerKg when average power and weight are available', () => {
const activity = new activity_1.Activity(new Date(), new Date(), activity_types_1.ActivityTypes.Cycling, new creator_1.Creator('test'));
activity.addStream(new stream_1.Stream(data_power_1.DataPower.type, [280, 280, 280]));
activity.addStat(new data_weight_1.DataWeight(70));
activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity);
const powerAvg = activity.getStat(data_power_avg_1.DataPowerAvg.type);
const powerWattsPerKg = activity.getStat(data_power_watts_per_kg_1.DataPowerWattsPerKg.type);
expect(powerAvg).toBeDefined();
expect(powerAvg === null || powerAvg === void 0 ? void 0 : powerAvg.getValue()).toBe(280);
expect(powerWattsPerKg).toBeDefined();
expect(powerWattsPerKg === null || powerWattsPerKg === void 0 ? void 0 : powerWattsPerKg.getValue()).toBe(4);
});
it('should not generate PowerWattsPerKg when weight is missing', () => {
const activity = new activity_1.Activity(new Date(), new Date(), activity_types_1.ActivityTypes.Cycling, new creator_1.Creator('test'));
activity.addStream(new stream_1.Stream(data_power_1.DataPower.type, [280, 280, 280]));
activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity);
expect(activity.getStat(data_power_avg_1.DataPowerAvg.type)).toBeDefined();
expect(activity.getStat(data_power_watts_per_kg_1.DataPowerWattsPerKg.type)).toBeUndefined();
});
it('should generate unit STATS even if unit STREAMS are disabled', () => {
const activity = new activity_1.Activity(new Date(), new Date(), activity_types_1.ActivityTypes.Running, new creator_1.Creator('test'));
activity.parseOptions = {
streams: { smooth: {}, fixAbnormal: {} },
maxActivityDurationDays: 14,
generateUnitStreams: false, // DISABLE streams
deviceInfoMode: 'raw'
};
// Add a speed stream [10 m/s, 20 m/s]
// Max speed = 20 m/s
activity.addStream(new stream_1.Stream(data_speed_1.DataSpeed.type, [10, 20]));
activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity);
// 1. Verify Unit Streams are missing (as requested)
expect(activity.hasStreamData(data_speed_2.DataSpeedKilometersPerHour.type)).toBe(false);
// 2. Verify Derived Base Streams are PRESENT (The fix)
expect(activity.hasStreamData(data_pace_1.DataPace.type)).toBe(true);
// 3. Verify Stats are PRESENT (Safety Check)
// 20 m/s = 72 km/h
const allStats = Array.from(activity.getStats().values());
const speedMaxKmh = allStats.find(s => s.getType() === data_speed_max_1.DataSpeedMaxKilometersPerHour.type);
expect(speedMaxKmh).toBeDefined();
expect(speedMaxKmh === null || speedMaxKmh === void 0 ? void 0 : speedMaxKmh.getValue()).toBe(72);
});
it('should generate DataSwimPace when generateUnitStreams = false for swimming', () => {
const activity = new activity_1.Activity(new Date(), new Date(), activity_types_1.ActivityTypes.Swimming, new creator_1.Creator('test'));
activity.parseOptions = {
streams: { smooth: {}, fixAbnormal: {} },
maxActivityDurationDays: 14,
generateUnitStreams: false,
deviceInfoMode: 'raw'
};
activity.addStream(new stream_1.Stream(data_speed_1.DataSpeed.type, [1, 2])); // m/s
activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity);
// Verify Unit Streams missing
expect(activity.hasStreamData(data_speed_2.DataSpeedKilometersPerHour.type)).toBe(false);
// Verify Derived Base Stream (Swim Pace) IS present
expect(activity.hasStreamData(data_swim_pace_1.DataSwimPace.type)).toBe(true);
});
it('should derive finite pace minimums from maximum speed even when minimum speed is zero', () => {
const activity = new activity_1.Activity(new Date(), new Date(), activity_types_1.ActivityTypes.Running, new creator_1.Creator('test'));
activity.addStream(new stream_1.Stream(data_speed_1.DataSpeed.type, [0, 3]));
activity.addStream(new stream_1.Stream(data_grade_adjusted_speed_1.DataGradeAdjustedSpeed.type, [0, 4]));
activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity);
const paceMin = activity.getStat(data_pace_min_1.DataPaceMin.type);
const paceMax = activity.getStat(data_pace_max_1.DataPaceMax.type);
const swimPaceMin = activity.getStat(data_swim_pace_min_1.DataSwimPaceMin.type);
const swimPaceMax = activity.getStat(data_swim_pace_max_1.DataSwimPaceMax.type);
const gapPaceMin = activity.getStat(data_grade_adjusted_pace_min_1.DataGradeAdjustedPaceMin.type);
expect(Number.isFinite(paceMin.getValue())).toBe(true);
expect(paceMin.getValue()).toBeCloseTo(333.3333333333, 8);
expect(paceMax.getValue()).toBe(Infinity);
expect(Number.isFinite(swimPaceMin.getValue())).toBe(true);
expect(swimPaceMin.getValue()).toBeCloseTo(33.3333333333, 8);
expect(swimPaceMax.getValue()).toBe(Infinity);
expect(Number.isFinite(gapPaceMin.getValue())).toBe(true);
expect(gapPaceMin.getValue()).toBeCloseTo(250, 8);
});
it('should generate DataDistanceMiles when generateUnitStreams = true', () => {
const activity = new activity_1.Activity(new Date(), new Date(), activity_types_1.ActivityTypes.Running, new creator_1.Creator('test'), new activity_parsing_options_1.ActivityParsingOptions({ generateUnitStreams: true }));
activity.addStream(new stream_1.Stream(data_distance_1.DataDistance.type, [1000, 2000]));
activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity);
// Should generate miles
expect(activity.hasStreamData(data_distance_1.DataDistanceMiles.type)).toBe(true);
});
it('should NOT generate DataDistanceMiles when generateUnitStreams = false', () => {
const activity = new activity_1.Activity(new Date(), new Date(), activity_types_1.ActivityTypes.Running, new creator_1.Creator('test'));
activity.parseOptions = {
streams: { smooth: {}, fixAbnormal: {} },
maxActivityDurationDays: 14,
generateUnitStreams: false,
deviceInfoMode: 'raw'
};
activity.addStream(new stream_1.Stream(data_distance_1.DataDistance.type, [1000, 2000]));
activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity);
// Should NOT generate miles
expect(activity.hasStreamData(data_distance_1.DataDistanceMiles.type)).toBe(false);
// But base Distance should still be there (it was added manually)
expect(activity.hasStreamData(data_distance_1.DataDistance.type)).toBe(true);
});
it('should generate unit streams for Mountain Biking using DataSpeed', () => {
const speedData = [10, 20, 30]; // m/s
const speedStream = new stream_1.Stream(data_speed_1.DataSpeed.type, speedData);
// Mountain Biking (defaults to Cycling group)
const unitStreams = activity_utilities_1.ActivityUtilities.createUnitStreamsFromStreams([speedStream], activity_types_1.ActivityTypes.MountainBiking, undefined, // Auto-detect all known unit types
{ includeDerivedTypes: true, includeUnitVariants: true });
const kmhStream = unitStreams.find(s => s.type === 'Speed in kilometers per hour');
expect(kmhStream).toBeDefined();
if (kmhStream) {
expect(kmhStream.getData()[0]).toBeCloseTo(36, 1); // 10 m/s = 36 km/h
}
});
it('should NOT generate ascent but SHOULD generate descent for AlpineSkiing', () => {
const activity = new activity_1.Activity(new Date(), new Date(), activity_types_1.ActivityTypes.AlpineSkiing, new creator_1.Creator('test'), new activity_parsing_options_1.ActivityParsingOptions({
streams: { smooth: { altitudeSmooth: false }, fixAbnormal: {} }
}));
activity.addStream(new stream_1.Stream(data_altitude_1.DataAltitude.type, [100, 200, 300, 200, 100])); // 200m gain, 200m loss
activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity);
expect(activity.getStat(data_ascent_1.DataAscent.type)).toBeUndefined();
expect(activity.getStat(data_descent_1.DataDescent.type)).toBeDefined();
expect(activity.getStat(data_descent_1.DataDescent.type).getValue()).toBe(200);
});
it('should generate ascent/descent for Running (NOT excluded)', () => {
const activity = new activity_1.Activity(new Date(), new Date(), activity_types_1.ActivityTypes.Running, new creator_1.Creator('test'), new activity_parsing_options_1.ActivityParsingOptions({
streams: { smooth: { altitudeSmooth: false }, fixAbnormal: {} }
}));
activity.addStream(new stream_1.Stream(data_altitude_1.DataAltitude.type, [100, 150, 200, 150, 100])); // 100m gain, 100m loss
activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity);
expect(activity.getStat(data_ascent_1.DataAscent.type)).toBeDefined();
expect(activity.getStat(data_descent_1.DataDescent.type)).toBeDefined();
expect(activity.getStat(data_ascent_1.DataAscent.type).getValue()).toBe(100);
expect(activity.getStat(data_descent_1.DataDescent.type).getValue()).toBe(100);
});
it('should generate ascent/descent for Kayaking (specifically NOT excluded)', () => {
const activity = new activity_1.Activity(new Date(), new Date(), activity_types_1.ActivityTypes.Kayaking, new creator_1.Creator('test'));
activity.addStream(new stream_1.Stream(data_altitude_1.DataAltitude.type, [100, 150, 200, 150, 100]));
activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity);
expect(activity.getStat(data_ascent_1.DataAscent.type)).toBeDefined();
expect(activity.getStat(data_descent_1.DataDescent.type)).toBeDefined();
});
it('should NOT generate ascent OR descent for Swimming (excluded)', () => {
const activity = new activity_1.Activity(new Date(), new Date(), activity_types_1.ActivityTypes.Swimming, new creator_1.Creator('test'));
activity.addStream(new stream_1.Stream(data_altitude_1.DataAltitude.type, [100, 150, 200, 150, 100]));
activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity);
expect(activity.getStat(data_ascent_1.DataAscent.type)).toBeUndefined();
expect(activity.getStat(data_descent_1.DataDescent.type)).toBeUndefined();
});
it('should NOT generate ascent but SHOULD generate descent for Diving', () => {
const activity = new activity_1.Activity(new Date(), new Date(), activity_types_1.ActivityTypes.Diving, new creator_1.Creator('test'), new activity_parsing_options_1.ActivityParsingOptions({
streams: { smooth: { altitudeSmooth: false }, fixAbnormal: {} }
}));
// Diving 10m down (alt 0 to -10)
activity.addStream(new stream_1.Stream(data_altitude_1.DataAltitude.type, [0, -2, -5, -8, -10]));
activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity);
expect(activity.getStat(data_ascent_1.DataAscent.type)).toBeUndefined();
expect(activity.getStat(data_descent_1.DataDescent.type)).toBeDefined();
expect(activity.getStat(data_descent_1.DataDescent.type).getValue()).toBe(10);
});
it('should generate min/max/avg stats for Absolute Pressure when pressure stream exists', () => {
const activity = new activity_1.Activity(new Date(), new Date(), activity_types_1.ActivityTypes.Running, new creator_1.Creator('test'));
activity.addStream(new stream_1.Stream(data_absolute_pressure_1.DataAbsolutePressure.type, [1000, 1007, 1003]));
activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity);
expect(activity.getStat(data_absolute_pressure_min_1.DataAbsolutePressureMin.type).getValue()).toBe(1000);
expect(activity.getStat(data_absolute_pressure_max_1.DataAbsolutePressureMax.type).getValue()).toBe(1007);
expect(activity.getStat(data_absolute_pressure_avg_1.DataAbsolutePressureAvg.type).getValue()).toBeCloseTo(1003.3333333333, 10);
});
it('should generate min/max/avg stats for Grade when grade stream exists', () => {
const activity = new activity_1.Activity(new Date(), new Date(), activity_types_1.ActivityTypes.Running, new creator_1.Creator('test'));
activity.addStream(new stream_1.Stream(data_grade_1.DataGrade.type, [-6, 0, 4, 10]));
activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity);
expect(activity.getStat(data_grade_min_1.DataGradeMin.type).getValue()).toBe(-6);
expect(activity.getStat(data_grade_max_1.DataGradeMax.type).getValue()).toBe(10);
expect(activity.getStat(data_grade_avg_1.DataGradeAvg.type).getValue()).toBeCloseTo(2, 5);
});
it('should generate min/max/avg stats for EVPE when stream exists', () => {
const activity = new activity_1.Activity(new Date(), new Date(), activity_types_1.ActivityTypes.Running, new creator_1.Creator('test'));
activity.addStream(new stream_1.Stream(data_evpe_1.DataEVPE.type, [4.1, 5.3, 4.7]));
activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity);
expect(activity.getStat(data_evpe_min_1.DataEVPEMin.type).getValue()).toBe(4.1);
expect(activity.getStat(data_evpe_max_1.DataEVPEMax.type).getValue()).toBe(5.3);
expect(activity.getStat(data_evpe_avg_1.DataEVPEAvg.type).getValue()).toBeCloseTo(4.7, 10);
});
it('should generate min/max/avg stats for EHPE when stream exists', () => {
const activity = new activity_1.Activity(new Date(), new Date(), activity_types_1.ActivityTypes.Running, new creator_1.Creator('test'));
activity.addStream(new stream_1.Stream(data_ehpe_1.DataEHPE.type, [3.2, 4.1, 3.7]));
activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity);
expect(activity.getStat(data_ehpe_min_1.DataEHPEMin.type).getValue()).toBe(3.2);
expect(activity.getStat(data_ehpe_max_1.DataEHPEMax.type).getValue()).toBe(4.1);
expect(activity.getStat(data_ehpe_avg_1.DataEHPEAvg.type).getValue()).toBeCloseTo(3.6666666667, 10);
});
it('should generate min/max/avg stats for Satellite 5 Best SNR when stream exists', () => {
const activity = new activity_1.Activity(new Date(), new Date(), activity_types_1.ActivityTypes.Running, new creator_1.Creator('test'));
activity.addStream(new stream_1.Stream(data_satellite_5_best_snr_1.DataSatellite5BestSNR.type, [31.1, 34.5, 32.9]));
activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity);
expect(activity.getStat(data_satellite_5_best_snr_min_1.DataSatellite5BestSNRMin.type).getValue()).toBe(31.1);
expect(activity.getStat(data_satellite_5_best_snr_max_1.DataSatellite5BestSNRMax.type).getValue()).toBe(34.5);
expect(activity.getStat(data_satellite_5_best_snr_avg_1.DataSatellite5BestSNRAvg.type).getValue()).toBeCloseTo(32.8333333333, 10);
});
it('should generate min/max/avg stats for Number of Satellites when stream exists', () => {
const activity = new activity_1.Activity(new Date(), new Date(), activity_types_1.ActivityTypes.Running, new creator_1.Creator('test'));
activity.addStream(new stream_1.Stream(data_number_of_satellites_1.DataNumberOfSatellites.type, [9, 11, 10]));
activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity);
expect(activity.getStat(data_number_of_satellites_min_1.DataNumberOfSatellitesMin.type).getValue()).toBe(9);
expect(activity.getStat(data_number_of_satellites_max_1.DataNumberOfSatellitesMax.type).getValue()).toBe(11);
expect(activity.getStat(data_number_of_satellites_avg_1.DataNumberOfSatellitesAvg.type).getValue()).toBeCloseTo(10, 10);
});
it('should generate min/max/avg stats for Leg Stiffness when stream exists', () => {
const activity = new activity_1.Activity(new Date(), new Date(), activity_types_1.ActivityTypes.Running, new creator_1.Creator('test'));
activity.addStream(new stream_1.Stream(data_leg_stiffness_1.DataLegStiffness.type, [8.2, 9.1, 8.7]));
activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity);
expect(activity.getStat(data_leg_stiffness_min_1.DataLegStiffnessMin.type).getValue()).toBe(8.2);
expect(activity.getStat(data_leg_stiffness_max_1.DataLegStiffnessMax.type).getValue()).toBe(9.1);
expect(activity.getStat(data_leg_stiffness_avg_1.DataLegStiffnessAvg.type).getValue()).toBeCloseTo(8.6666666667, 10);
});
it('should generate power balance stats from a left-only balance stream', () => {
const activity = new activity_1.Activity(new Date(), new Date(), activity_types_1.ActivityTypes.Cycling, new creator_1.Creator('test'));
activity.addStream(new stream_1.Stream(data_power_balance_left_1.DataPowerBalanceLeft.type, [51, 52, 53]));
activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity);
expect(activity.getStat(data_power_balance_left_1.DataPowerBalanceLeft.type).getValue()).toBe(52);
expect(activity.getStat(data_power_balance_right_1.DataPowerBalanceRight.type).getValue()).toBe(48);
});
it('should generate running dynamics balance stats from left or right streams', () => {
const activity = new activity_1.Activity(new Date(), new Date(), activity_types_1.ActivityTypes.Running, new creator_1.Creator('test'));
activity.addStream(new stream_1.Stream(data_running_dynamics_balance_1.DataVerticalOscillationBalanceLeft.type, [48, 49]));
activity.addStream(new stream_1.Stream(data_running_dynamics_balance_1.DataLegSpringStiffnessBalanceRight.type, [52, 53]));
activity.addStream(new stream_1.Stream(data_running_dynamics_balance_1.DataImpactLoadingRateBalanceLeft.type, [50, 50]));
activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity);
expect(activity.getStat(data_running_dynamics_balance_1.DataVerticalOscillationBalanceLeft.type).getValue()).toBe(48.5);
expect(activity.getStat(data_running_dynamics_balance_1.DataVerticalOscillationBalanceRight.type).getValue()).toBe(51.5);
expect(activity.getStat(data_running_dynamics_balance_1.DataLegSpringStiffnessBalanceLeft.type).getValue()).toBe(47.5);
expect(activity.getStat(data_running_dynamics_balance_1.DataLegSpringStiffnessBalanceRight.type).getValue()).toBe(52.5);
expect(activity.getStat(data_running_dynamics_balance_1.DataImpactLoadingRateBalanceLeft.type).getValue()).toBe(50);
expect(activity.getStat(data_running_dynamics_balance_1.DataImpactLoadingRateBalanceRight.type).getValue()).toBe(50);
});
it('should generate min/max/avg stats for Vertical Ratio when stream exists', () => {
const activity = new activity_1.Activity(new Date(), new Date(), activity_types_1.ActivityTypes.Running, new creator_1.Creator('test'));
activity.addStream(new stream_1.Stream(data_vertical_ratio_1.DataVerticalRatio.type, [7.5, 8.1, 7.9]));
activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity);
expect(activity.getStat(data_vertical_ratio_min_1.DataVerticalRatioMin.type).getValue()).toBe(7.5);
expect(activity.getStat(data_vertical_ratio_max_1.DataVerticalRatioMax.type).getValue()).toBe(8.1);
expect(activity.getStat(data_vertical_ratio_avg_1.DataVerticalRatioAvg.type).getValue()).toBeCloseTo(7.8333333333, 10);
});
it('should generate min/max/avg stats for Garmin stamina streams', () => {
const activity = new activity_1.Activity(new Date(), new Date(), activity_types_1.ActivityTypes.Running, new creator_1.Creator('test'));
activity.addStream(new stream_1.Stream(data_stamina_1.DataStamina.type, [95, 66, 34]));
activity.addStream(new stream_1.Stream(data_stamina_1.DataPotentialStamina.type, [95, 80, 66]));
activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity);
expect(activity.getStat(data_stamina_1.DataStaminaMin.type).getValue()).toBe(34);
expect(activity.getStat(data_stamina_1.DataStaminaMax.type).getValue()).toBe(95);
expect(activity.getStat(data_stamina_1.DataStaminaAvg.type).getValue()).toBe(65);
expect(activity.getStat(data_stamina_1.DataPotentialStaminaMin.type).getValue()).toBe(66);
expect(activity.getStat(data_stamina_1.DataPotentialStaminaMax.type).getValue()).toBe(95);
expect(activity.getStat(data_stamina_1.DataPotentialStaminaAvg.type).getValue()).toBeCloseTo(80.3333333333, 10);
});
it('should generate BOTH ascent and descent for Kitesurfing', () => {
const activity = new activity_1.Activity(new Date(), new Date(), activity_types_1.ActivityTypes.Kitesurfing, new creator_1.Creator('test'), new activity_parsing_options_1.ActivityParsingOptions({
streams: { smooth: { altitudeSmooth: false }, fixAbnormal: {} }
}));
// Up 10m then down 10m
activity.addStream(new stream_1.Stream(data_altitude_1.DataAltitude.type, [0, 5, 10, 5, 0]));
activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity);
expect(activity.getStat(data_ascent_1.DataAscent.type)).toBeDefined();
expect(activity.getStat(data_ascent_1.DataAscent.type).getValue()).toBe(10);
expect(activity.getStat(data_descent_1.DataDescent.type)).toBeDefined();
expect(activity.getStat(data_descent_1.DataDescent.type).getValue()).toBe(10);
});
});
});