WorkoutCalculator.spec.js (11404B)
1 import { beforeEach, test, expect } from 'vitest'; 2 import { shallowMount } from '@vue/test-utils'; 3 import WorkoutCalculator from '@/views/WorkoutCalculator.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(WorkoutCalculator); 14 15 // Assert options are initialized 16 expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.globalOptions).to.deep.equal({ 17 defaultUnitSystem: detectDefaultUnitSystem(), 18 racePredictionOptions: { 19 model: 'AverageModel', 20 riegelExponent: 1.06, 21 }, 22 }); 23 expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({ 24 customTargetNames: false, 25 input: { 26 distanceValue: 5, 27 distanceUnit: 'kilometers', 28 time: 1200, 29 }, 30 selectedTargetSet: '_workout_targets', 31 }); 32 expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.targetSets) 33 .to.deep.equal({ _workout_targets: defaultTargetSets._workout_targets }); 34 expect(wrapper.findComponent({ name: 'single-output-table' }).vm.targets) 35 .to.deep.equal(defaultTargetSets._workout_targets.targets); 36 }); 37 38 test('should load options from localStorage', async () => { 39 const targetSets = { 40 '_workout_targets': { 41 name: 'Workout targets #1', 42 targets: [ 43 { 44 splitValue: 400, splitUnit: 'meters', 45 type: 'distance', distanceValue: 1, distanceUnit: 'miles', 46 }, 47 { 48 splitValue: 800, splitUnit: 'meters', 49 type: 'distance', distanceValue: 5, distanceUnit: 'kilometers', 50 }, 51 { 52 splitValue: 1600, splitUnit: 'meters', 53 type: 'time', time: 3600, 54 }, 55 { 56 splitValue: 2, splitUnit: 'miles', 57 type: 'time', time: 7200, 58 }, 59 ], 60 }, 61 'B': { 62 name: 'Workout targets #2', 63 targets: [ 64 { 65 distanceUnit: 'miles', distanceValue: 2, 66 splitUnit: 'meters', splitValue: 400, 67 type: 'distance', 68 }, 69 { 70 time: 6000, 71 splitUnit: 'kilometers', splitValue: 2, 72 type: 'time', 73 }, 74 { 75 distanceUnit: 'kilometers', distanceValue: 5, 76 splitUnit: 'miles', splitValue: 1, 77 type: 'distance' 78 }, 79 ], 80 }, 81 }; 82 83 // Initialize localStorage 84 localStorage.setItem('running-tools.global-options', JSON.stringify({ 85 defaultUnitSystem: 'imperial', 86 racePredictionOptions: { 87 model: 'PurdyPointsModel', 88 riegelExponent: 1.2, 89 }, 90 })); 91 localStorage.setItem('running-tools.workout-calculator-target-sets', JSON.stringify(targetSets)); 92 localStorage.setItem('running-tools.workout-calculator-options', JSON.stringify({ 93 customTargetNames: true, 94 input: { 95 distanceValue: 1, 96 distanceUnit: 'miles', 97 time: 600, 98 }, 99 selectedTargetSet: 'B', 100 })); 101 102 // Initialize component 103 const wrapper = shallowMount(WorkoutCalculator); 104 105 // Assert options are loaded 106 expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.globalOptions).to.deep.equal({ 107 defaultUnitSystem: 'imperial', 108 racePredictionOptions: { 109 model: 'PurdyPointsModel', 110 riegelExponent: 1.2, 111 }, 112 }); 113 expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({ 114 customTargetNames: true, 115 input: { 116 distanceValue: 1, 117 distanceUnit: 'miles', 118 time: 600, 119 }, 120 selectedTargetSet: 'B', 121 }); 122 expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.targetSets) 123 .to.deep.equal(targetSets); 124 expect(wrapper.findComponent({ name: 'single-output-table' }).vm.targets) 125 .to.deep.equal(targetSets.B.targets); 126 }); 127 128 test('should save options to localStorage when modified', async () => { 129 const targetSets = { 130 '_workout_targets': { 131 name: 'Workout targets #1', 132 targets: [ 133 { 134 splitValue: 400, splitUnit: 'meters', 135 type: 'distance', distanceValue: 1, distanceUnit: 'miles', 136 }, 137 { 138 splitValue: 800, splitUnit: 'meters', 139 type: 'distance', distanceValue: 5, distanceUnit: 'kilometers', 140 }, 141 { 142 splitValue: 1600, splitUnit: 'meters', 143 type: 'time', time: 3600, 144 }, 145 { 146 splitValue: 2, splitUnit: 'miles', 147 type: 'time', time: 7200, 148 }, 149 ], 150 }, 151 'B': { 152 name: 'Workout targets #2', 153 targets: [ 154 { 155 distanceUnit: 'miles', distanceValue: 2, 156 splitUnit: 'meters', splitValue: 400, 157 type: 'distance', 158 }, 159 { 160 time: 6000, 161 splitUnit: 'kilometers', splitValue: 2, 162 type: 'time', 163 }, 164 { 165 distanceUnit: 'kilometers', distanceValue: 5, 166 splitUnit: 'miles', splitValue: 1, 167 type: 'distance' 168 }, 169 ], 170 }, 171 }; 172 173 // Initialize component 174 const wrapper = shallowMount(WorkoutCalculator); 175 176 // Update options 177 await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ 178 defaultUnitSystem: 'imperial', 179 racePredictionOptions: { 180 model: 'CameronModel', 181 riegelExponent: 1.3, 182 }, 183 }, 'globalOptions'); 184 185 // Assert data saved to localStorage 186 expect(localStorage.getItem('running-tools.global-options')).to.equal(JSON.stringify({ 187 defaultUnitSystem: 'imperial', 188 racePredictionOptions: { 189 model: 'CameronModel', 190 riegelExponent: 1.3, 191 }, 192 })); 193 194 // Update input race 195 await wrapper.findComponent({ name: 'pace-input' }).setValue({ 196 distanceValue: 1, 197 distanceUnit: 'miles', 198 time: 600, 199 }); 200 201 // Assert data saved to localStorage 202 expect(localStorage.getItem('running-tools.workout-calculator-options')).to.equal(JSON.stringify({ 203 customTargetNames: false, 204 input: { 205 distanceValue: 1, 206 distanceUnit: 'miles', 207 time: 600, 208 }, 209 selectedTargetSet: '_workout_targets', 210 })); 211 212 // Update target name customization 213 await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ 214 customTargetNames: true, 215 input: { 216 distanceValue: 1, 217 distanceUnit: 'miles', 218 time: 600, 219 }, 220 selectedTargetSet: '_workout_targets', 221 }, 'options'); 222 223 // Assert data saved to localStorage 224 expect(localStorage.getItem('running-tools.workout-calculator-options')).to.equal(JSON.stringify({ 225 customTargetNames: true, 226 input: { 227 distanceValue: 1, 228 distanceUnit: 'miles', 229 time: 600, 230 }, 231 selectedTargetSet: '_workout_targets', 232 })); 233 234 // Update target sets and selected target set 235 await wrapper.findComponent({ name: 'advanced-options-input' }).setValue(targetSets, 236 'targetSets'); 237 await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ 238 customTargetNames: true, 239 input: { 240 distanceValue: 1, 241 distanceUnit: 'miles', 242 time: 600, 243 }, 244 selectedTargetSet: 'B', 245 }, 'options'); 246 247 // Assert data saved to localStorage 248 expect(localStorage.getItem('running-tools.workout-calculator-target-sets')) 249 .to.equal(JSON.stringify(targetSets)); 250 expect(localStorage.getItem('running-tools.workout-calculator-options')).to.equal(JSON.stringify({ 251 customTargetNames: true, 252 input: { 253 distanceValue: 1, 254 distanceUnit: 'miles', 255 time: 600, 256 }, 257 selectedTargetSet: 'B', 258 })); 259 }); 260 261 test('should correctly predict workout splits', async () => { 262 // Initialize component 263 const wrapper = shallowMount(WorkoutCalculator); 264 265 // Enter input race data 266 await wrapper.findComponent({ name: 'pace-input' }).setValue({ 267 distanceValue: 5, 268 distanceUnit: 'kilometers', 269 time: 1200, 270 }); 271 272 // Calculate result 273 const calculateResult = wrapper.findComponent({ name: 'single-output-table' }).vm.calculateResult; 274 const result = calculateResult({ 275 splitValue: 1, splitUnit: 'kilometers', 276 type: 'distance', distanceValue: 10, distanceUnit: 'kilometers', 277 }); 278 279 // Assert result is correct 280 expect(result.key).to.equal('1 km @ 10 km'); 281 expect(result.value).to.equal('4:09.48'); 282 expect(result.result).to.equal('value'); 283 expect(result.sort).to.be.closeTo(249.48, 0.01); 284 }); 285 286 test('should correctly handle null target set', async () => { 287 // Initialize component 288 const wrapper = shallowMount(WorkoutCalculator); 289 290 // Switch to invalid target set 291 await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ 292 customTargetNames: false, 293 input: { 294 distanceValue: 5, 295 distanceUnit: 'kilometers', 296 time: 1200, 297 }, 298 selectedTargetSet: 'does_not_exist', 299 }, 'options'); 300 301 // Assert empty array passed to SingleOutputTable component 302 expect(wrapper.findComponent({ name: 'single-output-table' }).vm.targets).to.deep.equal([]); 303 304 // Switch to valid target set 305 await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ 306 customTargetNames: false, 307 input: { 308 distanceValue: 5, 309 distanceUnit: 'kilometers', 310 time: 1200, 311 }, 312 selectedTargetSet: '_workout_targets', 313 }, 'options'); 314 315 // Assert valid targets passed to SingleOutputTable component 316 const workoutTargets = defaultTargetSets._workout_targets.targets; 317 expect(wrapper.findComponent({ name: 'single-output-table' }).vm.targets) 318 .to.deep.equal(workoutTargets); 319 }); 320 321 test('should correctly calculate results according to options', async () => { 322 // Initialize component 323 const wrapper = shallowMount(WorkoutCalculator); 324 325 // Enter input race data 326 await wrapper.findComponent({ name: 'pace-input' }).setValue({ 327 distanceValue: 2, 328 distanceUnit: 'miles', 329 time: 630, 330 }); 331 332 // Update model and Riegel exponent 333 await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ 334 defaultUnitSystem: 'imperial', 335 racePredictionOptions: { 336 model: 'RiegelModel', 337 riegelExponent: 1.10, 338 }, 339 }, 'globalOptions'); 340 341 // Calculate result 342 const calculateResult = wrapper.findComponent({ name: 'single-output-table' }).vm.calculateResult; 343 let result = calculateResult({ 344 customName: 'foo', 345 splitValue: 1, splitUnit: 'kilometers', 346 type: 'distance', distanceValue: 10, distanceUnit: 'kilometers', 347 }); 348 349 // Assert result is correct 350 expect(result.key).to.equal('1 km @ 10 km'); 351 expect(result.value).to.equal('3:39.23'); 352 353 // Update target name customization 354 await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ 355 customTargetNames: true, 356 input: { 357 distanceValue: 2, 358 distanceUnit: 'miles', 359 time: 630, 360 }, 361 selectedTargetSet: '_workout_targets', 362 }, 'options'); 363 364 // Calculate result 365 result = calculateResult({ 366 customName: 'foo', 367 splitValue: 1, splitUnit: 'kilometers', 368 type: 'distance', distanceValue: 10, distanceUnit: 'kilometers', 369 }); 370 371 // Assert result is correct 372 expect(result.key).to.equal('foo'); 373 expect(result.value).to.equal('3:39.23'); 374 }); 375 376 test('should correctly set AdvancedOptionsInput type prop', async () => { 377 // Initialize component 378 const wrapper = shallowMount(WorkoutCalculator); 379 380 // Assert type prop is correctly set 381 expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.type).to.equal('workout'); 382 });