commit cd745cb3c2383acfad03df9d1d576bb3278540b9
parent 6b28796f0472785d82c9effcc626d6232d4d54c1
Author: Asher Morgan <59518073+ashermorgan@users.noreply.github.com>
Date: Sun, 17 Aug 2025 18:47:37 -0700
Sync race prediction options globally
Move defaultUnitSystem and *-calculator-options.racePredictionOptions to
a new global-options localStorage entry. Migration scripts and
corresponding end-to-end test not yet updated.
Diffstat:
16 files changed, 1096 insertions(+), 837 deletions(-)
diff --git a/src/components/AdvancedOptionsInput.vue b/src/components/AdvancedOptionsInput.vue
@@ -1,7 +1,7 @@
<template>
<div>
Default units:
- <select v-model="defaultUnitSystem" aria-label="Default units">
+ <select v-model="globalOptions.defaultUnitSystem" aria-label="Default units">
<option value="imperial">Miles</option>
<option value="metric">Kilometers</option>
</select>
@@ -9,7 +9,8 @@
<div>
Target set:
- <target-set-selector :setType="props.type" :default-unit-system="defaultUnitSystem"
+ <target-set-selector :setType="props.type"
+ :default-unit-system="globalOptions.defaultUnitSystem"
v-model:selected-target-set="options.selectedTargetSet" v-model:target-sets="targetSets"
:customWorkoutNames="props.type === Calculators.Workout ?
(options as WorkoutOptions).customTargetNames : false"/>
@@ -33,7 +34,7 @@
<div v-if="props.type === Calculators.Race || props.type === Calculators.Workout">
Prediction model:
- <select v-model="(options as RaceOptions).predictionOptions.model"
+ <select v-model="globalOptions.racePredictionOptions.model"
aria-label="Prediction model">
<option :value="RacePredictionModels.AverageModel">Average</option>
<option :value="RacePredictionModels.PurdyPointsModel">Purdy Points Model</option>
@@ -44,10 +45,10 @@
</div>
<div v-if="props.type === Calculators.Race || props.type === Calculators.Workout"
- v-show="(options as RaceOptions).predictionOptions.model == RacePredictionModels.AverageModel
- || (options as RaceOptions).predictionOptions.model == RacePredictionModels.RiegelModel">
+ v-show="globalOptions.racePredictionOptions.model == RacePredictionModels.AverageModel
+ || globalOptions.racePredictionOptions.model == RacePredictionModels.RiegelModel">
Riegel exponent:
- <decimal-input v-model="(options as RaceOptions).predictionOptions.riegelExponent"
+ <decimal-input v-model="globalOptions.racePredictionOptions.riegelExponent"
aria-label="Riegel exponent" :min="1" :max="1.3" :digits="2" :step="0.01"/>
(default: 1.06)
</div>
@@ -55,11 +56,11 @@
<script setup lang="ts">
import { Calculators } from '@/core/calculators';
-import type { BatchOptions, StandardOptions, RaceOptions,
+import type { BatchOptions, GlobalOptions, StandardOptions, RaceOptions,
WorkoutOptions } from '@/core/calculators';
import { RacePredictionModels } from '@/core/racePrediction';
import type { TargetSets } from '@/core/targets';
-import { UnitSystems, formatDistance } from '@/core/units';
+import { formatDistance } from '@/core/units';
import type { DistanceTime } from '@/core/units';
import DecimalInput from '@/components/DecimalInput.vue';
@@ -69,11 +70,6 @@ import useObjectModel from '@/composables/useObjectModel';
type CalculatorOptions = StandardOptions | RaceOptions | WorkoutOptions;
-/*
- * The default unit system
- */
-const defaultUnitSystem = defineModel<UnitSystems>('defaultUnitSystem');
-
const props = defineProps<{
/*
* The batch calculator input (if applicable, used to generate custom batch label placeholder)
@@ -86,6 +82,11 @@ const props = defineProps<{
batchOptions?: BatchOptions,
/*
+ * The global options
+ */
+ globalOptions: GlobalOptions,
+
+ /*
* The calculator options
*/
options: CalculatorOptions,
@@ -101,10 +102,14 @@ const props = defineProps<{
targetSets: TargetSets,
}>();
-// Generate internal refs tied to options and targetSets props
-const emit = defineEmits(['update:batchOptions', 'update:options', 'update:targetSets']);
+// Generate internal refs tied to batchOptions, globalOptions, options and targetSets props
+const emit = defineEmits([
+ 'update:batchOptions', 'update:globalOptions', 'update:options', 'update:targetSets'
+]);
const batchOptions = useObjectModel<BatchOptions | undefined>(() => props.batchOptions, (x) =>
emit('update:batchOptions', x));
+const globalOptions = useObjectModel<GlobalOptions>(() => props.globalOptions, (x) =>
+ emit('update:globalOptions', x));
const options = useObjectModel<CalculatorOptions>(() => props.options, (x) =>
emit('update:options', x));
const targetSets = useObjectModel<TargetSets>(() => props.targetSets, (x) =>
diff --git a/src/core/calculators.ts b/src/core/calculators.ts
@@ -6,8 +6,8 @@ import * as racePrediction from '@/core/racePrediction';
import type { RacePredictionOptions } from '@/core/racePrediction';
import { TargetTypes, workoutTargetToString } from '@/core/targets';
import type { StandardTarget, WorkoutTarget } from '@/core/targets';
-import { DistanceUnits, UnitSystems, convertDistance, formatDistance, formatDuration, formatPace,
- getDefaultDistanceUnit, getDefaultPaceUnit } from '@/core/units';
+import { DistanceUnits, UnitSystems, convertDistance, detectDefaultUnitSystem, formatDistance,
+ formatDuration, formatPace, getDefaultDistanceUnit, getDefaultPaceUnit } from '@/core/units';
import type { DistanceTime } from '@/core/units';
/*
@@ -35,12 +35,14 @@ export interface RaceStats {
/*
* The type for the options specific to each calculator
*/
+export interface GlobalOptions {
+ defaultUnitSystem: UnitSystems,
+ racePredictionOptions: RacePredictionOptions,
+};
export interface StandardOptions {
selectedTargetSet: string,
-}
-export interface RaceOptions extends StandardOptions {
- predictionOptions: RacePredictionOptions,
};
+export type RaceOptions = StandardOptions;
export interface WorkoutOptions extends RaceOptions {
customTargetNames: boolean,
};
@@ -73,6 +75,10 @@ export interface TargetResult {
/*
* The default input and options for each calculator
*/
+export const defaultGlobalOptions: GlobalOptions = {
+ defaultUnitSystem: detectDefaultUnitSystem(),
+ racePredictionOptions: racePrediction.defaultRacePredictionOptions,
+};
export const defaultInput: DistanceTime = {
distanceValue: 5,
distanceUnit: DistanceUnits.Kilometers,
@@ -88,10 +94,6 @@ export const defaultPaceOptions: StandardOptions = {
selectedTargetSet: '_pace_targets',
};
export const defaultRaceOptions: RaceOptions = {
- predictionOptions: {
- model: racePrediction.RacePredictionModels.AverageModel,
- riegelExponent: 1.06,
- },
selectedTargetSet: '_race_targets',
};
export const defaultSplitOptions: StandardOptions = {
@@ -99,7 +101,6 @@ export const defaultSplitOptions: StandardOptions = {
};
export const defaultWorkoutOptions: WorkoutOptions = {
customTargetNames: false,
- ...defaultRaceOptions,
selectedTargetSet: '_workout_targets',
};
diff --git a/src/core/racePrediction.ts b/src/core/racePrediction.ts
@@ -22,6 +22,14 @@ export interface RacePredictionOptions {
};
/*
+ * The default race prediction options
+ */
+export const defaultRacePredictionOptions = {
+ model: RacePredictionModels.AverageModel,
+ riegelExponent: 1.06,
+};
+
+/*
* The type for internal variables used by the Purdy Points race prediction model
*/
interface PurdyPointsVariables {
diff --git a/src/views/BatchCalculator.vue b/src/views/BatchCalculator.vue
@@ -28,7 +28,7 @@
<h2>Advanced Options</h2>
</summary>
<advanced-options-input :batch-input="input" v-model:batch-options="options"
- v-model:defaultUnitSystem="defaultUnitSystem" v-model:options="calcOptions"
+ v-model:globalOptions="globalOptions" v-model:options="calcOptions"
v-model:targetSets="targetSets" :type="options.calculator"/>
</details>
@@ -44,10 +44,10 @@
import { computed } from 'vue';
import * as calculators from '@/core/calculators';
-import type { BatchOptions, RaceOptions, StandardOptions, TargetResult,
+import type { BatchOptions, GlobalOptions, RaceOptions, StandardOptions, TargetResult,
WorkoutOptions } from '@/core/calculators';
import * as targetUtils from '@/core/targets';
-import { UnitSystems, detectDefaultUnitSystem, formatDistance } from '@/core/units';
+import { formatDistance } from '@/core/units';
import type { Distance, DistanceTime } from '@/core/units';
import AdvancedOptionsInput from '@/components/AdvancedOptionsInput.vue';
@@ -70,9 +70,9 @@ const options = useStorage<BatchOptions>('batch-calculator-options',
calculators.defaultBatchOptions);
/*
- * The default unit system
+ * The global options
*/
-const defaultUnitSystem = useStorage<UnitSystems>('default-unit-system', detectDefaultUnitSystem());
+const globalOptions = useStorage<GlobalOptions>('global-options', calculators.defaultGlobalOptions);
/*
* The target sets for each calculator
@@ -193,16 +193,17 @@ const calcOptions = computed<StandardOptions | RaceOptions | WorkoutOptions>({
const calculateResult = computed<(x: DistanceTime, y: targetUtils.Target) => TargetResult>(() => {
switch(options.value.calculator) {
case (calculators.Calculators.Pace): {
- return (x,y) => calculators.calculatePaceResults(x, y, defaultUnitSystem.value, false);
+ return (x,y) => calculators.calculatePaceResults(x, y, globalOptions.value.defaultUnitSystem,
+ false);
}
case (calculators.Calculators.Race): {
- return (x,y) => calculators.calculateRaceResults(x, y, raceOptions.value.predictionOptions,
- defaultUnitSystem.value, false);
+ return (x,y) => calculators.calculateRaceResults(x, y,
+ globalOptions.value.racePredictionOptions, globalOptions.value.defaultUnitSystem, false);
}
default:
case (calculators.Calculators.Workout): {
return (x,y) => calculators.calculateWorkoutResults(x, y as targetUtils.WorkoutTarget,
- workoutOptions.value.predictionOptions, workoutOptions.value.customTargetNames, false);
+ globalOptions.value.racePredictionOptions, workoutOptions.value.customTargetNames, false);
}
}
});
diff --git a/src/views/PaceCalculator.vue b/src/views/PaceCalculator.vue
@@ -9,25 +9,24 @@
<summary>
<h2>Advanced Options</h2>
</summary>
- <advanced-options-input v-model:defaultUnitSystem="defaultUnitSystem"
+ <advanced-options-input v-model:globalOptions="globalOptions"
v-model:options="options" v-model:targetSets="targetSets" :type="Calculators.Pace"/>
</details>
<h2>Equivalent Paces</h2>
<single-output-table class="output" :calculate-result="x =>
- calculatePaceResults(input, x, defaultUnitSystem, true)"
+ calculatePaceResults(input, x, globalOptions.defaultUnitSystem, true)"
:targets="targetSets[options.selectedTargetSet] ?
targetSets[options.selectedTargetSet].targets : []"/>
</div>
</template>
<script setup lang="ts">
-import { Calculators, calculatePaceResults, defaultInput,
+import { Calculators, calculatePaceResults, defaultGlobalOptions, defaultInput,
defaultPaceOptions } from '@/core/calculators';
-import type { StandardOptions } from '@/core/calculators';
+import type { GlobalOptions, StandardOptions } from '@/core/calculators';
import { defaultPaceTargetSets } from '@/core/targets';
import type { StandardTargetSets } from '@/core/targets';
-import { UnitSystems, detectDefaultUnitSystem } from '@/core/units';
import type { DistanceTime } from '@/core/units';
import AdvancedOptionsInput from '@/components/AdvancedOptionsInput.vue';
@@ -42,9 +41,9 @@ import useStorage from '@/composables/useStorage';
const input = useStorage<DistanceTime>('pace-calculator-input', defaultInput);
/*
- * The default unit system
+ * The global options
*/
-const defaultUnitSystem = useStorage<UnitSystems>('default-unit-system', detectDefaultUnitSystem());
+const globalOptions = useStorage<GlobalOptions>('global-options', defaultGlobalOptions);
/*
* The current selected target set
diff --git a/src/views/RaceCalculator.vue b/src/views/RaceCalculator.vue
@@ -26,14 +26,14 @@
<summary>
<h2>Advanced Options</h2>
</summary>
- <advanced-options-input v-model:defaultUnitSystem="defaultUnitSystem"
+ <advanced-options-input v-model:globalOptions="globalOptions"
v-model:options="options" v-model:targetSets="targetSets" :type="Calculators.Race"/>
</details>
<h2>Equivalent Race Results</h2>
<single-output-table class="output" show-pace
- :calculate-result="x => calculateRaceResults(input, x, options.predictionOptions,
- defaultUnitSystem, true)"
+ :calculate-result="x => calculateRaceResults(input, x, globalOptions.racePredictionOptions,
+ globalOptions.defaultUnitSystem, true)"
:targets="targetSets[options.selectedTargetSet] ?
targetSets[options.selectedTargetSet].targets : []"/>
</div>
@@ -42,12 +42,12 @@
<script setup lang="ts">
import { computed } from 'vue';
-import { Calculators, calculateRaceResults, calculateRaceStats, defaultInput,
+import { Calculators, calculateRaceResults, calculateRaceStats, defaultGlobalOptions, defaultInput,
defaultRaceOptions } from '@/core/calculators';
-import type { RaceOptions, RaceStats } from '@/core/calculators';
+import type { GlobalOptions, RaceOptions, RaceStats } from '@/core/calculators';
import { defaultRaceTargetSets } from '@/core/targets';
import type { StandardTargetSets } from '@/core/targets';
-import { UnitSystems, detectDefaultUnitSystem, formatNumber } from '@/core/units';
+import { formatNumber } from '@/core/units';
import type { DistanceTime } from '@/core/units';
import AdvancedOptionsInput from '@/components/AdvancedOptionsInput.vue';
@@ -62,9 +62,9 @@ import useStorage from '@/composables/useStorage';
const input = useStorage<DistanceTime>('race-calculator-input', defaultInput);
/*
- * The default unit system
+ * The global options
*/
-const defaultUnitSystem = useStorage<UnitSystems>('default-unit-system', detectDefaultUnitSystem());
+const globalOptions = useStorage<GlobalOptions>('global-options', defaultGlobalOptions);
/*
* The race calculator options
@@ -72,7 +72,7 @@ const defaultUnitSystem = useStorage<UnitSystems>('default-unit-system', detectD
const options = useStorage<RaceOptions>('race-calculator-options', defaultRaceOptions);
/*
- * The target sets
+ * The race calculator target sets
*/
const targetSets = useStorage<StandardTargetSets>('race-calculator-target-sets',
defaultRaceTargetSets);
diff --git a/src/views/SplitCalculator.vue b/src/views/SplitCalculator.vue
@@ -1,12 +1,13 @@
<template>
<div class="calculator">
<div class="input">
- <advanced-options-input v-model:defaultUnitSystem="defaultUnitSystem"
+ <advanced-options-input v-model:globalOptions="globalOptions"
v-model:options="options" v-model:targetSets="targetSets" :type="Calculators.Split"/>
</div>
<div class="output">
- <split-output-table :default-unit-system="defaultUnitSystem" v-model="targetSet"/>
+ <split-output-table :default-unit-system="globalOptions.defaultUnitSystem"
+ v-model="targetSet"/>
</div>
</div>
</template>
@@ -14,11 +15,10 @@
<script setup lang="ts">
import { computed } from 'vue';
-import { Calculators, defaultSplitOptions } from '@/core/calculators';
-import type { StandardOptions } from '@/core/calculators';
+import { Calculators, defaultGlobalOptions, defaultSplitOptions } from '@/core/calculators';
+import type { GlobalOptions, StandardOptions } from '@/core/calculators';
import { defaultSplitTargetSets } from '@/core/targets';
import type { SplitTargetSets } from '@/core/targets';
-import { UnitSystems, detectDefaultUnitSystem } from '@/core/units';
import AdvancedOptionsInput from '@/components/AdvancedOptionsInput.vue';
import SplitOutputTable from '@/components/SplitOutputTable.vue';
@@ -26,9 +26,9 @@ import SplitOutputTable from '@/components/SplitOutputTable.vue';
import useStorage from '@/composables/useStorage';
/*
- * The default unit system
+ * The global options
*/
-const defaultUnitSystem = useStorage<UnitSystems>('default-unit-system', detectDefaultUnitSystem());
+const globalOptions = useStorage<GlobalOptions>('global-options', defaultGlobalOptions);
/*
* The split calculator options
@@ -36,7 +36,7 @@ const defaultUnitSystem = useStorage<UnitSystems>('default-unit-system', detectD
const options = useStorage<StandardOptions>('split-calculator-options', defaultSplitOptions);
/*
- * The default output targets
+ * The split calculator target sets
*/
const targetSets = useStorage<SplitTargetSets>('split-calculator-target-sets',
defaultSplitTargetSets);
diff --git a/src/views/WorkoutCalculator.vue b/src/views/WorkoutCalculator.vue
@@ -9,26 +9,25 @@
<summary>
<h2>Advanced Options</h2>
</summary>
- <advanced-options-input v-model:defaultUnitSystem="defaultUnitSystem"
+ <advanced-options-input v-model:globalOptions="globalOptions"
v-model:options="options" v-model:targetSets="targetSets" :type="Calculators.Workout"/>
</details>
<h2>Workout Splits</h2>
<single-output-table class="output"
:calculate-result="x => calculateWorkoutResults(input, x as WorkoutTarget,
- options.predictionOptions, options.customTargetNames, true)"
+ globalOptions.racePredictionOptions, options.customTargetNames, true)"
:targets="targetSets[options.selectedTargetSet] ?
targetSets[options.selectedTargetSet].targets : []"/>
</div>
</template>
<script setup lang="ts">
-import { Calculators, calculateWorkoutResults, defaultInput,
+import { Calculators, calculateWorkoutResults, defaultGlobalOptions, defaultInput,
defaultWorkoutOptions } from '@/core/calculators';
-import type { WorkoutOptions } from '@/core/calculators';
+import type { GlobalOptions, WorkoutOptions } from '@/core/calculators';
import { defaultWorkoutTargetSets } from '@/core/targets';
import type { WorkoutTarget, WorkoutTargetSets } from '@/core/targets';
-import { UnitSystems, detectDefaultUnitSystem } from '@/core/units';
import type { DistanceTime } from '@/core/units';
import AdvancedOptionsInput from '@/components/AdvancedOptionsInput.vue';
@@ -43,9 +42,9 @@ import useStorage from '@/composables/useStorage';
const input = useStorage<DistanceTime>('workout-calculator-input', defaultInput);
/*
- * The default unit system
+ * The global options
*/
-const defaultUnitSystem = useStorage<UnitSystems>('default-unit-system', detectDefaultUnitSystem());
+const globalOptions = useStorage<GlobalOptions>('global-options', defaultGlobalOptions);
/*
* The race prediction options
diff --git a/tests/e2e/batch-calculator.spec.js b/tests/e2e/batch-calculator.spec.js
@@ -105,15 +105,15 @@ test('Batch calculator', async ({ page }) => {
await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m');
await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(17);
await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30');
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:15');
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:24');
await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(17);
await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('2:44');
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('2:56');
await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(17);
await expect(page.getByRole('row')).toHaveCount(16);
// Change Riegel exponent
- await expect(page.getByLabel('Prediction model')).toHaveValue('AverageModel');
+ await expect(page.getByLabel('Prediction model')).toHaveValue('RiegelModel');
await page.getByLabel('Riegel Exponent').fill('1.12');
// Assert race results are correct
@@ -124,7 +124,7 @@ test('Batch calculator', async ({ page }) => {
await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:12');
await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(17);
await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('2:40');
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('2:42');
await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(17);
await expect(page.getByRole('row')).toHaveCount(16);
}
@@ -141,7 +141,7 @@ test('Batch calculator', async ({ page }) => {
await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:12');
await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(17);
await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('2:40');
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('2:42');
await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(17);
await expect(page.getByRole('row')).toHaveCount(16);
}
@@ -164,7 +164,7 @@ test('Batch calculator', async ({ page }) => {
await expect(page.getByRole('row')).toHaveCount(16);
}
- // Assert workout results are correct (inputs and options not reset)
+ // Assert workout results are correct (inputs not reset, but updated options are used)
{
await page.getByLabel('Calculator').selectOption('Workout Calculator');
await expect(page.getByLabel('Target name customization')).toHaveValue("true");
@@ -172,10 +172,10 @@ test('Batch calculator', async ({ page }) => {
await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m @ 5 km');
await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(5);
await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30');
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:41');
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:45');
await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(5);
await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:17');
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:22');
await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(5);
await expect(page.getByRole('row')).toHaveCount(16);
}
diff --git a/tests/e2e/cross-calculator.spec.js b/tests/e2e/cross-calculator.spec.js
@@ -110,9 +110,9 @@ test('Cross-calculator', async ({ page }) => {
await page.getByLabel('Input race duration minutes').fill('5');
await page.getByLabel('Input race duration seconds').fill('1');
- // Change prediction model and enable target name customization
+ // Change riegel exponent and enable target name customization
await page.getByText('Advanced Options').click();
- await page.getByLabel('Prediction model').selectOption('V̇O₂ Max Model');
+ await page.getByLabel('Riegel Exponent').fill('1.12');
await page.getByLabel('Target name customization').selectOption('Enabled');
// Change default units (should update on other calculators too)
@@ -125,15 +125,15 @@ test('Cross-calculator', async ({ page }) => {
await page.getByRole('link', { name: 'Back' }).click();
await page.getByRole('button', { name: 'Batch Calculator' }).click();
- // Assert pace results are correct (inputs and options not reset)
+ // Assert race results are correct (inputs and options not reset)
await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi');
await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m');
await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(17);
await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30');
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:24');
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:12');
await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(17);
await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('2:56');
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('2:42');
await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(17);
await expect(page.getByRole('row')).toHaveCount(16);
@@ -157,10 +157,10 @@ test('Cross-calculator', async ({ page }) => {
await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m @ 5 km');
await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(5);
await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30');
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:42');
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:45');
await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(5);
await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:17');
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:22');
await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(5);
await expect(page.getByRole('row')).toHaveCount(16);
@@ -182,8 +182,8 @@ test('Cross-calculator', async ({ page }) => {
await page.getByRole('button', { name: 'Race Calculator' }).click();
// Assert race predictions are correct (input race not resset and new prediction model loaded)
- await expect(page.getByRole('row').nth(5)).toHaveText('1 mi' + '5:02.17' + '3:08 / km');
- await expect(page.getByRole('row').nth(10)).toHaveText('5 km' + '16:44.87' + '3:21 / km');
+ await expect(page.getByRole('row').nth(5)).toHaveText('1 mi' + '4:49.86' + '3:00 / km');
+ await expect(page.getByRole('row').nth(10)).toHaveText('5 km' + '17:11.78' + '3:26 / km');
await expect(page.getByRole('row')).toHaveCount(17);
// Return to split calculator
@@ -212,16 +212,22 @@ test('Cross-calculator', async ({ page }) => {
// Assert workout splits are correct (input race and prediction model not reset)
await expect(page.getByRole('row').nth(1)).toHaveText('400 m @ 1 mi' + '1:14.81');
- await expect(page.getByRole('row').nth(3)).toHaveText('1600 m @ 1:00:00' + '5:53.56');
+ await expect(page.getByRole('row').nth(3)).toHaveText('1600 m @ 1:00:00' + '6:30.40');
await expect(page.getByRole('row')).toHaveCount(5);
}
// Assert localStorage entries are correct
{
- // Assert general localStorage entries are correct
+ // Assert global localStorage entries are correct
expect(await page.evaluate(() => localStorage.length)).toEqual(16);
- expect(await page.evaluate(() => localStorage.getItem('running-tools.default-unit-system')))
- .toEqual(JSON.stringify('metric'));
+ expect(await page.evaluate(() => localStorage.getItem('running-tools.global-options')))
+ .toEqual(JSON.stringify({
+ defaultUnitSystem: 'metric',
+ racePredictionOptions: {
+ model: 'RiegelModel',
+ riegelExponent: 1.12,
+ },
+ }));
// Assert localStorage entries for the batch calculator are correct
expect(await page.evaluate(() =>
@@ -304,8 +310,6 @@ test('Cross-calculator', async ({ page }) => {
}));
expect(await page.evaluate(() =>
localStorage.getItem('running-tools.race-calculator-options'))).toEqual(JSON.stringify({
- model: 'RiegelModel',
- riegelExponent: 1.06,
selectedTargetSet: '_race_targets',
}));
expect(await page.evaluate(() =>
@@ -383,8 +387,6 @@ test('Cross-calculator', async ({ page }) => {
expect(await page.evaluate(() =>
localStorage.getItem('running-tools.workout-calculator-options'))).toEqual(JSON.stringify({
customTargetNames: true,
- model: 'VO2MaxModel',
- riegelExponent: 1.06,
selectedTargetSet: '_workout_targets',
}));
expect(await page.evaluate(() =>
@@ -416,19 +418,19 @@ test('Cross-calculator', async ({ page }) => {
// Reload app and assert the updated options are loaded
// Identical to the previous "go back and assert the options are not resset" section
{
- // Reload app and go to batch calculator
- await page.goto('/');
+ // Return to batch calculator
+ await page.getByRole('link', { name: 'Back' }).click();
await page.getByRole('button', { name: 'Batch Calculator' }).click();
- // Assert pace results are correct (inputs and options not reset)
+ // Assert race results are correct (inputs and options not reset)
await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi');
await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m');
await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(17);
await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30');
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:24');
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:12');
await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(17);
await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('2:56');
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('2:42');
await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(17);
await expect(page.getByRole('row')).toHaveCount(16);
@@ -452,10 +454,10 @@ test('Cross-calculator', async ({ page }) => {
await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m @ 5 km');
await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(5);
await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30');
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:42');
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:45');
await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(5);
await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:17');
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:22');
await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(5);
await expect(page.getByRole('row')).toHaveCount(16);
@@ -477,8 +479,8 @@ test('Cross-calculator', async ({ page }) => {
await page.getByRole('button', { name: 'Race Calculator' }).click();
// Assert race predictions are correct (input race not resset and new prediction model loaded)
- await expect(page.getByRole('row').nth(5)).toHaveText('1 mi' + '5:02.17' + '3:08 / km');
- await expect(page.getByRole('row').nth(10)).toHaveText('5 km' + '16:44.87' + '3:21 / km');
+ await expect(page.getByRole('row').nth(5)).toHaveText('1 mi' + '4:49.86' + '3:00 / km');
+ await expect(page.getByRole('row').nth(10)).toHaveText('5 km' + '17:11.78' + '3:26 / km');
await expect(page.getByRole('row')).toHaveCount(17);
// Return to split calculator
@@ -507,7 +509,7 @@ test('Cross-calculator', async ({ page }) => {
// Assert workout splits are correct (input race and prediction model not reset)
await expect(page.getByRole('row').nth(1)).toHaveText('400 m @ 1 mi' + '1:14.81');
- await expect(page.getByRole('row').nth(3)).toHaveText('1600 m @ 1:00:00' + '5:53.56');
+ await expect(page.getByRole('row').nth(3)).toHaveText('1600 m @ 1:00:00' + '6:30.40');
await expect(page.getByRole('row')).toHaveCount(5);
}
});
diff --git a/tests/unit/components/AdvancedOptionsInput.spec.js b/tests/unit/components/AdvancedOptionsInput.spec.js
@@ -6,7 +6,13 @@ test('should be correctly render pace options according to props', () => {
// Initialize component
const wrapper = shallowMount(AdvancedOptionsInput, {
propsData: {
- defaultUnitSystem: 'metric',
+ globalOptions: {
+ defaultUnitSystem: 'metric',
+ racePredictionOptions: {
+ model: 'CameronModel',
+ riegelExponent: 1.30,
+ },
+ },
options: {
selectedTargetSet: 'B',
},
@@ -66,12 +72,14 @@ test('should be correctly render race options according to props', () => {
// Initialize component
const wrapper = shallowMount(AdvancedOptionsInput, {
propsData: {
- defaultUnitSystem: 'metric',
- options: {
- predictionOptions: {
+ globalOptions: {
+ defaultUnitSystem: 'metric',
+ racePredictionOptions: {
model: 'PurdyPointsModel',
riegelExponent: 1.2,
},
+ },
+ options: {
selectedTargetSet: '_new',
},
type: 'race',
@@ -98,12 +106,14 @@ test('should render riegel exponent field only for supported race prediction mod
// Initialize component
const wrapper = shallowMount(AdvancedOptionsInput, {
propsData: {
- defaultUnitSystem: 'metric',
- options: {
- predictionOptions: {
+ globalOptions: {
+ defaultUnitSystem: 'metric',
+ racePredictionOptions: {
model: 'AverageModel',
riegelExponent: 1.2,
},
+ },
+ options: {
selectedTargetSet: '_new',
},
type: 'race',
@@ -136,7 +146,13 @@ test('should be correctly render split options according to props', () => {
// Initialize component
const wrapper = shallowMount(AdvancedOptionsInput, {
propsData: {
- defaultUnitSystem: 'metric',
+ globalOptions: {
+ defaultUnitSystem: 'metric',
+ racePredictionOptions: {
+ model: 'CameronModel',
+ riegelExponent: 1.30,
+ },
+ },
options: {
selectedTargetSet: '_new',
},
@@ -162,13 +178,15 @@ test('should be correctly render workout options according to props', () => {
// Initialize component
const wrapper = shallowMount(AdvancedOptionsInput, {
propsData: {
- defaultUnitSystem: 'metric',
- options: {
- customTargetNames: true,
- predictionOptions: {
+ globalOptions: {
+ defaultUnitSystem: 'metric',
+ racePredictionOptions: {
model: 'PurdyPointsModel',
riegelExponent: 1.2,
},
+ },
+ options: {
+ customTargetNames: true,
selectedTargetSet: '_new',
},
targetSets: {},
@@ -193,7 +211,13 @@ test('should only show batch column label field when applicable', async () => {
// Initialize component with workout target name customization enabled
const wrapper = shallowMount(AdvancedOptionsInput, {
propsData: {
- defaultUnitSystem: 'metric',
+ globalOptions: {
+ defaultUnitSystem: 'metric',
+ racePredictionOptions: {
+ model: 'CameronModel',
+ riegelExponent: 1.30,
+ },
+ },
options: {
customTargetNames: true,
predictionOptions: {
@@ -225,7 +249,13 @@ test('should only show batch column label field when applicable', async () => {
label: 'foo',
rows: 15,
},
- defaultUnitSystem: 'metric',
+ globalOptions: {
+ defaultUnitSystem: 'metric',
+ racePredictionOptions: {
+ model: 'CameronModel',
+ riegelExponent: 1.30,
+ },
+ },
options: {
customTargetNames: false, // disabled
predictionOptions: {
@@ -254,7 +284,13 @@ test('should only show batch column label field when applicable', async () => {
label: 'foo',
rows: 15,
},
- defaultUnitSystem: 'metric',
+ globalOptions: {
+ defaultUnitSystem: 'metric',
+ racePredictionOptions: {
+ model: 'CameronModel',
+ riegelExponent: 1.30,
+ },
+ },
options: {
customTargetNames: true, // enabled
predictionOptions: {
@@ -285,7 +321,13 @@ test('should only show batch column label field when applicable', async () => {
label: 'foo',
rows: 15,
},
- defaultUnitSystem: 'metric',
+ globalOptions: {
+ defaultUnitSystem: 'metric',
+ racePredictionOptions: {
+ model: 'CameronModel',
+ riegelExponent: 1.30,
+ },
+ },
options: {
predictionOptions: {
model: 'PurdyPointsModel',
@@ -306,7 +348,13 @@ test('should pass correct props to TargetSetSelector', async () => {
// Initialize component
const wrapper = shallowMount(AdvancedOptionsInput, {
propsData: {
- defaultUnitSystem: 'metric',
+ globalOptions: {
+ defaultUnitSystem: 'metric',
+ racePredictionOptions: {
+ model: 'CameronModel',
+ riegelExponent: 1.30,
+ },
+ },
options: {
customTargetNames: false,
predictionOptions: {
@@ -372,13 +420,15 @@ test('should emit input events when options are modified', async () => {
// Initialize component
const wrapper = shallowMount(AdvancedOptionsInput, {
propsData: {
- defaultUnitSystem: 'metric',
- options: {
- customTargetNames: false,
- predictionOptions: {
+ globalOptions: {
+ defaultUnitSystem: 'metric',
+ racePredictionOptions: {
model: 'AverageModel',
riegelExponent: 1.06,
},
+ },
+ options: {
+ customTargetNames: false,
selectedTargetSet: '_new',
},
targetSets: {},
@@ -412,7 +462,29 @@ test('should emit input events when options are modified', async () => {
await wrapper.findComponent({ name: 'decimal-input' }).setValue(1.3);
// Assert correct update events emitted
- expect(wrapper.emitted()['update:defaultUnitSystem']).to.deep.equal([['imperial']]);
+ expect(wrapper.emitted()['update:globalOptions']).to.deep.equal([
+ [{
+ defaultUnitSystem: 'imperial',
+ racePredictionOptions: {
+ model: 'AverageModel',
+ riegelExponent: 1.06,
+ },
+ }],
+ [{
+ defaultUnitSystem: 'imperial',
+ racePredictionOptions: {
+ model: 'CameronModel',
+ riegelExponent: 1.06,
+ },
+ }],
+ [{
+ defaultUnitSystem: 'imperial',
+ racePredictionOptions: {
+ model: 'CameronModel',
+ riegelExponent: 1.3,
+ },
+ }],
+ ]);
expect(wrapper.emitted()['update:targetSets']).to.deep.equal([[{
'A': {
name: '1st target set v2',
@@ -433,36 +505,12 @@ test('should emit input events when options are modified', async () => {
}]]);
expect(wrapper.emitted()['update:options']).to.deep.equal([
[{
- customTargetNames: false,
- predictionOptions: {
- model: 'AverageModel',
- riegelExponent: 1.06,
- },
- selectedTargetSet: 'B',
+ customTargetNames: false,
+ selectedTargetSet: 'B',
}],
[{
- customTargetNames: true,
- predictionOptions: {
- model: 'AverageModel',
- riegelExponent: 1.06,
- },
- selectedTargetSet: 'B',
- }],
- [{
- customTargetNames: true,
- predictionOptions: {
- model: 'CameronModel',
- riegelExponent: 1.06,
- },
- selectedTargetSet: 'B',
- }],
- [{
- customTargetNames: true,
- predictionOptions: {
- model: 'CameronModel',
- riegelExponent: 1.3,
- },
- selectedTargetSet: 'B',
+ customTargetNames: true,
+ selectedTargetSet: 'B',
}],
]);
});
diff --git a/tests/unit/views/BatchCalculator.spec.js b/tests/unit/views/BatchCalculator.spec.js
@@ -4,7 +4,30 @@ import BatchCalculator from '@/views/BatchCalculator.vue';
beforeEach(() => {
localStorage.clear();
-})
+});
+
+test('should load global options from localStorage', async () => {
+ // Initialize localStorage
+ localStorage.setItem('running-tools.global-options', JSON.stringify({
+ defaultUnitSystem: 'imperial',
+ racePredictionOptions: {
+ model: 'PurdyPointsModel',
+ riegelExponent: 1.2,
+ },
+ }));
+
+ // Initialize component
+ const wrapper = shallowMount(BatchCalculator);
+
+ // Assert data loaded
+ expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.globalOptions).to.deep.equal({
+ defaultUnitSystem: 'imperial',
+ racePredictionOptions: {
+ model: 'PurdyPointsModel',
+ riegelExponent: 1.2,
+ },
+ });
+});
test('should load input from localStorage', async () => {
// Initialize localStorage
@@ -30,25 +53,6 @@ test('should load input from localStorage', async () => {
});
});
-test('should save input to localStorage when modified', async () => {
- // Initialize component
- const wrapper = shallowMount(BatchCalculator);
-
- // Update input pace
- await wrapper.findComponent({ name: 'pace-input' }).setValue({
- distanceValue: 2,
- distanceUnit: 'miles',
- time: 600,
- });
-
- // Assert input saved
- expect(localStorage.getItem('running-tools.batch-calculator-input')).to.equal(JSON.stringify({
- distanceValue: 2,
- distanceUnit: 'miles',
- time: 600,
- }));
-});
-
test('should load batch options from localStorage', async () => {
// Initialize localStorage
localStorage.setItem('running-tools.batch-calculator-options', JSON.stringify({
@@ -73,87 +77,6 @@ test('should load batch options from localStorage', async () => {
});
});
-test('should save batch options to localStorage when modified', async () => {
- // Initialize component
- const wrapper = shallowMount(BatchCalculator);
-
- // Update increment value
- await wrapper.findComponent({ name: 'time-input' }).setValue(32);
-
- // Assert options saved
- expect(localStorage.getItem('running-tools.batch-calculator-options')).to.equal(JSON.stringify({
- calculator: 'workout',
- increment: 32,
- label: '',
- rows: 20,
- }));
-
- // Update number of rows
- await wrapper.findComponent({ name: 'integer-input' }).setValue(15);
-
- // Assert options saved
- expect(localStorage.getItem('running-tools.batch-calculator-options')).to.equal(JSON.stringify({
- calculator: 'workout',
- increment: 32,
- label: '',
- rows: 15,
- }));
-
- // Update batch column label
- await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
- calculator: 'workout',
- increment: 32,
- label: 'foo',
- rows: 15,
- }, 'batch-options');
-
- // Assert options saved
- expect(localStorage.getItem('running-tools.batch-calculator-options')).to.equal(JSON.stringify({
- calculator: 'workout',
- increment: 32,
- label: 'foo',
- rows: 15,
- }));
-
- // Update active calculator
- await wrapper.find('select[aria-label="Calculator"]').setValue('race');
-
- // Assert options saved
- expect(localStorage.getItem('running-tools.batch-calculator-options')).to.equal(JSON.stringify({
- calculator: 'race',
- increment: 32,
- label: 'foo',
- rows: 15,
- }));
-});
-
-test('should load default units setting from localStorage', async () => {
- // Initialize localStorage
- localStorage.setItem('running-tools.default-unit-system', '"metric"');
-
- // Initialize component
- const wrapper = shallowMount(BatchCalculator);
-
- // Assert default units setting loaded
- expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.defaultUnitSystem)
- .to.equal('metric');
-});
-
-test('should save default units setting from localStorage when modified', async () => {
- // Initialize localStorage
- localStorage.setItem('running-tools.default-unit-system', '"metric"');
-
- // Initialize component
- const wrapper = shallowMount(BatchCalculator);
-
- // Change default units setting
- await wrapper.findComponent({ name: 'advanced-options-input' })
- .setValue('imperial', 'defaultUnitSystem');
-
- // New default units should be saved to localStorage
- expect(localStorage.getItem('running-tools.default-unit-system')).to.equal('"imperial"');
-});
-
test('should load calculator options from localStorage', async () => {
// Initialize localStorage
const selectedTargetSets = [
@@ -210,18 +133,10 @@ test('should load calculator options from localStorage', async () => {
selectedTargetSet: 'A',
}));
localStorage.setItem('running-tools.race-calculator-options', JSON.stringify({
- predictionOptions: {
- model: 'PurdyPointsModel',
- riegelExponent: 1.2,
- },
selectedTargetSet: 'C',
}));
localStorage.setItem('running-tools.workout-calculator-options', JSON.stringify({
customTargetNames: true,
- predictionOptions: {
- model: 'RiegelModel',
- riegelExponent: 1.1,
- },
selectedTargetSet: 'E',
}));
@@ -239,10 +154,6 @@ test('should load calculator options from localStorage', async () => {
// Assert race calculator options are loaded
await wrapper.find('select[aria-label="Calculator"]').setValue('race');
expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({
- predictionOptions: {
- model: 'PurdyPointsModel',
- riegelExponent: 1.2,
- },
selectedTargetSet: 'C',
});
expect(wrapper.findComponent({ name: 'double-output-table' }).vm.targets)
@@ -252,16 +163,111 @@ test('should load calculator options from localStorage', async () => {
await wrapper.find('select[aria-label="Calculator"]').setValue('workout');
expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({
customTargetNames: true,
- predictionOptions: {
- model: 'RiegelModel',
- riegelExponent: 1.1,
- },
selectedTargetSet: 'E',
});
expect(wrapper.findComponent({ name: 'double-output-table' }).vm.targets)
.to.deep.equal(selectedTargetSets[2].targets);
});
+test('should save global options to localStorage when modified', async () => {
+ // Initialize localStorage
+ localStorage.setItem('running-tools.default-unit-system', '"metric"');
+
+ // Initialize component
+ const wrapper = shallowMount(BatchCalculator);
+
+ // Change default units setting
+ await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
+ defaultUnitSystem: 'imperial',
+ racePredictionOptions: {
+ model: 'CameronModel',
+ riegelExponent: 1.30,
+ },
+ }, 'globalOptions');
+
+ // New default units should be saved to localStorage
+ expect(localStorage.getItem('running-tools.global-options')).to.equal(JSON.stringify({
+ defaultUnitSystem: 'imperial',
+ racePredictionOptions: {
+ model: 'CameronModel',
+ riegelExponent: 1.30,
+ },
+ }));
+});
+
+test('should save input to localStorage when modified', async () => {
+ // Initialize component
+ const wrapper = shallowMount(BatchCalculator);
+
+ // Update input pace
+ await wrapper.findComponent({ name: 'pace-input' }).setValue({
+ distanceValue: 2,
+ distanceUnit: 'miles',
+ time: 600,
+ });
+
+ // Assert input saved
+ expect(localStorage.getItem('running-tools.batch-calculator-input')).to.equal(JSON.stringify({
+ distanceValue: 2,
+ distanceUnit: 'miles',
+ time: 600,
+ }));
+});
+
+test('should save batch options to localStorage when modified', async () => {
+ // Initialize component
+ const wrapper = shallowMount(BatchCalculator);
+
+ // Update increment value
+ await wrapper.findComponent({ name: 'time-input' }).setValue(32);
+
+ // Assert options saved
+ expect(localStorage.getItem('running-tools.batch-calculator-options')).to.equal(JSON.stringify({
+ calculator: 'workout',
+ increment: 32,
+ label: '',
+ rows: 20,
+ }));
+
+ // Update number of rows
+ await wrapper.findComponent({ name: 'integer-input' }).setValue(15);
+
+ // Assert options saved
+ expect(localStorage.getItem('running-tools.batch-calculator-options')).to.equal(JSON.stringify({
+ calculator: 'workout',
+ increment: 32,
+ label: '',
+ rows: 15,
+ }));
+
+ // Update batch column label
+ await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
+ calculator: 'workout',
+ increment: 32,
+ label: 'foo',
+ rows: 15,
+ }, 'batch-options');
+
+ // Assert options saved
+ expect(localStorage.getItem('running-tools.batch-calculator-options')).to.equal(JSON.stringify({
+ calculator: 'workout',
+ increment: 32,
+ label: 'foo',
+ rows: 15,
+ }));
+
+ // Update active calculator
+ await wrapper.find('select[aria-label="Calculator"]').setValue('race');
+
+ // Assert options saved
+ expect(localStorage.getItem('running-tools.batch-calculator-options')).to.equal(JSON.stringify({
+ calculator: 'race',
+ increment: 32,
+ label: 'foo',
+ rows: 15,
+ }));
+});
+
test('should save calculator options to localStorage when modified', async () => {
// Initialize localStorage
const selectedTargetSets = [
@@ -309,10 +315,6 @@ test('should save calculator options to localStorage when modified', async () =>
}
}));
localStorage.setItem('running-tools.race-calculator-options', JSON.stringify({
- predictionOptions: {
- model: 'AverageModel',
- riegelExponent: 1.06,
- },
selectedTargetSet: 'D',
}));
localStorage.setItem('running-tools.workout-calculator-target-sets', JSON.stringify({
@@ -326,10 +328,6 @@ test('should save calculator options to localStorage when modified', async () =>
}));
localStorage.setItem('running-tools.workout-calculator-options', JSON.stringify({
customWorkoutNames: false,
- predictionOptions: {
- model: 'AverageModel',
- riegelExponent: 1.06,
- },
selectedTargetSet: 'F',
}));
@@ -347,10 +345,6 @@ test('should save calculator options to localStorage when modified', async () =>
// Update race calculator options
await wrapper.find('select[aria-label="Calculator"]').setValue('race');
await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
- predictionOptions: {
- model: 'PurdyPointsModel',
- riegelExponent: 1.2,
- },
selectedTargetSet: 'C',
}, 'options');
expect(wrapper.findComponent({ name: 'double-output-table' }).vm.targets)
@@ -360,10 +354,6 @@ test('should save calculator options to localStorage when modified', async () =>
await wrapper.find('select[aria-label="Calculator"]').setValue('workout');
await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
customTargetNames: true,
- predictionOptions: {
- model: 'RiegelModel',
- riegelExponent: 1.1,
- },
selectedTargetSet: 'E',
}, 'options');
expect(wrapper.findComponent({ name: 'double-output-table' }).vm.targets)
@@ -374,18 +364,10 @@ test('should save calculator options to localStorage when modified', async () =>
selectedTargetSet: 'A',
}));
expect(localStorage.getItem('running-tools.race-calculator-options')).to.equal(JSON.stringify({
- predictionOptions: {
- model: 'PurdyPointsModel',
- riegelExponent: 1.2,
- },
selectedTargetSet: 'C',
}));
expect(localStorage.getItem('running-tools.workout-calculator-options')).to.equal(JSON.stringify({
customTargetNames: true,
- predictionOptions: {
- model: 'RiegelModel',
- riegelExponent: 1.1,
- },
selectedTargetSet: 'E',
}));
});
@@ -471,10 +453,6 @@ test('should pass correct input props to DoubleOutputTable', async () => {
// Enable target name customization
await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
customTargetNames: true,
- predictionOptions: {
- model: 'AverageModel',
- riegelExponent: 1.06,
- },
selectedTargetSet: '_workout_targets',
}, 'options');
@@ -531,6 +509,13 @@ test('should correctly set AdvancedOptionsInput props', async () => {
// Assert props are correct for pace calculator
await wrapper.find('select[aria-label="Calculator"]').setValue('pace');
expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.type).to.equal('pace');
+ expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.globalOptions).to.deep.equal({
+ defaultUnitSystem: 'imperial',
+ racePredictionOptions: {
+ model: 'AverageModel',
+ riegelExponent: 1.06,
+ },
+ });
expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({
selectedTargetSet: '_pace_targets',
});
@@ -538,44 +523,49 @@ test('should correctly set AdvancedOptionsInput props', async () => {
// Assert props are correct for race calculator
await wrapper.find('select[aria-label="Calculator"]').setValue('race');
expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.type).to.equal('race');
- expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({
- predictionOptions: {
+ expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.globalOptions).to.deep.equal({
+ defaultUnitSystem: 'imperial',
+ racePredictionOptions: {
model: 'AverageModel',
riegelExponent: 1.06,
},
+ });
+ expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({
selectedTargetSet: '_race_targets',
});
// Assert props are correct for workout calculator
await wrapper.find('select[aria-label="Calculator"]').setValue('workout');
expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.type).to.equal('workout');
- expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({
- customTargetNames: false,
- predictionOptions: {
+ expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.globalOptions).to.deep.equal({
+ defaultUnitSystem: 'imperial',
+ racePredictionOptions: {
model: 'AverageModel',
riegelExponent: 1.06,
},
+ });
+ expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({
+ customTargetNames: false,
selectedTargetSet: '_workout_targets',
});
});
test('should correctly calculate outputs', async () => {
// Initialize localStorage
- localStorage.setItem('running-tools.race-calculator-options', JSON.stringify({
- selectedTargetSet: '_race_targets',
- predictionOptions: {
+ localStorage.setItem('running-tools.global-options', JSON.stringify({
+ defaultUnitSystem: 'imperial',
+ racePredictionOptions: {
model: 'PurdyPointsModel',
riegelExponent: 1.2,
},
}));
+ localStorage.setItem('running-tools.race-calculator-options', JSON.stringify({
+ selectedTargetSet: '_race_targets',
+ }));
localStorage.setItem('running-tools.workout-calculator-options', JSON.stringify({
+ customTargetNames: false,
selectedTargetSet: '_workout_targets',
- predictionOptions: {
- model: 'RiegelModel',
- riegelExponent: 1.1,
- },
}));
- localStorage.setItem('running-tools.default-unit-system', '"imperial"');
// Initialize component
const wrapper = shallowMount(BatchCalculator);
@@ -609,8 +599,8 @@ test('should correctly calculate outputs', async () => {
const workoutTarget = { type: 'time', time: 3600, splitValue: 1, splitUnit: 'miles' };
const result = calculate(input, workoutTarget);
expect(result.key).to.equal('1 mi @ 1:00:00');
- expect(result.value).to.equal('5:53');
+ expect(result.value).to.equal('5:29');
expect(result.pace).to.equal('');
- expect(result.sort).to.be.closeTo(353.07, 0.01);
+ expect(result.sort).to.be.closeTo(329.48, 0.01);
expect(result.result).to.equal('value');
});
diff --git a/tests/unit/views/PaceCalculator.spec.js b/tests/unit/views/PaceCalculator.spec.js
@@ -5,7 +5,162 @@ import { defaultTargetSets } from '@/core/targets';
beforeEach(() => {
localStorage.clear();
-})
+});
+
+test('should load global options from localStorage', async () => {
+ // Initialize localStorage
+ localStorage.setItem('running-tools.global-options', JSON.stringify({
+ defaultUnitSystem: 'imperial',
+ racePredictionOptions: {
+ model: 'PurdyPointsModel',
+ riegelExponent: 1.2,
+ },
+ }));
+
+ // Initialize component
+ const wrapper = shallowMount(PaceCalculator);
+
+ // Assert selection is loaded
+ expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.globalOptions
+ .defaultUnitSystem).to.equal('imperial');
+});
+
+test('should load input pace from localStorage', async () => {
+ // Initialize localStorage
+ localStorage.setItem('running-tools.pace-calculator-input', JSON.stringify({
+ distanceValue: 1,
+ distanceUnit: 'miles',
+ time: 600,
+ }));
+
+ // Initialize component
+ const wrapper = shallowMount(PaceCalculator);
+
+ // Assert data loaded
+ expect(wrapper.findComponent({ name: 'pace-input' }).vm.modelValue).to.deep.equal({
+ distanceValue: 1,
+ distanceUnit: 'miles',
+ time: 600,
+ });
+});
+
+test('should load local options and target sets from localStorage', async () => {
+ // Initialize localStorage
+ const targetSets = {
+ '_pace_targets': {
+ name: 'Pace targets #1',
+ targets: [
+ { type: 'distance', distanceValue: 400, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 800, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' },
+ ],
+ },
+ 'B': {
+ name: 'Pace targets #2',
+ targets: [
+ { type: 'distance', distanceValue: 1, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 2, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' },
+ ],
+ },
+ };
+ localStorage.setItem('running-tools.pace-calculator-target-sets', JSON.stringify(targetSets));
+ localStorage.setItem('running-tools.pace-calculator-options', JSON.stringify({
+ selectedTargetSet: 'B',
+ }));
+
+ // Initialize component
+ const wrapper = shallowMount(PaceCalculator);
+
+ // Assert selection is loaded
+ expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({
+ selectedTargetSet: 'B',
+ });
+ expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.targetSets)
+ .to.deep.equal(targetSets);
+ expect(wrapper.findComponent({ name: 'single-output-table' }).vm.targets)
+ .to.deep.equal(targetSets.B.targets);
+});
+
+test('should save global options to localStorage when modified', async () => {
+ // Initialize component
+ const wrapper = shallowMount(PaceCalculator);
+
+ // Change default units
+ await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
+ defaultUnitSystem: 'imperial',
+ racePredictionOptions: {
+ model: 'AverageModel',
+ riegelExponent: 1.06,
+ },
+ }, 'globalOptions');
+
+ // New default units should be saved to localStorage
+ expect(localStorage.getItem('running-tools.global-options')).to.equal(JSON.stringify({
+ defaultUnitSystem: 'imperial',
+ racePredictionOptions: {
+ model: 'AverageModel',
+ riegelExponent: 1.06,
+ },
+ }));
+});
+
+test('should save input pace to localStorage', async () => {
+ // Initialize component
+ const wrapper = shallowMount(PaceCalculator);
+
+ // Enter input pace data
+ await wrapper.findComponent({ name: 'pace-input' }).setValue({
+ distanceValue: 1,
+ distanceUnit: 'miles',
+ time: 600,
+ });
+
+ // Assert data saved to localStorage
+ expect(localStorage.getItem('running-tools.pace-calculator-input')).to.equal(JSON.stringify({
+ distanceValue: 1,
+ distanceUnit: 'miles',
+ time: 600,
+ }));
+});
+
+test('should save local options and target sets to localStorage when modified', async () => {
+ const targetSets = {
+ '_pace_targets': {
+ name: 'Pace targets #1',
+ targets: [
+ { type: 'distance', distanceValue: 400, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 800, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' },
+ ],
+ },
+ 'B': {
+ name: 'Pace targets #2',
+ targets: [
+ { type: 'distance', distanceValue: 1, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 2, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' },
+ ],
+ },
+ };
+
+ // Initialize component
+ const wrapper = shallowMount(PaceCalculator);
+
+ // Update target sets and selected target set
+ await wrapper.findComponent({ name: 'advanced-options-input' }).setValue(targetSets,
+ 'targetSets');
+ await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
+ selectedTargetSet: 'B',
+ }, 'options');
+
+ // New selected target set should be saved to localStorage
+ expect(localStorage.getItem('running-tools.pace-calculator-target-sets'))
+ .to.equal(JSON.stringify(targetSets));
+ expect(localStorage.getItem('running-tools.pace-calculator-options')).to.equal(JSON.stringify({
+ selectedTargetSet: 'B',
+ }));
+});
test('should correctly calculate time results', async () => {
// Initialize component
@@ -36,7 +191,7 @@ test('should correctly calculate time results', async () => {
});
});
-test('should correctly calculate distance results according to default units setting', async () => {
+test('should correctly calculate distance results according to global options', async () => {
// Initialize component
const wrapper = shallowMount(PaceCalculator);
@@ -48,8 +203,13 @@ test('should correctly calculate distance results according to default units set
});
// Set default units
- await wrapper.findComponent({ name: 'advanced-options-input' })
- .setValue('metric', 'defaultUnitSystem');
+ await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
+ defaultUnitSystem: 'metric',
+ racePredictionOptions: {
+ model: 'AverageModel',
+ riegelExponent: 1.06,
+ },
+ }, 'globalOptions');
// Get calculate result function
const calculateResult = wrapper.findComponent({ name: 'single-output-table' }).vm.calculateResult;
@@ -59,8 +219,13 @@ test('should correctly calculate distance results according to default units set
expect(result.key).to.equal('1.61 km');
// Change default units
- await wrapper.findComponent({ name: 'advanced-options-input' })
- .setValue('imperial', 'defaultUnitSystem');
+ await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
+ defaultUnitSystem: 'imperial',
+ racePredictionOptions: {
+ model: 'AverageModel',
+ riegelExponent: 1.06,
+ },
+ }, 'globalOptions');
// Assert result is correct
result = calculateResult({ type: 'time', time: 600 });
@@ -96,103 +261,10 @@ test('should correctly handle null target set', async () => {
.to.deep.equal(paceTargets);
});
-test('should load input pace from localStorage', async () => {
- // Initialize localStorage
- localStorage.setItem('running-tools.pace-calculator-input', JSON.stringify({
- distanceValue: 1,
- distanceUnit: 'miles',
- time: 600,
- }));
-
- // Initialize component
- const wrapper = shallowMount(PaceCalculator);
-
- // Assert data loaded
- expect(wrapper.findComponent({ name: 'pace-input' }).vm.modelValue).to.deep.equal({
- distanceValue: 1,
- distanceUnit: 'miles',
- time: 600,
- });
-});
-
-test('should save input pace to localStorage', async () => {
+test('should correctly set AdvancedOptionsInput type prop', async () => {
// Initialize component
const wrapper = shallowMount(PaceCalculator);
- // Enter input pace data
- await wrapper.findComponent({ name: 'pace-input' }).setValue({
- distanceValue: 1,
- distanceUnit: 'miles',
- time: 600,
- });
-
- // Assert data saved to localStorage
- expect(localStorage.getItem('running-tools.pace-calculator-input')).to.equal(JSON.stringify({
- distanceValue: 1,
- distanceUnit: 'miles',
- time: 600,
- }));
-});
-
-test('should load options from localStorage', async () => {
- // Initialize localStorage
- const targetSet2 = {
- name: 'Pace targets #2',
- targets: [
- { type: 'distance', distanceValue: 1, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 2, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' },
- ],
- };
- localStorage.setItem('running-tools.pace-calculator-target-sets', JSON.stringify({
- '_pace_targets': {
- name: 'Pace targets #1',
- targets: [
- { type: 'distance', distanceValue: 400, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 800, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' },
- ],
- },
- 'B': targetSet2,
- }));
- localStorage.setItem('running-tools.pace-calculator-options', JSON.stringify({
- selectedTargetSet: 'B',
- }));
-
- // Initialize component
- const wrapper = shallowMount(PaceCalculator);
-
- // Assert selection is loaded
- expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options)
- .to.deep.equal({ selectedTargetSet: 'B' });
- expect(wrapper.findComponent({ name: 'single-output-table' }).vm.targets)
- .to.deep.equal(targetSet2.targets);
-});
-
-test('should save options to localStorage when modified', async () => {
- // Initialize component
- const wrapper = shallowMount(PaceCalculator);
-
- // Select a new target set
- await wrapper.findComponent({ name: 'advanced-options-input' })
- .setValue({ selectedTargetSet: 'B' }, 'options');
-
- // New selected target set should be saved to localStorage
- expect(localStorage.getItem('running-tools.pace-calculator-options')).to.equal(JSON.stringify({
- selectedTargetSet: 'B',
- }));
-});
-
-test('should save default units setting to localStorage when modified', async () => {
- // Initialize component
- const wrapper = shallowMount(PaceCalculator);
-
- // Change default units
- await wrapper.findComponent({ name: 'advanced-options-input' })
- .setValue('metric', 'defaultUnitSystem');
- await wrapper.findComponent({ name: 'advanced-options-input' })
- .setValue('imperial', 'defaultUnitSystem');
-
- // New default units should be saved to localStorage
- expect(localStorage.getItem('running-tools.default-unit-system')).to.equal('"imperial"');
+ // Assert type prop is correctly set
+ expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.type).to.equal('pace');
});
diff --git a/tests/unit/views/RaceCalculator.spec.js b/tests/unit/views/RaceCalculator.spec.js
@@ -5,7 +5,167 @@ import { defaultTargetSets } from '@/core/targets';
beforeEach(() => {
localStorage.clear();
-})
+});
+
+test('should load global options from localStorage', async () => {
+ // Initialize localStorage
+ localStorage.setItem('running-tools.global-options', JSON.stringify({
+ defaultUnitSystem: 'imperial',
+ racePredictionOptions: {
+ model: 'PurdyPointsModel',
+ riegelExponent: 1.2,
+ },
+ }));
+
+ // Initialize component
+ const wrapper = shallowMount(RaceCalculator);
+
+ // Assert data loaded
+ expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.globalOptions).to.deep.equal({
+ defaultUnitSystem: 'imperial',
+ racePredictionOptions: {
+ model: 'PurdyPointsModel',
+ riegelExponent: 1.2,
+ },
+ });
+});
+
+test('should load input race from localStorage', async () => {
+ // Initialize localStorage
+ localStorage.setItem('running-tools.race-calculator-input', JSON.stringify({
+ distanceValue: 1,
+ distanceUnit: 'miles',
+ time: 600,
+ }));
+
+ // Initialize component
+ const wrapper = shallowMount(RaceCalculator);
+
+ // Assert data loaded
+ expect(wrapper.findComponent({ name: 'pace-input' }).vm.modelValue).to.deep.equal({
+ distanceValue: 1,
+ distanceUnit: 'miles',
+ time: 600,
+ });
+});
+
+test('should load local options and target sets from localStorage', async () => {
+ // Initialize localStorage
+ const targetSets = {
+ '_race_targets': {
+ name: 'Race targets #1',
+ targets: [
+ { type: 'distance', distanceValue: 400, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 800, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' },
+ ],
+ },
+ 'B': {
+ name: 'Race targets #2',
+ targets: [
+ { type: 'distance', distanceValue: 1, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 2, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' },
+ ],
+ },
+ };
+ localStorage.setItem('running-tools.race-calculator-target-sets', JSON.stringify(targetSets));
+ localStorage.setItem('running-tools.race-calculator-options', JSON.stringify({
+ selectedTargetSet: 'B',
+ }));
+
+ // Initialize component
+ const wrapper = shallowMount(RaceCalculator);
+
+ // Assert data loaded
+ expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({
+ selectedTargetSet: 'B',
+ });
+ expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.targetSets)
+ .to.deep.equal(targetSets);
+ expect(wrapper.findComponent({ name: 'single-output-table' }).vm.targets)
+ .to.deep.equal(targetSets.B.targets);
+});
+
+test('should save global options to localStorage when modified', async () => {
+ // Initialize component
+ const wrapper = shallowMount(RaceCalculator);
+
+ // Update options
+ await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
+ defaultUnitSystem: 'imperial',
+ racePredictionOptions: {
+ model: 'PurdyPointsModel',
+ riegelExponent: 1.2,
+ },
+ }, 'globalOptions');
+
+ // New global options should be saved to localStorage
+ expect(localStorage.getItem('running-tools.global-options')).to.equal(JSON.stringify({
+ defaultUnitSystem: 'imperial',
+ racePredictionOptions: {
+ model: 'PurdyPointsModel',
+ riegelExponent: 1.2,
+ },
+ }));
+});
+
+test('should save input race to localStorage', async () => {
+ // Initialize component
+ const wrapper = shallowMount(RaceCalculator);
+
+ // Enter input race data
+ await wrapper.findComponent({ name: 'pace-input' }).setValue({
+ distanceValue: 1,
+ distanceUnit: 'miles',
+ time: 600,
+ });
+
+ // Assert data saved to localStorage
+ expect(localStorage.getItem('running-tools.race-calculator-input')).to.equal(JSON.stringify({
+ distanceValue: 1,
+ distanceUnit: 'miles',
+ time: 600,
+ }));
+});
+
+test('should save local options and target sets to localStorage when modified', async () => {
+ const targetSets = {
+ '_race_targets': {
+ name: 'Race targets #1',
+ targets: [
+ { type: 'distance', distanceValue: 400, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 800, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' },
+ ],
+ },
+ 'B': {
+ name: 'Race targets #2',
+ targets: [
+ { type: 'distance', distanceValue: 1, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 2, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' },
+ ],
+ },
+ };
+
+ // Initialize component
+ const wrapper = shallowMount(RaceCalculator);
+
+ // Update target sets and selected target set
+ await wrapper.findComponent({ name: 'advanced-options-input' }).setValue(targetSets,
+ 'targetSets');
+ await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
+ selectedTargetSet: 'B',
+ }, 'options');
+
+ // Assert data saved to localStorage
+ expect(localStorage.getItem('running-tools.race-calculator-target-sets'))
+ .to.equal(JSON.stringify(targetSets));
+ expect(localStorage.getItem('running-tools.race-calculator-options')).to.equal(JSON.stringify({
+ selectedTargetSet: 'B',
+ }));
+});
test('should correctly predict race times', async () => {
// Initialize component
@@ -34,45 +194,6 @@ test('should correctly predict race times', async () => {
expect(result.sort).to.be.closeTo(2494.80, 0.01);
});
-test('should correctly calculate distance results according to default units setting', async () => {
- // Initialize component
- const wrapper = shallowMount(RaceCalculator);
-
- // Enter input race data
- await wrapper.findComponent({ name: 'pace-input' }).setValue({
- distanceValue: 5,
- distanceUnit: 'kilometers',
- time: 1200,
- });
-
- // Set default units
- await wrapper.findComponent({ name: 'advanced-options-input' })
- .setValue('metric', 'defaultUnitSystem');
-
- // Get calculate result function
- const calculateResult = wrapper.findComponent({ name: 'single-output-table' }).vm.calculateResult;
-
- // Assert result is correct
- let result = calculateResult({ type: 'time', time: 2495 });
- expect(result.key).to.equal('10.00 km');
- expect(result.value).to.equal('41:35');
- expect(result.pace).to.equal('4:09 / km');
- expect(result.result).to.equal('key');
- expect(result.sort).to.equal(2495);
-
- // Change default units
- await wrapper.findComponent({ name: 'advanced-options-input' })
- .setValue('imperial', 'defaultUnitSystem');
-
- // Assert result is correct
- result = calculateResult({ type: 'time', time: 2495 });
- expect(result.key).to.equal('6.21 mi');
- expect(result.value).to.equal('41:35');
- expect(result.pace).to.equal('6:41 / mi');
- expect(result.result).to.equal('key');
- expect(result.sort).to.equal(2495);
-});
-
test('should show paces in results table', async () => {
// Initialize component
const wrapper = shallowMount(RaceCalculator);
@@ -87,10 +208,6 @@ test('should correctly handle null target set', async () => {
// Switch to invalid target set
await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
- predictionOptions: {
- model: 'AverageModel',
- riegelExponent: 1.06,
- },
selectedTargetSet: 'does_not_exist',
}, 'options');
@@ -99,10 +216,6 @@ test('should correctly handle null target set', async () => {
// Switch to valid target set
await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
- predictionOptions: {
- model: 'AverageModel',
- riegelExponent: 1.06,
- },
selectedTargetSet: '_race_targets',
}, 'options');
@@ -135,7 +248,7 @@ test('should correctly calculate race statistics', async () => {
expect(vo2Max).to.equal('V̇O₂ Max: 49.8 ml/kg/min')
});
-test('should correctly calculate results according to model options', async () => {
+test('should correctly calculate results according to options', async () => {
// Initialize component
const wrapper = shallowMount(RaceCalculator);
@@ -146,18 +259,54 @@ test('should correctly calculate results according to model options', async () =
time: 1200,
});
+ // Set default units
+ await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
+ defaultUnitSystem: 'metric',
+ racePredictionOptions: {
+ model: 'AverageModel',
+ riegelExponent: 1.06,
+ },
+ }, 'globalOptions');
+
+ // Get calculate result function
+ const calculateResult = wrapper.findComponent({ name: 'single-output-table' }).vm.calculateResult;
+
+ // Assert result is correct
+ let result = calculateResult({ type: 'time', time: 2495 });
+ expect(result.key).to.equal('10.00 km');
+ expect(result.value).to.equal('41:35');
+ expect(result.pace).to.equal('4:09 / km');
+ expect(result.result).to.equal('key');
+ expect(result.sort).to.equal(2495);
+
+ // Change default units
+ await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
+ defaultUnitSystem: 'imperial', // changed from metric
+ racePredictionOptions: {
+ model: 'AverageModel',
+ riegelExponent: 1.06,
+ },
+ }, 'globalOptions');
+
+ // Assert result is correct
+ result = calculateResult({ type: 'time', time: 2495 });
+ expect(result.key).to.equal('6.21 mi');
+ expect(result.value).to.equal('41:35');
+ expect(result.pace).to.equal('6:41 / mi');
+ expect(result.result).to.equal('key');
+ expect(result.sort).to.equal(2495);
+
// Switch model
await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
- predictionOptions: {
- model: 'RiegelModel', // changed from the Riegel Model
+ defaultUnitSystem: 'imperial',
+ racePredictionOptions: {
+ model: 'RiegelModel', // changed from the Average Model
riegelExponent: 1.06,
},
- selectedTargetSet: '_race_targets',
- }, 'options');
+ }, 'globalOptions');
// Calculate result
- const calculateResult = wrapper.findComponent({ name: 'single-output-table' }).vm.calculateResult;
- let result = calculateResult({
+ result = calculateResult({
distanceValue: 10,
distanceUnit: 'kilometers',
type: 'distance',
@@ -168,12 +317,12 @@ test('should correctly calculate results according to model options', async () =
// Update Riegel Exponent
await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
- predictionOptions: {
+ defaultUnitSystem: 'imperial',
+ racePredictionOptions: {
model: 'RiegelModel',
riegelExponent: 1, // changed from 1.06
},
- selectedTargetSet: '_race_targets',
- }, 'options');
+ }, 'globalOptions');
// Calculate result
result = calculateResult({
@@ -186,121 +335,10 @@ test('should correctly calculate results according to model options', async () =
expect(result.value).to.equal('40:00.00');
});
-test('should load input race from localStorage', async () => {
- // Initialize localStorage
- localStorage.setItem('running-tools.race-calculator-input', JSON.stringify({
- distanceValue: 1,
- distanceUnit: 'miles',
- time: 600,
- }));
-
+test('should correctly set AdvancedOptionsInput type prop', async () => {
// Initialize component
const wrapper = shallowMount(RaceCalculator);
- // Assert data loaded
- expect(wrapper.findComponent({ name: 'pace-input' }).vm.modelValue).to.deep.equal({
- distanceValue: 1,
- distanceUnit: 'miles',
- time: 600,
- });
-});
-
-test('should save input race to localStorage', async () => {
- // Initialize component
- const wrapper = shallowMount(RaceCalculator);
-
- // Enter input race data
- await wrapper.findComponent({ name: 'pace-input' }).setValue({
- distanceValue: 1,
- distanceUnit: 'miles',
- time: 600,
- });
-
- // Assert data saved to localStorage
- expect(localStorage.getItem('running-tools.race-calculator-input')).to.equal(JSON.stringify({
- distanceValue: 1,
- distanceUnit: 'miles',
- time: 600,
- }));
-});
-
-test('should save default units setting to localStorage when modified', async () => {
- // Initialize component
- const wrapper = shallowMount(RaceCalculator);
-
- // Change default units
- await wrapper.findComponent({ name: 'advanced-options-input' })
- .setValue('metric', 'defaultUnitSystem');
- await wrapper.findComponent({ name: 'advanced-options-input' })
- .setValue('imperial', 'defaultUnitSystem');
-
- // New default units should be saved to localStorage
- expect(localStorage.getItem('running-tools.default-unit-system')).to.equal('"imperial"');
-});
-
-test('should load options from localStorage', async () => {
- // Initialize localStorage
- const targetSet2 = {
- name: 'Race targets #2',
- targets: [
- { type: 'distance', distanceValue: 1, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 2, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' },
- ],
- };
- localStorage.setItem('running-tools.race-calculator-target-sets', JSON.stringify({
- '_race_targets': {
- name: 'Race targets #1',
- targets: [
- { type: 'distance', distanceValue: 400, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 800, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' },
- ],
- },
- 'B': targetSet2,
- }));
- localStorage.setItem('running-tools.race-calculator-options', JSON.stringify({
- predictionOptions: {
- model: 'PurdyPointsModel',
- riegelExponent: 1.2,
- },
- selectedTargetSet: 'B',
- }));
-
- // Initialize component
- const wrapper = shallowMount(RaceCalculator);
-
- // Assert data loaded
- expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({
- predictionOptions: {
- model: 'PurdyPointsModel',
- riegelExponent: 1.2,
- },
- selectedTargetSet: 'B',
- });
- expect(wrapper.findComponent({ name: 'single-output-table' }).vm.targets)
- .to.deep.equal(targetSet2.targets);
-});
-
-test('should save options to localStorage when modified', async () => {
- // Initialize component
- const wrapper = shallowMount(RaceCalculator);
-
- // Update options
- await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
- predictionOptions: {
- model: 'CameronModel',
- riegelExponent: 1.30,
- },
- selectedTargetSet: 'B',
- }, 'options');
-
- // Assert data saved to localStorage
- expect(localStorage.getItem('running-tools.race-calculator-options')).to.equal(JSON.stringify({
- predictionOptions: {
- model: 'CameronModel',
- riegelExponent: 1.3,
- },
- selectedTargetSet: 'B',
- }));
+ // Assert type prop is correctly set
+ expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.type).to.equal('race');
});
diff --git a/tests/unit/views/SplitCalculator.spec.js b/tests/unit/views/SplitCalculator.spec.js
@@ -4,26 +4,31 @@ import SplitCalculator from '@/views/SplitCalculator.vue';
beforeEach(() => {
localStorage.clear();
-})
+});
-test('should load selected target set from localStorage', async () => {
+test('should load global options from localStorage', async () => {
// Initialize localStorage
- localStorage.setItem('running-tools.split-calculator-options', JSON.stringify({
- selectedTargetSet: 'B',
+ localStorage.setItem('running-tools.global-options', JSON.stringify({
+ defaultUnitSystem: 'imperial',
+ racePredictionOptions: {
+ model: 'AverageModel',
+ riegelExponent: 1.06,
+ },
}));
// Initialize component
const wrapper = shallowMount(SplitCalculator);
- // Assert selection is loaded
- expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({
- selectedTargetSet: 'B',
- });
+ // Assert data loaded
+ expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.globalOptions
+ .defaultUnitSystem).to.equal('imperial');
+ expect(wrapper.findComponent({ name: 'split-output-table' }).vm.defaultUnitSystem)
+ .to.equal('imperial');
});
-test('should load targets from localStorage and pass to splitOutputTable', async () => {
+test('should load local options and target sets from localStorage', async () => {
// Initialize localStorage
- localStorage.setItem('running-tools.split-calculator-target-sets', JSON.stringify({
+ const targetSets = {
'_split_targets': {
name: 'Split targets',
targets: [
@@ -40,156 +45,161 @@ test('should load targets from localStorage and pass to splitOutputTable', async
{ type: 'distance', distanceValue: 3000, distanceUnit: 'meters', split: 200 },
],
},
+ };
+ localStorage.setItem('running-tools.split-calculator-target-sets', JSON.stringify(targetSets));
+ localStorage.setItem('running-tools.split-calculator-options', JSON.stringify({
+ selectedTargetSet: 'B',
}));
// Initialize component
const wrapper = shallowMount(SplitCalculator);
- // Assert default split targets are initially loaded
+ // Assert data loaded
expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({
- selectedTargetSet: '_split_targets',
- });
- expect(wrapper.findComponent({ name: 'split-output-table' }).vm.modelValue).to.deep.equal([
- { type: 'distance', distanceValue: 1, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 2, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' },
- ]);
-
- // Select a new target set
- await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
selectedTargetSet: 'B',
- }, 'options');
-
- // Assert new target set is loaded
- expect(wrapper.findComponent({ name: 'split-output-table' }).vm.modelValue).to.deep.equal([
- { type: 'distance', distanceValue: 1, distanceUnit: 'kilometers', split: 180 },
- { type: 'distance', distanceValue: 2, distanceUnit: 'kilometers', split: 190 },
- { type: 'distance', distanceValue: 3000, distanceUnit: 'meters', split: 200 },
- ]);
+ });
+ expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.targetSets)
+ .to.deep.equal(targetSets);
+ expect(wrapper.findComponent({ name: 'split-output-table' }).vm.modelValue)
+ .to.deep.equal(targetSets.B.targets);
});
-test('should correctly handle null target set', async () => {
- // Initialize localStorage
- localStorage.setItem('running-tools.split-calculator-options', JSON.stringify({
- selectedTargetSet: 'does_not_exist',
- }));
-
+test('should save global options to localStorage when modified', async () => {
// Initialize component
const wrapper = shallowMount(SplitCalculator);
- // Assert selection is loaded
- expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({
- selectedTargetSet: 'does_not_exist',
- });
+ // Set default units setting
+ await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
+ defaultUnitSystem: 'metric',
+ racePredictionOptions: {
+ model: 'AverageModel',
+ riegelExponent: 1.06,
+ },
+ }, 'globalOptions');
- // Assert empty array passed to SplitOutputTable
- expect(wrapper.findComponent({ name: 'split-output-table' }).vm.modelValue).to.deep.equal([]);
+ // New default units should be saved to localStorage
+ expect(localStorage.getItem('running-tools.global-options')).to.equal(JSON.stringify({
+ defaultUnitSystem: 'metric',
+ racePredictionOptions: {
+ model: 'AverageModel',
+ riegelExponent: 1.06,
+ },
+ }));
- // Switch to valid target set
+ // Update default units setting
await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
- selectedTargetSet: '_split_targets',
- }, 'options');
+ defaultUnitSystem: 'imperial',
+ racePredictionOptions: {
+ model: 'AverageModel',
+ riegelExponent: 1.06,
+ },
+ }, 'globalOptions');
- // Assert non-empty target set passed to SplitOutputTable
- expect(wrapper.findComponent({ name: 'split-output-table' }).vm.modelValue).to.deep.equal([
- { type: 'distance', distanceValue: 1, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 2, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' },
- ]);
+ // New default units should be saved to localStorage
+ expect(localStorage.getItem('running-tools.global-options')).to.equal(JSON.stringify({
+ defaultUnitSystem: 'imperial',
+ racePredictionOptions: {
+ model: 'AverageModel',
+ riegelExponent: 1.06,
+ },
+ }));
});
-test('should update targets in localStorage when modified by splitOutputTable', async () => {
- // Initialize localStorage
- localStorage.setItem('running-tools.split-calculator-target-sets', JSON.stringify({
+test('should save local options and target sets to localStorage when modified', async () => {
+ const targetSets1 = {
'_split_targets': {
name: 'Split targets',
targets: [
- { type: 'distance', distanceValue: 1, distanceUnit: 'kilometers', split: 180 },
- { type: 'distance', distanceValue: 2, distanceUnit: 'kilometers', split: 180 },
- { type: 'distance', distanceValue: 3000, distanceUnit: 'meters', split: 180 },
+ { type: 'distance', distanceValue: 1, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 2, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' },
],
},
- }));
-
- // Initialize component
- const wrapper = shallowMount(SplitCalculator);
-
- // Update split times
- await wrapper.findComponent({ name: 'split-output-table' }).setValue([
- { type: 'distance', distanceValue: 1, distanceUnit: 'kilometers', split: 180 },
- { type: 'distance', distanceValue: 2, distanceUnit: 'kilometers', split: 190 },
- { type: 'distance', distanceValue: 3000, distanceUnit: 'meters', split: 200 },
- ]);
-
- // Assert targets correctly saved in localStorage
- expect(localStorage.getItem('running-tools.split-calculator-target-sets')).to.equal(JSON.stringify({
- '_split_targets': {
- name: 'Split targets',
+ 'B': {
+ name: 'Split targets #2',
targets: [
{ type: 'distance', distanceValue: 1, distanceUnit: 'kilometers', split: 180 },
{ type: 'distance', distanceValue: 2, distanceUnit: 'kilometers', split: 190 },
{ type: 'distance', distanceValue: 3000, distanceUnit: 'meters', split: 200 },
],
},
- }));
-});
+ };
+ const targetSets2 = {
+ '_split_targets': {
+ name: 'Split targets',
+ targets: [
+ { type: 'distance', distanceValue: 1, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 2, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' },
+ ],
+ },
+ 'B': {
+ name: 'Split targets #2',
+ targets: [
+ // split times modified:
+ { type: 'distance', distanceValue: 1, distanceUnit: 'kilometers', split: 185 },
+ { type: 'distance', distanceValue: 2, distanceUnit: 'kilometers', split: 195 },
+ { type: 'distance', distanceValue: 3000, distanceUnit: 'meters', split: 205 },
+ ],
+ },
+ };
-test('should save selected target set to localStorage when modified', async () => {
// Initialize component
const wrapper = shallowMount(SplitCalculator);
- // Select a new target set
+ // Update target sets and selected target set via AdvancedOptionsInput
+ await wrapper.findComponent({ name: 'advanced-options-input' }).setValue(targetSets1,
+ 'targetSets');
await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
- selectedTargetSet: '_race_targets',
+ selectedTargetSet: 'B',
}, 'options');
- // New selected target set should be saved to localStorage
+ // Assert data saved to localStorage
+ expect(localStorage.getItem('running-tools.split-calculator-target-sets'))
+ .to.equal(JSON.stringify(targetSets1));
expect(localStorage.getItem('running-tools.split-calculator-options')).to.equal(JSON.stringify({
- selectedTargetSet: '_race_targets',
+ selectedTargetSet: 'B',
}));
-});
-
-test('should load default units from localStorage and pass to splitOutputTable', async () => {
- // Initialize localStorage
- localStorage.setItem('running-tools.default-unit-system', '"metric"');
-
- // Initialize component
- const wrapper = shallowMount(SplitCalculator);
- // Assert default units setting is initialy loaded
- expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.defaultUnitSystem)
- .to.equal('metric');
-
- // Assert prop is correct
- expect(wrapper.findComponent({ name: 'split-output-table' }).vm.defaultUnitSystem)
- .to.equal('metric');
+ // Update target sets via SplitOutputTable
+ await wrapper.findComponent({ name: 'split-output-table' }).setValue(targetSets2.B.targets);
- // Change default units
- await wrapper.findComponent({ name: 'advanced-options-input' })
- .setValue('imperial', 'defaultUnitSystem');
-
- // Assert prop is correct
- expect(wrapper.findComponent({ name: 'split-output-table' }).vm.defaultUnitSystem)
- .to.equal('imperial');
+ // Assert data saved to localStorage
+ expect(localStorage.getItem('running-tools.split-calculator-target-sets'))
+ .to.equal(JSON.stringify(targetSets2));
+ expect(localStorage.getItem('running-tools.split-calculator-options')).to.equal(JSON.stringify({
+ selectedTargetSet: 'B',
+ }));
});
-test('should save default units setting to localStorage when modified', async () => {
+test('should correctly handle null target set', async () => {
+ // Initialize localStorage
+ localStorage.setItem('running-tools.split-calculator-options', JSON.stringify({
+ selectedTargetSet: 'does_not_exist',
+ }));
+
// Initialize component
const wrapper = shallowMount(SplitCalculator);
- // Set default units setting
- await wrapper.findComponent({ name: 'advanced-options-input' })
- .setValue('metric', 'defaultUnitSystem');
+ // Assert selection is loaded
+ expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({
+ selectedTargetSet: 'does_not_exist',
+ });
- // New default units should be saved to localStorage
- expect(localStorage.getItem('running-tools.default-unit-system')).to.equal('"metric"');
+ // Assert empty array passed to SplitOutputTable
+ expect(wrapper.findComponent({ name: 'split-output-table' }).vm.modelValue).to.deep.equal([]);
- // Set default units setting
- await wrapper.findComponent({ name: 'advanced-options-input' })
- .setValue('imperial', 'defaultUnitSystem');
+ // Switch to valid target set
+ await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
+ selectedTargetSet: '_split_targets',
+ }, 'options');
- // New default units should be saved to localStorage
- expect(localStorage.getItem('running-tools.default-unit-system')).to.equal('"imperial"');
+ // Assert non-empty target set passed to SplitOutputTable
+ expect(wrapper.findComponent({ name: 'split-output-table' }).vm.modelValue).to.deep.equal([
+ { type: 'distance', distanceValue: 1, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 2, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' },
+ ]);
});
test('should correctly set AdvancedOptionsInput type prop', async () => {
diff --git a/tests/unit/views/WorkoutCalculator.spec.js b/tests/unit/views/WorkoutCalculator.spec.js
@@ -5,94 +5,29 @@ import { defaultTargetSets } from '@/core/targets';
beforeEach(() => {
localStorage.clear();
-})
-
-test('should correctly predict workout splits', async () => {
- // Initialize component
- const wrapper = shallowMount(WorkoutCalculator);
-
- // Enter input race data
- await wrapper.findComponent({ name: 'pace-input' }).setValue({
- distanceValue: 5,
- distanceUnit: 'kilometers',
- time: 1200,
- });
-
- // Calculate result
- const calculateResult = wrapper.findComponent({ name: 'single-output-table' }).vm.calculateResult;
- const result = calculateResult({
- splitValue: 1, splitUnit: 'kilometers',
- type: 'distance', distanceValue: 10, distanceUnit: 'kilometers',
- });
-
- // Assert result is correct
- expect(result.key).to.equal('1 km @ 10 km');
- expect(result.value).to.equal('4:09.48');
- expect(result.result).to.equal('value');
- expect(result.sort).to.be.closeTo(249.48, 0.01);
});
-test('should correctly handle null target set', async () => {
- // Initialize component
- const wrapper = shallowMount(WorkoutCalculator);
-
- // Switch to invalid target set
- await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
- customTargetNames: false,
- model: 'AverageModel',
- riegelExponent: 1.06,
- selectedTargetSet: 'does_not_exist',
- }, 'options');
-
- // Assert empty array passed to SingleOutputTable component
- expect(wrapper.findComponent({ name: 'single-output-table' }).vm.targets).to.deep.equal([]);
-
- // Switch to valid target set
- await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
- customTargetNames: false,
- model: 'AverageModel',
- riegelExponent: 1.06,
- selectedTargetSet: '_workout_targets',
- }, 'options');
-
- // Assert valid targets passed to SingleOutputTable component
- const workoutTargets = defaultTargetSets._workout_targets.targets;
- expect(wrapper.findComponent({ name: 'single-output-table' }).vm.targets)
- .to.deep.equal(workoutTargets);
-});
+test('should load global options from localStorage', async () => {
+ // Initialize localStorage
+ localStorage.setItem('running-tools.global-options', JSON.stringify({
+ defaultUnitSystem: 'imperial',
+ racePredictionOptions: {
+ model: 'PurdyPointsModel',
+ riegelExponent: 1.2,
+ },
+ }));
-test('should correctly calculate results according to advanced model options', async () => {
// Initialize component
const wrapper = shallowMount(WorkoutCalculator);
- // Enter input race data
- await wrapper.findComponent({ name: 'pace-input' }).setValue({
- distanceValue: 5,
- distanceUnit: 'kilometers',
- time: 1200,
- });
-
- // Update model and Riegel Exponent
- await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
- customTargetNames: false,
- predictionOptions: {
- model: 'RiegelModel',
- riegelExponent: 1.10,
+ // Assert data loaded
+ expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.globalOptions).to.deep.equal({
+ defaultUnitSystem: 'imperial',
+ racePredictionOptions: {
+ model: 'PurdyPointsModel',
+ riegelExponent: 1.2,
},
- selectedTargetSet: '_workout_targets',
- }, 'options');
-
- // 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',
});
-
- // Assert result is correct
- expect(result.key).to.equal('1 km @ 10 km');
- expect(result.value).to.equal('4:17.23');
});
test('should load input race from localStorage', async () => {
@@ -114,6 +49,94 @@ test('should load input race from localStorage', async () => {
});
});
+test('should load local options and target sets from localStorage', async () => {
+ // Initialize localStorage
+ const targetSets = {
+ '_workout_targets': {
+ name: 'Workout targets #1',
+ targets: [
+ {
+ splitValue: 400, splitUnit: 'meters',
+ type: 'distance', distanceValue: 1, distanceUnit: 'miles',
+ },
+ {
+ splitValue: 800, splitUnit: 'meters',
+ type: 'distance', distanceValue: 5, distanceUnit: 'kilometers',
+ },
+ {
+ splitValue: 1600, splitUnit: 'meters',
+ type: 'time', time: 3600,
+ },
+ {
+ splitValue: 2, splitUnit: 'miles',
+ type: 'time', time: 7200,
+ },
+ ],
+ },
+ 'B': {
+ name: 'Workout targets #2',
+ targets: [
+ {
+ distanceUnit: 'miles', distanceValue: 2,
+ splitUnit: 'meters', splitValue: 400,
+ type: 'distance',
+ },
+ {
+ time: 6000,
+ splitUnit: 'kilometers', splitValue: 2,
+ type: 'time',
+ },
+ {
+ distanceUnit: 'kilometers', distanceValue: 5,
+ splitUnit: 'miles', splitValue: 1,
+ type: 'distance'
+ },
+ ],
+ },
+ };
+ localStorage.setItem('running-tools.workout-calculator-target-sets', JSON.stringify(targetSets));
+ localStorage.setItem('running-tools.workout-calculator-options', JSON.stringify({
+ customTargetNames: true,
+ selectedTargetSet: 'B',
+ }));
+
+ // Initialize component
+ const wrapper = shallowMount(WorkoutCalculator);
+
+ // Assert data loaded
+ expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({
+ customTargetNames: true,
+ selectedTargetSet: 'B',
+ });
+ expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.targetSets)
+ .to.deep.equal(targetSets);
+ expect(wrapper.findComponent({ name: 'single-output-table' }).vm.targets)
+ .to.deep.equal(targetSets.B.targets);
+});
+
+test('should save global options to localStorage when modified', async () => {
+ // Initialize component
+ const wrapper = shallowMount(WorkoutCalculator);
+
+ // Update options
+ await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
+ defaultUnitSystem: 'imperial',
+ racePredictionOptions: {
+ model: 'CameronModel',
+ riegelExponent: 1.3,
+ },
+ }, 'globalOptions');
+
+ // Assert data saved to localStorage
+ expect(localStorage.getItem('running-tools.global-options')).to.equal(JSON.stringify({
+ defaultUnitSystem: 'imperial',
+ racePredictionOptions: {
+ model: 'CameronModel',
+ riegelExponent: 1.3,
+ },
+ }));
+});
+
test('should save input race to localStorage', async () => {
// Initialize component
const wrapper = shallowMount(WorkoutCalculator);
@@ -133,43 +156,8 @@ test('should save input race to localStorage', async () => {
}));
});
-test('should save default units setting to localStorage when modified', async () => {
- // Initialize component
- const wrapper = shallowMount(WorkoutCalculator);
-
- // Change default units
- await wrapper.findComponent({ name: 'advanced-options-input' })
- .setValue('metric', 'defaultUnitSystem');
- await wrapper.findComponent({ name: 'advanced-options-input' })
- .setValue('imperial', 'defaultUnitSystem');
-
- // New default units should be saved to localStorage
- expect(localStorage.getItem('running-tools.default-unit-system')).to.equal('"imperial"');
-});
-
-test('should load options from localStorage', async () => {
- // Initialize localStorage
- const targetSet2 = {
- name: 'Workout targets #2',
- targets: [
- {
- distanceUnit: 'miles', distanceValue: 2,
- splitUnit: 'meters', splitValue: 400,
- type: 'distance',
- },
- {
- time: 6000,
- splitUnit: 'kilometers', splitValue: 2,
- type: 'time',
- },
- {
- distanceUnit: 'kilometers', distanceValue: 5,
- splitUnit: 'miles', splitValue: 1,
- type: 'distance'
- },
- ],
- };
- localStorage.setItem('running-tools.workout-calculator-target-sets', JSON.stringify({
+test('should save local options and target sets to localStorage when modified', async () => {
+ const targetSets = {
'_workout_targets': {
name: 'Workout targets #1',
targets: [
@@ -191,48 +179,146 @@ test('should load options from localStorage', async () => {
},
],
},
- 'B': targetSet2,
- }));
- localStorage.setItem('running-tools.workout-calculator-options', JSON.stringify({
+ 'B': {
+ name: 'Workout targets #2',
+ targets: [
+ {
+ distanceUnit: 'miles', distanceValue: 2,
+ splitUnit: 'meters', splitValue: 400,
+ type: 'distance',
+ },
+ {
+ time: 6000,
+ splitUnit: 'kilometers', splitValue: 2,
+ type: 'time',
+ },
+ {
+ distanceUnit: 'kilometers', distanceValue: 5,
+ splitUnit: 'miles', splitValue: 1,
+ type: 'distance'
+ },
+ ],
+ },
+ };
+
+ // Initialize component
+ const wrapper = shallowMount(WorkoutCalculator);
+
+ // Update target sets, selected target set, and target name customization
+ await wrapper.findComponent({ name: 'advanced-options-input' }).setValue(targetSets,
+ 'targetSets');
+ await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
+ customTargetNames: true,
+ selectedTargetSet: 'B',
+ }, 'options');
+
+ // Assert data saved to localStorage
+ expect(localStorage.getItem('running-tools.workout-calculator-target-sets'))
+ .to.equal(JSON.stringify(targetSets));
+ expect(localStorage.getItem('running-tools.workout-calculator-options')).to.equal(JSON.stringify({
customTargetNames: true,
- model: 'PurdyPointsModel',
- riegelExponent: 1.2,
selectedTargetSet: 'B',
}));
+});
+test('should correctly predict workout splits', async () => {
// Initialize component
const wrapper = shallowMount(WorkoutCalculator);
- // Assert data loaded
- expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({
- customTargetNames: true,
- model: 'PurdyPointsModel',
- riegelExponent: 1.2,
- selectedTargetSet: 'B',
+ // Enter input race data
+ await wrapper.findComponent({ name: 'pace-input' }).setValue({
+ distanceValue: 5,
+ distanceUnit: 'kilometers',
+ time: 1200,
+ });
+
+ // Calculate result
+ const calculateResult = wrapper.findComponent({ name: 'single-output-table' }).vm.calculateResult;
+ const result = calculateResult({
+ splitValue: 1, splitUnit: 'kilometers',
+ type: 'distance', distanceValue: 10, distanceUnit: 'kilometers',
});
+
+ // Assert result is correct
+ expect(result.key).to.equal('1 km @ 10 km');
+ expect(result.value).to.equal('4:09.48');
+ expect(result.result).to.equal('value');
+ expect(result.sort).to.be.closeTo(249.48, 0.01);
+});
+
+test('should correctly handle null target set', async () => {
+ // Initialize component
+ const wrapper = shallowMount(WorkoutCalculator);
+
+ // Switch to invalid target set
+ await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
+ customTargetNames: false,
+ selectedTargetSet: 'does_not_exist',
+ }, 'options');
+
+ // Assert empty array passed to SingleOutputTable component
+ expect(wrapper.findComponent({ name: 'single-output-table' }).vm.targets).to.deep.equal([]);
+
+ // Switch to valid target set
+ await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
+ customTargetNames: false,
+ selectedTargetSet: '_workout_targets',
+ }, 'options');
+
+ // Assert valid targets passed to SingleOutputTable component
+ const workoutTargets = defaultTargetSets._workout_targets.targets;
expect(wrapper.findComponent({ name: 'single-output-table' }).vm.targets)
- .to.deep.equal(targetSet2.targets);
+ .to.deep.equal(workoutTargets);
});
-test('should save options to localStorage when modified', async () => {
+test('should correctly calculate results according to options', async () => {
// Initialize component
const wrapper = shallowMount(WorkoutCalculator);
- // Update options
+ // Enter input race data
+ await wrapper.findComponent({ name: 'pace-input' }).setValue({
+ distanceValue: 5,
+ distanceUnit: 'kilometers',
+ time: 1200,
+ });
+
+ // Update model and Riegel exponent
+ await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
+ defaultUnitSystem: 'imperial',
+ racePredictionOptions: {
+ model: 'RiegelModel',
+ riegelExponent: 1.10,
+ },
+ }, 'globalOptions');
+
+ // 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',
+ });
+
+ // Assert result is correct
+ expect(result.key).to.equal('1 km @ 10 km');
+ expect(result.value).to.equal('4:17.23');
+
+ // Update target name customization
await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
customTargetNames: true,
- model: 'CameronModel',
- riegelExponent: 1.3,
- selectedTargetSet: 'B',
+ selectedTargetSet: '_workout_targets',
}, 'options');
- // Assert data saved to localStorage
- expect(localStorage.getItem('running-tools.workout-calculator-options')).to.equal(JSON.stringify({
- customTargetNames: true,
- model: 'CameronModel',
- riegelExponent: 1.3,
- selectedTargetSet: 'B',
- }));
+ // Calculate result
+ result = calculateResult({
+ customName: 'foo',
+ splitValue: 1, splitUnit: 'kilometers',
+ type: 'distance', distanceValue: 10, distanceUnit: 'kilometers',
+ });
+
+ // Assert result is correct
+ expect(result.key).to.equal('foo');
+ expect(result.value).to.equal('4:17.23');
});
test('should correctly set AdvancedOptionsInput type prop', async () => {