@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
109 lines (108 loc) • 5.21 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const power_curve_sampling_1 = require("./power-curve-sampling");
const data_duration_1 = require("../../data/data.duration");
const data_power_1 = require("../../data/data.power");
describe('power curve sampling', () => {
it('uses exact points and keeps the strongest duplicate', () => {
expect((0, power_curve_sampling_1.samplePowerCurveAtDuration)([
{ duration: 60, power: 290 },
{ duration: 60, power: 300 }
], 60)).toBe(300);
});
it('interpolates only inside a bounded reciprocal-duration bracket', () => {
const close = (0, power_curve_sampling_1.samplePowerCurveAtDuration)([
{ duration: 55, power: 310 },
{ duration: 65, power: 290 }
], 60);
expect(close).toBeCloseTo(299.167, 3);
expect((0, power_curve_sampling_1.samplePowerCurveAtDuration)([
{ duration: 30, power: 340 },
{ duration: 90, power: 270 }
], 60)).toBeNull();
});
it('accepts hydrated data values, watts per kg, and the exact bracket boundary', () => {
const points = [
{
duration: new data_duration_1.DataDuration(60),
power: new data_power_1.DataPower(300),
wattsPerKg: 4
},
{
duration: new data_duration_1.DataDuration(75),
power: new data_power_1.DataPower(280),
wattsPerKg: 3.5
}
];
expect((0, power_curve_sampling_1.samplePowerCurveAtDuration)(points, 70)).not.toBeNull();
expect((0, power_curve_sampling_1.samplePowerCurveAtDuration)(points, 60, { key: 'wattsPerKg' })).toBe(4);
});
it('normalizes unsorted input and ignores invalid points and unsafe ratio overrides', () => {
const points = [
{ duration: 65, power: 290 },
{ duration: -1, power: 999 },
{ duration: 55, power: 310 },
{ duration: 60, power: Number.NaN }
];
expect((0, power_curve_sampling_1.samplePowerCurveAtDuration)(points, 60)).toBeCloseTo(299.167, 3);
expect((0, power_curve_sampling_1.samplePowerCurveAtDuration)([
{ duration: 30, power: 340 },
{ duration: 90, power: 270 }
], 60, { maximumBracketDurationRatio: 999 })).toBeNull();
expect((0, power_curve_sampling_1.samplePowerCurveAtDuration)(points, 60, { maximumBracketDurationRatio: Number.NaN })).toBeCloseTo(299.167, 3);
});
it('ignores malformed or throwing hydrated values without coercing arbitrary objects', () => {
const recursive = { getValue: () => recursive };
expect(() => (0, power_curve_sampling_1.samplePowerCurveAtDuration)([
{ duration: { getValue: 60 }, power: 300 },
{
duration: {
getValue: () => {
throw new Error('bad value');
}
},
power: 300
},
{ duration: recursive, power: 300 },
{ duration: true, power: 300 },
{ duration: [], power: 300 },
{ duration: 60, power: { valueOf: () => 300 } },
{ duration: 60, power: 290 }
], 60)).not.toThrow();
expect((0, power_curve_sampling_1.samplePowerCurveAtDuration)([
{ duration: { getValue: 60 }, power: 300 },
{ duration: 60, power: 290 }
], 60)).toBe(290);
});
it('compares recent evidence as a percentage of the reference curve', () => {
expect((0, power_curve_sampling_1.comparePowerCurveWindows)([{ duration: 300, power: 280 }], [{ duration: 300, power: 300 }], [300, 1200])).toEqual([
{
durationSeconds: 300,
recentValue: 280,
referenceValue: 300,
retentionPercent: 93.33333333333333,
deltaPercent: -6.666666666666671
},
{
durationSeconds: 1200,
recentValue: null,
referenceValue: null,
retentionPercent: null,
deltaPercent: null
}
]);
});
it('normalizes each comparison window once for all requested durations', () => {
const recentDuration = jest.fn(() => 60);
const recentPower = jest.fn(() => 300);
const referenceDuration = jest.fn(() => 60);
const referencePower = jest.fn(() => 320);
const comparisons = (0, power_curve_sampling_1.comparePowerCurveWindows)([{ duration: { getValue: recentDuration }, power: { getValue: recentPower } }], [{ duration: { getValue: referenceDuration }, power: { getValue: referencePower } }], Array.from({ length: 1000 }, () => 60));
expect(comparisons).toHaveLength(1000);
expect(comparisons[999]).toMatchObject({ recentValue: 300, referenceValue: 320 });
expect(recentDuration).toHaveBeenCalledTimes(1);
expect(recentPower).toHaveBeenCalledTimes(1);
expect(referenceDuration).toHaveBeenCalledTimes(1);
expect(referencePower).toHaveBeenCalledTimes(1);
});
});