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 @@
+
+
+ Prediction Model:
+
+
+
+ Riegel Exponent:
+
+ (default: 1.06)
+
+
+
+
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,
+ }));
});