running-tools

A collection of tools for runners and their coaches
git clone https://git.ashermorgan.net/running-tools/
Log | Files | Refs | README

commit edcfdfbc9fa27f940f1b3accde172f5e96b490b7
parent 3ebace2a5a49947c003efcea22c3e8be79b0e031
Author: Asher Morgan <59518073+ashermorgan@users.noreply.github.com>
Date:   Sun,  6 Jul 2025 15:06:19 -0700

Extract advanced calculator options into component

Diffstat:
Asrc/components/AdvancedOptionsInput.vue | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/utils/calculators.ts | 12++++++++----
Msrc/views/BatchCalculator.vue | 74++++++++++++++++++++------------------------------------------------------
Msrc/views/PaceCalculator.vue | 18++++--------------
Msrc/views/RaceCalculator.vue | 20++++----------------
Msrc/views/SplitCalculator.vue | 20++++----------------
Msrc/views/WorkoutCalculator.vue | 30+++++-------------------------
Atests/unit/components/AdvancedOptionsInput.spec.js | 292+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtests/unit/views/BatchCalculator.spec.js | 56+++++++++++++++++++++++++++++++++++---------------------
Mtests/unit/views/PaceCalculator.spec.js | 28++++++++++++++++------------
Mtests/unit/views/RaceCalculator.spec.js | 55++++++++++++++++++++++++++++++-------------------------
Mtests/unit/views/SplitCalculator.spec.js | 48+++++++++++++++++++++++++++++-------------------
Mtests/unit/views/WorkoutCalculator.spec.js | 54+++++++++++++++++++++++++++++++++---------------------
13 files changed, 568 insertions(+), 227 deletions(-)

diff --git a/src/components/AdvancedOptionsInput.vue b/src/components/AdvancedOptionsInput.vue @@ -0,0 +1,88 @@ +<template> + <div> + Default units: + <select v-model="defaultUnitSystem" aria-label="Default units"> + <option value="imperial">Miles</option> + <option value="metric">Kilometers</option> + </select> + </div> + + <div> + Target Set: + <target-set-selector :setType="props.type === Calculators.Workout ? TargetSetTypes.Workout : + (props.type === Calculators.Split ? TargetSetTypes.Split : TargetSetTypes.Standard)" + v-model:selected-target-set="options.selectedTargetSet" + v-model:target-sets="targetSets" :default-unit-system="defaultUnitSystem" + :customWorkoutNames="props.type === Calculators.Workout ? + (options as WorkoutOptions).customTargetNames : false"/> + </div> + + <div v-if="props.type === Calculators.Workout"> + Target Name Customization: + <select v-model="(options as WorkoutOptions).customTargetNames" + aria-label="Target name customization"> + <option :value="false">Disabled</option> + <option :value="true">Enabled</option> + </select> + </div> + + <div v-if="props.type === Calculators.Race || props.type === Calculators.Workout"> + Prediction Model: + <select v-model="(options as RaceOptions).model" aria-label="Prediction model"> + <option value="AverageModel">Average</option> + <option value="PurdyPointsModel">Purdy Points Model</option> + <option value="VO2MaxModel">V&#775;O&#8322; Max Model</option> + <option value="CameronModel">Cameron's Model</option> + <option value="RiegelModel">Riegel's Model</option> + </select> + </div> + + <div v-if="props.type === Calculators.Race || props.type === Calculators.Workout"> + Riegel Exponent: + <decimal-input v-model="(options as RaceOptions).riegelExponent" + aria-label="Riegel exponent" :min="1" :max="1.3" :digits="2" :step="0.01"/> + (default: 1.06) + </div> +</template> + +<script setup lang="ts"> +import { Calculators } from '@/utils/calculators'; +import type { CalculatorOptions, RaceOptions, WorkoutOptions } from '@/utils/calculators'; +import { TargetSetTypes } from '@/utils/targets'; +import type { TargetSets } from '@/utils/targets'; +import { UnitSystems } from '@/utils/units'; + +import DecimalInput from '@/components/DecimalInput.vue'; +import TargetSetSelector from '@/components/TargetSetSelector.vue'; + +import useObjectModel from '@/composables/useObjectModel'; + +/* + * The default unit system + */ +const defaultUnitSystem = defineModel<UnitSystems>('defaultUnitSystem'); + +const props = defineProps<{ + /* + * The calculator options + */ + options: CalculatorOptions, + + /* + * The calculator type + */ + type: Calculators, + + /* + * The calculator target sets + */ + targetSets: TargetSets, +}>(); + +// Generate internal refs tied to options and targetSets props +const emit = defineEmits(['update:options', 'update:targetSets']); +const options = useObjectModel<CalculatorOptions>(() => props.options, (x) => + emit('update:options', x)); +const targetSets = useObjectModel<TargetSets>(() => props.targetSets, (x) => + emit('update:targetSets', x)); +</script> diff --git a/src/utils/calculators.ts b/src/utils/calculators.ts @@ -7,11 +7,14 @@ import { DistanceUnits, DistanceUnitData, UnitSystems, convertDistance, getDefaultDistanceUnit } from '@/utils/units'; import type { DistanceTime } from '@/utils/units'; +/* + * The four main calculators (batch and unit calculators not included) + */ export enum Calculators { - Pace, - Race, - Split, - Workout, + Pace = 'pace', + Race = 'race', + Split = 'split', + Workout = 'workout', } /* @@ -37,6 +40,7 @@ export interface RaceOptions extends StandardOptions { export interface WorkoutOptions extends RaceOptions { customTargetNames: boolean, }; +export type CalculatorOptions = StandardOptions | RaceOptions | WorkoutOptions; /* * The two possible result fields of a target result: "key" and "value" diff --git a/src/views/BatchCalculator.vue b/src/views/BatchCalculator.vue @@ -27,32 +27,8 @@ <summary> <h2>Advanced Options</h2> </summary> - <div> - Default units: - <select v-model="defaultUnitSystem" aria-label="Default units"> - <option value="imperial">Miles</option> - <option value="metric">Kilometers</option> - </select> - </div> - <div> - Target Set: - <target-set-selector v-model:selectedTargetSet="calcOptions.selectedTargetSet" - :set-type="options.calculator === BatchCompatableCalculators.Workout ? - targetUtils.TargetSetTypes.Workout : targetUtils.TargetSetTypes.Standard" - v-model:targetSets="targetSets" :default-unit-system="defaultUnitSystem" - :customWorkoutNames="options.calculator === BatchCompatableCalculators.Workout ? - (calcOptions as WorkoutOptions).customTargetNames : false"/> - </div> - <div v-if="options.calculator === 'workout'"> - Target Name Customization: - <select v-model="(calcOptions as WorkoutOptions).customTargetNames" - aria-label="Target name customization"> - <option :value="false">Disabled</option> - <option :value="true">Enabled</option> - </select> - </div> - <race-options-input v-if="options.calculator !== BatchCompatableCalculators.Pace" - v-model="calcOptions as RaceOptions"/> + <advanced-options-input v-model:defaultUnitSystem="defaultUnitSystem" + v-model:options="calcOptions" v-model:targetSets="targetSets" :type="options.calculator"/> </details> <h2>Batch Results</h2> @@ -74,29 +50,19 @@ import * as targetUtils from '@/utils/targets'; import { DistanceUnits, UnitSystems, detectDefaultUnitSystem } from '@/utils/units'; import type { Distance, DistanceTime } from '@/utils/units'; +import AdvancedOptionsInput from '@/components/AdvancedOptionsInput.vue'; import DoubleOutputTable from '@/components/DoubleOutputTable.vue'; import IntegerInput from '@/components/IntegerInput.vue'; import PaceInput from '@/components/PaceInput.vue'; -import RaceOptionsInput from '@/components/RaceOptionsInput.vue'; -import TargetSetSelector from '@/components/TargetSetSelector.vue'; import TimeInput from '@/components/TimeInput.vue'; import useStorage from '@/composables/useStorage'; /* - * The calculators that may be used from within the batch calculator - */ -enum BatchCompatableCalculators { - Pace = 'pace', - Race = 'race', - Workout = 'workout', -}; - -/* * The type for options specific to the batch calculator */ interface BatchCalculatorOptions { - calculator: BatchCompatableCalculators, + calculator: calcUtils.Calculators, increment: number, rows: number, }; @@ -114,7 +80,7 @@ const input = useStorage<DistanceTime>('batch-calculator-input', { * The batch input options */ const options = useStorage<BatchCalculatorOptions>('batch-calculator-options', { - calculator: BatchCompatableCalculators.Workout, + calculator: calcUtils.Calculators.Workout, increment: 15, rows: 20, }); @@ -180,30 +146,30 @@ const inputTimes = computed<Array<number>>(() => { const targetSets = computed<targetUtils.TargetSets>({ get: () => { switch (options.value.calculator) { - case (BatchCompatableCalculators.Pace): { + case (calcUtils.Calculators.Pace): { return paceTargetSets.value; } - case (BatchCompatableCalculators.Race): { + case (calcUtils.Calculators.Race): { return raceTargetSets.value; } default: - case (BatchCompatableCalculators.Workout): { + case (calcUtils.Calculators.Workout): { return workoutTargetSets.value; } } }, set: (newValue: targetUtils.TargetSets) => { switch (options.value.calculator) { - case (BatchCompatableCalculators.Pace): { + case (calcUtils.Calculators.Pace): { paceTargetSets.value = newValue as targetUtils.StandardTargetSets; break; } - case (BatchCompatableCalculators.Race): { + case (calcUtils.Calculators.Race): { raceTargetSets.value = newValue as targetUtils.StandardTargetSets; break; } default: - case (BatchCompatableCalculators.Workout): { + case (calcUtils.Calculators.Workout): { workoutTargetSets.value = newValue as targetUtils.WorkoutTargetSets; break; } @@ -217,30 +183,30 @@ const targetSets = computed<targetUtils.TargetSets>({ const calcOptions = computed<StandardOptions | RaceOptions | WorkoutOptions>({ get: () => { switch (options.value.calculator) { - case (BatchCompatableCalculators.Pace): { + case (calcUtils.Calculators.Pace): { return paceOptions.value; } - case (BatchCompatableCalculators.Race): { + case (calcUtils.Calculators.Race): { return raceOptions.value; } default: - case (BatchCompatableCalculators.Workout): { + case (calcUtils.Calculators.Workout): { return workoutOptions.value; } } }, set: (newValue: StandardOptions | RaceOptions | WorkoutOptions) => { switch(options.value.calculator) { - case (BatchCompatableCalculators.Pace): { + case (calcUtils.Calculators.Pace): { paceOptions.value = newValue as StandardOptions; break; } - case (BatchCompatableCalculators.Race): { + case (calcUtils.Calculators.Race): { raceOptions.value = newValue as RaceOptions; break; } default: - case (BatchCompatableCalculators.Workout): { + case (calcUtils.Calculators.Workout): { workoutOptions.value = newValue as WorkoutOptions; break; } @@ -253,15 +219,15 @@ const calcOptions = computed<StandardOptions | RaceOptions | WorkoutOptions>({ */ const calculateResult = computed<(x: DistanceTime, y: targetUtils.Target) => TargetResult>(() => { switch(options.value.calculator) { - case (BatchCompatableCalculators.Pace): { + case (calcUtils.Calculators.Pace): { return (x,y) => calcUtils.calculatePaceResults(x, y, defaultUnitSystem.value, false); } - case (BatchCompatableCalculators.Race): { + case (calcUtils.Calculators.Race): { return (x,y) => calcUtils.calculateRaceResults(x, y, raceOptions.value, defaultUnitSystem.value, false); } default: - case (BatchCompatableCalculators.Workout): { + case (calcUtils.Calculators.Workout): { return (x,y) => calcUtils.calculateWorkoutResults(x, y as targetUtils.WorkoutTarget, workoutOptions.value, false); } diff --git a/src/views/PaceCalculator.vue b/src/views/PaceCalculator.vue @@ -9,18 +9,8 @@ <summary> <h2>Advanced Options</h2> </summary> - <div> - Default units: - <select v-model="defaultUnitSystem" aria-label="Default units"> - <option value="imperial">Miles</option> - <option value="metric">Kilometers</option> - </select> - </div> - <div> - Target Set: - <target-set-selector v-model:selectedTargetSet="options.selectedTargetSet" - v-model:targetSets="targetSets" :default-unit-system="defaultUnitSystem"/> - </div> + <advanced-options-input v-model:defaultUnitSystem="defaultUnitSystem" + v-model:options="options" v-model:targetSets="targetSets" :type="Calculators.Pace"/> </details> <h2>Equivalent Paces</h2> @@ -32,16 +22,16 @@ </template> <script setup lang="ts"> -import { calculatePaceResults } from '@/utils/calculators'; +import { Calculators, calculatePaceResults } from '@/utils/calculators'; import type { StandardOptions } from '@/utils/calculators'; import { defaultTargetSets } from '@/utils/targets'; import type { StandardTargetSets } from '@/utils/targets'; import { DistanceUnits, UnitSystems, detectDefaultUnitSystem } from '@/utils/units'; import type { DistanceTime } from '@/utils/units'; +import AdvancedOptionsInput from '@/components/AdvancedOptionsInput.vue'; import PaceInput from '@/components/PaceInput.vue'; import SingleOutputTable from '@/components/SingleOutputTable.vue'; -import TargetSetSelector from '@/components/TargetSetSelector.vue'; import useStorage from '@/composables/useStorage'; diff --git a/src/views/RaceCalculator.vue b/src/views/RaceCalculator.vue @@ -26,19 +26,8 @@ <summary> <h2>Advanced Options</h2> </summary> - <div> - Default units: - <select v-model="defaultUnitSystem" aria-label="Default units"> - <option value="imperial">Miles</option> - <option value="metric">Kilometers</option> - </select> - </div> - <div> - Target Set: - <target-set-selector v-model:selectedTargetSet="options.selectedTargetSet" - v-model:targetSets="targetSets" :default-unit-system="defaultUnitSystem"/> - </div> - <race-options-input v-model="options"/> + <advanced-options-input v-model:defaultUnitSystem="defaultUnitSystem" + v-model:options="options" v-model:targetSets="targetSets" :type="Calculators.Race"/> </details> <h2>Equivalent Race Results</h2> @@ -52,7 +41,7 @@ <script setup lang="ts"> import { computed } from 'vue'; -import { calculateRaceResults, calculateRaceStats } from '@/utils/calculators'; +import { Calculators, calculateRaceResults, calculateRaceStats } from '@/utils/calculators'; import type { RaceOptions, RaceStats } from '@/utils/calculators'; import { formatNumber } from '@/utils/format'; import { RacePredictionModel } from '@/utils/races'; @@ -61,10 +50,9 @@ import type { StandardTargetSets } from '@/utils/targets'; import { DistanceUnits, UnitSystems, detectDefaultUnitSystem } from '@/utils/units'; import type { DistanceTime } from '@/utils/units'; +import AdvancedOptionsInput from '@/components/AdvancedOptionsInput.vue'; import PaceInput from '@/components/PaceInput.vue'; -import RaceOptionsInput from '@/components/RaceOptionsInput.vue'; import SingleOutputTable from '@/components/SingleOutputTable.vue'; -import TargetSetSelector from '@/components/TargetSetSelector.vue'; import useStorage from '@/composables/useStorage'; diff --git a/src/views/SplitCalculator.vue b/src/views/SplitCalculator.vue @@ -1,20 +1,8 @@ <template> <div class="calculator"> <div class="input"> - <div class="default-units"> - Default units: - <select v-model="defaultUnitSystem" aria-label="Default units"> - <option value="imperial">Miles</option> - <option value="metric">Kilometers</option> - </select> - </div> - - <div class="target-set"> - Target Set: - <target-set-selector v-model:selectedTargetSet="options.selectedTargetSet" - :set-type="TargetSetTypes.Split" v-model:targetSets="targetSets" - :default-unit-system="defaultUnitSystem"/> - </div> + <advanced-options-input v-model:defaultUnitSystem="defaultUnitSystem" + v-model:options="options" v-model:targetSets="targetSets" :type="Calculators.Split"/> </div> <div class="output"> @@ -26,14 +14,14 @@ <script setup lang="ts"> import { computed } from 'vue'; +import { Calculators } from '@/utils/calculators'; import type { StandardOptions } from '@/utils/calculators'; import { defaultTargetSets } from '@/utils/targets'; -import { TargetSetTypes } from '@/utils/targets'; import type { SplitTargetSet, SplitTargetSets } from '@/utils/targets'; import { UnitSystems, detectDefaultUnitSystem } from '@/utils/units'; +import AdvancedOptionsInput from '@/components/AdvancedOptionsInput.vue'; import SplitOutputTable from '@/components/SplitOutputTable.vue'; -import TargetSetSelector from '@/components/TargetSetSelector.vue'; import useStorage from '@/composables/useStorage'; diff --git a/src/views/WorkoutCalculator.vue b/src/views/WorkoutCalculator.vue @@ -9,27 +9,8 @@ <summary> <h2>Advanced Options</h2> </summary> - <div> - Default units: - <select v-model="defaultUnitSystem" aria-label="Default units"> - <option value="imperial">Miles</option> - <option value="metric">Kilometers</option> - </select> - </div> - <div> - Target Set: - <target-set-selector v-model:selectedTargetSet="options.selectedTargetSet" - :set-type="TargetSetTypes.Workout" :customWorkoutNames="options.customTargetNames" - v-model:targetSets="targetSets" :default-unit-system="defaultUnitSystem"/> - </div> - <div> - Target Name Customization: - <select v-model="options.customTargetNames" aria-label="Target name customization"> - <option :value="false">Disabled</option> - <option :value="true">Enabled</option> - </select> - </div> - <race-options-input v-model="options"/> + <advanced-options-input v-model:defaultUnitSystem="defaultUnitSystem" + v-model:options="options" v-model:targetSets="targetSets" :type="Calculators.Workout"/> </details> <h2>Workout Splits</h2> @@ -41,18 +22,17 @@ </template> <script setup lang="ts"> -import { calculateWorkoutResults } from '@/utils/calculators'; +import { Calculators, calculateWorkoutResults } from '@/utils/calculators'; import type { WorkoutOptions } from '@/utils/calculators'; import { RacePredictionModel } from '@/utils/races'; -import { TargetSetTypes, defaultTargetSets } from '@/utils/targets'; +import { defaultTargetSets } from '@/utils/targets'; import type { WorkoutTarget, WorkoutTargetSet, WorkoutTargetSets } from '@/utils/targets'; import { DistanceUnits, UnitSystems, detectDefaultUnitSystem } from '@/utils/units'; import type { DistanceTime } from '@/utils/units'; +import AdvancedOptionsInput from '@/components/AdvancedOptionsInput.vue'; import PaceInput from '@/components/PaceInput.vue'; -import RaceOptionsInput from '@/components/RaceOptionsInput.vue'; import SingleOutputTable from '@/components/SingleOutputTable.vue'; -import TargetSetSelector from '@/components/TargetSetSelector.vue'; import useStorage from '@/composables/useStorage'; diff --git a/tests/unit/components/AdvancedOptionsInput.spec.js b/tests/unit/components/AdvancedOptionsInput.spec.js @@ -0,0 +1,292 @@ +import { test, expect } from 'vitest'; +import { shallowMount } from '@vue/test-utils'; +import AdvancedOptionsInput from '@/components/AdvancedOptionsInput.vue'; + +test('should be correctly render pace options according to props', () => { + // Initialize component + const wrapper = shallowMount(AdvancedOptionsInput, { + propsData: { + defaultUnitSystem: 'metric', + options: { + selectedTargetSet: 'B', + }, + targetSets: { + 'A': { + name: '1st target set', + targets: [ + { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 3, distanceUnit: 'miles' }, + ], + }, + 'B': { + name: '2nd target set', + targets: [ + { type: 'distance', distanceValue: 1, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 10, distanceUnit: 'kilometers' }, + ], + }, + }, + type: 'pace', + }, + }); + + // Assert all input fields are correct + expect(wrapper.find('select[aria-label="Default units"]').element.value).to.equal('metric'); + expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.selectedTargetSet).to + .equal('B'); + expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.targetSets).to.deep.equal({ + 'A': { + name: '1st target set', + targets: [ + { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 3, distanceUnit: 'miles' }, + ], + }, + 'B': { + name: '2nd target set', + targets: [ + { type: 'distance', distanceValue: 1, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 10, distanceUnit: 'kilometers' }, + ], + }, + }); + expect(wrapper.findAll('select[aria-label="Target name customization"]')).to.have + .length(0); + expect(wrapper.findAll('select[aria-label="Prediction model"]')).to.have.length(0); + expect(wrapper.findAllComponents({ name: 'decimal-input' })).to.have.length(0); +}); + +test('should be correctly render race options according to props', () => { + // Initialize component + const wrapper = shallowMount(AdvancedOptionsInput, { + propsData: { + defaultUnitSystem: 'metric', + options: { + model: 'PurdyPointsModel', + riegelExponent: 1.2, + selectedTargetSet: '_new', + }, + type: 'race', + targetSets: {}, + }, + }); + + // Assert input fields are correct + expect(wrapper.find('select[aria-label="Default units"]').element.value).to + .equal('metric'); + expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.selectedTargetSet).to + .equal('_new'); + expect(wrapper.findAll('select[aria-label="Target name customization"]')).to.have + .length(0); + expect(wrapper.find('select[aria-label="Prediction model"]').element.value).to + .equal('PurdyPointsModel'); + expect(wrapper.findComponent({ name: 'decimal-input' }).vm.modelValue).to.equal(1.2); +}); + +test('should be correctly render split options according to props', () => { + // Initialize component + const wrapper = shallowMount(AdvancedOptionsInput, { + propsData: { + defaultUnitSystem: 'metric', + options: { + selectedTargetSet: '_new', + }, + targetSets: {}, + type: 'split', + }, + }); + + // Assert input fields are correct + expect(wrapper.find('select[aria-label="Default units"]').element.value).to + .equal('metric'); + expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.selectedTargetSet).to + .equal('_new'); + expect(wrapper.findAll('select[aria-label="Target name customization"]')).to.have + .length(0); + expect(wrapper.findAll('select[aria-label="Prediction model"]')).to.have.length(0); + expect(wrapper.findAllComponents({ name: 'decimal-input' })).to.have.length(0); +}); + +test('should be correctly render workout options according to props', () => { + // Initialize component + const wrapper = shallowMount(AdvancedOptionsInput, { + propsData: { + defaultUnitSystem: 'metric', + options: { + customTargetNames: true, + model: 'PurdyPointsModel', + riegelExponent: 1.2, + selectedTargetSet: '_new', + }, + targetSets: {}, + type: 'workout', + }, + }); + + // Assert input fields are correct + expect(wrapper.find('select[aria-label="Default units"]').element.value).to.equal('metric'); + expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.selectedTargetSet).to + .equal('_new'); + expect(wrapper.find('select[aria-label="Target name customization"]').element.value).to + .equal('true'); + expect(wrapper.find('select[aria-label="Prediction model"]').element.value).to + .equal('PurdyPointsModel'); + expect(wrapper.findComponent({ name: 'decimal-input' }).vm.modelValue).to.equal(1.2); +}); + +test('should pass correct props to TargetSetSelector', async () => { + // Initialize component + const wrapper = shallowMount(AdvancedOptionsInput, { + propsData: { + defaultUnitSystem: 'metric', + options: { + customTargetNames: false, + model: 'AverageModel', + riegelExponent: 1.06, + selectedTargetSet: 'B', + }, + targetSets: { + 'A': { + name: '1st target set v2', + targets: [ + { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 3, distanceUnit: 'miles' }, + ], + }, + 'B': { + name: '2nd target set', + targets: [ + { type: 'distance', distanceValue: 1, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 10, distanceUnit: 'kilometers' }, + ], + }, + }, + type: 'workout', + }, + }); + + // Assert props are correct + expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.selectedTargetSet).to.equal('B'); + expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.targetSets).to.deep.equal({ + 'A': { + name: '1st target set v2', + targets: [ + { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 3, distanceUnit: 'miles' }, + ], + }, + 'B': { + name: '2nd target set', + targets: [ + { type: 'distance', distanceValue: 1, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 10, distanceUnit: 'kilometers' }, + ], + }, + }); + expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.customWorkoutNames) + .to.equal(false); + + // Update options + await wrapper.find('select[aria-label="Target name customization"]').setValue('true'); + + // Assert props are updated + expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.customWorkoutNames) + .to.equal(true); +}); + +test('should emit input events when options are modified', async () => { + // Initialize component + const wrapper = shallowMount(AdvancedOptionsInput, { + propsData: { + defaultUnitSystem: 'metric', + options: { + customTargetNames: false, + model: 'AverageModel', + riegelExponent: 1.06, + selectedTargetSet: '_new', + }, + targetSets: {}, + type: 'workout', + }, + }); + + // Update options + await wrapper.find('select[aria-label="Default units"]').setValue('imperial'); + await wrapper.findComponent({ name: 'target-set-selector' }).setValue({ + 'A': { + name: '1st target set v2', + targets: [ + { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 3, distanceUnit: 'miles' }, + ], + }, + 'B': { + name: '2nd target set', + targets: [ + { type: 'distance', distanceValue: 1, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 10, distanceUnit: 'kilometers' }, + ], + }, + }, 'targetSets'); + await wrapper.findComponent({ name: 'target-set-selector' }).setValue('B', 'selectedTargetSet'); + await wrapper.find('select[aria-label="Target name customization"]').setValue('true'); + await wrapper.find('select[aria-label="Prediction model"]').setValue('CameronModel'); + 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:targetSets']).to.deep.equal([[{ + 'A': { + name: '1st target set v2', + targets: [ + { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 3, distanceUnit: 'miles' }, + ], + }, + 'B': { + name: '2nd target set', + targets: [ + { type: 'distance', distanceValue: 1, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 10, distanceUnit: 'kilometers' }, + ], + }, + }]]); + expect(wrapper.emitted()['update:options']).to.deep.equal([ + [{ + customTargetNames: false, + model: 'AverageModel', + riegelExponent: 1.06, + selectedTargetSet: 'B', + }], + [{ + customTargetNames: true, + model: 'AverageModel', + riegelExponent: 1.06, + selectedTargetSet: 'B', + }], + [{ + customTargetNames: true, + model: 'CameronModel', + riegelExponent: 1.06, + selectedTargetSet: 'B', + }], + [{ + customTargetNames: true, + model: 'CameronModel', + riegelExponent: 1.3, + selectedTargetSet: 'B', + }], + ]); +}); diff --git a/tests/unit/views/BatchCalculator.spec.js b/tests/unit/views/BatchCalculator.spec.js @@ -104,8 +104,7 @@ test('should load default units setting from localStorage', async () => { const wrapper = shallowMount(BatchCalculator); // Assert default units setting loaded - expect(wrapper.find('select[aria-label="Default units"]').element.value).to.equal('metric'); - expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.defaultUnitSystem) + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.defaultUnitSystem) .to.equal('metric'); }); @@ -117,7 +116,8 @@ test('should save default units setting from localStorage when modified', async const wrapper = shallowMount(BatchCalculator); // Change default units setting - await wrapper.find('select[aria-label="Default units"]').setValue('imperial'); + 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"'); @@ -195,32 +195,30 @@ test('should load calculator options from localStorage', async () => { // Assert pace calculator options are loaded await wrapper.find('select[aria-label="Calculator"]').setValue('pace'); - expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.selectedTargetSet).to.equal('A'); + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({ + selectedTargetSet: 'A', + }); expect(wrapper.findComponent({ name: 'double-output-table' }).vm.targets) .to.deep.equal(selectedTargetSets[0].targets); // Assert race calculator options are loaded await wrapper.find('select[aria-label="Calculator"]').setValue('race'); - expect(wrapper.findComponent({ name: 'RaceOptionsInput' }).vm.modelValue).to.deep.equal({ + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({ model: 'PurdyPointsModel', riegelExponent: 1.2, selectedTargetSet: 'C', }); - expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.selectedTargetSet).to.equal('C'); expect(wrapper.findComponent({ name: 'double-output-table' }).vm.targets) .to.deep.equal(selectedTargetSets[1].targets); // Assert workout calculator options are loaded await wrapper.find('select[aria-label="Calculator"]').setValue('workout'); - expect(wrapper.findComponent({ name: 'RaceOptionsInput' }).vm.modelValue).to.deep.equal({ + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({ customTargetNames: true, model: 'RiegelModel', riegelExponent: 1.1, selectedTargetSet: 'E', }); - expect(wrapper.find('select[aria-label="Target name customization"]').element.value) - .to.equal('true'); - expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.selectedTargetSet).to.equal('E'); expect(wrapper.findComponent({ name: 'double-output-table' }).vm.targets) .to.deep.equal(selectedTargetSets[2].targets); }); @@ -297,31 +295,30 @@ test('should save calculator options to localStorage when modified', async () => // Update pace calculator options X await wrapper.find('select[aria-label="Calculator"]').setValue('pace'); - await wrapper.findComponent({ name: 'target-set-selector' }).setValue('A', 'selectedTargetSet'); + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + selectedTargetSet: 'A', + }, 'options'); expect(wrapper.findComponent({ name: 'double-output-table' }).vm.targets) .to.deep.equal(selectedTargetSets[0].targets); // Update race calculator options await wrapper.find('select[aria-label="Calculator"]').setValue('race'); - await wrapper.findComponent({ name: 'RaceOptionsInput' }).setValue({ + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ model: 'PurdyPointsModel', riegelExponent: 1.2, - selectedTargetSet: 'D', - }); - await wrapper.findComponent({ name: 'target-set-selector' }).setValue('C', 'selectedTargetSet'); + selectedTargetSet: 'C', + }, 'options'); expect(wrapper.findComponent({ name: 'double-output-table' }).vm.targets) .to.deep.equal(selectedTargetSets[1].targets); // Update workout calculator options await wrapper.find('select[aria-label="Calculator"]').setValue('workout'); - await wrapper.findComponent({ name: 'RaceOptionsInput' }).setValue({ - customTargetNames: false, + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + customTargetNames: true, model: 'RiegelModel', riegelExponent: 1.1, - selectedTargetSet: 'F', - }); - wrapper.find('select[aria-label="Target name customization"]').setValue('true'); - await wrapper.findComponent({ name: 'target-set-selector' }).setValue('E', 'selectedTargetSet'); + selectedTargetSet: 'E', + }, 'options'); expect(wrapper.findComponent({ name: 'double-output-table' }).vm.targets) .to.deep.equal(selectedTargetSets[2].targets); @@ -399,6 +396,23 @@ test('should pass correct input props to DoubleOutputTable', async () => { ]); }); +test('should correctly set AdvancedOptionsInput type prop', async () => { + // Initialize component + const wrapper = shallowMount(BatchCalculator); + + // Assert prop is 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'); + + // Update race calculator options + await wrapper.find('select[aria-label="Calculator"]').setValue('race'); + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.type).to.equal('race'); + + // Update workout calculator options + await wrapper.find('select[aria-label="Calculator"]').setValue('workout'); + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.type).to.equal('workout'); +}); + test('should correctly calculate outputs', async () => { // Initialize localStorage localStorage.setItem('running-tools.race-calculator-options', JSON.stringify({ diff --git a/tests/unit/views/PaceCalculator.spec.js b/tests/unit/views/PaceCalculator.spec.js @@ -48,7 +48,8 @@ test('should correctly calculate distance results according to default units set }); // Set default units - await wrapper.find('select[aria-label="Default units"]').setValue('metric'); + await wrapper.findComponent({ name: 'advanced-options-input' }) + .setValue('metric', 'defaultUnitSystem'); // Get calculate result function const calculateResult = wrapper.findComponent({ name: 'single-output-table' }).vm.calculateResult; @@ -58,7 +59,8 @@ test('should correctly calculate distance results according to default units set expect(result.key).to.equal('1.61 km'); // Change default units - await wrapper.find('select[aria-label="Default units"]').setValue('imperial'); + await wrapper.findComponent({ name: 'advanced-options-input' }) + .setValue('imperial', 'defaultUnitSystem'); // Assert result is correct result = calculateResult({ type: 'time', time: 600 }); @@ -78,15 +80,15 @@ test('should correctly handle null target set', async () => { const wrapper = shallowMount(PaceCalculator); // Switch to invalid target set - await wrapper.findComponent({ name: 'target-set-selector' }) - .setValue('does_not_exist', 'selectedTargetSet'); + await wrapper.findComponent({ name: 'advanced-options-input' }) + .setValue({ 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: 'target-set-selector' }) - .setValue('_pace_targets', 'selectedTargetSet'); + await wrapper.findComponent({ name: 'advanced-options-input' }) + .setValue({ selectedTargetSet: '_pace_targets' }, 'options'); // Assert valid targets passed to SingleOutputTable component const paceTargets = defaultTargetSets._pace_targets.targets; @@ -161,8 +163,8 @@ test('should load options from localStorage', async () => { const wrapper = shallowMount(PaceCalculator); // Assert selection is loaded - expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.selectedTargetSet) - .to.equal('B'); + 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); }); @@ -172,8 +174,8 @@ test('should save options to localStorage when modified', async () => { const wrapper = shallowMount(PaceCalculator); // Select a new target set - await wrapper.findComponent({ name: 'target-set-selector' }) - .setValue('B', 'selectedTargetSet'); + 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({ @@ -186,8 +188,10 @@ test('should save default units setting to localStorage when modified', async () const wrapper = shallowMount(PaceCalculator); // Change default units - await wrapper.find('select[aria-label="Default units"]').setValue('metric'); - await wrapper.find('select[aria-label="Default units"]').setValue('imperial'); + 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"'); diff --git a/tests/unit/views/RaceCalculator.spec.js b/tests/unit/views/RaceCalculator.spec.js @@ -46,7 +46,8 @@ test('should correctly calculate distance results according to default units set }); // Set default units - await wrapper.find('select[aria-label="Default units"]').setValue('metric'); + await wrapper.findComponent({ name: 'advanced-options-input' }) + .setValue('metric', 'defaultUnitSystem'); // Get calculate result function const calculateResult = wrapper.findComponent({ name: 'single-output-table' }).vm.calculateResult; @@ -60,7 +61,8 @@ test('should correctly calculate distance results according to default units set expect(result.sort).to.equal(2495); // Change default units - await wrapper.find('select[aria-label="Default units"]').setValue('imperial'); + await wrapper.findComponent({ name: 'advanced-options-input' }) + .setValue('imperial', 'defaultUnitSystem'); // Assert result is correct result = calculateResult({ type: 'time', time: 2495 }); @@ -84,15 +86,21 @@ test('should correctly handle null target set', async () => { const wrapper = shallowMount(RaceCalculator); // Switch to invalid target set - await wrapper.findComponent({ name: 'target-set-selector' }) - .setValue('does_not_exist', 'selectedTargetSet'); + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + 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: 'target-set-selector' }) - .setValue('_race_targets', 'selectedTargetSet'); + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + model: 'AverageModel', + riegelExponent: 1.06, + selectedTargetSet: '_race_targets', + }, 'options'); // Assert valid targets passed to SingleOutputTable component const raceTargets = defaultTargetSets._race_targets.targets; @@ -135,11 +143,11 @@ test('should correctly calculate results according to model options', async () = }); // Switch model - await wrapper.findComponent({ name: 'RaceOptionsInput' }).setValue({ - model: 'RiegelModel', - riegelExponent: 1.06, // default value + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + model: 'RiegelModel', // changed from the Riegel Model + riegelExponent: 1.06, selectedTargetSet: '_race_targets', - }); + }, 'options'); // Calculate result const calculateResult = wrapper.findComponent({ name: 'single-output-table' }).vm.calculateResult; @@ -153,11 +161,11 @@ test('should correctly calculate results according to model options', async () = expect(result.value).to.equal('41:41.92'); // Update Riegel Exponent - await wrapper.findComponent({ name: 'RaceOptionsInput' }).setValue({ - model: 'RiegelModel', // existing value - riegelExponent: 1, + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + model: 'RiegelModel', + riegelExponent: 1, // changed from 1.06 selectedTargetSet: '_race_targets', - }); + }, 'options'); // Calculate result result = calculateResult({ @@ -213,8 +221,10 @@ test('should save default units setting to localStorage when modified', async () const wrapper = shallowMount(RaceCalculator); // Change default units - await wrapper.find('select[aria-label="Default units"]').setValue('metric'); - await wrapper.find('select[aria-label="Default units"]').setValue('imperial'); + 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"'); @@ -251,13 +261,11 @@ test('should load options from localStorage', async () => { const wrapper = shallowMount(RaceCalculator); // Assert data loaded - expect(wrapper.findComponent({ name: 'RaceOptionsInput' }).vm.modelValue).to.deep.equal({ + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({ model: 'PurdyPointsModel', riegelExponent: 1.2, selectedTargetSet: 'B', }); - expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.selectedTargetSet) - .to.equal('B'); expect(wrapper.findComponent({ name: 'single-output-table' }).vm.targets) .to.deep.equal(targetSet2.targets); }); @@ -267,13 +275,11 @@ test('should save options to localStorage when modified', async () => { const wrapper = shallowMount(RaceCalculator); // Update options - await wrapper.findComponent({ name: 'RaceOptionsInput' }).setValue({ + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ model: 'CameronModel', riegelExponent: 1.30, - selectedTargetSet: '_race_targets', - }); - await wrapper.findComponent({ name: 'target-set-selector' }) - .setValue('B', 'selectedTargetSet'); + selectedTargetSet: 'B', + }, 'options'); // Assert data saved to localStorage expect(localStorage.getItem('running-tools.race-calculator-options')).to.equal(JSON.stringify({ @@ -282,4 +288,3 @@ test('should save options to localStorage when modified', async () => { selectedTargetSet: 'B', })); }); - diff --git a/tests/unit/views/SplitCalculator.spec.js b/tests/unit/views/SplitCalculator.spec.js @@ -16,7 +16,9 @@ test('should load selected target set from localStorage', async () => { const wrapper = shallowMount(SplitCalculator); // Assert selection is loaded - expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.selectedTargetSet).to.equal('B'); + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({ + selectedTargetSet: 'B', + }); }); test('should load targets from localStorage and pass to splitOutputTable', async () => { @@ -44,8 +46,9 @@ test('should load targets from localStorage and pass to splitOutputTable', async const wrapper = shallowMount(SplitCalculator); // Assert default split targets are initially loaded - expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.selectedTargetSet) - .to.equal('_split_targets'); + 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' }, @@ -53,11 +56,11 @@ test('should load targets from localStorage and pass to splitOutputTable', async ]); // Select a new target set - await wrapper.findComponent({ name: 'target-set-selector' }).setValue('B', 'selectedTargetSet'); + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + selectedTargetSet: 'B', + }, 'options'); // Assert new target set is loaded - expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.selectedTargetSet) - .to.equal('B'); 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 }, @@ -75,15 +78,17 @@ test('should correctly handle null target set', async () => { const wrapper = shallowMount(SplitCalculator); // Assert selection is loaded - expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.selectedTargetSet) - .to.equal('does_not_exist'); + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({ + selectedTargetSet: 'does_not_exist', + }); // Assert empty array passed to SplitOutputTable expect(wrapper.findComponent({ name: 'split-output-table' }).vm.modelValue).to.deep.equal([]); // Switch to valid target set - await wrapper.findComponent({ name: 'target-set-selector' }) - .setValue('_split_targets', 'selectedTargetSet'); + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + selectedTargetSet: '_split_targets', + }, 'options'); // Assert non-empty target set passed to SplitOutputTable expect(wrapper.findComponent({ name: 'split-output-table' }).vm.modelValue).to.deep.equal([ @@ -134,8 +139,9 @@ test('should save selected target set to localStorage when modified', async () = const wrapper = shallowMount(SplitCalculator); // Select a new target set - await wrapper.findComponent({ name: 'target-set-selector' }) - .setValue('_race_targets', 'selectedTargetSet'); + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + selectedTargetSet: '_race_targets', + }, 'options'); // New selected target set should be saved to localStorage expect(localStorage.getItem('running-tools.split-calculator-options')).to.equal(JSON.stringify({ @@ -151,14 +157,16 @@ test('should load default units from localStorage and pass to splitOutputTable', const wrapper = shallowMount(SplitCalculator); // Assert default units setting is initialy loaded - expect(wrapper.find('select', { name: 'Default units' }).element.value).to.equal('metric'); + 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'); // Change default units - await wrapper.find('select').setValue('imperial'); + await wrapper.findComponent({ name: 'advanced-options-input' }) + .setValue('imperial', 'defaultUnitSystem'); // Assert prop is correct expect(wrapper.findComponent({ name: 'split-output-table' }).vm.defaultUnitSystem) @@ -170,22 +178,24 @@ test('should save default units setting to localStorage when modified', async () const wrapper = shallowMount(SplitCalculator); // Set default units setting - await wrapper.find('select').setValue('metric'); + await wrapper.findComponent({ name: 'advanced-options-input' }) + .setValue('metric', 'defaultUnitSystem'); // New default units should be saved to localStorage expect(localStorage.getItem('running-tools.default-unit-system')).to.equal('"metric"'); // Set default units setting - await wrapper.find('select').setValue('imperial'); + 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 correctly set targetSetSelector setType prop', async () => { +test('should correctly set AdvancedOptionsInput type prop', async () => { // Initialize component const wrapper = shallowMount(SplitCalculator); - // Assert setType prop is correctly set - expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.setType).to.equal('split'); + // Assert type prop is correctly set + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.type).to.equal('split'); }); diff --git a/tests/unit/views/WorkoutCalculator.spec.js b/tests/unit/views/WorkoutCalculator.spec.js @@ -37,15 +37,23 @@ test('should correctly handle null target set', async () => { const wrapper = shallowMount(WorkoutCalculator); // Switch to invalid target set - await wrapper.findComponent({ name: 'target-set-selector' }) - .setValue('does_not_exist', 'selectedTargetSet'); + 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: 'target-set-selector' }) - .setValue('_workout_targets', 'selectedTargetSet'); + 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; @@ -65,11 +73,12 @@ test('should correctly calculate results according to advanced model options', a }); // Update model and Riegel Exponent - await wrapper.findComponent({ name: 'RaceOptionsInput' }).setValue({ + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + customTargetNames: false, model: 'RiegelModel', riegelExponent: 1.10, selectedTargetSet: '_workout_targets', - }); + }, 'options'); // Calculate result const calculateResult = wrapper.findComponent({ name: 'single-output-table' }).vm.calculateResult; @@ -127,8 +136,10 @@ test('should save default units setting to localStorage when modified', async () const wrapper = shallowMount(WorkoutCalculator); // Change default units - await wrapper.find('select[aria-label="Default units"]').setValue('metric'); - await wrapper.find('select[aria-label="Default units"]').setValue('imperial'); + 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"'); @@ -191,18 +202,14 @@ test('should load options from localStorage', async () => { const wrapper = shallowMount(WorkoutCalculator); // Assert data loaded - expect(wrapper.findComponent({ name: 'RaceOptionsInput' }).vm.modelValue).to.deep.equal({ + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({ customTargetNames: true, model: 'PurdyPointsModel', riegelExponent: 1.2, selectedTargetSet: 'B', }); - expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.selectedTargetSet) - .to.equal('B'); expect(wrapper.findComponent({ name: 'single-output-table' }).vm.targets) .to.deep.equal(targetSet2.targets); - expect(wrapper.find('select[aria-label="Target name customization"]').element.value) - .to.equal('true'); }); test('should save options to localStorage when modified', async () => { @@ -210,15 +217,12 @@ test('should save options to localStorage when modified', async () => { const wrapper = shallowMount(WorkoutCalculator); // Update options - await wrapper.findComponent({ name: 'RaceOptionsInput' }).setValue({ - customTargetNames: false, + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + customTargetNames: true, model: 'CameronModel', - riegelExponent: 1.30, - selectedTargetSet: '_workout_targets', - }); - wrapper.find('select[aria-label="Target name customization"]').setValue('true'); - await wrapper.findComponent({ name: 'target-set-selector' }) - .setValue('B', 'selectedTargetSet'); + riegelExponent: 1.3, + selectedTargetSet: 'B', + }, 'options'); // Assert data saved to localStorage expect(localStorage.getItem('running-tools.workout-calculator-options')).to.equal(JSON.stringify({ @@ -228,3 +232,11 @@ test('should save options to localStorage when modified', async () => { selectedTargetSet: 'B', })); }); + +test('should correctly set AdvancedOptionsInput type prop', async () => { + // Initialize component + const wrapper = shallowMount(WorkoutCalculator); + + // Assert type prop is correctly set + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.type).to.equal('workout'); +});