running-tools

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

commit b3641bc4d8fb6ffd34390b7050f431e8495bf976
parent f8903eefac949b7eebf6e2c3a33bb6d55e2af521
Author: Asher Morgan <59518073+ashermorgan@users.noreply.github.com>
Date:   Sat, 19 Jul 2025 16:35:06 -0700

Implement custom batch column label option

Only available when the workout calculator is selected and target name
customization is enabled.

Diffstat:
Msrc/components/AdvancedOptionsInput.vue | 27++++++++++++++++++++++++---
Msrc/components/DoubleOutputTable.vue | 27++++++++++++++++-----------
Msrc/core/calculators.ts | 2++
Msrc/core/utils.ts | 7+++++++
Msrc/views/BatchCalculator.vue | 29+++++++++++++++++++++--------
Mtests/e2e/batch-calculator.spec.js | 7++++---
Mtests/e2e/cross-calculator.spec.js | 2++
Mtests/unit/components/AdvancedOptionsInput.spec.js | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtests/unit/components/DoubleOutputTable.spec.js | 6++++--
Mtests/unit/core/utils.spec.js | 16++++++++++------
Mtests/unit/views/BatchCalculator.spec.js | 148+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
11 files changed, 337 insertions(+), 47 deletions(-)

diff --git a/src/components/AdvancedOptionsInput.vue b/src/components/AdvancedOptionsInput.vue @@ -24,6 +24,13 @@ </select> </div> + <div v-if="batchOptions && props.batchInput && props.type === Calculators.Workout" + v-show="(options as WorkoutOptions).customTargetNames"> + Batch Column Label: + <input v-model="batchOptions.label" :placeholder="formatDistance(props.batchInput, false)" + aria-label="Batch column label"/> + </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"> @@ -47,10 +54,12 @@ <script setup lang="ts"> import { Calculators } from '@/core/calculators'; -import type { StandardOptions, RaceOptions, WorkoutOptions } from '@/core/calculators'; +import type { BatchOptions, StandardOptions, RaceOptions, + WorkoutOptions } from '@/core/calculators'; import { RacePredictionModels } from '@/core/racePrediction'; import type { TargetSets } from '@/core/targets'; -import { UnitSystems } from '@/core/units'; +import { UnitSystems, formatDistance } from '@/core/units'; +import type { DistanceTime } from '@/core/units'; import DecimalInput from '@/components/DecimalInput.vue'; import TargetSetSelector from '@/components/TargetSetSelector.vue'; @@ -66,6 +75,16 @@ const defaultUnitSystem = defineModel<UnitSystems>('defaultUnitSystem'); const props = defineProps<{ /* + * The batch calculator input (if applicable, used to generate custom batch label placeholder) + */ + batchInput?: DistanceTime, + + /* + * The batch calculator options (if applicable) + */ + batchOptions?: BatchOptions, + + /* * The calculator options */ options: CalculatorOptions, @@ -82,7 +101,9 @@ const props = defineProps<{ }>(); // Generate internal refs tied to options and targetSets props -const emit = defineEmits(['update:options', 'update:targetSets']); +const emit = defineEmits(['update:batchOptions', 'update:options', 'update:targetSets']); +const batchOptions = useObjectModel<BatchOptions | undefined>(() => props.batchOptions, (x) => + emit('update:batchOptions', x)); const options = useObjectModel<CalculatorOptions>(() => props.options, (x) => emit('update:options', x)); const targetSets = useObjectModel<TargetSets>(() => props.targetSets, (x) => diff --git a/src/components/DoubleOutputTable.vue b/src/components/DoubleOutputTable.vue @@ -32,40 +32,45 @@ import { computed } from 'vue'; import { ResultType } from '@/core/calculators'; import type { TargetResult } from '@/core/calculators'; import type { Target } from '@/core/targets'; -import { formatDistance, formatDuration } from '@/core/units'; +import { formatDuration } from '@/core/units'; import type { Distance, DistanceTime } from '@/core/units'; interface Props { - /** + /* * The method that generates the target table rows */ calculateResult: (x: DistanceTime, y: Target) => TargetResult, - /** - * The target set + /* + * The input distance */ - targets: Array<Target>, + inputDistance: Distance, - /** + /* * The set of input times */ inputTimes: Array<number>, - /** - * The input distance + /* + * The label to use for the first column */ - inputDistance: Distance, + label: string, + + /* + * The target set + */ + targets: Array<Target>, } const props = defineProps<Props>(); -/** +/* * The target table results */ const results = computed(() => { // Calculate results const results: Array<Array<string>> = [[ - formatDistance(props.inputDistance, false), + props.label ]]; props.inputTimes.forEach((input, y) => { diff --git a/src/core/calculators.ts b/src/core/calculators.ts @@ -47,6 +47,7 @@ export interface WorkoutOptions extends RaceOptions { export interface BatchOptions { calculator: Calculators.Pace | Calculators.Race | Calculators.Workout, increment: number, + label: string, rows: number, }; @@ -80,6 +81,7 @@ export const defaultInput: DistanceTime = { export const defaultBatchOptions: BatchOptions = { calculator: Calculators.Workout, increment: 15, + label: '', rows: 20, }; export const defaultPaceOptions: StandardOptions = { diff --git a/src/core/utils.ts b/src/core/utils.ts @@ -62,6 +62,13 @@ export function unsetLocalStorage(key: string) { export function migrateLocalStorage() { /* eslint-disable @typescript-eslint/no-explicit-any */ + // Add label property to batch-calculator-options (>1.4.1) + const batchOptions = getLocalStorage<any>('batch-calculator-options'); + if (batchOptions !== null && batchOptions.label === undefined) { + batchOptions.label = ''; + setLocalStorage('batch-calculator-options', batchOptions); + } + // Move pace-calculator-target-set into new pace-calculator-options (>1.4.1) const paceSelectedTargetSet = getLocalStorage<string>('pace-calculator-target-set'); if (paceSelectedTargetSet !== null) { diff --git a/src/views/BatchCalculator.vue b/src/views/BatchCalculator.vue @@ -16,9 +16,9 @@ <div> Calculator: <select aria-label="Calculator" v-model="options.calculator"> - <option value="pace">Pace Calculator</option> - <option value="race">Race Calculator</option> - <option value="workout">Workout Calculator</option> + <option :value="calculators.Calculators.Pace">Pace Calculator</option> + <option :value="calculators.Calculators.Race">Race Calculator</option> + <option :value="calculators.Calculators.Workout">Workout Calculator</option> </select> </div> </div> @@ -27,13 +27,14 @@ <summary> <h2>Advanced Options</h2> </summary> - <advanced-options-input v-model:defaultUnitSystem="defaultUnitSystem" - v-model:options="calcOptions" v-model:targetSets="targetSets" :type="options.calculator"/> + <advanced-options-input :batch-input="input" v-model:batch-options="options" + v-model:defaultUnitSystem="defaultUnitSystem" v-model:options="calcOptions" + v-model:targetSets="targetSets" :type="options.calculator"/> </details> <h2>Batch Results</h2> - <double-output-table class="output" :input-times="inputTimes" :input-distance="inputDistance" - :calculate-result="calculateResult" + <double-output-table class="output" :calculate-result="calculateResult" + :input-distance="inputDistance" :input-times="inputTimes" :label="batchColumnLabel" :targets="targetSets[calcOptions.selectedTargetSet] ? targetSets[calcOptions.selectedTargetSet].targets : []"/> </div> @@ -46,7 +47,7 @@ import * as calculators from '@/core/calculators'; import type { BatchOptions, RaceOptions, StandardOptions, TargetResult, WorkoutOptions } from '@/core/calculators'; import * as targetUtils from '@/core/targets'; -import { UnitSystems, detectDefaultUnitSystem } from '@/core/units'; +import { UnitSystems, detectDefaultUnitSystem, formatDistance } from '@/core/units'; import type { Distance, DistanceTime } from '@/core/units'; import AdvancedOptionsInput from '@/components/AdvancedOptionsInput.vue'; @@ -205,6 +206,18 @@ const calculateResult = computed<(x: DistanceTime, y: targetUtils.Target) => Tar } } }); + +/* + * The label to render for the batch column + */ +const batchColumnLabel = computed<string>(() => { + if (options.value.calculator == calculators.Calculators.Workout && + (calcOptions.value as WorkoutOptions).customTargetNames && options.value.label) { + return options.value.label; + } else { + return formatDistance(input.value, false); + } +}); </script> <style scoped> diff --git a/tests/e2e/batch-calculator.spec.js b/tests/e2e/batch-calculator.spec.js @@ -33,13 +33,14 @@ test('Batch calculator', async ({ page }) => { await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(5); await expect(page.getByRole('row')).toHaveCount(16); - // Change prediction model and enable customized target names + // Change prediction model, enable customized target names, and set custom batch column label await page.getByText('Advanced Options').click(); await page.getByLabel('Prediction model').selectOption('Riegel\'s Model'); await page.getByLabel('Target name customization').selectOption('Enabled'); + await page.getByLabel('Batch column label').fill('foo'); // Assert workout results are correct - await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi'); + await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('foo'); 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'); @@ -167,7 +168,7 @@ test('Batch calculator', async ({ page }) => { { await page.getByLabel('Calculator').selectOption('Workout Calculator'); await expect(page.getByLabel('Target name customization')).toHaveValue("true"); - await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi'); + await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('foo'); 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'); diff --git a/tests/e2e/cross-calculator.spec.js b/tests/e2e/cross-calculator.spec.js @@ -234,6 +234,7 @@ test('Cross-calculator', async ({ page }) => { localStorage.getItem('running-tools.batch-calculator-options'))).toEqual(JSON.stringify({ calculator: 'race', increment: 10, + label: '', rows: 15, })); @@ -739,6 +740,7 @@ test('v1.4.1 Migration', async ({ page }) => { calculator: 'race', increment: 10, rows: 15, + label: '', })); // Assert localStorage entries for the pace calculator are correct diff --git a/tests/unit/components/AdvancedOptionsInput.spec.js b/tests/unit/components/AdvancedOptionsInput.spec.js @@ -56,6 +56,8 @@ test('should be correctly render pace options according to props', () => { }); expect(wrapper.findAll('select[aria-label="Target name customization"]')).to.have .length(0); + expect(wrapper.findAll('input[aria-label="Batch column label"]')).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); }); @@ -82,6 +84,8 @@ test('should be correctly render race options according to props', () => { .equal('_new'); expect(wrapper.findAll('select[aria-label="Target name customization"]')).to.have .length(0); + expect(wrapper.findAll('input[aria-label="Batch column label"]')).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); @@ -144,6 +148,8 @@ test('should be correctly render split options according to props', () => { .equal('_new'); expect(wrapper.findAll('select[aria-label="Target name customization"]')).to.have .length(0); + expect(wrapper.findAll('input[aria-label="Batch column label"]')).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); }); @@ -170,11 +176,118 @@ test('should be correctly render workout options according to props', () => { .equal('_new'); expect(wrapper.find('select[aria-label="Target name customization"]').element.value).to .equal('true'); + expect(wrapper.findAll('input[aria-label="Batch column label"]')).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 only show batch column label field when applicable', async () => { + // Initialize component with workout target name customization enabled + const wrapper = shallowMount(AdvancedOptionsInput, { + propsData: { + defaultUnitSystem: 'metric', + options: { + customTargetNames: true, + model: 'PurdyPointsModel', + riegelExponent: 1.2, + selectedTargetSet: '_new', + }, + targetSets: {}, + type: 'workout', + }, + attachTo: document.body, + }); + + // Assert batch column label field is hidden + expect(wrapper.findAll('input[aria-label="Batch column label"]')).to.have + .length(0); + + // Add batchInput and batchOptions but disable workout target name customization + await wrapper.setProps({ + batchInput: { // added + distanceValue: 2, + distanceUnit: 'miles', + time: 600, + }, + batchOptions: { // added + calculator: 'workout', + increment: 32, + label: 'foo', + rows: 15, + }, + defaultUnitSystem: 'metric', + options: { + customTargetNames: false, // disabled + model: 'PurdyPointsModel', + riegelExponent: 1.2, + selectedTargetSet: '_new', + }, + targetSets: {}, + type: 'workout', + }); + + // Assert batch column label field is still hidden + expect(wrapper.find('input[aria-label="Batch column label"]').isVisible()).to.equal(false); + + // Enable workout target name customization + await wrapper.setProps({ + batchInput: { + distanceValue: 2, + distanceUnit: 'miles', + time: 600, + }, + batchOptions: { + calculator: 'workout', + increment: 32, + label: 'foo', + rows: 15, + }, + defaultUnitSystem: 'metric', + options: { + customTargetNames: true, // enabled + model: 'PurdyPointsModel', + riegelExponent: 1.2, + selectedTargetSet: '_new', + }, + targetSets: {}, + type: 'workout', + }); + + // Assert batch column label field is now visible + expect(wrapper.find('input[aria-label="Batch column label"]').isVisible()).to.equal(true); + expect(wrapper.find('input[aria-label="Batch column label"]').element.placeholder).to.equal('2 mi') + expect(wrapper.find('input[aria-label="Batch column label"]').element.value).to.equal('foo') + + // Switch to race calculator + await wrapper.setProps({ + batchInput: { + distanceValue: 2, + distanceUnit: 'miles', + time: 600, + }, + batchOptions: { + calculator: 'workout', + increment: 32, + label: 'foo', + rows: 15, + }, + defaultUnitSystem: 'metric', + options: { + model: 'PurdyPointsModel', + riegelExponent: 1.2, + selectedTargetSet: '_new', + }, + targetSets: {}, + type: 'race', // changed + }); + + // Assert batch column label field is hidden again + expect(wrapper.findAll('input[aria-label="Batch column label"]')).to.have + .length(0); +}); + test('should pass correct props to TargetSetSelector', async () => { // Initialize component const wrapper = shallowMount(AdvancedOptionsInput, { diff --git a/tests/unit/components/DoubleOutputTable.spec.js b/tests/unit/components/DoubleOutputTable.spec.js @@ -34,12 +34,13 @@ test('should correctly render table body rows and headers', () => { distanceUnit: 'miles', distanceValue: 2, }, + label: 'foo', }, }); // Assert headers are correctly generated from first row of results const headers = wrapper.findAll('th'); - expect(headers[0].element.textContent).to.equal('2 mi'); + expect(headers[0].element.textContent).to.equal('foo'); expect(headers[1].element.textContent).to.equal('key1'); expect(headers[2].element.textContent).to.equal('key2'); expect(headers[3].element.textContent).to.equal('key3'); @@ -80,12 +81,13 @@ test('Should display message when inputs are empty', () => { distanceUnit: 'miles', distanceValue: 2, }, + label: 'foo', }, }); // Assert headers are correctly generated const headers = wrapper.findAll('th'); - expect(headers[0].element.textContent).to.equal('2 mi'); + expect(headers[0].element.textContent).to.equal('foo'); expect(headers.length).to.equal(1); // Assert results are correctly rendered diff --git a/tests/unit/core/utils.spec.js b/tests/unit/core/utils.spec.js @@ -180,6 +180,8 @@ describe('set method', () => { describe('migrate method', () => { test('should correctly migrate <=1.4.1 calculator options', async () => { // Initialize localStorage + localStorage.setItem('running-tools.batch-calculator-options', + '{"calculator":"race","increment":32,"rows":15}'); localStorage.setItem('running-tools.pace-calculator-target-set', '"A"'); localStorage.setItem('running-tools.race-calculator-options', '{"model":"RiegelModel","riegelExponent":1.07}'); @@ -193,6 +195,8 @@ describe('migrate method', () => { utils.migrateLocalStorage(); // Assert localStorage entries correctly migrated + expect(localStorage.getItem('running-tools.batch-calculator-options')).to.equal( + '{"calculator":"race","increment":32,"rows":15,"label":""}'); expect(localStorage.getItem('running-tools.pace-calculator-options')).to.equal( '{"selectedTargetSet":"A"}'); expect(localStorage.getItem('running-tools.pace-calculator-target-set')).to.equal(null); @@ -209,9 +213,7 @@ describe('migrate method', () => { }); test('should correctly migrate partial <=1.4.1 calculator options', async () => { - // Initialize localStorage (*-target-set options missing) - localStorage.setItem('running-tools.race-calculator-options', - '{"model":"RiegelModel","riegelExponent":1.07}'); + // Initialize localStorage (workout-target-set option missing) localStorage.setItem('running-tools.workout-calculator-options', '{"model":"RiegelModel","riegelExponent":1.08}'); @@ -219,16 +221,15 @@ describe('migrate method', () => { utils.migrateLocalStorage(); // Assert localStorage entries correctly migrated - expect(localStorage.getItem('running-tools.race-calculator-options')).to.equal( - '{"model":"RiegelModel","riegelExponent":1.07,"selectedTargetSet":"_race_targets"}'); expect(localStorage.getItem('running-tools.workout-calculator-options')).to.equal( '{"model":"RiegelModel","riegelExponent":1.08,"selectedTargetSet":"_workout_targets",' + '"customTargetNames":false}'); }); - test('should not modify >1.4.1 calculator options', async () => { // Initialize localStorage + localStorage.setItem('running-tools.batch-calculator-options', + '{"calculator":"race","increment":32,"label":"foo","rows":15}'); localStorage.setItem('running-tools.pace-calculator-options', '{"selectedTargetSet":"A"}'); localStorage.setItem('running-tools.race-calculator-options', @@ -243,6 +244,8 @@ describe('migrate method', () => { utils.migrateLocalStorage(); // Assert localStorage entries not modified + expect(localStorage.getItem('running-tools.batch-calculator-options')).to.equal( + '{"calculator":"race","increment":32,"label":"foo","rows":15}'); expect(localStorage.getItem('running-tools.pace-calculator-options')).to.equal( '{"selectedTargetSet":"A"}'); expect(localStorage.getItem('running-tools.race-calculator-options')).to.equal( @@ -259,6 +262,7 @@ describe('migrate method', () => { utils.migrateLocalStorage(); // Assert localStorage entries not modified + expect(localStorage.getItem('running-tools.batch-calculator-options')).to.equal(null); expect(localStorage.getItem('running-tools.pace-calculator-options')).to.equal(null); expect(localStorage.getItem('running-tools.race-calculator-options')).to.equal(null); expect(localStorage.getItem('running-tools.split-calculator-options')).to.equal(null); diff --git a/tests/unit/views/BatchCalculator.spec.js b/tests/unit/views/BatchCalculator.spec.js @@ -23,6 +23,11 @@ test('should load input from localStorage', async () => { distanceUnit: 'miles', time: 600, }); + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.batchInput).to.deep.equal({ + distanceValue: 2, + distanceUnit: 'miles', + time: 600, + }); }); test('should save input to localStorage when modified', async () => { @@ -49,6 +54,7 @@ test('should load batch options from localStorage', async () => { localStorage.setItem('running-tools.batch-calculator-options', JSON.stringify({ calculator: 'race', increment: 32, + label: 'foo', rows: 15, })); @@ -59,39 +65,64 @@ test('should load batch options from localStorage', async () => { expect(wrapper.find('select[aria-label="Calculator"]').element.value).to.equal('race'); expect(wrapper.findComponent({ name: 'time-input' }).vm.modelValue).to.equal(32); expect(wrapper.findComponent({ name: 'integer-input' }).vm.modelValue).to.equal(15); + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.batchOptions).to.deep.equal({ + calculator: 'race', + increment: 32, + label: 'foo', + rows: 15, + }); }); test('should save batch options to localStorage when modified', async () => { // Initialize component const wrapper = shallowMount(BatchCalculator); - // Update active calculator - await wrapper.find('select[aria-label="Calculator"]').setValue('race'); + // 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: 'race', - increment: 15, + calculator: 'workout', + increment: 32, + label: '', rows: 20, })); - // Update increment value - await wrapper.findComponent({ name: 'time-input' }).setValue(32); + // 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: 'race', + calculator: 'workout', increment: 32, - rows: 20, + label: '', + rows: 15, })); - // Update number of rows - await wrapper.findComponent({ name: 'integer-input' }).setValue(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, })); }); @@ -352,6 +383,7 @@ test('should pass correct input props to DoubleOutputTable', async () => { 1200, 1215, 1230, 1245, 1260, 1275, 1290, 1305, 1320, 1335, 1350, 1365, 1380, 1395, 1410, 1425, 1440, 1455, 1470, 1485, ]); + expect(wrapper.findComponent({ name: 'double-output-table' }).vm.label).to.equal('5 km'); // Change input pace await wrapper.findComponent({ name: 'pace-input' }).setValue({ @@ -369,6 +401,7 @@ test('should pass correct input props to DoubleOutputTable', async () => { 600, 615, 630, 645, 660, 675, 690, 705, 720, 735, 750, 765, 780, 795, 810, 825, 840, 855, 870, 885, ]); + expect(wrapper.findComponent({ name: 'double-output-table' }).vm.label).to.equal('2 mi'); // Change increment value await wrapper.findComponent({ name: 'time-input' }).setValue(10); @@ -382,6 +415,7 @@ test('should pass correct input props to DoubleOutputTable', async () => { 600, 610, 620, 630, 640, 650, 660, 670, 680, 690, 700, 710, 720, 730, 740, 750, 760, 770, 780, 790, ]); + expect(wrapper.findComponent({ name: 'double-output-table' }).vm.label).to.equal('2 mi'); // Change number of rows await wrapper.findComponent({ name: 'integer-input' }).setValue(15); @@ -394,23 +428,109 @@ test('should pass correct input props to DoubleOutputTable', async () => { expect(wrapper.findComponent({ name: 'double-output-table' }).vm.inputTimes).to.deep.equal([ 600, 610, 620, 630, 640, 650, 660, 670, 680, 690, 700, 710, 720, 730, 740, ]); + expect(wrapper.findComponent({ name: 'double-output-table' }).vm.label).to.equal('2 mi'); + + // Enter custom batch column label + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + calculator: 'workout', + increment: 10, + label: 'foo', + rows: 15, + }, 'batchOptions'); + + // Assert that the props are updated + expect(wrapper.findComponent({ name: 'double-output-table' }).vm.inputDistance).to.deep.equal({ + distanceValue: 2, + distanceUnit: 'miles', + }); + expect(wrapper.findComponent({ name: 'double-output-table' }).vm.inputTimes).to.deep.equal([ + 600, 610, 620, 630, 640, 650, 660, 670, 680, 690, 700, 710, 720, 730, 740, + ]); + expect(wrapper.findComponent({ name: 'double-output-table' }).vm.label).to.equal('2 mi'); + + // Enable target name customization + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + customTargetNames: true, + model: 'AverageModel', + riegelExponent: 1.06, + selectedTargetSet: '_workout_targets', + }, 'options'); + + // Assert that the props are updated + expect(wrapper.findComponent({ name: 'double-output-table' }).vm.inputDistance).to.deep.equal({ + distanceValue: 2, + distanceUnit: 'miles', + }); + expect(wrapper.findComponent({ name: 'double-output-table' }).vm.inputTimes).to.deep.equal([ + 600, 610, 620, 630, 640, 650, 660, 670, 680, 690, 700, 710, 720, 730, 740, + ]); + expect(wrapper.findComponent({ name: 'double-output-table' }).vm.label).to.equal('foo'); + + // Switch calculators + await wrapper.find('select[aria-label="Calculator"]').setValue('race'); + + // Assert that the props are updated + expect(wrapper.findComponent({ name: 'double-output-table' }).vm.inputDistance).to.deep.equal({ + distanceValue: 2, + distanceUnit: 'miles', + }); + expect(wrapper.findComponent({ name: 'double-output-table' }).vm.inputTimes).to.deep.equal([ + 600, 610, 620, 630, 640, 650, 660, 670, 680, 690, 700, 710, 720, 730, 740, + ]); + expect(wrapper.findComponent({ name: 'double-output-table' }).vm.label).to.equal('2 mi'); }); -test('should correctly set AdvancedOptionsInput type prop', async () => { +test('should correctly set AdvancedOptionsInput props', async () => { // Initialize component const wrapper = shallowMount(BatchCalculator); - // Assert prop is correct for pace calculator + // Set input pace and batch options + await wrapper.findComponent({ name: 'pace-input' }).setValue({ + distanceValue: 2, + distanceUnit: 'miles', + time: 600, + }); + await wrapper.findComponent({ name: 'time-input' }).setValue(32); + await wrapper.findComponent({ name: 'integer-input' }).setValue(15); + + // Assert batch props are correct + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.batchInput).to.deep.equal({ + distanceValue: 2, + distanceUnit: 'miles', + time: 600, + }); + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.batchOptions).to.deep.equal({ + calculator: 'workout', + increment: 32, + label: '', + rows: 15, + }); + + // 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.options).to.deep.equal({ + selectedTargetSet: '_pace_targets', + }); - // Update race calculator options + // 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({ + model: 'AverageModel', + riegelExponent: 1.06, + selectedTargetSet: '_race_targets', + }); - // Update workout calculator options + // 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, + model: 'AverageModel', + riegelExponent: 1.06, + selectedTargetSet: '_workout_targets', + }); }); test('should correctly calculate outputs', async () => {