cross-calculator.spec.js (56899B)
1 import { test, expect } from '@playwright/test'; 2 3 test('Cross-calculator', async ({ page }) => { 4 await page.goto('/'); 5 6 // Set various options in the different calculators 7 { 8 // Go to batch calculator 9 await page.getByRole('button', { name: 'Batch Calculator' }).click(); 10 11 // Enter input pace (2 mi in 10:30) 12 await page.getByLabel('Input distance value').fill('2'); 13 await page.getByLabel('Input distance unit').selectOption('Miles'); 14 await page.getByLabel('Input duration hours').fill('0'); 15 await page.getByLabel('Input duration minutes').fill('10'); 16 await page.getByLabel('Input duration seconds').fill('30'); 17 18 // Enter batch options (15 x 10s increments) 19 await page.getByLabel('Duration increment minutes').fill('0'); 20 await page.getByLabel('Duration increment seconds').fill('10'); 21 await page.getByLabel('Number of rows').fill('15'); 22 23 // Change calculator 24 await page.getByLabel('Calculator').selectOption('Race Calculator'); 25 26 // Change prediction model 27 await page.getByText('Advanced Options').click(); 28 await page.getByLabel('Prediction model').selectOption('Riegel\'s Model'); 29 30 // Go to pace calculator 31 await page.goto('/'); 32 await page.getByRole('button', { name: 'Pace Calculator' }).click(); 33 34 // Enter input pace (2 mi in 15:30) 35 await page.getByLabel('Input distance value').fill('2'); 36 await page.getByLabel('Input distance unit').selectOption('Miles'); 37 await page.getByLabel('Input duration hours').fill('0'); 38 await page.getByLabel('Input duration minutes').fill('15'); 39 await page.getByLabel('Input duration seconds').fill('30'); 40 41 // Create custom target set 42 await page.getByText('Advanced Options').click(); 43 await page.getByLabel('Selected target set').selectOption('[ Create New Target Set ]'); 44 await expect(page.getByRole('row').nth(4)).toHaveText('There aren\'t any targets in this set yet.'); 45 await expect(page.getByRole('row')).toHaveCount(5); 46 47 // Edit new target set 48 await expect(page.getByRole('row').nth(1)).toHaveText('There aren\'t any targets in this set yet.'); 49 await expect(page.getByLabel('Target set label')).toHaveValue('New target set'); 50 await page.getByLabel('Target set label').fill('800m Splits'); 51 await page.getByRole('button', { name: 'Add distance target' }).click(); 52 await page.getByLabel('Target distance value').nth(0).fill('0.4'); 53 await page.getByLabel('Target distance unit').nth(0).selectOption('Kilometers'); 54 await page.getByRole('button', { name: 'Add distance target' }).click(); 55 await page.getByLabel('Target distance value').nth(1).fill('800'); 56 await page.getByLabel('Target distance unit').nth(1).selectOption('Meters'); 57 await page.getByRole('button', { name: 'Add time target' }).click(); 58 await page.getByRole('button', { name: 'Close' }).click(); 59 60 // Go to race calculator 61 await page.getByRole('link', { name: 'Back' }).click(); 62 await page.getByRole('button', { name: 'Race Calculator' }).click(); 63 64 // Enter input race (2 mi in 10:30) 65 await page.getByLabel('Input race distance value').fill('2'); 66 await page.getByLabel('Input race distance unit').selectOption('Miles'); 67 await page.getByLabel('Input race duration hours').fill('0'); 68 await page.getByLabel('Input race duration minutes').fill('10'); 69 await page.getByLabel('Input race duration seconds').fill('30'); 70 71 // Go to split calculator 72 await page.getByRole('link', { name: 'Back' }).click(); 73 await page.getByRole('button', { name: 'Split Calculator' }).click(); 74 75 // Edit target set 76 await page.getByRole('button', { name: 'Edit target set' }).click(); 77 await page.getByLabel('Target set label').fill('5K 1600m Splits'); 78 await page.getByLabel('Target distance value').nth(0).fill('1.6'); 79 await page.getByLabel('Target distance unit').nth(0).selectOption('Kilometers'); 80 await page.getByLabel('Target distance value').nth(1).fill('3.2'); 81 await page.getByLabel('Target distance unit').nth(1).selectOption('Kilometers'); 82 await page.getByRole('button', { name: 'Close' }).click(); 83 84 // Enter input 5K splits (7:00, 6:30, 6:30) 85 await page.getByLabel('Split duration minutes').nth(0).fill('7'); 86 await page.getByLabel('Split duration seconds').nth(0).fill('0'); 87 await page.getByLabel('Split duration minutes').nth(1).fill('6'); 88 await page.getByLabel('Split duration seconds').nth(1).fill('30'); 89 await page.getByLabel('Split duration minutes').nth(2).fill('6'); 90 await page.getByLabel('Split duration seconds').nth(2).fill('30'); 91 92 // Go to unit calculator 93 await page.getByRole('link', { name: 'Back' }).click(); 94 await page.getByRole('button', { name: 'Unit Calculator' }).click(); 95 96 // Convert speed and pace units (10 kph to time per mile) 97 await page.getByLabel('Selected unit category').selectOption('Speed & Pace'); 98 await page.getByLabel('Input units').selectOption('Kilometers per Hour'); 99 await page.getByLabel('Input value').fill('10'); 100 await page.getByLabel('Output units').selectOption('Time per Mile'); 101 102 // Go to workout calculator 103 await page.getByRole('link', { name: 'Back' }).click(); 104 await page.getByRole('button', { name: 'Workout Calculator' }).click(); 105 106 // Enter input race (1 mi in 5:01) 107 await page.getByLabel('Input race distance value').fill('1'); 108 await page.getByLabel('Input race distance unit').selectOption('Miles'); 109 await page.getByLabel('Input race duration hours').fill('0'); 110 await page.getByLabel('Input race duration minutes').fill('5'); 111 await page.getByLabel('Input race duration seconds').fill('1'); 112 113 // Change riegel exponent and enable target name customization 114 await page.getByText('Advanced Options').click(); 115 await page.getByLabel('Riegel Exponent').fill('1.12'); 116 await page.getByLabel('Workout name customization').selectOption('Enabled'); 117 118 // Change default units (should update on other calculators too) 119 await page.getByLabel('Default units').selectOption('Kilometers'); 120 } 121 122 // Go back and assert the options are not reset 123 { 124 // Return to batch calculator 125 await page.getByRole('link', { name: 'Back' }).click(); 126 await page.getByRole('button', { name: 'Batch Calculator' }).click(); 127 128 // Assert race results are correct (inputs and options not reset) 129 await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi'); 130 await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m'); 131 await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(17); 132 await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30'); 133 await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:12'); 134 await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(17); 135 await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50'); 136 await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('2:42'); 137 await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(17); 138 await expect(page.getByRole('row')).toHaveCount(16); 139 140 // Assert pace results are correct (inputs and options not reset, new pace targets loaded) 141 await page.getByLabel('Calculator').selectOption('Pace Calculator'); 142 await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi'); 143 await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m'); 144 await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(4); 145 await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30'); 146 await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:37'); 147 await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(4); 148 await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50'); 149 await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:11'); 150 await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(4); 151 await expect(page.getByRole('row')).toHaveCount(16); 152 153 // Assert workout results are correct (new workout options loaded) 154 await page.getByLabel('Calculator').selectOption('Workout Calculator'); 155 await expect(page.getByLabel('Workout name customization')).toHaveValue("true"); 156 await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi'); 157 await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m @ 5 km'); 158 await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(5); 159 await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30'); 160 await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:45'); 161 await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(5); 162 await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50'); 163 await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:22'); 164 await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(5); 165 await expect(page.getByRole('row')).toHaveCount(16); 166 167 // Reset selected calculator 168 await page.getByLabel('Calculator').selectOption('Race Calculator'); 169 170 // Return to pace calculator 171 await page.getByRole('link', { name: 'Back' }).click(); 172 await page.getByRole('button', { name: 'Pace Calculator' }).click(); 173 174 // Assert paces are correct (input pace not reset) 175 await expect(page.getByRole('row').nth(1)).toHaveText('0.4 km' + '1:55.58'); 176 await expect(page.getByRole('row').nth(2)).toHaveText('800 m' + '3:51.15'); 177 await expect(page.getByRole('row').nth(3)).toHaveText('2.08 km' + '10:00'); 178 await expect(page.getByRole('row')).toHaveCount(4); 179 180 // Return to race calculator 181 await page.getByRole('link', { name: 'Back' }).click(); 182 await page.getByRole('button', { name: 'Race Calculator' }).click(); 183 184 // Assert race predictions are correct (input race not resset and new prediction model loaded) 185 await expect(page.getByRole('row').nth(5)).toHaveText('1 mi' + '4:49.86' + '3:00 / km'); 186 await expect(page.getByRole('row').nth(10)).toHaveText('5 km' + '17:11.78' + '3:26 / km'); 187 await expect(page.getByRole('row')).toHaveCount(17); 188 189 // Return to split calculator 190 await page.getByRole('link', { name: 'Back' }).click(); 191 await page.getByRole('button', { name: 'Split Calculator' }).click(); 192 193 // Assert times and paces are correct (split times not reset) 194 await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('7:00.00'); 195 await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('4:23 / km'); 196 await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('13:30.00'); 197 await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('4:04 / km'); 198 await expect(page.getByRole('row').nth(3).getByRole('cell').nth(1)).toHaveText('20:00.00'); 199 await expect(page.getByRole('row').nth(3).getByRole('cell').nth(3)).toHaveText('3:37 / km'); 200 await expect(page.getByRole('row')).toHaveCount(4); 201 202 // Return to unit calculator 203 await page.getByRole('link', { name: 'Back' }).click(); 204 await page.getByRole('button', { name: 'Unit Calculator' }).click(); 205 206 // Assert result is correct (state not reset) 207 await expect(page.getByLabel('Output value')).toHaveText('00:09:39.364'); 208 209 // Return to workout calculator 210 await page.getByRole('link', { name: 'Back' }).click(); 211 await page.getByRole('button', { name: 'Workout Calculator' }).click(); 212 213 // Assert workout splits are correct (input race and prediction model not reset) 214 await expect(page.getByRole('row').nth(1)).toHaveText('400 m @ 1 mi' + '1:14.81'); 215 await expect(page.getByRole('row').nth(3)).toHaveText('1600 m @ 1:00:00' + '6:30.40'); 216 await expect(page.getByRole('row')).toHaveCount(5); 217 } 218 219 // Assert localStorage entries are correct 220 { 221 // Assert global localStorage entries are correct 222 expect(await page.evaluate(() => localStorage.length)).toEqual(12); 223 expect(await page.evaluate(() => localStorage.getItem('running-tools.global-options'))) 224 .toEqual(JSON.stringify({ 225 defaultUnitSystem: 'metric', 226 racePredictionOptions: { 227 model: 'RiegelModel', 228 riegelExponent: 1.12, 229 }, 230 })); 231 232 // Assert localStorage entries for the batch calculator are correct 233 expect(await page.evaluate(() => 234 localStorage.getItem('running-tools.batch-calculator-options'))).toEqual(JSON.stringify({ 235 calculator: 'race', 236 increment: 10, 237 input: { 238 distanceValue: 2, 239 distanceUnit: 'miles', 240 time: 630, 241 }, 242 label: '', 243 rows: 15, 244 })); 245 246 // Assert localStorage entries for the pace calculator are correct 247 const paceCalculatorKey = parseInt(JSON.parse(await page.evaluate(() => 248 localStorage.getItem('running-tools.pace-calculator-options'))).selectedTargetSet); 249 expect(paceCalculatorKey - parseInt(Date.now().toString())).toBeLessThan(100000); 250 expect(await page.evaluate(() => 251 localStorage.getItem('running-tools.pace-calculator-options'))).toEqual(JSON.stringify({ 252 input: { 253 distanceValue: 2, 254 distanceUnit: 'miles', 255 time: 930, 256 }, 257 selectedTargetSet: paceCalculatorKey.toString(), 258 })); 259 expect(await page.evaluate(() => 260 localStorage.getItem('running-tools.pace-calculator-target-sets'))).toEqual(JSON.stringify({ 261 _pace_targets: { 262 name: 'Common Pace Targets', 263 targets: [ 264 { type: 'distance', distanceValue: 100, distanceUnit: 'meters' }, 265 { type: 'distance', distanceValue: 200, distanceUnit: 'meters' }, 266 { type: 'distance', distanceValue: 300, distanceUnit: 'meters' }, 267 { type: 'distance', distanceValue: 400, distanceUnit: 'meters' }, 268 { type: 'distance', distanceValue: 600, distanceUnit: 'meters' }, 269 { type: 'distance', distanceValue: 800, distanceUnit: 'meters' }, 270 { type: 'distance', distanceValue: 1000, distanceUnit: 'meters' }, 271 { type: 'distance', distanceValue: 1200, distanceUnit: 'meters' }, 272 { type: 'distance', distanceValue: 1500, distanceUnit: 'meters' }, 273 { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' }, 274 { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, 275 { type: 'distance', distanceValue: 2, distanceUnit: 'kilometers' }, 276 { type: 'distance', distanceValue: 3, distanceUnit: 'kilometers' }, 277 { type: 'distance', distanceValue: 3200, distanceUnit: 'meters' }, 278 { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, 279 { type: 'distance', distanceValue: 4, distanceUnit: 'kilometers' }, 280 { type: 'distance', distanceValue: 3, distanceUnit: 'miles' }, 281 { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, 282 { type: 'distance', distanceValue: 6, distanceUnit: 'kilometers' }, 283 { type: 'distance', distanceValue: 8, distanceUnit: 'kilometers' }, 284 { type: 'distance', distanceValue: 5, distanceUnit: 'miles' }, 285 { type: 'distance', distanceValue: 6, distanceUnit: 'miles' }, 286 { type: 'distance', distanceValue: 10, distanceUnit: 'kilometers' }, 287 { type: 'distance', distanceValue: 8, distanceUnit: 'miles' }, 288 { type: 'distance', distanceValue: 10, distanceUnit: 'miles' }, 289 { type: 'distance', distanceValue: 0.5, distanceUnit: 'marathons' }, 290 { type: 'distance', distanceValue: 1, distanceUnit: 'marathons' }, 291 { type: 'time', time: 600 }, 292 { type: 'time', time: 1800 }, 293 { type: 'time', time: 3600 }, 294 ], 295 }, 296 [paceCalculatorKey.toString()]: { 297 name: '800m Splits', 298 targets: [ 299 { type: 'distance', distanceValue: 0.4, distanceUnit: 'kilometers' }, 300 { type: 'distance', distanceValue: 800, distanceUnit: 'meters' }, 301 { type: 'time', time: 600 }, 302 ], 303 }, 304 })); 305 306 // Assert localStorage entries for the race calculator are correct 307 expect(await page.evaluate(() => 308 localStorage.getItem('running-tools.race-calculator-options'))).toEqual(JSON.stringify({ 309 input: { 310 distanceValue: 2, 311 distanceUnit: 'miles', 312 time: 630, 313 }, 314 selectedTargetSet: '_race_targets', 315 })); 316 expect(await page.evaluate(() => 317 localStorage.getItem('running-tools.race-calculator-target-sets'))).toEqual(JSON.stringify({ 318 _race_targets: { 319 name: 'Common Race Targets', 320 targets: [ 321 { type: 'distance', distanceValue: 400, distanceUnit: 'meters' }, 322 { type: 'distance', distanceValue: 800, distanceUnit: 'meters' }, 323 { type: 'distance', distanceValue: 1500, distanceUnit: 'meters' }, 324 { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' }, 325 { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, 326 { type: 'distance', distanceValue: 3000, distanceUnit: 'meters' }, 327 { type: 'distance', distanceValue: 3200, distanceUnit: 'meters' }, 328 { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, 329 { type: 'distance', distanceValue: 3, distanceUnit: 'miles' }, 330 { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, 331 { type: 'distance', distanceValue: 6, distanceUnit: 'kilometers' }, 332 { type: 'distance', distanceValue: 8, distanceUnit: 'kilometers' }, 333 { type: 'distance', distanceValue: 10, distanceUnit: 'kilometers' }, 334 { type: 'distance', distanceValue: 15, distanceUnit: 'kilometers' }, 335 { type: 'distance', distanceValue: 0.5, distanceUnit: 'marathons' }, 336 { type: 'distance', distanceValue: 1, distanceUnit: 'marathons' }, 337 ], 338 }, 339 })); 340 341 // Assert localStorage entries for the split calculator are correct 342 expect(await page.evaluate(() => 343 localStorage.getItem('running-tools.split-calculator-options'))) 344 .toEqual(JSON.stringify({ 345 selectedTargetSet: '_split_targets', 346 })); 347 expect(await page.evaluate(() => 348 localStorage.getItem('running-tools.split-calculator-target-sets'))).toEqual(JSON.stringify({ 349 _split_targets: { 350 name: '5K 1600m Splits', 351 targets: [ 352 { type: 'distance', distanceValue: 1.6, distanceUnit: 'kilometers', splitTime: 420 }, 353 { type: 'distance', distanceValue: 3.2, distanceUnit: 'kilometers', splitTime: 390 }, 354 { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers', splitTime: 390 }, 355 ], 356 }, 357 })); 358 359 // Assert localStorage entries for the unit calculator are correct 360 expect(await page.evaluate(() => localStorage.getItem('running-tools.unit-calculator-category'))) 361 .toEqual(JSON.stringify('speed_and_pace')); 362 expect(await page.evaluate(() => 363 localStorage.getItem('running-tools.unit-calculator-inputs'))).toEqual(JSON.stringify({ 364 distance: { 365 inputValue: 1, 366 inputUnit: 'miles', 367 outputUnit: 'kilometers', 368 }, 369 time: { 370 inputValue: 1, 371 inputUnit: 'seconds', 372 outputUnit: 'hh:mm:ss', 373 }, 374 speed_and_pace: { 375 inputValue: 10, 376 inputUnit: 'kilometers_per_hour', 377 outputUnit: 'seconds_per_mile', 378 }, 379 })); 380 381 // Assert localStorage entries for the workout calculator are correct 382 expect(await page.evaluate(() => 383 localStorage.getItem('running-tools.workout-calculator-options'))).toEqual(JSON.stringify({ 384 customTargetNames: true, 385 input: { 386 distanceValue: 1, 387 distanceUnit: 'miles', 388 time: 301, 389 }, 390 selectedTargetSet: '_workout_targets', 391 })); 392 expect(await page.evaluate(() => 393 localStorage.getItem('running-tools.workout-calculator-target-sets'))).toEqual(JSON.stringify({ 394 _workout_targets: { 395 name: 'Common Workout Targets', 396 targets: [ 397 { 398 splitValue: 400, splitUnit: 'meters', 399 type: 'distance', distanceValue: 1, distanceUnit: 'miles', 400 }, 401 { 402 splitValue: 800, splitUnit: 'meters', 403 type: 'distance', distanceValue: 5, distanceUnit: 'kilometers', 404 }, 405 { 406 splitValue: 1600, splitUnit: 'meters', 407 type: 'time', time: 3600, 408 }, 409 { 410 splitValue: 1, splitUnit: 'miles', 411 type: 'distance', distanceValue: 1, distanceUnit: 'marathons', 412 }, 413 ], 414 }, 415 })); 416 } 417 418 // Reload app and assert the updated options are loaded 419 // Identical to the previous "go back and assert the options are not resset" section 420 { 421 // Return to batch calculator 422 await page.getByRole('link', { name: 'Back' }).click(); 423 await page.getByRole('button', { name: 'Batch Calculator' }).click(); 424 425 // Assert race results are correct (inputs and options not reset) 426 await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi'); 427 await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m'); 428 await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(17); 429 await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30'); 430 await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:12'); 431 await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(17); 432 await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50'); 433 await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('2:42'); 434 await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(17); 435 await expect(page.getByRole('row')).toHaveCount(16); 436 437 // Assert pace results are correct (inputs and options not reset, new pace targets loaded) 438 await page.getByLabel('Calculator').selectOption('Pace Calculator'); 439 await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi'); 440 await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m'); 441 await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(4); 442 await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30'); 443 await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:37'); 444 await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(4); 445 await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50'); 446 await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:11'); 447 await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(4); 448 await expect(page.getByRole('row')).toHaveCount(16); 449 450 // Assert workout results are correct (new workout options loaded) 451 await page.getByLabel('Calculator').selectOption('Workout Calculator'); 452 await expect(page.getByLabel('Workout name customization')).toHaveValue("true"); 453 await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi'); 454 await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m @ 5 km'); 455 await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(5); 456 await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30'); 457 await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:45'); 458 await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(5); 459 await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50'); 460 await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:22'); 461 await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(5); 462 await expect(page.getByRole('row')).toHaveCount(16); 463 464 // Reset selected calculator 465 await page.getByLabel('Calculator').selectOption('Race Calculator'); 466 467 // Return to pace calculator 468 await page.getByRole('link', { name: 'Back' }).click(); 469 await page.getByRole('button', { name: 'Pace Calculator' }).click(); 470 471 // Assert paces are correct (input pace not reset) 472 await expect(page.getByRole('row').nth(1)).toHaveText('0.4 km' + '1:55.58'); 473 await expect(page.getByRole('row').nth(2)).toHaveText('800 m' + '3:51.15'); 474 await expect(page.getByRole('row').nth(3)).toHaveText('2.08 km' + '10:00'); 475 await expect(page.getByRole('row')).toHaveCount(4); 476 477 // Return to race calculator 478 await page.getByRole('link', { name: 'Back' }).click(); 479 await page.getByRole('button', { name: 'Race Calculator' }).click(); 480 481 // Assert race predictions are correct (input race not resset and new prediction model loaded) 482 await expect(page.getByRole('row').nth(5)).toHaveText('1 mi' + '4:49.86' + '3:00 / km'); 483 await expect(page.getByRole('row').nth(10)).toHaveText('5 km' + '17:11.78' + '3:26 / km'); 484 await expect(page.getByRole('row')).toHaveCount(17); 485 486 // Return to split calculator 487 await page.getByRole('link', { name: 'Back' }).click(); 488 await page.getByRole('button', { name: 'Split Calculator' }).click(); 489 490 // Assert times and paces are correct (split times not reset) 491 await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('7:00.00'); 492 await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('4:23 / km'); 493 await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('13:30.00'); 494 await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('4:04 / km'); 495 await expect(page.getByRole('row').nth(3).getByRole('cell').nth(1)).toHaveText('20:00.00'); 496 await expect(page.getByRole('row').nth(3).getByRole('cell').nth(3)).toHaveText('3:37 / km'); 497 await expect(page.getByRole('row')).toHaveCount(4); 498 499 // Return to unit calculator 500 await page.getByRole('link', { name: 'Back' }).click(); 501 await page.getByRole('button', { name: 'Unit Calculator' }).click(); 502 503 // Assert result is correct (state not reset) 504 await expect(page.getByLabel('Output value')).toHaveText('00:09:39.364'); 505 506 // Return to workout calculator 507 await page.getByRole('link', { name: 'Back' }).click(); 508 await page.getByRole('button', { name: 'Workout Calculator' }).click(); 509 510 // Assert workout splits are correct (input race and prediction model not reset) 511 await expect(page.getByRole('row').nth(1)).toHaveText('400 m @ 1 mi' + '1:14.81'); 512 await expect(page.getByRole('row').nth(3)).toHaveText('1600 m @ 1:00:00' + '6:30.40'); 513 await expect(page.getByRole('row')).toHaveCount(5); 514 } 515 }); 516 517 test('v1.4.1 Migration', async ({ page }) => { 518 await page.goto('/'); 519 520 // Set v1.4.1 localStorage entries 521 { 522 // Set general localStorage entries 523 await page.evaluate(() => localStorage.setItem('running-tools.default-unit-system', 524 JSON.stringify('metric'))); 525 526 // Set batch calculator localStorage entries 527 await page.evaluate(() => localStorage.setItem('running-tools.batch-calculator-input', 528 JSON.stringify({ 529 distanceValue: 2, 530 distanceUnit: 'miles', 531 time: 630, 532 }) 533 )); 534 await page.evaluate(() => localStorage.setItem('running-tools.batch-calculator-options', 535 JSON.stringify({ 536 calculator: 'race', 537 increment: 10, 538 rows: 15, 539 }) 540 )); 541 542 // Set pace calculator localStorage entries 543 await page.evaluate(() => localStorage.setItem('running-tools.pace-calculator-input', 544 JSON.stringify({ 545 distanceValue: 2, 546 distanceUnit: 'miles', 547 time: 930, 548 }) 549 )); 550 await page.evaluate(() => localStorage.setItem('running-tools.pace-calculator-target-set', 551 JSON.stringify('123456789'))); // Property moved after v1.4.1 552 await page.evaluate(() => localStorage.setItem('running-tools.pace-calculator-target-sets', 553 JSON.stringify({ 554 _pace_targets: { 555 name: 'Common Pace Targets', 556 targets: [ 557 { type: 'distance', distanceValue: 100, distanceUnit: 'meters' }, 558 { type: 'distance', distanceValue: 200, distanceUnit: 'meters' }, 559 { type: 'distance', distanceValue: 300, distanceUnit: 'meters' }, 560 { type: 'distance', distanceValue: 400, distanceUnit: 'meters' }, 561 { type: 'distance', distanceValue: 600, distanceUnit: 'meters' }, 562 { type: 'distance', distanceValue: 800, distanceUnit: 'meters' }, 563 { type: 'distance', distanceValue: 1000, distanceUnit: 'meters' }, 564 { type: 'distance', distanceValue: 1200, distanceUnit: 'meters' }, 565 { type: 'distance', distanceValue: 1500, distanceUnit: 'meters' }, 566 { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' }, 567 { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, 568 { type: 'distance', distanceValue: 2, distanceUnit: 'kilometers' }, 569 { type: 'distance', distanceValue: 3, distanceUnit: 'kilometers' }, 570 { type: 'distance', distanceValue: 3200, distanceUnit: 'meters' }, 571 { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, 572 { type: 'distance', distanceValue: 4, distanceUnit: 'kilometers' }, 573 { type: 'distance', distanceValue: 3, distanceUnit: 'miles' }, 574 { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, 575 { type: 'distance', distanceValue: 6, distanceUnit: 'kilometers' }, 576 { type: 'distance', distanceValue: 8, distanceUnit: 'kilometers' }, 577 { type: 'distance', distanceValue: 5, distanceUnit: 'miles' }, 578 { type: 'distance', distanceValue: 6, distanceUnit: 'miles' }, 579 { type: 'distance', distanceValue: 10, distanceUnit: 'kilometers' }, 580 { type: 'distance', distanceValue: 8, distanceUnit: 'miles' }, 581 { type: 'distance', distanceValue: 10, distanceUnit: 'miles' }, 582 { type: 'distance', distanceValue: 0.5, distanceUnit: 'marathons' }, 583 { type: 'distance', distanceValue: 1, distanceUnit: 'marathons' }, 584 { type: 'time', time: 600 }, 585 { type: 'time', time: 1800 }, 586 { type: 'time', time: 3600 }, 587 ], 588 }, 589 '123456789': { 590 name: '800m Splits', 591 targets: [ 592 { type: 'distance', distanceValue: 0.4, distanceUnit: 'kilometers' }, 593 { type: 'distance', distanceValue: 800, distanceUnit: 'meters' }, 594 { type: 'time', time: 600 }, 595 ], 596 }, 597 }) 598 )); 599 600 // Set race calculator localStorage entries 601 await page.evaluate(() => localStorage.setItem('running-tools.race-calculator-input', 602 JSON.stringify({ 603 distanceValue: 2, 604 distanceUnit: 'miles', 605 time: 630, 606 }) 607 )); 608 await page.evaluate(() => localStorage.setItem('running-tools.race-calculator-options', 609 JSON.stringify({ 610 model: 'RiegelModel', 611 riegelExponent: 1.06, 612 }) 613 )); 614 await page.evaluate(() => localStorage.setItem('running-tools.race-calculator-target-set', 615 JSON.stringify('_race_targets'))); // Property moved after v1.4.1 616 await page.evaluate(() => localStorage.setItem('running-tools.race-calculator-target-sets', 617 JSON.stringify({ 618 _race_targets: { 619 name: 'Common Race Targets', 620 targets: [ 621 { type: 'distance', distanceValue: 400, distanceUnit: 'meters' }, 622 { type: 'distance', distanceValue: 800, distanceUnit: 'meters' }, 623 { type: 'distance', distanceValue: 1500, distanceUnit: 'meters' }, 624 { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' }, 625 { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, 626 { type: 'distance', distanceValue: 3000, distanceUnit: 'meters' }, 627 { type: 'distance', distanceValue: 3200, distanceUnit: 'meters' }, 628 { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, 629 { type: 'distance', distanceValue: 3, distanceUnit: 'miles' }, 630 { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, 631 { type: 'distance', distanceValue: 6, distanceUnit: 'kilometers' }, 632 { type: 'distance', distanceValue: 8, distanceUnit: 'kilometers' }, 633 { type: 'distance', distanceValue: 10, distanceUnit: 'kilometers' }, 634 { type: 'distance', distanceValue: 15, distanceUnit: 'kilometers' }, 635 { type: 'distance', distanceValue: 0.5, distanceUnit: 'marathons' }, 636 { type: 'distance', distanceValue: 1, distanceUnit: 'marathons' }, 637 ], 638 }, 639 }) 640 )); 641 642 // Set split calculator localStorage entries 643 await page.evaluate(() => localStorage.setItem('running-tools.split-calculator-target-set', 644 JSON.stringify('_split_targets'))); // Property moved after v1.4.1 645 await page.evaluate(() => localStorage.setItem('running-tools.split-calculator-target-sets', 646 JSON.stringify({ 647 _split_targets: { 648 name: '5K 1600m Splits', 649 targets: [ 650 { type: 'distance', distanceValue: 1.6, distanceUnit: 'kilometers', splitTime: 420 }, 651 { type: 'distance', distanceValue: 3.2, distanceUnit: 'kilometers', splitTime: 390 }, 652 { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers', splitTime: 390 }, 653 ], 654 }, 655 }) 656 )); 657 658 // Set unit calculator localStorage entries 659 await page.evaluate(() => localStorage.setItem('running-tools.unit-calculator-category', 660 JSON.stringify('speed_and_pace'))); 661 await page.evaluate(() => localStorage.setItem('running-tools.unit-calculator-inputs', 662 JSON.stringify({ 663 distance: { 664 inputValue: 1, 665 inputUnit: 'miles', 666 outputUnit: 'kilometers', 667 }, 668 time: { 669 inputValue: 1, 670 inputUnit: 'seconds', 671 outputUnit: 'hh:mm:ss', 672 }, 673 speed_and_pace: { 674 inputValue: 10, 675 inputUnit: 'kilometers_per_hour', 676 outputUnit: 'seconds_per_mile', 677 }, 678 }) 679 )); 680 681 // Set workout calculator localStorage entries 682 await page.evaluate(() => localStorage.setItem('running-tools.workout-calculator-input', 683 JSON.stringify({ 684 distanceValue: 1, 685 distanceUnit: 'miles', 686 time: 301, 687 }) 688 )); 689 await page.evaluate(() => localStorage.setItem('running-tools.workout-calculator-options', 690 JSON.stringify({ 691 // customTargetNames property added after v1.4.1 692 model: 'VO2MaxModel', 693 riegelExponent: 1.06, 694 }) 695 )); 696 await page.evaluate(() => localStorage.setItem('running-tools.workout-calculator-target-set', 697 JSON.stringify('_workout_targets'))); // Property moved after v1.4.1 698 await page.evaluate(() => localStorage.setItem('running-tools.workout-calculator-target-sets', 699 JSON.stringify({ 700 _workout_targets: { 701 name: 'Common Workout Targets', 702 targets: [ 703 { 704 splitValue: 400, splitUnit: 'meters', 705 type: 'distance', distanceValue: 1, distanceUnit: 'miles', 706 }, 707 { 708 splitValue: 800, splitUnit: 'meters', 709 type: 'distance', distanceValue: 5, distanceUnit: 'kilometers', 710 }, 711 { 712 splitValue: 1600, splitUnit: 'meters', 713 type: 'time', time: 3600, 714 }, 715 { 716 splitValue: 1, splitUnit: 'miles', 717 type: 'distance', distanceValue: 1, distanceUnit: 'marathons', 718 }, 719 ], 720 }, 721 }) 722 )); 723 } 724 725 // Reload app and assert the proper localStorage migrations were performed 726 { 727 // Reload the app and assert general localStorage entries are correct 728 await page.goto('/'); 729 expect(await page.evaluate(() => localStorage.length)).toEqual(12); 730 expect(await page.evaluate(() => 731 localStorage.getItem('running-tools.default-unit-system'))).toBeNull(); 732 expect(await page.evaluate(() => 733 localStorage.getItem('running-tools.global-options'))).toEqual(JSON.stringify({ 734 defaultUnitSystem: 'metric', 735 racePredictionOptions: { 736 model: 'RiegelModel', 737 riegelExponent: 1.06, 738 }, 739 })); 740 741 // Assert localStorage entries for the batch calculator are correct 742 expect(await page.evaluate(() => 743 localStorage.getItem('running-tools.batch-calculator-input'))).toBeNull(); 744 expect(await page.evaluate(() => 745 localStorage.getItem('running-tools.batch-calculator-options'))).toEqual(JSON.stringify({ 746 calculator: 'race', 747 increment: 10, 748 rows: 15, 749 label: '', 750 input: { 751 distanceValue: 2, 752 distanceUnit: 'miles', 753 time: 630, 754 }, 755 })); 756 757 // Assert localStorage entries for the pace calculator are correct 758 expect(await page.evaluate(() => 759 localStorage.getItem('running-tools.pace-calculator-input'))).toBeNull(); 760 expect(await page.evaluate(() => localStorage.getItem('running-tools.pace-calculator-options'))) 761 .toEqual(JSON.stringify({ 762 input: { 763 distanceValue: 2, 764 distanceUnit: 'miles', 765 time: 930, 766 }, 767 selectedTargetSet: '123456789', 768 })); 769 expect(await page.evaluate(() => 770 localStorage.getItem('running-tools.pace-calculator-target-set'))).toBeNull(); 771 expect(await page.evaluate(() => 772 localStorage.getItem('running-tools.pace-calculator-target-sets'))).toEqual(JSON.stringify({ 773 _pace_targets: { 774 name: 'Common Pace Targets', 775 targets: [ 776 { type: 'distance', distanceValue: 100, distanceUnit: 'meters' }, 777 { type: 'distance', distanceValue: 200, distanceUnit: 'meters' }, 778 { type: 'distance', distanceValue: 300, distanceUnit: 'meters' }, 779 { type: 'distance', distanceValue: 400, distanceUnit: 'meters' }, 780 { type: 'distance', distanceValue: 600, distanceUnit: 'meters' }, 781 { type: 'distance', distanceValue: 800, distanceUnit: 'meters' }, 782 { type: 'distance', distanceValue: 1000, distanceUnit: 'meters' }, 783 { type: 'distance', distanceValue: 1200, distanceUnit: 'meters' }, 784 { type: 'distance', distanceValue: 1500, distanceUnit: 'meters' }, 785 { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' }, 786 { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, 787 { type: 'distance', distanceValue: 2, distanceUnit: 'kilometers' }, 788 { type: 'distance', distanceValue: 3, distanceUnit: 'kilometers' }, 789 { type: 'distance', distanceValue: 3200, distanceUnit: 'meters' }, 790 { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, 791 { type: 'distance', distanceValue: 4, distanceUnit: 'kilometers' }, 792 { type: 'distance', distanceValue: 3, distanceUnit: 'miles' }, 793 { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, 794 { type: 'distance', distanceValue: 6, distanceUnit: 'kilometers' }, 795 { type: 'distance', distanceValue: 8, distanceUnit: 'kilometers' }, 796 { type: 'distance', distanceValue: 5, distanceUnit: 'miles' }, 797 { type: 'distance', distanceValue: 6, distanceUnit: 'miles' }, 798 { type: 'distance', distanceValue: 10, distanceUnit: 'kilometers' }, 799 { type: 'distance', distanceValue: 8, distanceUnit: 'miles' }, 800 { type: 'distance', distanceValue: 10, distanceUnit: 'miles' }, 801 { type: 'distance', distanceValue: 0.5, distanceUnit: 'marathons' }, 802 { type: 'distance', distanceValue: 1, distanceUnit: 'marathons' }, 803 { type: 'time', time: 600 }, 804 { type: 'time', time: 1800 }, 805 { type: 'time', time: 3600 }, 806 ], 807 }, 808 '123456789': { 809 name: '800m Splits', 810 targets: [ 811 { type: 'distance', distanceValue: 0.4, distanceUnit: 'kilometers' }, 812 { type: 'distance', distanceValue: 800, distanceUnit: 'meters' }, 813 { type: 'time', time: 600 }, 814 ], 815 }, 816 })); 817 818 // Assert localStorage entries for the race calculator are correct 819 expect(await page.evaluate(() => 820 localStorage.getItem('running-tools.race-calculator-input'))).toBeNull(); 821 expect(await page.evaluate(() => localStorage.getItem('running-tools.race-calculator-options'))) 822 .toEqual(JSON.stringify({ 823 input: { 824 distanceValue: 2, 825 distanceUnit: 'miles', 826 time: 630, 827 }, 828 selectedTargetSet: '_race_targets', 829 })); 830 expect(await page.evaluate(() => 831 localStorage.getItem('running-tools.race-calculator-target-set'))).toBeNull(); 832 expect(await page.evaluate(() => 833 localStorage.getItem('running-tools.race-calculator-target-sets'))).toEqual(JSON.stringify({ 834 _race_targets: { 835 name: 'Common Race Targets', 836 targets: [ 837 { type: 'distance', distanceValue: 400, distanceUnit: 'meters' }, 838 { type: 'distance', distanceValue: 800, distanceUnit: 'meters' }, 839 { type: 'distance', distanceValue: 1500, distanceUnit: 'meters' }, 840 { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' }, 841 { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, 842 { type: 'distance', distanceValue: 3000, distanceUnit: 'meters' }, 843 { type: 'distance', distanceValue: 3200, distanceUnit: 'meters' }, 844 { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, 845 { type: 'distance', distanceValue: 3, distanceUnit: 'miles' }, 846 { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, 847 { type: 'distance', distanceValue: 6, distanceUnit: 'kilometers' }, 848 { type: 'distance', distanceValue: 8, distanceUnit: 'kilometers' }, 849 { type: 'distance', distanceValue: 10, distanceUnit: 'kilometers' }, 850 { type: 'distance', distanceValue: 15, distanceUnit: 'kilometers' }, 851 { type: 'distance', distanceValue: 0.5, distanceUnit: 'marathons' }, 852 { type: 'distance', distanceValue: 1, distanceUnit: 'marathons' }, 853 ], 854 }, 855 })); 856 857 // Assert localStorage entries for the split calculator are correct 858 expect(await page.evaluate(() => localStorage.getItem('running-tools.split-calculator-options'))) 859 .toEqual(JSON.stringify({ 860 selectedTargetSet: '_split_targets', 861 })); 862 expect(await page.evaluate(() => 863 localStorage.getItem('running-tools.split-calculator-target-set'))).toBeNull(); 864 expect(await page.evaluate(() => 865 localStorage.getItem('running-tools.split-calculator-target-sets'))).toEqual(JSON.stringify({ 866 _split_targets: { 867 name: '5K 1600m Splits', 868 targets: [ 869 { type: 'distance', distanceValue: 1.6, distanceUnit: 'kilometers', splitTime: 420 }, 870 { type: 'distance', distanceValue: 3.2, distanceUnit: 'kilometers', splitTime: 390 }, 871 { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers', splitTime: 390 }, 872 ], 873 }, 874 })); 875 876 // Assert localStorage entries for the unit calculator are correct 877 expect(await page.evaluate(() => localStorage.getItem('running-tools.unit-calculator-category'))) 878 .toEqual(JSON.stringify('speed_and_pace')); 879 expect(await page.evaluate(() => 880 localStorage.getItem('running-tools.unit-calculator-inputs'))).toEqual(JSON.stringify({ 881 distance: { 882 inputValue: 1, 883 inputUnit: 'miles', 884 outputUnit: 'kilometers', 885 }, 886 time: { 887 inputValue: 1, 888 inputUnit: 'seconds', 889 outputUnit: 'hh:mm:ss', 890 }, 891 speed_and_pace: { 892 inputValue: 10, 893 inputUnit: 'kilometers_per_hour', 894 outputUnit: 'seconds_per_mile', 895 }, 896 })); 897 898 // Assert localStorage entries for the workout calculator are correct 899 expect(await page.evaluate(() => 900 localStorage.getItem('running-tools.workout-calculator-input'))).toBeNull(); 901 expect(await page.evaluate(() => 902 localStorage.getItem('running-tools.workout-calculator-options'))).toEqual(JSON.stringify({ 903 customTargetNames: false, 904 input: { 905 distanceValue: 1, 906 distanceUnit: 'miles', 907 time: 301, 908 }, 909 selectedTargetSet: '_workout_targets', 910 })); 911 expect(await page.evaluate(() => 912 localStorage.getItem('running-tools.workout-calculator-target-set'))).toBeNull(); 913 expect(await page.evaluate(() => 914 localStorage.getItem('running-tools.workout-calculator-target-sets'))).toEqual(JSON.stringify({ 915 _workout_targets: { 916 name: 'Common Workout Targets', 917 targets: [ 918 { 919 splitValue: 400, splitUnit: 'meters', 920 type: 'distance', distanceValue: 1, distanceUnit: 'miles', 921 }, 922 { 923 splitValue: 800, splitUnit: 'meters', 924 type: 'distance', distanceValue: 5, distanceUnit: 'kilometers', 925 }, 926 { 927 splitValue: 1600, splitUnit: 'meters', 928 type: 'time', time: 3600, 929 }, 930 { 931 splitValue: 1, splitUnit: 'miles', 932 type: 'distance', distanceValue: 1, distanceUnit: 'marathons', 933 }, 934 ], 935 }, 936 })); 937 } 938 939 // Assert UI options are up to date 940 // Very similar to the previous "go back and assert the options are not resset" section 941 { 942 // Assert batch options are correct 943 await page.getByRole('button', { name: 'Batch Calculator' }).click(); 944 await expect(page.getByLabel('Input distance value')).toHaveValue('2.00'); 945 await expect(page.getByLabel('Input distance unit')).toHaveValue('miles'); 946 await expect(page.getByLabel('Input duration hours')).toHaveValue('0'); 947 await expect(page.getByLabel('Input duration minutes')).toHaveValue('10'); 948 await expect(page.getByLabel('Input duration seconds')).toHaveValue('30.00'); 949 await expect(page.getByLabel('Duration increment minutes')).toHaveValue('00'); 950 await expect(page.getByLabel('Duration increment seconds')).toHaveValue('10.00'); 951 await expect(page.getByLabel('Number of rows')).toHaveValue('15'); 952 await expect(page.getByLabel('Calculator')).toHaveValue('race'); 953 954 // Assert advanced options are correct for race calculator mode 955 await page.getByText('Advanced Options').click(); 956 await expect(page.getByLabel('Default units')).toHaveValue('metric'); 957 await expect(page.getByLabel('Selected target set')).toHaveValue('_race_targets'); 958 await expect(page.getByLabel('Prediction model')).toHaveValue('RiegelModel'); 959 await expect(page.getByLabel('Riegel Exponent')).toHaveValue('1.06'); 960 961 // Assert race results are correct (inputs and options not reset) 962 await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi'); 963 await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m'); 964 await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(17); 965 await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30'); 966 await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:24'); 967 await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(17); 968 await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50'); 969 await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('2:56'); 970 await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(17); 971 await expect(page.getByRole('row')).toHaveCount(16); 972 973 // Assert advanced options are correct for pace calculator mode 974 await page.getByLabel('Calculator').selectOption('Pace Calculator'); 975 await expect(page.getByLabel('Default units')).toHaveValue('metric'); 976 await expect(page.getByLabel('Selected target set')).toHaveValue('123456789'); 977 978 // Assert pace results are correct (inputs and options not reset, new pace targets loaded) 979 await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi'); 980 await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m'); 981 await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(4); 982 await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30'); 983 await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:37'); 984 await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(4); 985 await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50'); 986 await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:11'); 987 await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(4); 988 await expect(page.getByRole('row')).toHaveCount(16); 989 990 // Assert advanced options are correct for workout calculator mode 991 await page.getByLabel('Calculator').selectOption('Workout Calculator'); 992 await page.getByText('Advanced Options').click(); 993 await expect(page.getByLabel('Default units')).toHaveValue('metric'); 994 await expect(page.getByLabel('Selected target set')).toHaveValue('_workout_targets'); 995 await expect(page.getByLabel('Workout name customization')).toHaveValue('false'); 996 await expect(page.getByLabel('Prediction model')).toHaveValue('RiegelModel'); 997 await expect(page.getByLabel('Riegel Exponent')).toHaveValue('1.06'); 998 999 // Assert workout results are correct (new workout options loaded) 1000 await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi'); 1001 await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m @ 5 km'); 1002 await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(5); 1003 await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30'); 1004 await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:41'); 1005 await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(5); 1006 await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50'); 1007 await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:17'); 1008 await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(5); 1009 await expect(page.getByRole('row')).toHaveCount(16); 1010 1011 // Reset selected calculator 1012 await page.getByLabel('Calculator').selectOption('Race Calculator'); 1013 1014 // Return to pace calculator 1015 await page.getByRole('link', { name: 'Back' }).click(); 1016 await page.getByRole('button', { name: 'Pace Calculator' }).click(); 1017 1018 // Assert pace calculator options are correct 1019 await expect(page.getByLabel('Input distance value')).toHaveValue('2.00'); 1020 await expect(page.getByLabel('Input distance unit')).toHaveValue('miles'); 1021 await expect(page.getByLabel('Input duration hours')).toHaveValue('0'); 1022 await expect(page.getByLabel('Input duration minutes')).toHaveValue('15'); 1023 await expect(page.getByLabel('Input duration seconds')).toHaveValue('30.00'); 1024 await page.getByText('Advanced Options').click(); 1025 await expect(page.getByLabel('Default units')).toHaveValue('metric'); 1026 await expect(page.getByLabel('Selected target set')).toHaveValue('123456789'); 1027 1028 // Assert paces are correct (input pace not reset) 1029 await expect(page.getByRole('row').nth(1)).toHaveText('0.4 km' + '1:55.58'); 1030 await expect(page.getByRole('row').nth(2)).toHaveText('800 m' + '3:51.15'); 1031 await expect(page.getByRole('row').nth(3)).toHaveText('2.08 km' + '10:00'); 1032 await expect(page.getByRole('row')).toHaveCount(4); 1033 1034 // Return to race calculator 1035 await page.getByRole('link', { name: 'Back' }).click(); 1036 await page.getByRole('button', { name: 'Race Calculator' }).click(); 1037 1038 // Assert race calculator options are correct 1039 await expect(page.getByLabel('Input race distance value')).toHaveValue('2.00'); 1040 await expect(page.getByLabel('Input race distance unit')).toHaveValue('miles'); 1041 await expect(page.getByLabel('Input race duration hours')).toHaveValue('0'); 1042 await expect(page.getByLabel('Input race duration minutes')).toHaveValue('10'); 1043 await expect(page.getByLabel('Input race duration seconds')).toHaveValue('30.00'); 1044 await page.getByText('Advanced Options').click(); 1045 await expect(page.getByLabel('Default units')).toHaveValue('metric'); 1046 await expect(page.getByLabel('Selected target set')).toHaveValue('_race_targets'); 1047 await expect(page.getByLabel('Prediction model')).toHaveValue('RiegelModel'); 1048 await expect(page.getByLabel('Riegel Exponent')).toHaveValue('1.06'); 1049 1050 // Assert race predictions are correct (input race not resset and new prediction model loaded) 1051 await expect(page.getByRole('row').nth(5)).toHaveText('1 mi' + '5:02.17' + '3:08 / km'); 1052 await expect(page.getByRole('row').nth(10)).toHaveText('5 km' + '16:44.87' + '3:21 / km'); 1053 await expect(page.getByRole('row')).toHaveCount(17); 1054 1055 // Return to split calculator 1056 await page.getByRole('link', { name: 'Back' }).click(); 1057 await page.getByRole('button', { name: 'Split Calculator' }).click(); 1058 1059 // Assert split calculator options are correct 1060 await expect(page.getByLabel('Default units')).toHaveValue('metric'); 1061 await expect(page.getByLabel('Selected target set')).toHaveValue('_split_targets'); 1062 1063 // Assert times and paces are correct (split times not reset) 1064 await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('7:00.00'); 1065 await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('4:23 / km'); 1066 await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('13:30.00'); 1067 await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('4:04 / km'); 1068 await expect(page.getByRole('row').nth(3).getByRole('cell').nth(1)).toHaveText('20:00.00'); 1069 await expect(page.getByRole('row').nth(3).getByRole('cell').nth(3)).toHaveText('3:37 / km'); 1070 await expect(page.getByRole('row')).toHaveCount(4); 1071 1072 // Return to unit calculator 1073 await page.getByRole('link', { name: 'Back' }).click(); 1074 await page.getByRole('button', { name: 'Unit Calculator' }).click(); 1075 1076 // Assert result is correct (state not reset) 1077 await expect(page.getByLabel('Output value')).toHaveText('00:09:39.364'); 1078 1079 // Return to workout calculator 1080 await page.getByRole('link', { name: 'Back' }).click(); 1081 await page.getByRole('button', { name: 'Workout Calculator' }).click(); 1082 1083 // Assert workout calculator options are correct 1084 await expect(page.getByLabel('Input race distance value')).toHaveValue('1.00'); 1085 await expect(page.getByLabel('Input race distance unit')).toHaveValue('miles'); 1086 await expect(page.getByLabel('Input race duration hours')).toHaveValue('0'); 1087 await expect(page.getByLabel('Input race duration minutes')).toHaveValue('05'); 1088 await expect(page.getByLabel('Input race duration seconds')).toHaveValue('01.00'); 1089 await page.getByText('Advanced Options').click(); 1090 await expect(page.getByLabel('Default units')).toHaveValue('metric'); 1091 await expect(page.getByLabel('Selected target set')).toHaveValue('_workout_targets'); 1092 await expect(page.getByLabel('Workout name customization')).toHaveValue('false'); 1093 await expect(page.getByLabel('Prediction model')).toHaveValue('RiegelModel'); 1094 await expect(page.getByLabel('Riegel Exponent')).toHaveValue('1.06'); 1095 1096 // Assert workout splits are correct (input race and prediction model not reset) 1097 await expect(page.getByRole('row').nth(1)).toHaveText('400 m @ 1 mi' + '1:14.81'); 1098 await expect(page.getByRole('row').nth(3)).toHaveText('1600 m @ 1:00:00' + '5:44.38'); 1099 await expect(page.getByRole('row')).toHaveCount(5); 1100 } 1101 });