diff --git a/src/components/RaceOptions.vue b/src/components/RaceOptions.vue new file mode 100644 index 0000000..1cfef9f --- /dev/null +++ b/src/components/RaceOptions.vue @@ -0,0 +1,30 @@ + + + diff --git a/src/views/RaceCalculator.vue b/src/views/RaceCalculator.vue index dd1a521..00722a9 100644 --- a/src/views/RaceCalculator.vue +++ b/src/views/RaceCalculator.vue @@ -37,22 +37,7 @@ -
- Prediction Model: - -
-
- Riegel Exponent: - - (default: 1.06) -
+

Equivalent Race Results

@@ -69,8 +54,8 @@ import raceUtils from '@/utils/races'; import targetUtils from '@/utils/targets'; import unitUtils from '@/utils/units'; -import DecimalInput from '@/components/DecimalInput.vue'; import PaceInput from '@/components/PaceInput.vue'; +import RaceOptions from '@/components/RaceOptions.vue'; import SimpleTargetTable from '@/components/SimpleTargetTable.vue'; import TargetSetSelector from '@/components/TargetSetSelector.vue'; @@ -93,12 +78,10 @@ const defaultUnitSystem = useStorage('default-unit-system', unitUtils.detectDefa /** * The race prediction model */ -const model = useStorage('race-calculator-model', 'AverageModel'); - -/** -* The value of the exponent in Riegel's Model -*/ -const riegelExponent = useStorage('race-calculator-riegel-exponent', 1.06); +const options = useStorage('race-calculator-options', { + model: 'AverageModel', + riegelExponent: 1.06, +}); /** * The current selected target set @@ -131,11 +114,11 @@ function predictResult(target) { // Get prediction let time; - switch (model.value) { + switch (options.value.model) { default: case 'AverageModel': time = raceUtils.AverageModel.predictTime(d1.value, input.value.time, d2, - riegelExponent.value); + options.value.riegelExponent); break; case 'PurdyPointsModel': time = raceUtils.PurdyPointsModel.predictTime(d1.value, input.value.time, d2); @@ -145,7 +128,7 @@ function predictResult(target) { break; case 'RiegelModel': time = raceUtils.RiegelModel.predictTime(d1.value, input.value.time, d2, - riegelExponent.value); + options.value.riegelExponent); break; case 'CameronModel': time = raceUtils.CameronModel.predictTime(d1.value, input.value.time, d2); @@ -157,11 +140,11 @@ function predictResult(target) { } else { // Get prediction let distance; - switch (model.value) { + switch (options.value.model) { default: case 'AverageModel': distance = raceUtils.AverageModel.predictDistance(input.value.time, d1.value, target.time, - riegelExponent.value); + options.value.riegelExponent); break; case 'PurdyPointsModel': distance = raceUtils.PurdyPointsModel.predictDistance(input.value.time, d1.value, @@ -172,7 +155,7 @@ function predictResult(target) { break; case 'RiegelModel': distance = raceUtils.RiegelModel.predictDistance(input.value.time, d1.value, target.time, - riegelExponent.value); + options.value.riegelExponent); break; case 'CameronModel': distance = raceUtils.CameronModel.predictDistance(input.value.time, d1.value, target.time); diff --git a/tests/unit/components/RaceOptions.spec.js b/tests/unit/components/RaceOptions.spec.js new file mode 100644 index 0000000..1dceea8 --- /dev/null +++ b/tests/unit/components/RaceOptions.spec.js @@ -0,0 +1,38 @@ +import { test, expect } from 'vitest'; +import { shallowMount } from '@vue/test-utils'; +import RaceOptions from '@/components/RaceOptions.vue'; + +test('should be initialized to modelValue', () => { + // Initialize component + const wrapper = shallowMount(RaceOptions, { + propsData: { + modelValue: { + model: 'PurdyPointsModel', + riegelExponent: 1.2, + } + }, + }); + + // Assert input fields are correct + expect(wrapper.find('select').element.value).to.equal('PurdyPointsModel'); + expect(wrapper.findComponent({ name: 'decimal-input' }).vm.modelValue).to.equal(1.2); +}); + +test('should update modelValue when inputs are modified', async () => { + // Initialize component + const wrapper = shallowMount(RaceOptions); + + // Update model + await wrapper.find('select').setValue('CameronModel'); + expect(wrapper.vm.modelValue).to.deep.equal({ + model: 'CameronModel', + riegelExponent: 1.06, + }); + + // Update Riegel exponent + await wrapper.findComponent({ name: 'decimal-input' }).setValue(1.3); + expect(wrapper.vm.modelValue).to.deep.equal({ + model: 'CameronModel', + riegelExponent: 1.3, + }); +}); diff --git a/tests/unit/views/RaceCalculator.spec.js b/tests/unit/views/RaceCalculator.spec.js index 40a08ed..7c803da 100644 --- a/tests/unit/views/RaceCalculator.spec.js +++ b/tests/unit/views/RaceCalculator.spec.js @@ -132,7 +132,10 @@ test('should correctly calculate results according to advanced model options', a }); // Switch model - await wrapper.find('select[aria-label="Prediction model"]').setValue('RiegelModel'); + await wrapper.findComponent({ name: 'RaceOptions' }).setValue({ + model: 'RiegelModel', + riegelExponent: 1.06, // default value + }); // Calculate result const calculateResult = wrapper.findComponent({ name: 'simple-target-table' }).vm.calculateResult; @@ -146,8 +149,10 @@ test('should correctly calculate results according to advanced model options', a expect(result.time).to.be.closeTo(2502, 1); // Update Riegel Exponent - expect(wrapper.findComponent('[aria-label="Riegel exponent"').vm.modelValue).to.equal(1.06); - await wrapper.findComponent('[aria-label="Riegel exponent"').setValue(1); + await wrapper.findComponent({ name: 'RaceOptions' }).setValue({ + model: 'RiegelModel', // existing value + riegelExponent: 1, + }); // Calculate result result = calculateResult({ @@ -240,16 +245,19 @@ test('should save default units setting to localStorage when modified', async () test('should load advanced model options from localStorage', async () => { // Initialize localStorage - localStorage.setItem('running-tools.race-calculator-model', '"PurdyPointsModel"'); - localStorage.setItem('running-tools.race-calculator-riegel-exponent', '1.20'); + localStorage.setItem('running-tools.race-calculator-options', JSON.stringify({ + model: 'PurdyPointsModel', + riegelExponent: 1.2, + })); // Initialize component const wrapper = shallowMount(RaceCalculator); // Assert data loaded - expect(wrapper.find('select[aria-label="Prediction model"]').element.value) - .to.equal('PurdyPointsModel'); - expect(wrapper.findComponent('[aria-label="Riegel exponent"]').vm.modelValue).to.equal(1.20); + expect(wrapper.findComponent({ name: 'RaceOptions' }).vm.modelValue).to.deep.equal({ + model: 'PurdyPointsModel', + riegelExponent: 1.2, + }); }); test('should save advanced model options to localStorage when modified', async () => { @@ -257,11 +265,15 @@ test('should save advanced model options to localStorage when modified', async ( const wrapper = shallowMount(RaceCalculator); // Update advanced model options - await wrapper.find('select[aria-label="Prediction model"]').setValue('CameronModel'); - await wrapper.findComponent('[aria-label="Riegel exponent"]').setValue(1.30); + await wrapper.findComponent({ name: 'RaceOptions' }).setValue({ + model: 'CameronModel', + riegelExponent: 1.30, + }); // Assert data saved to localStorage - expect(localStorage.getItem('running-tools.race-calculator-model')).to.equal('"CameronModel"'); - expect(localStorage.getItem('running-tools.race-calculator-riegel-exponent')).to.equal('1.3'); + expect(localStorage.getItem('running-tools.race-calculator-options')).to.equal(JSON.stringify({ + model: 'CameronModel', + riegelExponent: 1.3, + })); });