commit b9cc28896bcd4de991568fcddc67f35859338766
parent cedfddc830eee593b31732900a421953551b70d2
Author: Asher Morgan <59518073+ashermorgan@users.noreply.github.com>
Date: Sat, 31 May 2025 12:44:50 -0700
Implement target name customization option
Diffstat:
5 files changed, 259 insertions(+), 216 deletions(-)
diff --git a/src/utils/calculators.js b/src/utils/calculators.js
@@ -141,7 +141,7 @@ export function calculateRaceStats(input) {
* Predict workout results from a target
* @param {Object} input The input race
* @param {Object} target The workout target
- * @param {Object} options The race prediction options
+ * @param {Object} options The workout options
* @param {Boolean} preciseDurations Whether to return precise, unrounded, durations
* @returns {Object} The result
*/
@@ -169,7 +169,7 @@ export function calculateWorkoutResults(input, target, options, preciseDurations
// Return result
return {
- key: target.customName || workoutTargetToString(target),
+ key: (options.customTargetNames && target.customName) || workoutTargetToString(target),
value: formatDuration(t3, 3, preciseDurations ? 2 : 0, true),
pace: '', // Pace not used in workout calculator
result: 'value',
diff --git a/src/views/BatchCalculator.vue b/src/views/BatchCalculator.vue
@@ -38,9 +38,16 @@
Target Set:
<target-set-selector v-model:selectedTargetSet="selectedTargetSet"
:setType="options.calculator === 'workout' ? 'workout' : 'standard'"
- :customWorkoutNames="true" v-model:targetSets="targetSets"
+ :customWorkoutNames="advancedOptions.customTargetNames" v-model:targetSets="targetSets"
:default-unit-system="defaultUnitSystem"/>
</div>
+ <div v-if="options.calculator === 'workout'">
+ Target Name Customization:
+ <select v-model="advancedOptions.customTargetNames" aria-label="Target name customization">
+ <option :value="false">Disabled</option>
+ <option :value="true">Enabled</option>
+ </select>
+ </div>
<race-options v-if="options.calculator !== 'pace'" v-model="advancedOptions"/>
</details>
@@ -118,6 +125,7 @@ const raceOptions = useStorage('race-calculator-options', {
riegelExponent: 1.06,
});
const workoutOptions = useStorage('workout-calculator-options', {
+ customTargetNames: false,
model: 'AverageModel',
riegelExponent: 1.06,
});
diff --git a/src/views/WorkoutCalculator.vue b/src/views/WorkoutCalculator.vue
@@ -19,9 +19,16 @@
<div>
Target Set:
<target-set-selector v-model:selectedTargetSet="selectedTargetSet" setType="workout"
- :customWorkoutNames="true" v-model:targetSets="targetSets"
+ :customWorkoutNames="options.customTargetNames" v-model:targetSets="targetSets"
:default-unit-system="defaultUnitSystem"/>
</div>
+ <div>
+ Target Name Customization:
+ <select v-model="options.customTargetNames" aria-label="Target name customization">
+ <option :value="false">Disabled</option>
+ <option :value="true">Enabled</option>
+ </select>
+ </div>
<race-options v-model="options"/>
</details>
@@ -62,6 +69,7 @@ const defaultUnitSystem = useStorage('default-unit-system', detectDefaultUnitSys
* The race prediction options
*/
const options = useStorage('workout-calculator-options', {
+ customTargetNames: false,
model: 'AverageModel',
riegelExponent: 1.06,
});
diff --git a/tests/unit/utils/calculators.spec.js b/tests/unit/utils/calculators.spec.js
@@ -1,229 +1,255 @@
-import { test, expect } from 'vitest';
+import { describe, test, expect } from 'vitest';
import * as calculatorUtils from '@/utils/calculators';
-test('should correctly calculate pace times', () => {
- const input = {
- distanceValue: 1,
- distanceUnit: 'kilometers',
- time: 100,
- };
- const target = {
- distanceValue: 20,
- distanceUnit: 'meters',
- type: 'distance',
- };
-
- const result = calculatorUtils.calculatePaceResults(input, target, 'metric');
-
- expect(result).to.deep.equal({
- key: '20 m',
- value: '0:02.00',
- pace: '1:40 / km',
- result: 'value',
- sort: 2,
+describe('calculatePaceResults method', () => {
+ test('should correctly calculate pace times', () => {
+ const input = {
+ distanceValue: 1,
+ distanceUnit: 'kilometers',
+ time: 100,
+ };
+ const target = {
+ distanceValue: 20,
+ distanceUnit: 'meters',
+ type: 'distance',
+ };
+
+ const result = calculatorUtils.calculatePaceResults(input, target, 'metric');
+
+ expect(result).to.deep.equal({
+ key: '20 m',
+ value: '0:02.00',
+ pace: '1:40 / km',
+ result: 'value',
+ sort: 2,
+ });
});
-});
-test('should correctly calculate pace distances according to default units setting', () => {
- const input = {
- distanceValue: 2,
- distanceUnit: 'miles',
- time: 1200,
- };
- const target = {
- time: 600,
- type: 'time',
- };
-
- const result1 = calculatorUtils.calculatePaceResults(input, target, 'metric');
- const result2 = calculatorUtils.calculatePaceResults(input, target, 'imperial');
-
- expect(result1.key).to.equal('1.61 km');
- expect(result1.value).to.equal('10:00');
- expect(result1.pace).to.equal('6:13 / km');
- expect(result1.result).to.equal('key');
- expect(result1.sort).to.be.closeTo(600, 0.01);
-
- expect(result2.key).to.equal('1.00 mi');
- expect(result2.value).to.equal('10:00');
- expect(result2.pace).to.equal('10:00 / mi');
- expect(result2.result).to.equal('key');
- expect(result2.sort).to.be.closeTo(600, 0.01);
+ test('should correctly calculate pace distances according to default units setting', () => {
+ const input = {
+ distanceValue: 2,
+ distanceUnit: 'miles',
+ time: 1200,
+ };
+ const target = {
+ time: 600,
+ type: 'time',
+ };
+
+ const result1 = calculatorUtils.calculatePaceResults(input, target, 'metric');
+ const result2 = calculatorUtils.calculatePaceResults(input, target, 'imperial');
+
+ expect(result1.key).to.equal('1.61 km');
+ expect(result1.value).to.equal('10:00');
+ expect(result1.pace).to.equal('6:13 / km');
+ expect(result1.result).to.equal('key');
+ expect(result1.sort).to.be.closeTo(600, 0.01);
+
+ expect(result2.key).to.equal('1.00 mi');
+ expect(result2.value).to.equal('10:00');
+ expect(result2.pace).to.equal('10:00 / mi');
+ expect(result2.result).to.equal('key');
+ expect(result2.sort).to.be.closeTo(600, 0.01);
+ });
});
-test('should correctly predict race times', () => {
- const input = {
- distanceValue: 5,
- distanceUnit: 'kilometers',
- time: 1200,
- };
- const target = {
- distanceValue: 10,
- distanceUnit: 'kilometers',
- type: 'distance',
- };
- const options = {
- model: 'AverageModel',
- riegelExponent: 1.06,
- }
-
- const result = calculatorUtils.calculateRaceResults(input, target, options, 'imperial');
-
- expect(result.key).to.equal('10 km');
- expect(result.value).to.equal('41:34.80');
- expect(result.pace).to.equal('6:42 / mi');
- expect(result.result).to.equal('value');
- expect(result.sort).to.be.closeTo(2494.80, 0.01);
-});
+describe('calculateRaceResults method', () => {
+ test('should correctly predict race times', () => {
+ const input = {
+ distanceValue: 5,
+ distanceUnit: 'kilometers',
+ time: 1200,
+ };
+ const target = {
+ distanceValue: 10,
+ distanceUnit: 'kilometers',
+ type: 'distance',
+ };
+ const options = {
+ model: 'AverageModel',
+ riegelExponent: 1.06,
+ }
+
+ const result = calculatorUtils.calculateRaceResults(input, target, options, 'imperial');
+
+ expect(result.key).to.equal('10 km');
+ expect(result.value).to.equal('41:34.80');
+ expect(result.pace).to.equal('6:42 / mi');
+ expect(result.result).to.equal('value');
+ expect(result.sort).to.be.closeTo(2494.80, 0.01);
+ });
-test('should correctly calculate race distances according to default units setting', () => {
- const input = {
- distanceValue: 5,
- distanceUnit: 'kilometers',
- time: 1200,
- };
- const target = {
- time: 2495,
- type: 'time',
- };
- const options = {
- model: 'AverageModel',
- riegelExponent: 1.06,
- }
-
- const result1 = calculatorUtils.calculateRaceResults(input, target, options, 'metric');
- const result2 = calculatorUtils.calculateRaceResults(input, target, options, 'imperial');
-
- expect(result1.key).to.equal('10.00 km');
- expect(result1.value).to.equal('41:35');
- expect(result1.pace).to.equal('4:09 / km');
- expect(result1.result).to.equal('key');
- expect(result1.sort).to.equal(2495);
-
- expect(result2.key).to.equal('6.21 mi');
- expect(result2.value).to.equal('41:35');
- expect(result2.pace).to.equal('6:41 / mi');
- expect(result2.result).to.equal('key');
- expect(result2.sort).to.equal(2495);
-});
+ test('should correctly calculate race distances according to default units setting', () => {
+ const input = {
+ distanceValue: 5,
+ distanceUnit: 'kilometers',
+ time: 1200,
+ };
+ const target = {
+ time: 2495,
+ type: 'time',
+ };
+ const options = {
+ model: 'AverageModel',
+ riegelExponent: 1.06,
+ }
+
+ const result1 = calculatorUtils.calculateRaceResults(input, target, options, 'metric');
+ const result2 = calculatorUtils.calculateRaceResults(input, target, options, 'imperial');
+
+ expect(result1.key).to.equal('10.00 km');
+ expect(result1.value).to.equal('41:35');
+ expect(result1.pace).to.equal('4:09 / km');
+ expect(result1.result).to.equal('key');
+ expect(result1.sort).to.equal(2495);
+
+ expect(result2.key).to.equal('6.21 mi');
+ expect(result2.value).to.equal('41:35');
+ expect(result2.pace).to.equal('6:41 / mi');
+ expect(result2.result).to.equal('key');
+ expect(result2.sort).to.equal(2495);
+ });
-test('should correctly predict race times according to race options', () => {
- const input = {
- distanceValue: 2,
- distanceUnit: 'miles',
- time: 630,
- };
- const target = {
- distanceValue: 5,
- distanceUnit: 'kilometers',
- type: 'distance',
- };
- const options = {
- model: 'RiegelModel',
- riegelExponent: 1.12,
- }
-
- const result = calculatorUtils.calculateRaceResults(input, target, options, 'imperial');
-
- expect(result.key).to.equal('5 km');
- expect(result.value).to.equal('17:11.77');
- expect(result.pace).to.equal('5:32 / mi');
- expect(result.result).to.equal('value');
- expect(result.sort).to.be.closeTo(1031.77, 0.01);
+ test('should correctly predict race times according to race options', () => {
+ const input = {
+ distanceValue: 2,
+ distanceUnit: 'miles',
+ time: 630,
+ };
+ const target = {
+ distanceValue: 5,
+ distanceUnit: 'kilometers',
+ type: 'distance',
+ };
+ const options = {
+ model: 'RiegelModel',
+ riegelExponent: 1.12,
+ }
+
+ const result = calculatorUtils.calculateRaceResults(input, target, options, 'imperial');
+
+ expect(result.key).to.equal('5 km');
+ expect(result.value).to.equal('17:11.77');
+ expect(result.pace).to.equal('5:32 / mi');
+ expect(result.result).to.equal('value');
+ expect(result.sort).to.be.closeTo(1031.77, 0.01);
+ });
});
-test('should correctly calculate race statistics', () => {
- const input = {
- distanceValue: 5,
- distanceUnit: 'kilometers',
- time: 1200,
- };
+describe('calculateRaceStats method', () => {
+ test('should correctly calculate race statistics', () => {
+ const input = {
+ distanceValue: 5,
+ distanceUnit: 'kilometers',
+ time: 1200,
+ };
- const results = calculatorUtils.calculateRaceStats(input);
+ const results = calculatorUtils.calculateRaceStats(input);
- expect(results.purdyPoints).to.be.closeTo(454.5, 0.1);
- expect(results.vo2).to.be.closeTo(47.4, 0.1);
- expect(results.vo2MaxPercentage).to.be.closeTo(95.3, 0.1);
- expect(results.vo2Max).to.be.closeTo(49.8, 0.1);
+ expect(results.purdyPoints).to.be.closeTo(454.5, 0.1);
+ expect(results.vo2).to.be.closeTo(47.4, 0.1);
+ expect(results.vo2MaxPercentage).to.be.closeTo(95.3, 0.1);
+ expect(results.vo2Max).to.be.closeTo(49.8, 0.1);
+ });
});
-test('should correctly calculate distance-based workouts according to race options', () => {
- const input = {
- distanceValue: 2,
- distanceUnit: 'miles',
- time: 630,
- };
- const target = {
- distanceValue: 5,
- distanceUnit: 'kilometers', // 5k split is ~17:11.77
- splitValue: 1000,
- splitUnit: 'meters',
- type: 'distance',
- };
- const options = {
- model: 'RiegelModel',
- riegelExponent: 1.12,
- }
-
- const result = calculatorUtils.calculateWorkoutResults(input, target, options);
-
- expect(result.key).to.equal('1000 m @ 5 km');
- expect(result.value).to.equal('3:26.35');
- expect(result.pace).to.equal('');
- expect(result.result).to.equal('value');
- expect(result.sort).to.be.closeTo(206.35, 0.01);
-});
+describe('calculateWorkoutResults method', () => {
+ test('should correctly calculate distance-based workouts according to race options', () => {
+ const input = {
+ distanceValue: 2,
+ distanceUnit: 'miles',
+ time: 630,
+ };
+ const target = {
+ distanceValue: 5,
+ distanceUnit: 'kilometers', // 5k split is ~17:11.77
+ splitValue: 1000,
+ splitUnit: 'meters',
+ type: 'distance',
+ };
+ const options = {
+ customTargetNames: false,
+ model: 'RiegelModel',
+ riegelExponent: 1.12,
+ }
+
+ const result = calculatorUtils.calculateWorkoutResults(input, target, options);
+
+ expect(result.key).to.equal('1000 m @ 5 km');
+ expect(result.value).to.equal('3:26.35');
+ expect(result.pace).to.equal('');
+ expect(result.result).to.equal('value');
+ expect(result.sort).to.be.closeTo(206.35, 0.01);
+ });
-test('should correctly calculate distance-based workouts with custom names', () => {
- const input = {
- distanceValue: 2,
- distanceUnit: 'miles',
- time: 630,
- };
- const target = {
- distanceValue: 5,
- distanceUnit: 'kilometers', // 5k split is ~17:11.77
- splitValue: 1000,
- splitUnit: 'meters',
- type: 'distance',
- customName: 'my custom name',
- };
- const options = {
- model: 'RiegelModel',
- riegelExponent: 1.12,
- }
-
- const result = calculatorUtils.calculateWorkoutResults(input, target, options);
-
- expect(result.key).to.equal('my custom name');
- expect(result.value).to.equal('3:26.35');
- expect(result.pace).to.equal('');
- expect(result.result).to.equal('value');
- expect(result.sort).to.be.closeTo(206.35, 0.01);
-});
+ test('should correctly calculate distance-based workouts according to custom names', () => {
+ const input = {
+ distanceValue: 2,
+ distanceUnit: 'miles',
+ time: 630,
+ };
+ const target_1 = {
+ distanceValue: 5,
+ distanceUnit: 'kilometers', // 5k split is ~17:11.77
+ splitValue: 1000,
+ splitUnit: 'meters',
+ type: 'distance',
+ // no custom name
+ };
+ const target_2 = {
+ distanceValue: 5,
+ distanceUnit: 'kilometers', // 5k split is ~17:11.77
+ splitValue: 1000,
+ splitUnit: 'meters',
+ type: 'distance',
+ customName: 'my custom name',
+ };
+ const options_a = {
+ customTargetNames: false,
+ model: 'RiegelModel',
+ riegelExponent: 1.12,
+ };
+ const options_b = {
+ customTargetNames: true,
+ model: 'RiegelModel',
+ riegelExponent: 1.12,
+ };
+
+ const result1a = calculatorUtils.calculateWorkoutResults(input, target_1, options_a);
+ const result1b = calculatorUtils.calculateWorkoutResults(input, target_1, options_b);
+ const result2a = calculatorUtils.calculateWorkoutResults(input, target_2, options_a);
+ const result2b = calculatorUtils.calculateWorkoutResults(input, target_2, options_b);
+
+ expect(result1a.key).to.equal('1000 m @ 5 km');
+ expect(result1b.key).to.equal('1000 m @ 5 km');
+ expect(result2a.key).to.equal('1000 m @ 5 km');
+ expect(result2b.key).to.equal('my custom name');
+ });
-test('should correctly calculate time-based workouts', () => {
- const input = {
- distanceValue: 5,
- distanceUnit: 'kilometers',
- time: 1200,
- };
- const target = {
- time: 2495, // ~10k split is 41:35
- splitValue: 1,
- splitUnit: 'miles',
- type: 'time',
- };
- const options = {
- model: 'AverageModel',
- riegelExponent: 1.06,
- }
-
- const result = calculatorUtils.calculateWorkoutResults(input, target, options);
-
- expect(result.key).to.equal('1 mi @ 41:35');
- expect(result.value).to.equal('6:41.50');
- expect(result.pace).to.equal('');
- expect(result.result).to.equal('value');
- expect(result.sort).to.be.closeTo(401.50, 0.01);
+ test('should correctly calculate time-based workouts', () => {
+ const input = {
+ distanceValue: 5,
+ distanceUnit: 'kilometers',
+ time: 1200,
+ };
+ const target = {
+ time: 2495, // ~10k split is 41:35
+ splitValue: 1,
+ splitUnit: 'miles',
+ type: 'time',
+ };
+ const options = {
+ customTargetNames: false,
+ model: 'AverageModel',
+ riegelExponent: 1.06,
+ }
+
+ const result = calculatorUtils.calculateWorkoutResults(input, target, options);
+
+ expect(result.key).to.equal('1 mi @ 41:35');
+ expect(result.value).to.equal('6:41.50');
+ expect(result.pace).to.equal('');
+ expect(result.result).to.equal('value');
+ expect(result.sort).to.be.closeTo(401.50, 0.01);
+ });
});
diff --git a/tests/unit/views/WorkoutCalculator.spec.js b/tests/unit/views/WorkoutCalculator.spec.js
@@ -73,6 +73,7 @@ test('should correctly calculate results according to advanced model options', a
// Calculate result
const calculateResult = wrapper.findComponent({ name: 'single-output-table' }).vm.calculateResult;
let result = calculateResult({
+ customName: 'foo',
splitValue: 1, splitUnit: 'kilometers',
type: 'distance', distanceValue: 10, distanceUnit: 'kilometers',
});