PaceCalculator.spec.js (9538B)
1 import { beforeEach, test, expect } from 'vitest'; 2 import { shallowMount } from '@vue/test-utils'; 3 import PaceCalculator from '@/views/PaceCalculator.vue'; 4 import { defaultTargetSets } from '@/core/targets'; 5 import { detectDefaultUnitSystem } from '@/core/units'; 6 7 beforeEach(() => { 8 localStorage.clear(); 9 }); 10 11 test('should initialize options to default values', async () => { 12 // Initialize component 13 const wrapper = shallowMount(PaceCalculator); 14 15 // Assert options are initialized 16 expect(wrapper.findComponent({ name: 'pace-input' }).vm.modelValue).to.deep.equal({ 17 distanceValue: 5, 18 distanceUnit: 'kilometers', 19 time: 1200, 20 }); 21 expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.globalOptions 22 .defaultUnitSystem).to.equal(detectDefaultUnitSystem()); 23 expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({ 24 input: { 25 distanceValue: 5, 26 distanceUnit: 'kilometers', 27 time: 1200, 28 }, 29 selectedTargetSet: '_pace_targets', 30 }); 31 expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.targetSets) 32 .to.deep.equal({ _pace_targets: defaultTargetSets._pace_targets }); 33 expect(wrapper.findComponent({ name: 'single-output-table' }).vm.targets) 34 .to.deep.equal(defaultTargetSets._pace_targets.targets); 35 }); 36 37 test('should load options from localStorage', async () => { 38 const targetSets = { 39 '_pace_targets': { 40 name: 'Pace targets #1', 41 targets: [ 42 { type: 'distance', distanceValue: 400, distanceUnit: 'meters' }, 43 { type: 'distance', distanceValue: 800, distanceUnit: 'meters' }, 44 { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' }, 45 ], 46 }, 47 'B': { 48 name: 'Pace targets #2', 49 targets: [ 50 { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, 51 { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, 52 { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, 53 ], 54 }, 55 }; 56 57 // Initialize localStorage 58 localStorage.setItem('running-tools.global-options', JSON.stringify({ 59 defaultUnitSystem: 'imperial', 60 racePredictionOptions: { 61 model: 'PurdyPointsModel', 62 riegelExponent: 1.2, 63 }, 64 })); 65 localStorage.setItem('running-tools.pace-calculator-target-sets', JSON.stringify(targetSets)); 66 localStorage.setItem('running-tools.pace-calculator-options', JSON.stringify({ 67 input: { 68 distanceValue: 1, 69 distanceUnit: 'miles', 70 time: 600, 71 }, 72 selectedTargetSet: 'B', 73 })); 74 75 // Initialize component 76 const wrapper = shallowMount(PaceCalculator); 77 78 // Assert options are loaded 79 expect(wrapper.findComponent({ name: 'pace-input' }).vm.modelValue).to.deep.equal({ 80 distanceValue: 1, 81 distanceUnit: 'miles', 82 time: 600, 83 }); 84 expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.globalOptions 85 .defaultUnitSystem).to.equal('imperial'); 86 expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({ 87 input: { 88 distanceValue: 1, 89 distanceUnit: 'miles', 90 time: 600, 91 }, 92 selectedTargetSet: 'B', 93 }); 94 expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.targetSets) 95 .to.deep.equal(targetSets); 96 expect(wrapper.findComponent({ name: 'single-output-table' }).vm.targets) 97 .to.deep.equal(targetSets.B.targets); 98 }); 99 100 test('should save options to localStorage when modified', async () => { 101 const targetSets = { 102 '_pace_targets': { 103 name: 'Pace targets #1', 104 targets: [ 105 { type: 'distance', distanceValue: 400, distanceUnit: 'meters' }, 106 { type: 'distance', distanceValue: 800, distanceUnit: 'meters' }, 107 { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' }, 108 ], 109 }, 110 'B': { 111 name: 'Pace targets #2', 112 targets: [ 113 { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, 114 { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, 115 { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, 116 ], 117 }, 118 }; 119 120 // Initialize component 121 const wrapper = shallowMount(PaceCalculator); 122 123 // Change default units 124 await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ 125 defaultUnitSystem: 'imperial', 126 racePredictionOptions: { 127 model: 'AverageModel', 128 riegelExponent: 1.06, 129 }, 130 }, 'globalOptions'); 131 132 // New default units should be saved to localStorage 133 expect(localStorage.getItem('running-tools.global-options')).to.equal(JSON.stringify({ 134 defaultUnitSystem: 'imperial', 135 racePredictionOptions: { 136 model: 'AverageModel', 137 riegelExponent: 1.06, 138 }, 139 })); 140 141 // Update input pace 142 await wrapper.findComponent({ name: 'pace-input' }).setValue({ 143 distanceValue: 1, 144 distanceUnit: 'miles', 145 time: 600, 146 }); 147 148 // New input pace should be saved to localStorage 149 expect(localStorage.getItem('running-tools.pace-calculator-options')).to.equal(JSON.stringify({ 150 input: { 151 distanceValue: 1, 152 distanceUnit: 'miles', 153 time: 600, 154 }, 155 selectedTargetSet: '_pace_targets', 156 })); 157 158 // Update target sets and selected target set 159 await wrapper.findComponent({ name: 'advanced-options-input' }).setValue(targetSets, 160 'targetSets'); 161 await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ 162 input: { 163 distanceValue: 1, 164 distanceUnit: 'miles', 165 time: 600, 166 }, 167 selectedTargetSet: 'B', 168 }, 'options'); 169 170 // New selected target set should be saved to localStorage 171 expect(localStorage.getItem('running-tools.pace-calculator-target-sets')) 172 .to.equal(JSON.stringify(targetSets)); 173 expect(localStorage.getItem('running-tools.pace-calculator-options')).to.equal(JSON.stringify({ 174 input: { 175 distanceValue: 1, 176 distanceUnit: 'miles', 177 time: 600, 178 }, 179 selectedTargetSet: 'B', 180 })); 181 }); 182 183 test('should correctly calculate time results', async () => { 184 // Initialize component 185 const wrapper = shallowMount(PaceCalculator); 186 187 // Enter input pace data 188 await wrapper.findComponent({ name: 'pace-input' }).setValue({ 189 distanceValue: 1, 190 distanceUnit: 'kilometers', 191 time: 100, 192 }); 193 194 // Calculate result 195 const calculateResult = wrapper.findComponent({ name: 'single-output-table' }).vm.calculateResult; 196 const result = calculateResult({ 197 distanceValue: 20, 198 distanceUnit: 'meters', 199 type: 'distance', 200 }); 201 202 // Assert result is correct 203 expect(result).to.deep.equal({ 204 key: '20 m', 205 value: '0:02.00', 206 pace: '2:41 / mi', 207 result: 'value', 208 sort: 2, 209 }); 210 }); 211 212 test('should correctly calculate distance results according to global options', async () => { 213 // Initialize component 214 const wrapper = shallowMount(PaceCalculator); 215 216 // Enter input pace data 217 await wrapper.findComponent({ name: 'pace-input' }).setValue({ 218 distanceValue: 2, 219 distanceUnit: 'miles', 220 time: 1200, 221 }); 222 223 // Set default units 224 await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ 225 defaultUnitSystem: 'metric', 226 racePredictionOptions: { 227 model: 'AverageModel', 228 riegelExponent: 1.06, 229 }, 230 }, 'globalOptions'); 231 232 // Get calculate result function 233 const calculateResult = wrapper.findComponent({ name: 'single-output-table' }).vm.calculateResult; 234 235 // Assert result is correct 236 let result = calculateResult({ type: 'time', time: 600 }); 237 expect(result.key).to.equal('1.61 km'); 238 239 // Change default units 240 await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ 241 defaultUnitSystem: 'imperial', 242 racePredictionOptions: { 243 model: 'AverageModel', 244 riegelExponent: 1.06, 245 }, 246 }, 'globalOptions'); 247 248 // Assert result is correct 249 result = calculateResult({ type: 'time', time: 600 }); 250 expect(result.key).to.equal('1.00 mi'); 251 }); 252 253 test('should not show paces in results table', async () => { 254 // Initialize component 255 const wrapper = shallowMount(PaceCalculator); 256 257 // Assert paces are not shown in results table 258 expect(wrapper.findComponent({ name: 'single-output-table' }).vm.showPace).to.equal(false); 259 }); 260 261 test('should correctly handle null target set', async () => { 262 // Initialize component 263 const wrapper = shallowMount(PaceCalculator); 264 265 // Switch to invalid target set 266 await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ 267 input: { 268 distanceValue: 5, 269 distanceUnit: 'kilometers', 270 time: 1200, 271 }, 272 selectedTargetSet: 'does_not_exist' 273 }, 'options'); 274 275 // Assert empty array passed to SingleOutputTable component 276 expect(wrapper.findComponent({ name: 'single-output-table' }).vm.targets).to.deep.equal([]); 277 278 // Switch to valid target set 279 await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ 280 input: { 281 distanceValue: 5, 282 distanceUnit: 'kilometers', 283 time: 1200, 284 }, 285 selectedTargetSet: '_pace_targets' 286 }, 'options'); 287 288 // Assert valid targets passed to SingleOutputTable component 289 const paceTargets = defaultTargetSets._pace_targets.targets; 290 expect(wrapper.findComponent({ name: 'single-output-table' }).vm.targets) 291 .to.deep.equal(paceTargets); 292 }); 293 294 test('should correctly set AdvancedOptionsInput type prop', async () => { 295 // Initialize component 296 const wrapper = shallowMount(PaceCalculator); 297 298 // Assert type prop is correctly set 299 expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.type).to.equal('pace'); 300 });