commit 2decd85c70b0d1cd43d31ebec1b923af520f2f93
parent de99040e38f6fd15376fdb547ebcfeb313a788f9
Author: Asher Morgan <59518073+ashermorgan@users.noreply.github.com>
Date: Mon, 25 Mar 2024 14:50:49 -0700
Improve view tests
Diffstat:
4 files changed, 920 insertions(+), 83 deletions(-)
diff --git a/tests/unit/views/PaceCalculator.spec.js b/tests/unit/views/PaceCalculator.spec.js
@@ -1,23 +1,23 @@
-/* eslint-disable no-underscore-dangle */
-
-import { test, expect } from 'vitest';
+import { beforeEach, test, expect } from 'vitest';
import { shallowMount } from '@vue/test-utils';
import PaceCalculator from '@/views/PaceCalculator.vue';
-import unitUtils from '@/utils/units';
-test('should correctly calculate times', async () => {
+beforeEach(() => {
+ localStorage.clear();
+})
+
+test('should correctly calculate time results', async () => {
// Initialize component
const wrapper = shallowMount(PaceCalculator);
- // Override input values
- await wrapper.setData({
- inputDistance: 1,
- inputUnit: 'kilometers',
- inputTime: 100,
- });
+ // Enter input pace data
+ await wrapper.findComponent({ name: 'decimal-input' }).setValue(1);
+ await wrapper.find('select[aria-label="Input distance unit"]').setValue('kilometers');
+ await wrapper.findComponent({ name: 'time-input' }).setValue(100);
- // Calculate paces
- const result = wrapper.vm.calculatePace({
+ // Calculate result
+ const calculateResult = wrapper.findComponent({ name: 'simple-target-table' }).vm.calculateResult;
+ const result = calculateResult({
distanceValue: 20,
distanceUnit: 'meters',
result: 'time',
@@ -32,28 +32,172 @@ test('should correctly calculate times', async () => {
});
});
-test('should correctly calculate distances', async () => {
+test('should correctly calculate distance results according to default units setting', async () => {
+ // Initialize component
+ const wrapper = shallowMount(PaceCalculator, {
+ data() {
+ return {
+ defaultUnitSystem: 'metric',
+ };
+ },
+ });
+
+ // Enter input pace data
+ await wrapper.findComponent({ name: 'decimal-input' }).setValue(2);
+ await wrapper.find('select[aria-label="Input distance unit"]').setValue('miles');
+ await wrapper.findComponent({ name: 'time-input' }).setValue(1200);
+
+ // Get calculate result function
+ const calculateResult = wrapper.findComponent({ name: 'simple-target-table' }).vm.calculateResult;
+
+ // Assert result is correct
+ let result = calculateResult({ result: 'distance', time: 600 });
+ expect(result.distanceValue).to.be.closeTo(1.609, 0.001);
+ expect(result.distanceUnit).to.equal('kilometers');
+
+ // Change default units
+ await wrapper.find('select[aria-label="Default units"]').setValue('imperial');
+
+ // Assert result is correct
+ result = calculateResult({ result: 'distance', time: 600 });
+ expect(result.distanceValue).to.equal(1);
+ expect(result.distanceUnit).to.equal('miles');
+ expect(result.time).to.equal(600);
+ expect(result.result).to.equal('distance');
+});
+
+test('should not show paces in results table', async () => {
// Initialize component
const wrapper = shallowMount(PaceCalculator);
- // Override input values
- await wrapper.setData({
- inputDistance: 1,
- inputUnit: 'miles',
- inputTime: 100,
+ // Assert paces are not shown in results table
+ expect(wrapper.findComponent({ name: 'simple-target-table' }).vm.showPace).to.equal(false);
+});
+
+test('should correctly handle null target set', async () => {
+ // Initialize component
+ const paceTargets = [
+ { result: 'time', distanceValue: 1, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 2, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 5, distanceUnit: 'kilometers' },
+ ];
+ const wrapper = shallowMount(PaceCalculator, {
+ data() {
+ return {
+ targetSets: {
+ '_pace_targets': {
+ name: 'Common pace targets',
+ targets: paceTargets,
+ },
+ '_race_targets': null,
+ },
+ };
+ },
});
- // Calculate paces
- const result = wrapper.vm.calculatePace({
- time: 200,
- result: 'distance',
+ // Switch to invalid target set
+ await wrapper.findComponent({ name: 'target-set-selector' }).setValue('_race_targets');
+
+ // Assert empty array passed to SimpleTargetTable component
+ expect(wrapper.findComponent({ name: 'simple-target-table' }).vm.targets).to.deep.equal([]);
+
+ // Switch to valid target set
+ await wrapper.findComponent({ name: 'target-set-selector' }).setValue('_pace_targets');
+
+ // Assert valid targets passed to SimpleTargetTable component
+ expect(wrapper.findComponent({ name: 'simple-target-table' }).vm.targets).to.deep.equal(paceTargets);
+});
+
+test('should load input pace from localStorage', async () => {
+ // Initialize localStorage
+ localStorage.setItem('running-tools.pace-calculator-input-distance', '1');
+ localStorage.setItem('running-tools.pace-calculator-input-unit', '"miles"');
+ localStorage.setItem('running-tools.pace-calculator-input-time', '600');
+
+ // Initialize component
+ const wrapper = shallowMount(PaceCalculator);
+
+ // Assert data loaded
+ expect(wrapper.findComponent({ name: 'decimal-input' }).vm.modelValue).to.equal(1);
+ expect(wrapper.find('select[aria-label="Input distance unit"]').element.value).to.equal('miles');
+ expect(wrapper.findComponent({ name: 'time-input' }).vm.modelValue).to.equal(600);
+});
+
+test('should save input pace to localStorage', async () => {
+ // Initialize component
+ const wrapper = shallowMount(PaceCalculator);
+
+ // Enter input pace data
+ await wrapper.findComponent({ name: 'decimal-input' }).setValue(1);
+ await wrapper.find('select[aria-label="Input distance unit"]').setValue('miles');
+ await wrapper.findComponent({ name: 'time-input' }).setValue(600);
+
+ // Assert data saved to localStorage
+ expect(localStorage.getItem('running-tools.pace-calculator-input-distance')).to.equal('1');
+ expect(localStorage.getItem('running-tools.pace-calculator-input-unit')).to.equal('"miles"');
+ expect(localStorage.getItem('running-tools.pace-calculator-input-time')).to.equal('600');
+});
+
+test('should load selected target set from localStorage', async () => {
+ // Initialize localStorage
+ localStorage.setItem('running-tools.pace-calculator-target-set', '"_race_targets"');
+
+ // Initialize component
+ const raceTargets = [
+ { result: 'time', distanceValue: 2, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 3, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 6, distanceUnit: 'kilometers' },
+ ];
+ const wrapper = shallowMount(PaceCalculator, {
+ data() {
+ return {
+ targetSets: {
+ '_pace_targets': {
+ name: 'Common pace targets',
+ targets: [
+ { result: 'time', distanceValue: 1, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 2, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 5, distanceUnit: 'kilometers' },
+ ],
+ },
+ '_race_targets': {
+ name: 'Common race targets',
+ targets: raceTargets,
+ },
+ },
+ };
+ },
});
- // Assert result is correct
- expect(result).to.deep.equal({
- distanceValue: unitUtils.convertDistance(2, 'miles', unitUtils.getDefaultDistanceUnit(unitUtils.detectDefaultUnitSystem())),
- distanceUnit: unitUtils.getDefaultDistanceUnit(unitUtils.detectDefaultUnitSystem()),
- time: 200,
- result: 'distance',
+ // Assert selection is loaded
+ expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.modelValue).to.equal('_race_targets');
+ expect(wrapper.findComponent({ name: 'simple-target-table' }).vm.targets).to.deep.equal(raceTargets);
+});
+
+test('should save selected target set to localStorage when modified', async () => {
+ // Initialize component
+ const wrapper = shallowMount(PaceCalculator);
+
+ // Select a new target set
+ await wrapper.findComponent({ name: 'target-set-selector' }).setValue('_race_targets');
+
+ // New selected target set should be saved to localStorage
+ expect(localStorage.getItem('running-tools.pace-calculator-target-set')).to.equal('"_race_targets"');
+});
+
+test('should save default units setting to localStorage when modified', async () => {
+ // Initialize component
+ const wrapper = shallowMount(PaceCalculator, {
+ data() {
+ return {
+ defaultUnitSystem: 'metric',
+ };
+ },
});
+
+ // Change default units
+ await wrapper.find('select[aria-label="Default units"]').setValue('imperial');
+
+ // 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
@@ -1,63 +1,289 @@
-/* eslint-disable no-underscore-dangle */
-
-import { test, expect } from 'vitest';
+import { beforeEach, test, expect } from 'vitest';
import { shallowMount } from '@vue/test-utils';
-import raceUtils from '@/utils/races';
-import unitUtils from '@/utils/units';
import RaceCalculator from '@/views/RaceCalculator.vue';
+beforeEach(() => {
+ localStorage.clear();
+})
+
test('should correctly predict race times', async () => {
// Initialize component
const wrapper = shallowMount(RaceCalculator);
- // Override input values
- await wrapper.setData({
- inputDistance: 5,
- inputUnit: 'kilometers',
- inputTime: 1200,
+ // Enter input pace data
+ await wrapper.findComponent({ name: 'decimal-input' }).setValue(5);
+ await wrapper.find('select[aria-label="Input distance unit"]').setValue('kilometers');
+ await wrapper.findComponent({ name: 'time-input' }).setValue(1200);
+
+ // Calculate result
+ const calculateResult = wrapper.findComponent({ name: 'simple-target-table' }).vm.calculateResult;
+ const result = calculateResult({
+ distanceValue: 10,
+ distanceUnit: 'kilometers',
+ result: 'time',
+ });
+
+ // Assert result is correct
+ expect(result.time).to.be.closeTo(2495, 1);
+ expect(result.distanceValue).to.equal(10);
+ expect(result.distanceUnit).to.equal('kilometers');
+ expect(result.result).to.equal('time');
+});
+
+test('should correctly calculate distance results according to default units setting', async () => {
+ // Initialize component
+ const wrapper = shallowMount(RaceCalculator, {
+ data() {
+ return {
+ defaultUnitSystem: 'metric',
+ };
+ },
});
- // Predict race times
- const result = wrapper.vm.predictResult({
+ // Enter input pace data
+ await wrapper.findComponent({ name: 'decimal-input' }).setValue(5);
+ await wrapper.find('select[aria-label="Input distance unit"]').setValue('kilometers');
+ await wrapper.findComponent({ name: 'time-input' }).setValue(1200);
+
+ // Get calculate result function
+ const calculateResult = wrapper.findComponent({ name: 'simple-target-table' }).vm.calculateResult;
+
+ // Assert result is correct
+ let result = calculateResult({ result: 'distance', time: 2495 });
+ expect(result.distanceValue).to.be.closeTo(10, 0.01);
+ expect(result.distanceUnit).to.equal('kilometers');
+ expect(result.time).to.equal(2495);
+ expect(result.result).to.equal('distance');
+
+ // Change default units
+ await wrapper.find('select[aria-label="Default units"]').setValue('imperial');
+
+ // Assert result is correct
+ result = calculateResult({ result: 'distance', time: 2495 });
+ expect(result.distanceValue).to.be.closeTo(6.214, 0.01);
+ expect(result.distanceUnit).to.equal('miles');
+ expect(result.time).to.equal(2495);
+ expect(result.result).to.equal('distance');
+});
+
+test('should show paces in results table', async () => {
+ // Initialize component
+ const wrapper = shallowMount(RaceCalculator);
+
+ // Assert paces are shown in results table
+ expect(wrapper.findComponent({ name: 'simple-target-table' }).vm.showPace).to.equal(true);
+});
+
+test('should correctly handle null target set', async () => {
+ // Initialize component
+ const raceTargets = [
+ { result: 'time', distanceValue: 1, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 2, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 5, distanceUnit: 'kilometers' },
+ ];
+ const wrapper = shallowMount(RaceCalculator, {
+ data() {
+ return {
+ targetSets: {
+ '_pace_targets': null,
+ '_race_targets': {
+ name: 'Common race targets',
+ targets: raceTargets,
+ },
+ },
+ };
+ },
+ });
+
+ // Switch to invalid target set
+ await wrapper.findComponent({ name: 'target-set-selector' }).setValue('_pace_targets');
+
+ // Assert empty array passed to SimpleTargetTable component
+ expect(wrapper.findComponent({ name: 'simple-target-table' }).vm.targets).to.deep.equal([]);
+
+ // Switch to valid target set
+ await wrapper.findComponent({ name: 'target-set-selector' }).setValue('_race_targets');
+
+ // Assert valid targets passed to SimpleTargetTable component
+ expect(wrapper.findComponent({ name: 'simple-target-table' }).vm.targets).to.deep.equal(raceTargets);
+});
+
+test('should correctly calculate race statistics', async () => {
+ // Initialize component
+ const wrapper = shallowMount(RaceCalculator);
+
+ // Enter input pace data
+ await wrapper.findComponent({ name: 'decimal-input' }).setValue(5);
+ await wrapper.find('select[aria-label="Input distance unit"]').setValue('kilometers');
+ await wrapper.findComponent({ name: 'time-input' }).setValue(1200);
+
+ // Get race statistics
+ const raceStats = wrapper.findAll('details')[0];
+ const purdyPoints = raceStats.findAll('div')[0].element.textContent.trim();
+ const vo2 = raceStats.findAll('div')[1].element.textContent.trim();
+ const vo2Max = raceStats.findAll('div')[2].element.textContent.trim();
+
+ // Assert race statistics are correct
+ expect(purdyPoints).to.equal('Purdy Points: 454.5');
+ expect(vo2).to.equal('V̇O₂: 47.4 ml/kg/min (95.3% of max)')
+ expect(vo2Max).to.equal('V̇O₂ Max: 49.8 ml/kg/min')
+});
+
+test('should correctly calculate results according to advanced model options', async () => {
+ // Initialize component
+ const wrapper = shallowMount(RaceCalculator);
+
+ // Enter input pace data
+ await wrapper.findComponent({ name: 'decimal-input' }).setValue(5);
+ await wrapper.find('select[aria-label="Input distance unit"]').setValue('kilometers');
+ await wrapper.findComponent({ name: 'time-input' }).setValue(1200);
+
+ // Switch model
+ await wrapper.find('select[aria-label="Prediction model"]').setValue('RiegelModel');
+
+ // Calculate result
+ const calculateResult = wrapper.findComponent({ name: 'simple-target-table' }).vm.calculateResult;
+ let result = calculateResult({
distanceValue: 10,
distanceUnit: 'kilometers',
result: 'time',
});
// Assert result is correct
- const prediction = raceUtils.AverageModel.predictTime(5000, 1200, 10000);
- expect(result).to.deep.equal({
+ 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);
+
+ // Calculate result
+ result = calculateResult({
distanceValue: 10,
distanceUnit: 'kilometers',
- time: prediction,
result: 'time',
});
+
+ // Assert result is correct
+ expect(result.time).to.equal(2400);
});
-test('should correctly predict race distances', async () => {
+test('should load input pace from localStorage', async () => {
+ // Initialize localStorage
+ localStorage.setItem('running-tools.race-calculator-input-distance', '1');
+ localStorage.setItem('running-tools.race-calculator-input-unit', '"miles"');
+ localStorage.setItem('running-tools.race-calculator-input-time', '600');
+
// Initialize component
const wrapper = shallowMount(RaceCalculator);
- // Override input values
- await wrapper.setData({
- inputDistance: 5,
- inputUnit: 'kilometers',
- inputTime: 1200,
- });
+ // Assert data loaded
+ expect(wrapper.findComponent({ name: 'decimal-input' }).vm.modelValue).to.equal(1);
+ expect(wrapper.find('select[aria-label="Input distance unit"]').element.value).to.equal('miles');
+ expect(wrapper.findComponent({ name: 'time-input' }).vm.modelValue).to.equal(600);
+});
- // Predict race distances
- const result = wrapper.vm.predictResult({
- time: 2460,
- result: 'distance',
+test('should save input pace to localStorage', async () => {
+ // Initialize component
+ const wrapper = shallowMount(RaceCalculator);
+
+ // Enter input pace data
+ await wrapper.findComponent({ name: 'decimal-input' }).setValue(1);
+ await wrapper.find('select[aria-label="Input distance unit"]').setValue('miles');
+ await wrapper.findComponent({ name: 'time-input' }).setValue(600);
+
+ // Assert data saved to localStorage
+ expect(localStorage.getItem('running-tools.race-calculator-input-distance')).to.equal('1');
+ expect(localStorage.getItem('running-tools.race-calculator-input-unit')).to.equal('"miles"');
+ expect(localStorage.getItem('running-tools.race-calculator-input-time')).to.equal('600');
+});
+
+test('should load selected target set from localStorage', async () => {
+ // Initialize localStorage
+ localStorage.setItem('running-tools.race-calculator-target-set', '"_pace_targets"');
+
+ // Initialize component
+ const paceTargets = [
+ { result: 'time', distanceValue: 2, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 3, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 6, distanceUnit: 'kilometers' },
+ ];
+ const wrapper = shallowMount(RaceCalculator, {
+ data() {
+ return {
+ targetSets: {
+ '_pace_targets': {
+ name: 'Common pace targets',
+ targets: paceTargets,
+ },
+ '_race_targets': {
+ name: 'Common race targets',
+ targets: [
+ { result: 'time', distanceValue: 1, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 2, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 5, distanceUnit: 'kilometers' },
+ ],
+ },
+ },
+ };
+ },
});
- // Assert result is correct
- const prediction = raceUtils.AverageModel.predictDistance(1200, 5000, 2460);
- expect(result).to.deep.equal({
- distanceValue: unitUtils.convertDistance(prediction, 'meters',
- unitUtils.getDefaultDistanceUnit(unitUtils.detectDefaultUnitSystem())),
- distanceUnit: unitUtils.getDefaultDistanceUnit(unitUtils.detectDefaultUnitSystem()),
- time: 2460,
- result: 'distance',
+ // Assert selection is loaded
+ expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.modelValue).to.equal('_pace_targets');
+ expect(wrapper.findComponent({ name: 'simple-target-table' }).vm.targets).to.deep.equal(paceTargets);
+});
+
+test('should save selected target set to localStorage when modified', async () => {
+ // Initialize component
+ const wrapper = shallowMount(RaceCalculator);
+
+ // Select a new target set
+ await wrapper.findComponent({ name: 'target-set-selector' }).setValue('_pace_targets');
+
+ // New selected target set should be saved to localStorage
+ expect(localStorage.getItem('running-tools.race-calculator-target-set')).to.equal('"_pace_targets"');
+});
+
+test('should save default units setting to localStorage when modified', async () => {
+ // Initialize component
+ const wrapper = shallowMount(RaceCalculator, {
+ data() {
+ return {
+ defaultUnitSystem: 'metric',
+ };
+ },
});
+
+ // Change default units
+ await wrapper.find('select[aria-label="Default units"]').setValue('imperial');
+
+ // New default units should be saved to localStorage
+ expect(localStorage.getItem('running-tools.default-unit-system')).to.equal('"imperial"');
});
+
+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');
+
+ // 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);
+});
+
+test('should save advanced model options to localStorage when modified', async () => {
+ // Initialize component
+ 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);
+
+ // 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');
+});
+
diff --git a/tests/unit/views/SplitCalculator.spec.js b/tests/unit/views/SplitCalculator.spec.js
@@ -1,11 +1,305 @@
-/* eslint-disable no-underscore-dangle */
-
-import { test, expect } from 'vitest';
+import { beforeEach, test, expect } from 'vitest';
import { shallowMount } from '@vue/test-utils';
import SplitCalculator from '@/views/SplitCalculator.vue';
-import unitUtils from '@/utils/units';
-test('should correctly calculate split paces and total times', async () => {
+beforeEach(() => {
+ localStorage.clear();
+})
+
+test('should initialize undefined splits to 0:00.00', async () => {
+ // Initialize component
+ const wrapper = shallowMount(SplitCalculator, {
+ data() {
+ return {
+ targetSets: {
+ '_split_targets': {
+ name: 'Split targets',
+ targets: [
+ { result: 'time', distanceValue: 1, distanceUnit: 'kilometers' },
+ { result: 'time', distanceValue: 2, distanceUnit: 'kilometers' },
+ { result: 'time', distanceValue: 3000, distanceUnit: 'meters' },
+ ],
+ },
+ },
+ defaultUnitSystem: 'metric',
+ };
+ },
+ });
+
+ // Assert results are correct
+ const rows = wrapper.findAll('tbody tr');
+ expect(rows[0].findAll('td')[0].element.textContent).to.equal('1 km');
+ expect(rows[0].findAll('td')[1].element.textContent).to.equal('0:00.00');
+ expect(rows[0].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(0);
+ expect(rows[0].findAll('td')[3].element.textContent).to.equal('0:00 / km');
+ expect(rows[0].findAll('td').length).to.equal(4);
+ expect(rows[1].findAll('td')[0].element.textContent).to.equal('2 km');
+ expect(rows[1].findAll('td')[1].element.textContent).to.equal('0:00.00');
+ expect(rows[1].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(0);
+ expect(rows[1].findAll('td')[3].element.textContent).to.equal('0:00 / km');
+ expect(rows[1].findAll('td').length).to.equal(4);
+ expect(rows[2].findAll('td')[0].element.textContent).to.equal('3000 m');
+ expect(rows[2].findAll('td')[1].element.textContent).to.equal('0:00.00');
+ expect(rows[2].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(0);
+ expect(rows[2].findAll('td')[3].element.textContent).to.equal('0:00 / km');
+ expect(rows[2].findAll('td').length).to.equal(4);
+ expect(rows.length).to.equal(3);
+});
+
+test('should correctly load split times from split targets', async () => {
+ // Initialize component
+ const wrapper = shallowMount(SplitCalculator, {
+ data() {
+ return {
+ targetSets: {
+ '_split_targets': {
+ name: 'Split targets',
+ targets: [
+ { result: 'time', distanceValue: 1, distanceUnit: 'kilometers', split: 180 },
+ { result: 'time', distanceValue: 2, distanceUnit: 'kilometers', split: 190 },
+ { result: 'time', distanceValue: 3000, distanceUnit: 'meters', split: 200 },
+ ],
+ },
+ },
+ defaultUnitSystem: 'metric',
+ };
+ },
+ });
+
+ // Assert results are correct
+ const rows = wrapper.findAll('tbody tr');
+ expect(rows[0].findAll('td')[0].element.textContent).to.equal('1 km');
+ expect(rows[0].findAll('td')[1].element.textContent).to.equal('3:00.00');
+ expect(rows[0].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(180);
+ expect(rows[0].findAll('td')[3].element.textContent).to.equal('3:00 / km');
+ expect(rows[0].findAll('td').length).to.equal(4);
+ expect(rows[1].findAll('td')[0].element.textContent).to.equal('2 km');
+ expect(rows[1].findAll('td')[1].element.textContent).to.equal('6:10.00');
+ expect(rows[1].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(190);
+ expect(rows[1].findAll('td')[3].element.textContent).to.equal('3:10 / km');
+ expect(rows[1].findAll('td').length).to.equal(4);
+ expect(rows[2].findAll('td')[0].element.textContent).to.equal('3000 m');
+ expect(rows[2].findAll('td')[1].element.textContent).to.equal('9:30.00');
+ expect(rows[2].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(200);
+ expect(rows[2].findAll('td')[3].element.textContent).to.equal('3:20 / km');
+ expect(rows[2].findAll('td').length).to.equal(4);
+ expect(rows.length).to.equal(3);
+});
+
+test('should correctly handle null target set', async () => {
+ // Initialize component
+ const wrapper = shallowMount(SplitCalculator, {
+ data() {
+ return {
+ targetSets: {
+ '_split_targets': {
+ name: 'Split targets',
+ targets: [
+ { result: 'time', distanceValue: 1, distanceUnit: 'kilometers', split: 180 },
+ { result: 'time', distanceValue: 2, distanceUnit: 'kilometers', split: 190 },
+ { result: 'time', distanceValue: 3000, distanceUnit: 'meters', split: 200 },
+ ],
+ },
+ 'B': null,
+ },
+ selectedTargetSet: 'B',
+ defaultUnitSystem: 'metric',
+ };
+ },
+ });
+
+ // Assert results are empty
+ let rows = wrapper.findAll('tbody tr');
+ expect(rows[0].findAll('td')[0].element.textContent.trim()).to.equal('There aren\'t any targets in this set yet.');
+ expect(rows[0].findAll('td').length).to.equal(1);
+ expect(rows.length).to.equal(1);
+
+ // Switch to valid target set
+ await wrapper.findComponent({ name: 'target-set-selector' }).setValue('_split_targets');
+
+ // Assert results are correct
+ rows = wrapper.findAll('tbody tr');
+ expect(rows[0].findAll('td')[0].element.textContent).to.equal('1 km');
+ expect(rows[0].findAll('td')[1].element.textContent).to.equal('3:00.00');
+ expect(rows[0].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(180);
+ expect(rows[0].findAll('td')[3].element.textContent).to.equal('3:00 / km');
+ expect(rows[0].findAll('td').length).to.equal(4);
+ expect(rows[1].findAll('td')[0].element.textContent).to.equal('2 km');
+ expect(rows[1].findAll('td')[1].element.textContent).to.equal('6:10.00');
+ expect(rows[1].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(190);
+ expect(rows[1].findAll('td')[3].element.textContent).to.equal('3:10 / km');
+ expect(rows[1].findAll('td').length).to.equal(4);
+ expect(rows[2].findAll('td')[0].element.textContent).to.equal('3000 m');
+ expect(rows[2].findAll('td')[1].element.textContent).to.equal('9:30.00');
+ expect(rows[2].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(200);
+ expect(rows[2].findAll('td')[3].element.textContent).to.equal('3:20 / km');
+ expect(rows[2].findAll('td').length).to.equal(4);
+ expect(rows.length).to.equal(3);
+});
+
+test('should correctly calculate paces and cululative times from entered split times', async () => {
+ // Initialize component
+ const wrapper = shallowMount(SplitCalculator, {
+ data() {
+ return {
+ targetSets: {
+ '_split_targets': {
+ name: 'Split targets',
+ targets: [
+ { result: 'time', distanceValue: 1, distanceUnit: 'kilometers', split: 180 },
+ { result: 'time', distanceValue: 2, distanceUnit: 'kilometers', split: 180 },
+ { result: 'time', distanceValue: 3000, distanceUnit: 'meters', split: 180 },
+ ],
+ },
+ },
+ defaultUnitSystem: 'metric',
+ };
+ },
+ });
+
+ // Update split times
+ await wrapper.findAllComponents({ name: 'time-input' })[1].setValue(190);
+ await wrapper.findAllComponents({ name: 'time-input' })[2].setValue(200);
+
+ // Assert results are correct
+ const rows = wrapper.findAll('tbody tr');
+ expect(rows[0].findAll('td')[0].element.textContent).to.equal('1 km');
+ expect(rows[0].findAll('td')[1].element.textContent).to.equal('3:00.00');
+ expect(rows[0].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(180);
+ expect(rows[0].findAll('td')[3].element.textContent).to.equal('3:00 / km');
+ expect(rows[0].findAll('td').length).to.equal(4);
+ expect(rows[1].findAll('td')[0].element.textContent).to.equal('2 km');
+ expect(rows[1].findAll('td')[1].element.textContent).to.equal('6:10.00');
+ expect(rows[1].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(190);
+ expect(rows[1].findAll('td')[3].element.textContent).to.equal('3:10 / km');
+ expect(rows[1].findAll('td').length).to.equal(4);
+ expect(rows[2].findAll('td')[0].element.textContent).to.equal('3000 m');
+ expect(rows[2].findAll('td')[1].element.textContent).to.equal('9:30.00');
+ expect(rows[2].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(200);
+ expect(rows[2].findAll('td')[3].element.textContent).to.equal('3:20 / km');
+ expect(rows[2].findAll('td').length).to.equal(4);
+ expect(rows.length).to.equal(3);
+});
+
+test('should correctly sort split targets', async () => {
+ // Initialize component
+ const wrapper = shallowMount(SplitCalculator, {
+ data() {
+ return {
+ targetSets: {
+ '_split_targets': {
+ name: 'Split targets',
+ targets: [
+ { result: 'time', distanceValue: 2, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 1, distanceUnit: 'kilometers' },
+ { result: 'time', distanceValue: 2, distanceUnit: 'kilometers' },
+ ],
+ },
+ },
+ defaultUnitSystem: 'metric',
+ };
+ },
+ });
+
+ // Assert results are correct
+ const rows = wrapper.findAll('tbody tr');
+ expect(rows[0].findAll('td')[0].element.textContent).to.equal('1 km');
+ expect(rows[0].findAll('td')[1].element.textContent).to.equal('0:00.00');
+ expect(rows[0].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(0);
+ expect(rows[0].findAll('td')[3].element.textContent).to.equal('0:00 / km');
+ expect(rows[0].findAll('td').length).to.equal(4);
+ expect(rows[1].findAll('td')[0].element.textContent).to.equal('2 km');
+ expect(rows[1].findAll('td')[1].element.textContent).to.equal('0:00.00');
+ expect(rows[1].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(0);
+ expect(rows[1].findAll('td')[3].element.textContent).to.equal('0:00 / km');
+ expect(rows[1].findAll('td').length).to.equal(4);
+ expect(rows[2].findAll('td')[0].element.textContent).to.equal('2 mi');
+ expect(rows[2].findAll('td')[1].element.textContent).to.equal('0:00.00');
+ expect(rows[2].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(0);
+ expect(rows[2].findAll('td')[3].element.textContent).to.equal('0:00 / km');
+ expect(rows[2].findAll('td').length).to.equal(4);
+ expect(rows.length).to.equal(3);
+});
+
+test('should ignore time based targets', async () => {
+ // Initialize component
+ const wrapper = shallowMount(SplitCalculator, {
+ data() {
+ return {
+ targetSets: {
+ '_split_targets': {
+ name: 'Split targets',
+ targets: [
+ { result: 'time', distanceValue: 1, distanceUnit: 'kilometers' },
+ { result: 'distance', time: 600 },
+ { result: 'time', distanceValue: 2, distanceUnit: 'kilometers' },
+ { result: 'time', distanceValue: 3000, distanceUnit: 'meters' },
+ ],
+ },
+ },
+ defaultUnitSystem: 'metric',
+ };
+ },
+ });
+
+ // Assert results are correct
+ const rows = wrapper.findAll('tbody tr');
+ expect(rows[0].findAll('td')[0].element.textContent).to.equal('1 km');
+ expect(rows[0].findAll('td')[1].element.textContent).to.equal('0:00.00');
+ expect(rows[0].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(0);
+ expect(rows[0].findAll('td')[3].element.textContent).to.equal('0:00 / km');
+ expect(rows[0].findAll('td').length).to.equal(4);
+ expect(rows[1].findAll('td')[0].element.textContent).to.equal('2 km');
+ expect(rows[1].findAll('td')[1].element.textContent).to.equal('0:00.00');
+ expect(rows[1].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(0);
+ expect(rows[1].findAll('td')[3].element.textContent).to.equal('0:00 / km');
+ expect(rows[1].findAll('td').length).to.equal(4);
+ expect(rows[2].findAll('td')[0].element.textContent).to.equal('3000 m');
+ expect(rows[2].findAll('td')[1].element.textContent).to.equal('0:00.00');
+ expect(rows[2].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(0);
+ expect(rows[2].findAll('td')[3].element.textContent).to.equal('0:00 / km');
+ expect(rows[2].findAll('td').length).to.equal(4);
+ expect(rows.length).to.equal(3);
+});
+
+test('should correctly save split times with split targets in localStorage', async () => {
+ // Initialize component
+ const wrapper = shallowMount(SplitCalculator, {
+ data() {
+ return {
+ targetSets: {
+ '_split_targets': {
+ name: 'Split targets',
+ targets: [
+ { result: 'time', distanceValue: 1, distanceUnit: 'kilometers', split: 180 },
+ { result: 'time', distanceValue: 2, distanceUnit: 'kilometers', split: 180 },
+ { result: 'time', distanceValue: 3000, distanceUnit: 'meters', split: 180 },
+ ],
+ },
+ },
+ defaultUnitSystem: 'metric',
+ };
+ },
+ });
+
+ // Update split times
+ await wrapper.findAllComponents({ name: 'time-input' })[1].setValue(190);
+ await wrapper.findAllComponents({ name: 'time-input' })[2].setValue(200);
+
+ // Assert targets correctly saved in localStorage
+ expect(localStorage.getItem('running-tools.target-sets')).to.equal(JSON.stringify({
+ '_split_targets': {
+ name: 'Split targets',
+ targets: [
+ { result: 'time', distanceValue: 1, distanceUnit: 'kilometers', split: 180 },
+ { result: 'time', distanceValue: 2, distanceUnit: 'kilometers', split: 190 },
+ { result: 'time', distanceValue: 3000, distanceUnit: 'meters', split: 200 },
+ ],
+ },
+ }));
+});
+
+test('should update results when a new target set is selected', async () => {
// Initialize component
const wrapper = shallowMount(SplitCalculator, {
data() {
@@ -14,24 +308,200 @@ test('should correctly calculate split paces and total times', async () => {
'_split_targets': {
name: 'Split targets',
targets: [
- { result: 'time', distanceValue: 2, distanceUnit: 'miles', split: 60 },
- { result: 'time', distanceValue: 4, distanceUnit: 'miles', split: 70 },
- { result: 'time', distanceValue: 10, distanceUnit: 'kilometers', split: 80 },
+ { result: 'time', distanceValue: 1, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 2, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 5, distanceUnit: 'kilometers' },
+ ],
+ },
+ 'B': {
+ name: 'Split targets #2',
+ targets: [
+ { result: 'time', distanceValue: 1, distanceUnit: 'kilometers', split: 180 },
+ { result: 'time', distanceValue: 2, distanceUnit: 'kilometers', split: 190 },
+ { result: 'time', distanceValue: 3000, distanceUnit: 'meters', split: 200 },
],
},
},
+ defaultUnitSystem: 'metric',
};
},
});
+ // Assert default split targets are initially loaded
+ expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.modelValue).to.equal('_split_targets');
+ expect(wrapper.findAll('tbody td')[0].element.textContent).to.equal('1 mi');
+
+ // Select a new target set
+ await wrapper.findComponent({ name: 'target-set-selector' }).setValue('B');
+
// Assert results are correct
- const final_split_distance = 10000 - unitUtils.convertDistance(4, 'miles', 'meters');
- const final_split_pace = unitUtils.convertPace(80 / final_split_distance, 'seconds_per_meter',
- 'seconds_per_mile')
- expect(wrapper.vm.results[0].totalTime).to.equal(60);
- expect(wrapper.vm.results[0].pace).to.equal(30);
- expect(wrapper.vm.results[1].totalTime).to.equal(130);
- expect(wrapper.vm.results[1].pace).to.equal(35);
- expect(wrapper.vm.results[2].totalTime).to.equal(210);
- expect(final_split_pace - wrapper.vm.results[2].pace).to.lessThan(0.00001);
+ const rows = wrapper.findAll('tbody tr');
+ expect(rows[0].findAll('td')[0].element.textContent).to.equal('1 km');
+ expect(rows[0].findAll('td')[1].element.textContent).to.equal('3:00.00');
+ expect(rows[0].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(180);
+ expect(rows[0].findAll('td')[3].element.textContent).to.equal('3:00 / km');
+ expect(rows[0].findAll('td').length).to.equal(4);
+ expect(rows[1].findAll('td')[0].element.textContent).to.equal('2 km');
+ expect(rows[1].findAll('td')[1].element.textContent).to.equal('6:10.00');
+ expect(rows[1].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(190);
+ expect(rows[1].findAll('td')[3].element.textContent).to.equal('3:10 / km');
+ expect(rows[1].findAll('td').length).to.equal(4);
+ expect(rows[2].findAll('td')[0].element.textContent).to.equal('3000 m');
+ expect(rows[2].findAll('td')[1].element.textContent).to.equal('9:30.00');
+ expect(rows[2].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(200);
+ expect(rows[2].findAll('td')[3].element.textContent).to.equal('3:20 / km');
+ expect(rows[2].findAll('td').length).to.equal(4);
+ expect(rows.length).to.equal(3);
+});
+
+test('should load selected target set from localStorage', async () => {
+ // Initialize localStorage
+ localStorage.setItem('running-tools.split-calculator-target-set', '"B"');
+
+ // Initialize component
+ const wrapper = shallowMount(SplitCalculator, {
+ data() {
+ return {
+ targetSets: {
+ '_split_targets': {
+ name: 'Split targets',
+ targets: [
+ { result: 'time', distanceValue: 1, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 2, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 5, distanceUnit: 'kilometers' },
+ ],
+ },
+ 'B': {
+ name: 'Split targets #2',
+ targets: [
+ { result: 'time', distanceValue: 1, distanceUnit: 'kilometers', split: 180 },
+ { result: 'time', distanceValue: 2, distanceUnit: 'kilometers', split: 190 },
+ { result: 'time', distanceValue: 3000, distanceUnit: 'meters', split: 200 },
+ ],
+ },
+ },
+ defaultUnitSystem: 'metric',
+ };
+ },
+ });
+
+ // Assert selection is loaded
+ expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.modelValue).to.equal('B');
+
+ // Assert results are correct
+ const rows = wrapper.findAll('tbody tr');
+ expect(rows[0].findAll('td')[0].element.textContent).to.equal('1 km');
+ expect(rows[0].findAll('td')[1].element.textContent).to.equal('3:00.00');
+ expect(rows[0].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(180);
+ expect(rows[0].findAll('td')[3].element.textContent).to.equal('3:00 / km');
+ expect(rows[0].findAll('td').length).to.equal(4);
+ expect(rows[1].findAll('td')[0].element.textContent).to.equal('2 km');
+ expect(rows[1].findAll('td')[1].element.textContent).to.equal('6:10.00');
+ expect(rows[1].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(190);
+ expect(rows[1].findAll('td')[3].element.textContent).to.equal('3:10 / km');
+ expect(rows[1].findAll('td').length).to.equal(4);
+ expect(rows[2].findAll('td')[0].element.textContent).to.equal('3000 m');
+ expect(rows[2].findAll('td')[1].element.textContent).to.equal('9:30.00');
+ expect(rows[2].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(200);
+ expect(rows[2].findAll('td')[3].element.textContent).to.equal('3:20 / km');
+ expect(rows[2].findAll('td').length).to.equal(4);
+ expect(rows.length).to.equal(3);
+});
+
+test('should save selected target set to localStorage when modified', async () => {
+ // Initialize component
+ const wrapper = shallowMount(SplitCalculator, {
+ data() {
+ return {
+ targetSets: {
+ '_split_targets': {
+ name: 'Split targets',
+ targets: [
+ { result: 'time', distanceValue: 1, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 2, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 5, distanceUnit: 'kilometers' },
+ ],
+ },
+ 'B': {
+ name: 'Split targets #2',
+ targets: [
+ { result: 'time', distanceValue: 1, distanceUnit: 'kilometers', split: 180 },
+ { result: 'time', distanceValue: 2, distanceUnit: 'kilometers', split: 190 },
+ { result: 'time', distanceValue: 3000, distanceUnit: 'meters', split: 200 },
+ ],
+ },
+ },
+ defaultUnitSystem: 'metric',
+ };
+ },
+ });
+
+ // Select a new target set
+ await wrapper.findComponent({ name: 'target-set-selector' }).setValue('B');
+
+ // New selected target set should be saved to localStorage
+ expect(localStorage.getItem('running-tools.split-calculator-target-set')).to.equal('"B"');
+});
+
+test('should update paces according to default units setting', async () => {
+ // Initialize component
+ const wrapper = shallowMount(SplitCalculator, {
+ data() {
+ return {
+ targetSets: {
+ '_split_targets': {
+ name: 'Split targets',
+ targets: [
+ { result: 'time', distanceValue: 1, distanceUnit: 'miles', split: 300 },
+ { result: 'time', distanceValue: 2, distanceUnit: 'miles', split: 300 },
+ { result: 'time', distanceValue: 5, distanceUnit: 'kilometers', split: 330 },
+ ],
+ },
+ },
+ defaultUnitSystem: 'metric',
+ };
+ },
+ });
+
+ // Assert paces are correct
+ let rows = wrapper.findAll('tbody tr');
+ expect(rows[0].findAll('td')[3].element.textContent).to.equal('3:06 / km');
+ expect(rows[1].findAll('td')[3].element.textContent).to.equal('3:06 / km');
+ expect(rows[2].findAll('td')[3].element.textContent).to.equal('3:05 / km');
+
+ // Change default units
+ await wrapper.find('select').setValue('imperial');
+
+ // Assert paces are correct
+ rows = wrapper.findAll('tbody tr');
+ expect(rows[0].findAll('td')[3].element.textContent).to.equal('5:00 / mi');
+ expect(rows[1].findAll('td')[3].element.textContent).to.equal('5:00 / mi');
+ expect(rows[2].findAll('td')[3].element.textContent).to.equal('4:58 / mi');
+});
+
+test('should save default units setting to localStorage when modified', async () => {
+ // Initialize component
+ const wrapper = shallowMount(SplitCalculator, {
+ data() {
+ return {
+ targetSets: {
+ '_split_targets': {
+ name: 'Split targets',
+ targets: [
+ { result: 'time', distanceValue: 1, distanceUnit: 'miles', split: 300 },
+ { result: 'time', distanceValue: 2, distanceUnit: 'miles', split: 300 },
+ { result: 'time', distanceValue: 5, distanceUnit: 'kilometers', split: 330 },
+ ],
+ },
+ },
+ defaultUnitSystem: 'metric',
+ };
+ },
+ });
+
+ // Change default units
+ await wrapper.find('select').setValue('imperial');
+
+ // 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/UnitCalculator.spec.js b/tests/unit/views/UnitCalculator.spec.js
@@ -1,8 +1,5 @@
-/* eslint-disable no-underscore-dangle */
-
import { beforeEach, test, expect } from 'vitest';
import { shallowMount } from '@vue/test-utils';
-import unitUtils from '@/utils/units';
import UnitCalculator from '@/views/UnitCalculator.vue';
beforeEach(() => {