commit 4c16d9764e24b6f53bb2ff8afb813f75f884246c
parent f769b7223707836c499faa9aecb3f791810a9e6a
Author: Asher Morgan <59518073+ashermorgan@users.noreply.github.com>
Date: Sat, 12 Jul 2025 14:02:41 -0700
Organize e2e tests into sections
Diffstat:
7 files changed, 1831 insertions(+), 1776 deletions(-)
diff --git a/tests/e2e/batch-calculator.spec.js b/tests/e2e/batch-calculator.spec.js
@@ -1,175 +1,181 @@
import { test, expect } from '@playwright/test';
test('Batch calculator', async ({ page }) => {
- // Structure:
- // - Test workout batch results, including modified prediction model and custom target names
- // - Test pace batch results, including modified default units
- // - Test race batch results, including modified Riegel exponent
- // - Reload page
- // - Assert race batch results are still the same
- // - Assert pace batch results are still the same
- // - Assert workout batch results are still the same
-
await page.goto('/');
- // Go to batch calculator
- await page.getByRole('button', { name: 'Batch Calculator' }).click();
- await expect(page).toHaveTitle('Batch Calculator - Running Tools');
-
- // Enter input pace (2 mi in 10:30)
- await page.getByLabel('Input distance value').fill('2');
- await page.getByLabel('Input distance unit').selectOption('Miles');
- await page.getByLabel('Input duration hours').fill('0');
- await page.getByLabel('Input duration minutes').fill('10');
- await page.getByLabel('Input duration seconds').fill('30');
-
- // Enter batch options (15 x 10s increments)
- await page.getByLabel('Duration increment minutes').fill('0');
- await page.getByLabel('Duration increment seconds').fill('10');
- await page.getByLabel('Number of rows').fill('15');
-
- // Assert workout results are correct
- await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi');
- await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m @ 5 km');
- await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(5);
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30');
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:41');
- await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(5);
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:17');
- await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(5);
- await expect(page.getByRole('row')).toHaveCount(16);
-
- // Change prediction model and enable customized target names
- await page.getByText('Advanced Options').click();
- await page.getByLabel('Prediction model').selectOption('Riegel\'s Model');
- await page.getByLabel('Target name customization').selectOption('Enabled');
-
- // Assert workout results are correct
- await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi');
- await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m @ 5 km');
- await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(5);
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30');
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:41');
- await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(5);
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:17');
- await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(5);
- await expect(page.getByRole('row')).toHaveCount(16);
-
- // Change calculator
- await expect(page.getByLabel('Calculator')).toHaveValue('workout');
- await page.getByLabel('Calculator').selectOption('Pace Calculator');
-
- // Assert pace results are correct
- await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi');
- await expect(page.getByRole('row').nth(0).getByRole('cell').nth(6)).toHaveText('800 m');
- await expect(page.getByRole('row').nth(0).getByRole('cell').nth(28)).toHaveText('10:00');
- await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(31);
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30');
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(6)).toHaveText('2:37');
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(28)).toHaveText('1.90 mi');
- await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(31);
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(6)).toHaveText('3:11');
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(28)).toHaveText('1.56 mi');
- await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(31);
- await expect(page.getByRole('row')).toHaveCount(16);
-
- // Assert prediction options are hidden
- await expect(page.getByLabel('Prediction model')).toHaveCount(0);
-
- // Change default units
- await page.getByLabel('Default units').selectOption('Kilometers');
-
- // Assert pace results are correct
- await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi');
- await expect(page.getByRole('row').nth(0).getByRole('cell').nth(6)).toHaveText('800 m');
- await expect(page.getByRole('row').nth(0).getByRole('cell').nth(28)).toHaveText('10:00');
- await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(31);
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30');
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(6)).toHaveText('2:37');
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(28)).toHaveText('3.07 km');
- await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(31);
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(6)).toHaveText('3:11');
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(28)).toHaveText('2.51 km');
- await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(31);
- await expect(page.getByRole('row')).toHaveCount(16);
-
- // Change calculator
- await page.getByLabel('Calculator').selectOption('Race Calculator');
-
- // Assert race results are correct
- await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi');
- await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m');
- await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(17);
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30');
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:15');
- await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(17);
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('2:44');
- await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(17);
- await expect(page.getByRole('row')).toHaveCount(16);
-
- // Change Riegel exponent
- await expect(page.getByLabel('Prediction model')).toHaveValue('AverageModel');
- await page.getByLabel('Riegel Exponent').fill('1.12');
-
- // Assert race results are correct
- await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi');
- await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m');
- await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(17);
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30');
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:12');
- await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(17);
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('2:40');
- await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(17);
- await expect(page.getByRole('row')).toHaveCount(16);
+ // Test workout batch results, including modified prediction model and custom target names
+ {
+ // Go to batch calculator
+ await page.getByRole('button', { name: 'Batch Calculator' }).click();
+ await expect(page).toHaveTitle('Batch Calculator - Running Tools');
+
+ // Enter input pace (2 mi in 10:30)
+ await page.getByLabel('Input distance value').fill('2');
+ await page.getByLabel('Input distance unit').selectOption('Miles');
+ await page.getByLabel('Input duration hours').fill('0');
+ await page.getByLabel('Input duration minutes').fill('10');
+ await page.getByLabel('Input duration seconds').fill('30');
+
+ // Enter batch options (15 x 10s increments)
+ await page.getByLabel('Duration increment minutes').fill('0');
+ await page.getByLabel('Duration increment seconds').fill('10');
+ await page.getByLabel('Number of rows').fill('15');
+
+ // Assert workout results are correct
+ await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi');
+ await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m @ 5 km');
+ await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(5);
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30');
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:41');
+ await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(5);
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:17');
+ await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(5);
+ await expect(page.getByRole('row')).toHaveCount(16);
+
+ // Change prediction model and enable customized target names
+ await page.getByText('Advanced Options').click();
+ await page.getByLabel('Prediction model').selectOption('Riegel\'s Model');
+ await page.getByLabel('Target name customization').selectOption('Enabled');
+
+ // Assert workout results are correct
+ await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi');
+ await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m @ 5 km');
+ await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(5);
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30');
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:41');
+ await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(5);
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:17');
+ await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(5);
+ await expect(page.getByRole('row')).toHaveCount(16);
+ }
+
+ // Test pace batch results, including modified default units
+ {
+ // Change calculator
+ await expect(page.getByLabel('Calculator')).toHaveValue('workout');
+ await page.getByLabel('Calculator').selectOption('Pace Calculator');
+
+ // Assert pace results are correct
+ await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi');
+ await expect(page.getByRole('row').nth(0).getByRole('cell').nth(6)).toHaveText('800 m');
+ await expect(page.getByRole('row').nth(0).getByRole('cell').nth(28)).toHaveText('10:00');
+ await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(31);
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30');
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(6)).toHaveText('2:37');
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(28)).toHaveText('1.90 mi');
+ await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(31);
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(6)).toHaveText('3:11');
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(28)).toHaveText('1.56 mi');
+ await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(31);
+ await expect(page.getByRole('row')).toHaveCount(16);
+
+ // Assert prediction options are hidden
+ await expect(page.getByLabel('Prediction model')).toHaveCount(0);
+
+ // Change default units
+ await page.getByLabel('Default units').selectOption('Kilometers');
+
+ // Assert pace results are correct
+ await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi');
+ await expect(page.getByRole('row').nth(0).getByRole('cell').nth(6)).toHaveText('800 m');
+ await expect(page.getByRole('row').nth(0).getByRole('cell').nth(28)).toHaveText('10:00');
+ await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(31);
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30');
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(6)).toHaveText('2:37');
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(28)).toHaveText('3.07 km');
+ await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(31);
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(6)).toHaveText('3:11');
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(28)).toHaveText('2.51 km');
+ await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(31);
+ await expect(page.getByRole('row')).toHaveCount(16);
+ }
+
+ // Test race batch results, including modified Riegel exponent
+ {
+ // Change calculator
+ await page.getByLabel('Calculator').selectOption('Race Calculator');
+
+ // Assert race results are correct
+ await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi');
+ await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m');
+ await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(17);
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30');
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:15');
+ await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(17);
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('2:44');
+ await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(17);
+ await expect(page.getByRole('row')).toHaveCount(16);
+
+ // Change Riegel exponent
+ await expect(page.getByLabel('Prediction model')).toHaveValue('AverageModel');
+ await page.getByLabel('Riegel Exponent').fill('1.12');
+
+ // Assert race results are correct
+ await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi');
+ await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m');
+ await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(17);
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30');
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:12');
+ await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(17);
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('2:40');
+ await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(17);
+ await expect(page.getByRole('row')).toHaveCount(16);
+ }
// Reload page
await page.reload();
// Assert race results are correct (inputs and options not reset)
- await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi');
- await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m');
- await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(17);
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30');
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:12');
- await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(17);
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('2:40');
- await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(17);
- await expect(page.getByRole('row')).toHaveCount(16);
+ {
+ await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi');
+ await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m');
+ await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(17);
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30');
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:12');
+ await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(17);
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('2:40');
+ await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(17);
+ await expect(page.getByRole('row')).toHaveCount(16);
+ }
// Assert pace results are correct (inputs and options not reset)
- await page.getByLabel('Calculator').selectOption('Pace Calculator');
- await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi');
- await expect(page.getByRole('row').nth(0).getByRole('cell').nth(6)).toHaveText('800 m');
- await expect(page.getByRole('row').nth(0).getByRole('cell').nth(28)).toHaveText('10:00');
- await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(31);
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30');
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(6)).toHaveText('2:37');
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(28)).toHaveText('3.07 km');
- await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(31);
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(6)).toHaveText('3:11');
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(28)).toHaveText('2.51 km');
- await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(31);
- await expect(page.getByRole('row')).toHaveCount(16);
+ {
+ await page.getByLabel('Calculator').selectOption('Pace Calculator');
+ await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi');
+ await expect(page.getByRole('row').nth(0).getByRole('cell').nth(6)).toHaveText('800 m');
+ await expect(page.getByRole('row').nth(0).getByRole('cell').nth(28)).toHaveText('10:00');
+ await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(31);
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30');
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(6)).toHaveText('2:37');
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(28)).toHaveText('3.07 km');
+ await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(31);
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(6)).toHaveText('3:11');
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(28)).toHaveText('2.51 km');
+ await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(31);
+ await expect(page.getByRole('row')).toHaveCount(16);
+ }
// Assert workout results are correct (inputs and options not reset)
- await page.getByLabel('Calculator').selectOption('Workout Calculator');
- await expect(page.getByLabel('Target name customization')).toHaveValue("true");
- await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi');
- await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m @ 5 km');
- await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(5);
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30');
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:41');
- await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(5);
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:17');
- await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(5);
- await expect(page.getByRole('row')).toHaveCount(16);
+ {
+ await page.getByLabel('Calculator').selectOption('Workout Calculator');
+ await expect(page.getByLabel('Target name customization')).toHaveValue("true");
+ await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi');
+ await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m @ 5 km');
+ await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(5);
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30');
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:41');
+ await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(5);
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:17');
+ await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(5);
+ await expect(page.getByRole('row')).toHaveCount(16);
+ }
});
diff --git a/tests/e2e/cross-calculator.spec.js b/tests/e2e/cross-calculator.spec.js
@@ -1,1008 +1,1021 @@
import { test, expect } from '@playwright/test';
test('Cross-calculator', async ({ page }) => {
- // Structure:
- // - Set various options in the different calculators
- // - Go back and assert the options are not reset
- // - Assert localStorage entries are correct
- // - Reload app and assert the options are loaded
-
- // Go to batch calculator
await page.goto('/');
- await page.getByRole('button', { name: 'Batch Calculator' }).click();
-
- // Enter input pace (2 mi in 10:30)
- await page.getByLabel('Input distance value').fill('2');
- await page.getByLabel('Input distance unit').selectOption('Miles');
- await page.getByLabel('Input duration hours').fill('0');
- await page.getByLabel('Input duration minutes').fill('10');
- await page.getByLabel('Input duration seconds').fill('30');
-
- // Enter batch options (15 x 10s increments)
- await page.getByLabel('Duration increment minutes').fill('0');
- await page.getByLabel('Duration increment seconds').fill('10');
- await page.getByLabel('Number of rows').fill('15');
-
- // Change calculator
- await page.getByLabel('Calculator').selectOption('Race Calculator');
-
- // Change prediction model
- await page.getByText('Advanced Options').click();
- await page.getByLabel('Prediction model').selectOption('Riegel\'s Model');
- // Go to pace calculator
- await page.goto('/');
- await page.getByRole('button', { name: 'Pace Calculator' }).click();
-
- // Enter input pace (2 mi in 15:30)
- await page.getByLabel('Input distance value').fill('2');
- await page.getByLabel('Input distance unit').selectOption('Miles');
- await page.getByLabel('Input duration hours').fill('0');
- await page.getByLabel('Input duration minutes').fill('15');
- await page.getByLabel('Input duration seconds').fill('30');
-
- // Create custom target set
- await page.getByText('Advanced Options').click();
- await page.getByLabel('Selected target set').selectOption('[ Create New Target Set ]');
- await expect(page.getByRole('row').nth(4)).toHaveText('There aren\'t any targets in this set yet.');
- await expect(page.getByRole('row')).toHaveCount(5);
-
- // Edit new target set
- await expect(page.getByRole('row').nth(1)).toHaveText('There aren\'t any targets in this set yet.');
- await expect(page.getByLabel('Target set label')).toHaveValue('New target set');
- await page.getByLabel('Target set label').fill('800m Splits');
- await page.getByRole('button', { name: 'Add distance target' }).click();
- await page.getByLabel('Target distance value').nth(0).fill('0.4');
- await page.getByLabel('Target distance unit').nth(0).selectOption('Kilometers');
- await page.getByRole('button', { name: 'Add distance target' }).click();
- await page.getByLabel('Target distance value').nth(1).fill('800');
- await page.getByLabel('Target distance unit').nth(1).selectOption('Meters');
- await page.getByRole('button', { name: 'Add time target' }).click();
- await page.getByRole('button', { name: 'Close' }).click();
-
- // Go to race calculator
- await page.getByRole('link', { name: 'Back' }).click();
- await page.getByRole('button', { name: 'Race Calculator' }).click();
-
- // Enter input race (2 mi in 10:30)
- await page.getByLabel('Input race distance value').fill('2');
- await page.getByLabel('Input race distance unit').selectOption('Miles');
- await page.getByLabel('Input race duration hours').fill('0');
- await page.getByLabel('Input race duration minutes').fill('10');
- await page.getByLabel('Input race duration seconds').fill('30');
-
- // Go to split calculator
- await page.getByRole('link', { name: 'Back' }).click();
- await page.getByRole('button', { name: 'Split Calculator' }).click();
-
- // Edit target set
- await page.getByRole('button', { name: 'Edit target set' }).click();
- await page.getByLabel('Target set label').fill('5K 1600m Splits');
- await page.getByLabel('Target distance value').nth(0).fill('1.6');
- await page.getByLabel('Target distance unit').nth(0).selectOption('Kilometers');
- await page.getByLabel('Target distance value').nth(1).fill('3.2');
- await page.getByLabel('Target distance unit').nth(1).selectOption('Kilometers');
- await page.getByRole('button', { name: 'Close' }).click();
-
- // Enter input 5K splits (7:00, 6:30, 6:30)
- await page.getByLabel('Split duration minutes').nth(0).fill('7');
- await page.getByLabel('Split duration seconds').nth(0).fill('0');
- await page.getByLabel('Split duration minutes').nth(1).fill('6');
- await page.getByLabel('Split duration seconds').nth(1).fill('30');
- await page.getByLabel('Split duration minutes').nth(2).fill('6');
- await page.getByLabel('Split duration seconds').nth(2).fill('30');
-
- // Go to unit calculator
- await page.getByRole('link', { name: 'Back' }).click();
- await page.getByRole('button', { name: 'Unit Calculator' }).click();
-
- // Convert speed and pace units (10 kph to time per mile)
- await page.getByLabel('Selected unit category').selectOption('Speed & Pace');
- await page.getByLabel('Input units').selectOption('Kilometers per Hour');
- await page.getByLabel('Input value').fill('10');
- await page.getByLabel('Output units').selectOption('Time per Mile');
-
- // Go to workout calculator
- await page.getByRole('link', { name: 'Back' }).click();
- await page.getByRole('button', { name: 'Workout Calculator' }).click();
-
- // Enter input race (1 mi in 5:01)
- await page.getByLabel('Input race distance value').fill('1');
- await page.getByLabel('Input race distance unit').selectOption('Miles');
- await page.getByLabel('Input race duration hours').fill('0');
- await page.getByLabel('Input race duration minutes').fill('5');
- await page.getByLabel('Input race duration seconds').fill('1');
-
- // Change prediction model and enable target name customization
- await page.getByText('Advanced Options').click();
- await page.getByLabel('Prediction model').selectOption('V̇O₂ Max Model');
- await page.getByLabel('Target name customization').selectOption('Enabled');
-
- // Change default units (should update on other calculators too)
- await page.getByLabel('Default units').selectOption('Kilometers');
-
- // Return to batch calculator
- await page.getByRole('link', { name: 'Back' }).click();
- await page.getByRole('button', { name: 'Batch Calculator' }).click();
-
- // Assert pace results are correct (inputs and options not reset)
- await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi');
- await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m');
- await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(17);
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30');
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:24');
- await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(17);
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('2:56');
- await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(17);
- await expect(page.getByRole('row')).toHaveCount(16);
-
- // Assert pace results are correct (inputs and options not reset, new pace targets loaded)
- await page.getByLabel('Calculator').selectOption('Pace Calculator');
- await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi');
- await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m');
- await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(4);
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30');
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:37');
- await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(4);
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:11');
- await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(4);
- await expect(page.getByRole('row')).toHaveCount(16);
-
- // Assert workout results are correct (new workout options loaded)
- await page.getByLabel('Calculator').selectOption('Workout Calculator');
- await expect(page.getByLabel('Target name customization')).toHaveValue("true");
- await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi');
- await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m @ 5 km');
- await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(5);
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30');
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:42');
- await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(5);
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:17');
- await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(5);
- await expect(page.getByRole('row')).toHaveCount(16);
-
- // Reset selected calculator
- await page.getByLabel('Calculator').selectOption('Race Calculator');
-
- // Return to pace calculator
- await page.getByRole('link', { name: 'Back' }).click();
- await page.getByRole('button', { name: 'Pace Calculator' }).click();
-
- // Assert paces are correct (input pace not reset)
- await expect(page.getByRole('row').nth(1)).toHaveText('0.4 km' + '1:55.58');
- await expect(page.getByRole('row').nth(2)).toHaveText('800 m' + '3:51.15');
- await expect(page.getByRole('row').nth(3)).toHaveText('2.08 km' + '10:00');
- await expect(page.getByRole('row')).toHaveCount(4);
-
- // Return to race calculator
- await page.getByRole('link', { name: 'Back' }).click();
- await page.getByRole('button', { name: 'Race Calculator' }).click();
-
- // Assert race predictions are correct (input race not resset and new prediction model loaded)
- await expect(page.getByRole('row').nth(5)).toHaveText('1 mi' + '5:02.17' + '3:08 / km');
- await expect(page.getByRole('row').nth(10)).toHaveText('5 km' + '16:44.87' + '3:21 / km');
- await expect(page.getByRole('row')).toHaveCount(17);
-
- // Return to split calculator
- await page.getByRole('link', { name: 'Back' }).click();
- await page.getByRole('button', { name: 'Split Calculator' }).click();
-
- // Assert times and paces are correct (split times not reset)
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('7:00.00');
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('4:23 / km');
- await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('13:30.00');
- await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('4:04 / km');
- await expect(page.getByRole('row').nth(3).getByRole('cell').nth(1)).toHaveText('20:00.00');
- await expect(page.getByRole('row').nth(3).getByRole('cell').nth(3)).toHaveText('3:37 / km');
- await expect(page.getByRole('row')).toHaveCount(4);
-
- // Return to unit calculator
- await page.getByRole('link', { name: 'Back' }).click();
- await page.getByRole('button', { name: 'Unit Calculator' }).click();
-
- // Assert result is correct (state not reset)
- await expect(page.getByLabel('Output value')).toHaveText('00:09:39.364');
-
- // Return to workout calculator
- await page.getByRole('link', { name: 'Back' }).click();
- await page.getByRole('button', { name: 'Workout Calculator' }).click();
-
- // Assert workout splits are correct (input race and prediction model not reset)
- await expect(page.getByRole('row').nth(1)).toHaveText('400 m @ 1 mi' + '1:14.81');
- await expect(page.getByRole('row').nth(3)).toHaveText('1600 m @ 1:00:00' + '5:53.56');
- await expect(page.getByRole('row')).toHaveCount(5);
-
- // Assert general localStorage entries are correct
- expect(await page.evaluate(() => localStorage.length)).toEqual(16);
- expect(await page.evaluate(() => localStorage.getItem('running-tools.default-unit-system')))
- .toEqual(JSON.stringify('metric'));
-
- // Assert localStorage entries for the batch calculator are correct
- expect(await page.evaluate(() =>
- localStorage.getItem('running-tools.batch-calculator-input'))).toEqual(JSON.stringify({
- distanceValue: 2,
- distanceUnit: 'miles',
- time: 630,
- }));
- expect(await page.evaluate(() =>
- localStorage.getItem('running-tools.batch-calculator-options'))).toEqual(JSON.stringify({
- calculator: 'race',
- increment: 10,
- rows: 15,
- }));
-
- // Assert localStorage entries for the pace calculator are correct
- expect(await page.evaluate(() =>
- localStorage.getItem('running-tools.pace-calculator-input'))).toEqual(JSON.stringify({
- distanceValue: 2,
- distanceUnit: 'miles',
- time: 930,
- }));
- const paceCalculatorKey = parseInt(JSON.parse(await page.evaluate(() =>
- localStorage.getItem('running-tools.pace-calculator-options'))).selectedTargetSet);
- expect(paceCalculatorKey - parseInt(Date.now().toString())).toBeLessThan(100000);
- expect(await page.evaluate(() =>
- localStorage.getItem('running-tools.pace-calculator-target-sets'))).toEqual(JSON.stringify({
- _pace_targets: {
- name: 'Common Pace Targets',
- targets: [
- { type: 'distance', distanceValue: 100, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 200, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 300, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 400, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 600, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 800, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 1000, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 1200, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 1500, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 1, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 2, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 3, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 3200, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 2, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 4, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 3, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 6, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 8, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 5, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 6, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 10, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 8, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 10, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 0.5, distanceUnit: 'marathons' },
- { type: 'distance', distanceValue: 1, distanceUnit: 'marathons' },
- { type: 'time', time: 600 },
- { type: 'time', time: 1800 },
- { type: 'time', time: 3600 },
- ],
- },
- [paceCalculatorKey.toString()]: {
- name: '800m Splits',
- targets: [
- { type: 'distance', distanceValue: 0.4, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 800, distanceUnit: 'meters' },
- { type: 'time', time: 600 },
- ],
- },
- }));
-
- // Assert localStorage entries for the race calculator are correct
- expect(await page.evaluate(() =>
- localStorage.getItem('running-tools.race-calculator-input'))).toEqual(JSON.stringify({
- distanceValue: 2,
- distanceUnit: 'miles',
- time: 630,
- }));
- expect(await page.evaluate(() =>
- localStorage.getItem('running-tools.race-calculator-options'))).toEqual(JSON.stringify({
- model: 'RiegelModel',
- riegelExponent: 1.06,
- selectedTargetSet: '_race_targets',
- }));
- expect(await page.evaluate(() =>
- localStorage.getItem('running-tools.race-calculator-target-sets'))).toEqual(JSON.stringify({
- _race_targets: {
- name: 'Common Race Targets',
- targets: [
- { type: 'distance', distanceValue: 400, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 800, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 1500, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 1, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 3000, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 3200, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 2, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 3, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 6, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 8, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 10, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 15, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 0.5, distanceUnit: 'marathons' },
- { type: 'distance', distanceValue: 1, distanceUnit: 'marathons' },
- ],
- },
- }));
-
- // Assert localStorage entries for the split calculator are correct
- expect(await page.evaluate(() =>
- localStorage.getItem('running-tools.split-calculator-options')))
- .toEqual(JSON.stringify({
- selectedTargetSet: '_split_targets',
- }));
- expect(await page.evaluate(() =>
- localStorage.getItem('running-tools.split-calculator-target-sets'))).toEqual(JSON.stringify({
- _split_targets: {
- name: '5K 1600m Splits',
- targets: [
- { type: 'distance', distanceValue: 1.6, distanceUnit: 'kilometers', splitTime: 420 },
- { type: 'distance', distanceValue: 3.2, distanceUnit: 'kilometers', splitTime: 390 },
- { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers', splitTime: 390 },
- ],
- },
- }));
-
- // Assert localStorage entries for the unit calculator are correct
- expect(await page.evaluate(() => localStorage.getItem('running-tools.unit-calculator-category')))
- .toEqual(JSON.stringify('speed_and_pace'));
- expect(await page.evaluate(() =>
- localStorage.getItem('running-tools.unit-calculator-inputs'))).toEqual(JSON.stringify({
- distance: {
- inputValue: 1,
- inputUnit: 'miles',
- outputUnit: 'kilometers',
- },
- time: {
- inputValue: 1,
- inputUnit: 'seconds',
- outputUnit: 'hh:mm:ss',
- },
- speed_and_pace: {
- inputValue: 10,
- inputUnit: 'kilometers_per_hour',
- outputUnit: 'seconds_per_mile',
- },
- }));
-
- // Assert localStorage entries for the workout calculator are correct
- expect(await page.evaluate(() =>
- localStorage.getItem('running-tools.workout-calculator-input'))).toEqual(JSON.stringify({
- distanceValue: 1,
- distanceUnit: 'miles',
- time: 301,
- }));
- expect(await page.evaluate(() =>
- localStorage.getItem('running-tools.workout-calculator-options'))).toEqual(JSON.stringify({
- customTargetNames: true,
- model: 'VO2MaxModel',
- riegelExponent: 1.06,
- selectedTargetSet: '_workout_targets',
- }));
- expect(await page.evaluate(() =>
- localStorage.getItem('running-tools.workout-calculator-target-sets'))).toEqual(JSON.stringify({
- _workout_targets: {
- name: 'Common Workout Targets',
- targets: [
- {
- splitValue: 400, splitUnit: 'meters',
- type: 'distance', distanceValue: 1, distanceUnit: 'miles',
- },
- {
- splitValue: 800, splitUnit: 'meters',
- type: 'distance', distanceValue: 5, distanceUnit: 'kilometers',
- },
- {
- splitValue: 1600, splitUnit: 'meters',
- type: 'time', time: 3600,
- },
- {
- splitValue: 1, splitUnit: 'miles',
- type: 'distance', distanceValue: 1, distanceUnit: 'marathons',
- },
- ],
- },
- }));
-
- // Reload app and go to batch calculator
- await page.goto('/');
- await page.getByRole('button', { name: 'Batch Calculator' }).click();
-
- // Assert pace results are correct (inputs and options not reset)
- await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi');
- await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m');
- await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(17);
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30');
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:24');
- await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(17);
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('2:56');
- await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(17);
- await expect(page.getByRole('row')).toHaveCount(16);
-
- // Assert pace results are correct (inputs and options not reset, new pace targets loaded)
- await page.getByLabel('Calculator').selectOption('Pace Calculator');
- await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi');
- await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m');
- await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(4);
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30');
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:37');
- await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(4);
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:11');
- await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(4);
- await expect(page.getByRole('row')).toHaveCount(16);
-
- // Assert workout results are correct (new workout options loaded)
- await page.getByLabel('Calculator').selectOption('Workout Calculator');
- await expect(page.getByLabel('Target name customization')).toHaveValue("true");
- await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi');
- await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m @ 5 km');
- await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(5);
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30');
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:42');
- await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(5);
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:17');
- await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(5);
- await expect(page.getByRole('row')).toHaveCount(16);
-
- // Reset selected calculator
- await page.getByLabel('Calculator').selectOption('Race Calculator');
-
- // Return to pace calculator
- await page.getByRole('link', { name: 'Back' }).click();
- await page.getByRole('button', { name: 'Pace Calculator' }).click();
-
- // Assert paces are correct (input pace not reset)
- await expect(page.getByRole('row').nth(1)).toHaveText('0.4 km' + '1:55.58');
- await expect(page.getByRole('row').nth(2)).toHaveText('800 m' + '3:51.15');
- await expect(page.getByRole('row').nth(3)).toHaveText('2.08 km' + '10:00');
- await expect(page.getByRole('row')).toHaveCount(4);
-
- // Return to race calculator
- await page.getByRole('link', { name: 'Back' }).click();
- await page.getByRole('button', { name: 'Race Calculator' }).click();
-
- // Assert race predictions are correct (input race not resset and new prediction model loaded)
- await expect(page.getByRole('row').nth(5)).toHaveText('1 mi' + '5:02.17' + '3:08 / km');
- await expect(page.getByRole('row').nth(10)).toHaveText('5 km' + '16:44.87' + '3:21 / km');
- await expect(page.getByRole('row')).toHaveCount(17);
-
- // Return to split calculator
- await page.getByRole('link', { name: 'Back' }).click();
- await page.getByRole('button', { name: 'Split Calculator' }).click();
-
- // Assert times and paces are correct (split times not reset)
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('7:00.00');
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('4:23 / km');
- await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('13:30.00');
- await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('4:04 / km');
- await expect(page.getByRole('row').nth(3).getByRole('cell').nth(1)).toHaveText('20:00.00');
- await expect(page.getByRole('row').nth(3).getByRole('cell').nth(3)).toHaveText('3:37 / km');
- await expect(page.getByRole('row')).toHaveCount(4);
-
- // Return to unit calculator
- await page.getByRole('link', { name: 'Back' }).click();
- await page.getByRole('button', { name: 'Unit Calculator' }).click();
-
- // Assert result is correct (state not reset)
- await expect(page.getByLabel('Output value')).toHaveText('00:09:39.364');
-
- // Return to workout calculator
- await page.getByRole('link', { name: 'Back' }).click();
- await page.getByRole('button', { name: 'Workout Calculator' }).click();
-
- // Assert workout splits are correct (input race and prediction model not reset)
- await expect(page.getByRole('row').nth(1)).toHaveText('400 m @ 1 mi' + '1:14.81');
- await expect(page.getByRole('row').nth(3)).toHaveText('1600 m @ 1:00:00' + '5:53.56');
- await expect(page.getByRole('row')).toHaveCount(5);
+ // Set various options in the different calculators
+ {
+ // Go to batch calculator
+ await page.getByRole('button', { name: 'Batch Calculator' }).click();
+
+ // Enter input pace (2 mi in 10:30)
+ await page.getByLabel('Input distance value').fill('2');
+ await page.getByLabel('Input distance unit').selectOption('Miles');
+ await page.getByLabel('Input duration hours').fill('0');
+ await page.getByLabel('Input duration minutes').fill('10');
+ await page.getByLabel('Input duration seconds').fill('30');
+
+ // Enter batch options (15 x 10s increments)
+ await page.getByLabel('Duration increment minutes').fill('0');
+ await page.getByLabel('Duration increment seconds').fill('10');
+ await page.getByLabel('Number of rows').fill('15');
+
+ // Change calculator
+ await page.getByLabel('Calculator').selectOption('Race Calculator');
+
+ // Change prediction model
+ await page.getByText('Advanced Options').click();
+ await page.getByLabel('Prediction model').selectOption('Riegel\'s Model');
+
+ // Go to pace calculator
+ await page.goto('/');
+ await page.getByRole('button', { name: 'Pace Calculator' }).click();
+
+ // Enter input pace (2 mi in 15:30)
+ await page.getByLabel('Input distance value').fill('2');
+ await page.getByLabel('Input distance unit').selectOption('Miles');
+ await page.getByLabel('Input duration hours').fill('0');
+ await page.getByLabel('Input duration minutes').fill('15');
+ await page.getByLabel('Input duration seconds').fill('30');
+
+ // Create custom target set
+ await page.getByText('Advanced Options').click();
+ await page.getByLabel('Selected target set').selectOption('[ Create New Target Set ]');
+ await expect(page.getByRole('row').nth(4)).toHaveText('There aren\'t any targets in this set yet.');
+ await expect(page.getByRole('row')).toHaveCount(5);
+
+ // Edit new target set
+ await expect(page.getByRole('row').nth(1)).toHaveText('There aren\'t any targets in this set yet.');
+ await expect(page.getByLabel('Target set label')).toHaveValue('New target set');
+ await page.getByLabel('Target set label').fill('800m Splits');
+ await page.getByRole('button', { name: 'Add distance target' }).click();
+ await page.getByLabel('Target distance value').nth(0).fill('0.4');
+ await page.getByLabel('Target distance unit').nth(0).selectOption('Kilometers');
+ await page.getByRole('button', { name: 'Add distance target' }).click();
+ await page.getByLabel('Target distance value').nth(1).fill('800');
+ await page.getByLabel('Target distance unit').nth(1).selectOption('Meters');
+ await page.getByRole('button', { name: 'Add time target' }).click();
+ await page.getByRole('button', { name: 'Close' }).click();
+
+ // Go to race calculator
+ await page.getByRole('link', { name: 'Back' }).click();
+ await page.getByRole('button', { name: 'Race Calculator' }).click();
+
+ // Enter input race (2 mi in 10:30)
+ await page.getByLabel('Input race distance value').fill('2');
+ await page.getByLabel('Input race distance unit').selectOption('Miles');
+ await page.getByLabel('Input race duration hours').fill('0');
+ await page.getByLabel('Input race duration minutes').fill('10');
+ await page.getByLabel('Input race duration seconds').fill('30');
+
+ // Go to split calculator
+ await page.getByRole('link', { name: 'Back' }).click();
+ await page.getByRole('button', { name: 'Split Calculator' }).click();
+
+ // Edit target set
+ await page.getByRole('button', { name: 'Edit target set' }).click();
+ await page.getByLabel('Target set label').fill('5K 1600m Splits');
+ await page.getByLabel('Target distance value').nth(0).fill('1.6');
+ await page.getByLabel('Target distance unit').nth(0).selectOption('Kilometers');
+ await page.getByLabel('Target distance value').nth(1).fill('3.2');
+ await page.getByLabel('Target distance unit').nth(1).selectOption('Kilometers');
+ await page.getByRole('button', { name: 'Close' }).click();
+
+ // Enter input 5K splits (7:00, 6:30, 6:30)
+ await page.getByLabel('Split duration minutes').nth(0).fill('7');
+ await page.getByLabel('Split duration seconds').nth(0).fill('0');
+ await page.getByLabel('Split duration minutes').nth(1).fill('6');
+ await page.getByLabel('Split duration seconds').nth(1).fill('30');
+ await page.getByLabel('Split duration minutes').nth(2).fill('6');
+ await page.getByLabel('Split duration seconds').nth(2).fill('30');
+
+ // Go to unit calculator
+ await page.getByRole('link', { name: 'Back' }).click();
+ await page.getByRole('button', { name: 'Unit Calculator' }).click();
+
+ // Convert speed and pace units (10 kph to time per mile)
+ await page.getByLabel('Selected unit category').selectOption('Speed & Pace');
+ await page.getByLabel('Input units').selectOption('Kilometers per Hour');
+ await page.getByLabel('Input value').fill('10');
+ await page.getByLabel('Output units').selectOption('Time per Mile');
+
+ // Go to workout calculator
+ await page.getByRole('link', { name: 'Back' }).click();
+ await page.getByRole('button', { name: 'Workout Calculator' }).click();
+
+ // Enter input race (1 mi in 5:01)
+ await page.getByLabel('Input race distance value').fill('1');
+ await page.getByLabel('Input race distance unit').selectOption('Miles');
+ await page.getByLabel('Input race duration hours').fill('0');
+ await page.getByLabel('Input race duration minutes').fill('5');
+ await page.getByLabel('Input race duration seconds').fill('1');
+
+ // Change prediction model and enable target name customization
+ await page.getByText('Advanced Options').click();
+ await page.getByLabel('Prediction model').selectOption('V̇O₂ Max Model');
+ await page.getByLabel('Target name customization').selectOption('Enabled');
+
+ // Change default units (should update on other calculators too)
+ await page.getByLabel('Default units').selectOption('Kilometers');
+ }
+
+ // Go back and assert the options are not reset
+ {
+ // Return to batch calculator
+ await page.getByRole('link', { name: 'Back' }).click();
+ await page.getByRole('button', { name: 'Batch Calculator' }).click();
+
+ // Assert pace results are correct (inputs and options not reset)
+ await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi');
+ await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m');
+ await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(17);
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30');
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:24');
+ await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(17);
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('2:56');
+ await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(17);
+ await expect(page.getByRole('row')).toHaveCount(16);
+
+ // Assert pace results are correct (inputs and options not reset, new pace targets loaded)
+ await page.getByLabel('Calculator').selectOption('Pace Calculator');
+ await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi');
+ await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m');
+ await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(4);
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30');
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:37');
+ await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(4);
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:11');
+ await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(4);
+ await expect(page.getByRole('row')).toHaveCount(16);
+
+ // Assert workout results are correct (new workout options loaded)
+ await page.getByLabel('Calculator').selectOption('Workout Calculator');
+ await expect(page.getByLabel('Target name customization')).toHaveValue("true");
+ await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi');
+ await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m @ 5 km');
+ await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(5);
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30');
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:42');
+ await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(5);
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:17');
+ await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(5);
+ await expect(page.getByRole('row')).toHaveCount(16);
+
+ // Reset selected calculator
+ await page.getByLabel('Calculator').selectOption('Race Calculator');
+
+ // Return to pace calculator
+ await page.getByRole('link', { name: 'Back' }).click();
+ await page.getByRole('button', { name: 'Pace Calculator' }).click();
+
+ // Assert paces are correct (input pace not reset)
+ await expect(page.getByRole('row').nth(1)).toHaveText('0.4 km' + '1:55.58');
+ await expect(page.getByRole('row').nth(2)).toHaveText('800 m' + '3:51.15');
+ await expect(page.getByRole('row').nth(3)).toHaveText('2.08 km' + '10:00');
+ await expect(page.getByRole('row')).toHaveCount(4);
+
+ // Return to race calculator
+ await page.getByRole('link', { name: 'Back' }).click();
+ await page.getByRole('button', { name: 'Race Calculator' }).click();
+
+ // Assert race predictions are correct (input race not resset and new prediction model loaded)
+ await expect(page.getByRole('row').nth(5)).toHaveText('1 mi' + '5:02.17' + '3:08 / km');
+ await expect(page.getByRole('row').nth(10)).toHaveText('5 km' + '16:44.87' + '3:21 / km');
+ await expect(page.getByRole('row')).toHaveCount(17);
+
+ // Return to split calculator
+ await page.getByRole('link', { name: 'Back' }).click();
+ await page.getByRole('button', { name: 'Split Calculator' }).click();
+
+ // Assert times and paces are correct (split times not reset)
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('7:00.00');
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('4:23 / km');
+ await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('13:30.00');
+ await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('4:04 / km');
+ await expect(page.getByRole('row').nth(3).getByRole('cell').nth(1)).toHaveText('20:00.00');
+ await expect(page.getByRole('row').nth(3).getByRole('cell').nth(3)).toHaveText('3:37 / km');
+ await expect(page.getByRole('row')).toHaveCount(4);
+
+ // Return to unit calculator
+ await page.getByRole('link', { name: 'Back' }).click();
+ await page.getByRole('button', { name: 'Unit Calculator' }).click();
+
+ // Assert result is correct (state not reset)
+ await expect(page.getByLabel('Output value')).toHaveText('00:09:39.364');
+
+ // Return to workout calculator
+ await page.getByRole('link', { name: 'Back' }).click();
+ await page.getByRole('button', { name: 'Workout Calculator' }).click();
+
+ // Assert workout splits are correct (input race and prediction model not reset)
+ await expect(page.getByRole('row').nth(1)).toHaveText('400 m @ 1 mi' + '1:14.81');
+ await expect(page.getByRole('row').nth(3)).toHaveText('1600 m @ 1:00:00' + '5:53.56');
+ await expect(page.getByRole('row')).toHaveCount(5);
+ }
+
+ // Assert localStorage entries are correct
+ {
+ // Assert general localStorage entries are correct
+ expect(await page.evaluate(() => localStorage.length)).toEqual(16);
+ expect(await page.evaluate(() => localStorage.getItem('running-tools.default-unit-system')))
+ .toEqual(JSON.stringify('metric'));
+
+ // Assert localStorage entries for the batch calculator are correct
+ expect(await page.evaluate(() =>
+ localStorage.getItem('running-tools.batch-calculator-input'))).toEqual(JSON.stringify({
+ distanceValue: 2,
+ distanceUnit: 'miles',
+ time: 630,
+ }));
+ expect(await page.evaluate(() =>
+ localStorage.getItem('running-tools.batch-calculator-options'))).toEqual(JSON.stringify({
+ calculator: 'race',
+ increment: 10,
+ rows: 15,
+ }));
+
+ // Assert localStorage entries for the pace calculator are correct
+ expect(await page.evaluate(() =>
+ localStorage.getItem('running-tools.pace-calculator-input'))).toEqual(JSON.stringify({
+ distanceValue: 2,
+ distanceUnit: 'miles',
+ time: 930,
+ }));
+ const paceCalculatorKey = parseInt(JSON.parse(await page.evaluate(() =>
+ localStorage.getItem('running-tools.pace-calculator-options'))).selectedTargetSet);
+ expect(paceCalculatorKey - parseInt(Date.now().toString())).toBeLessThan(100000);
+ expect(await page.evaluate(() =>
+ localStorage.getItem('running-tools.pace-calculator-target-sets'))).toEqual(JSON.stringify({
+ _pace_targets: {
+ name: 'Common Pace Targets',
+ targets: [
+ { type: 'distance', distanceValue: 100, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 200, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 300, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 400, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 600, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 800, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 1000, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 1200, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 1500, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 1, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 2, distanceUnit: 'kilometers' },
+ { type: 'distance', distanceValue: 3, distanceUnit: 'kilometers' },
+ { type: 'distance', distanceValue: 3200, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 2, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 4, distanceUnit: 'kilometers' },
+ { type: 'distance', distanceValue: 3, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' },
+ { type: 'distance', distanceValue: 6, distanceUnit: 'kilometers' },
+ { type: 'distance', distanceValue: 8, distanceUnit: 'kilometers' },
+ { type: 'distance', distanceValue: 5, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 6, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 10, distanceUnit: 'kilometers' },
+ { type: 'distance', distanceValue: 8, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 10, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 0.5, distanceUnit: 'marathons' },
+ { type: 'distance', distanceValue: 1, distanceUnit: 'marathons' },
+ { type: 'time', time: 600 },
+ { type: 'time', time: 1800 },
+ { type: 'time', time: 3600 },
+ ],
+ },
+ [paceCalculatorKey.toString()]: {
+ name: '800m Splits',
+ targets: [
+ { type: 'distance', distanceValue: 0.4, distanceUnit: 'kilometers' },
+ { type: 'distance', distanceValue: 800, distanceUnit: 'meters' },
+ { type: 'time', time: 600 },
+ ],
+ },
+ }));
+
+ // Assert localStorage entries for the race calculator are correct
+ expect(await page.evaluate(() =>
+ localStorage.getItem('running-tools.race-calculator-input'))).toEqual(JSON.stringify({
+ distanceValue: 2,
+ distanceUnit: 'miles',
+ time: 630,
+ }));
+ expect(await page.evaluate(() =>
+ localStorage.getItem('running-tools.race-calculator-options'))).toEqual(JSON.stringify({
+ model: 'RiegelModel',
+ riegelExponent: 1.06,
+ selectedTargetSet: '_race_targets',
+ }));
+ expect(await page.evaluate(() =>
+ localStorage.getItem('running-tools.race-calculator-target-sets'))).toEqual(JSON.stringify({
+ _race_targets: {
+ name: 'Common Race Targets',
+ targets: [
+ { type: 'distance', distanceValue: 400, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 800, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 1500, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 1, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 3000, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 3200, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 2, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 3, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' },
+ { type: 'distance', distanceValue: 6, distanceUnit: 'kilometers' },
+ { type: 'distance', distanceValue: 8, distanceUnit: 'kilometers' },
+ { type: 'distance', distanceValue: 10, distanceUnit: 'kilometers' },
+ { type: 'distance', distanceValue: 15, distanceUnit: 'kilometers' },
+ { type: 'distance', distanceValue: 0.5, distanceUnit: 'marathons' },
+ { type: 'distance', distanceValue: 1, distanceUnit: 'marathons' },
+ ],
+ },
+ }));
+
+ // Assert localStorage entries for the split calculator are correct
+ expect(await page.evaluate(() =>
+ localStorage.getItem('running-tools.split-calculator-options')))
+ .toEqual(JSON.stringify({
+ selectedTargetSet: '_split_targets',
+ }));
+ expect(await page.evaluate(() =>
+ localStorage.getItem('running-tools.split-calculator-target-sets'))).toEqual(JSON.stringify({
+ _split_targets: {
+ name: '5K 1600m Splits',
+ targets: [
+ { type: 'distance', distanceValue: 1.6, distanceUnit: 'kilometers', splitTime: 420 },
+ { type: 'distance', distanceValue: 3.2, distanceUnit: 'kilometers', splitTime: 390 },
+ { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers', splitTime: 390 },
+ ],
+ },
+ }));
+
+ // Assert localStorage entries for the unit calculator are correct
+ expect(await page.evaluate(() => localStorage.getItem('running-tools.unit-calculator-category')))
+ .toEqual(JSON.stringify('speed_and_pace'));
+ expect(await page.evaluate(() =>
+ localStorage.getItem('running-tools.unit-calculator-inputs'))).toEqual(JSON.stringify({
+ distance: {
+ inputValue: 1,
+ inputUnit: 'miles',
+ outputUnit: 'kilometers',
+ },
+ time: {
+ inputValue: 1,
+ inputUnit: 'seconds',
+ outputUnit: 'hh:mm:ss',
+ },
+ speed_and_pace: {
+ inputValue: 10,
+ inputUnit: 'kilometers_per_hour',
+ outputUnit: 'seconds_per_mile',
+ },
+ }));
+
+ // Assert localStorage entries for the workout calculator are correct
+ expect(await page.evaluate(() =>
+ localStorage.getItem('running-tools.workout-calculator-input'))).toEqual(JSON.stringify({
+ distanceValue: 1,
+ distanceUnit: 'miles',
+ time: 301,
+ }));
+ expect(await page.evaluate(() =>
+ localStorage.getItem('running-tools.workout-calculator-options'))).toEqual(JSON.stringify({
+ customTargetNames: true,
+ model: 'VO2MaxModel',
+ riegelExponent: 1.06,
+ selectedTargetSet: '_workout_targets',
+ }));
+ expect(await page.evaluate(() =>
+ localStorage.getItem('running-tools.workout-calculator-target-sets'))).toEqual(JSON.stringify({
+ _workout_targets: {
+ name: 'Common Workout Targets',
+ targets: [
+ {
+ splitValue: 400, splitUnit: 'meters',
+ type: 'distance', distanceValue: 1, distanceUnit: 'miles',
+ },
+ {
+ splitValue: 800, splitUnit: 'meters',
+ type: 'distance', distanceValue: 5, distanceUnit: 'kilometers',
+ },
+ {
+ splitValue: 1600, splitUnit: 'meters',
+ type: 'time', time: 3600,
+ },
+ {
+ splitValue: 1, splitUnit: 'miles',
+ type: 'distance', distanceValue: 1, distanceUnit: 'marathons',
+ },
+ ],
+ },
+ }));
+ }
+
+ // Reload app and assert the updated options are loaded
+ // Identical to the previous "go back and assert the options are not resset" section
+ {
+ // Reload app and go to batch calculator
+ await page.goto('/');
+ await page.getByRole('button', { name: 'Batch Calculator' }).click();
+
+ // Assert pace results are correct (inputs and options not reset)
+ await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi');
+ await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m');
+ await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(17);
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30');
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:24');
+ await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(17);
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('2:56');
+ await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(17);
+ await expect(page.getByRole('row')).toHaveCount(16);
+
+ // Assert pace results are correct (inputs and options not reset, new pace targets loaded)
+ await page.getByLabel('Calculator').selectOption('Pace Calculator');
+ await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi');
+ await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m');
+ await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(4);
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30');
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:37');
+ await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(4);
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:11');
+ await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(4);
+ await expect(page.getByRole('row')).toHaveCount(16);
+
+ // Assert workout results are correct (new workout options loaded)
+ await page.getByLabel('Calculator').selectOption('Workout Calculator');
+ await expect(page.getByLabel('Target name customization')).toHaveValue("true");
+ await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi');
+ await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m @ 5 km');
+ await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(5);
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30');
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:42');
+ await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(5);
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:17');
+ await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(5);
+ await expect(page.getByRole('row')).toHaveCount(16);
+
+ // Reset selected calculator
+ await page.getByLabel('Calculator').selectOption('Race Calculator');
+
+ // Return to pace calculator
+ await page.getByRole('link', { name: 'Back' }).click();
+ await page.getByRole('button', { name: 'Pace Calculator' }).click();
+
+ // Assert paces are correct (input pace not reset)
+ await expect(page.getByRole('row').nth(1)).toHaveText('0.4 km' + '1:55.58');
+ await expect(page.getByRole('row').nth(2)).toHaveText('800 m' + '3:51.15');
+ await expect(page.getByRole('row').nth(3)).toHaveText('2.08 km' + '10:00');
+ await expect(page.getByRole('row')).toHaveCount(4);
+
+ // Return to race calculator
+ await page.getByRole('link', { name: 'Back' }).click();
+ await page.getByRole('button', { name: 'Race Calculator' }).click();
+
+ // Assert race predictions are correct (input race not resset and new prediction model loaded)
+ await expect(page.getByRole('row').nth(5)).toHaveText('1 mi' + '5:02.17' + '3:08 / km');
+ await expect(page.getByRole('row').nth(10)).toHaveText('5 km' + '16:44.87' + '3:21 / km');
+ await expect(page.getByRole('row')).toHaveCount(17);
+
+ // Return to split calculator
+ await page.getByRole('link', { name: 'Back' }).click();
+ await page.getByRole('button', { name: 'Split Calculator' }).click();
+
+ // Assert times and paces are correct (split times not reset)
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('7:00.00');
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('4:23 / km');
+ await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('13:30.00');
+ await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('4:04 / km');
+ await expect(page.getByRole('row').nth(3).getByRole('cell').nth(1)).toHaveText('20:00.00');
+ await expect(page.getByRole('row').nth(3).getByRole('cell').nth(3)).toHaveText('3:37 / km');
+ await expect(page.getByRole('row')).toHaveCount(4);
+
+ // Return to unit calculator
+ await page.getByRole('link', { name: 'Back' }).click();
+ await page.getByRole('button', { name: 'Unit Calculator' }).click();
+
+ // Assert result is correct (state not reset)
+ await expect(page.getByLabel('Output value')).toHaveText('00:09:39.364');
+
+ // Return to workout calculator
+ await page.getByRole('link', { name: 'Back' }).click();
+ await page.getByRole('button', { name: 'Workout Calculator' }).click();
+
+ // Assert workout splits are correct (input race and prediction model not reset)
+ await expect(page.getByRole('row').nth(1)).toHaveText('400 m @ 1 mi' + '1:14.81');
+ await expect(page.getByRole('row').nth(3)).toHaveText('1600 m @ 1:00:00' + '5:53.56');
+ await expect(page.getByRole('row')).toHaveCount(5);
+ }
});
test('v1.4.1 Migration', async ({ page }) => {
- // Structure:
- // - Set v1.4.1 localStorage entries
- // - Reload app
- // - Assert the proper localStorage migrations were performed
- // - Assert UI options are up to date
-
- // Set general localStorage entries
await page.goto('/');
- await page.evaluate(() => localStorage.setItem('running-tools.default-unit-system',
- JSON.stringify('metric')));
-
- // Set batch calculator localStorage entries
- await page.evaluate(() => localStorage.setItem('running-tools.batch-calculator-input',
- JSON.stringify({
- distanceValue: 2,
- distanceUnit: 'miles',
- time: 630,
- })
- ));
- await page.evaluate(() => localStorage.setItem('running-tools.batch-calculator-options',
- JSON.stringify({
- calculator: 'race',
- increment: 10,
- rows: 15,
- })
- ));
-
- // Set pace calculator localStorage entries
- await page.evaluate(() => localStorage.setItem('running-tools.pace-calculator-input',
- JSON.stringify({
- distanceValue: 2,
- distanceUnit: 'miles',
- time: 930,
- })
- ));
- await page.evaluate(() => localStorage.setItem('running-tools.pace-calculator-target-set',
- JSON.stringify('123456789'))); // Property moved after v1.4.1
- await page.evaluate(() => localStorage.setItem('running-tools.pace-calculator-target-sets',
- JSON.stringify({
- _pace_targets: {
- name: 'Common Pace Targets',
- targets: [
- { type: 'distance', distanceValue: 100, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 200, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 300, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 400, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 600, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 800, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 1000, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 1200, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 1500, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 1, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 2, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 3, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 3200, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 2, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 4, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 3, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 6, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 8, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 5, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 6, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 10, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 8, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 10, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 0.5, distanceUnit: 'marathons' },
- { type: 'distance', distanceValue: 1, distanceUnit: 'marathons' },
- { type: 'time', time: 600 },
- { type: 'time', time: 1800 },
- { type: 'time', time: 3600 },
- ],
- },
- '123456789': {
- name: '800m Splits',
- targets: [
- { type: 'distance', distanceValue: 0.4, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 800, distanceUnit: 'meters' },
- { type: 'time', time: 600 },
- ],
- },
- })
- ));
-
- // Set race calculator localStorage entries
- await page.evaluate(() => localStorage.setItem('running-tools.race-calculator-input',
- JSON.stringify({
- distanceValue: 2,
- distanceUnit: 'miles',
- time: 630,
- })
- ));
- await page.evaluate(() => localStorage.setItem('running-tools.race-calculator-options',
- JSON.stringify({
- model: 'RiegelModel',
- riegelExponent: 1.06,
- })
- ));
- await page.evaluate(() => localStorage.setItem('running-tools.race-calculator-target-set',
- JSON.stringify('_race_targets'))); // Property moved after v1.4.1
- await page.evaluate(() => localStorage.setItem('running-tools.race-calculator-target-sets',
- JSON.stringify({
- _race_targets: {
- name: 'Common Race Targets',
- targets: [
- { type: 'distance', distanceValue: 400, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 800, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 1500, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 1, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 3000, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 3200, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 2, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 3, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 6, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 8, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 10, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 15, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 0.5, distanceUnit: 'marathons' },
- { type: 'distance', distanceValue: 1, distanceUnit: 'marathons' },
- ],
- },
- })
- ));
-
- // Set split calculator localStorage entries
- await page.evaluate(() => localStorage.setItem('running-tools.split-calculator-target-set',
- JSON.stringify('_split_targets'))); // Property moved after v1.4.1
- await page.evaluate(() => localStorage.setItem('running-tools.split-calculator-target-sets',
- JSON.stringify({
- _split_targets: {
- name: '5K 1600m Splits',
- targets: [
- { type: 'distance', distanceValue: 1.6, distanceUnit: 'kilometers', splitTime: 420 },
- { type: 'distance', distanceValue: 3.2, distanceUnit: 'kilometers', splitTime: 390 },
- { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers', splitTime: 390 },
- ],
- },
- })
- ));
-
- // Set unit calculator localStorage entries
- await page.evaluate(() => localStorage.setItem('running-tools.unit-calculator-category',
- JSON.stringify('speed_and_pace')));
- await page.evaluate(() => localStorage.setItem('running-tools.unit-calculator-inputs',
- JSON.stringify({
- distance: {
- inputValue: 1,
- inputUnit: 'miles',
- outputUnit: 'kilometers',
- },
- time: {
- inputValue: 1,
- inputUnit: 'seconds',
- outputUnit: 'hh:mm:ss',
- },
- speed_and_pace: {
- inputValue: 10,
- inputUnit: 'kilometers_per_hour',
- outputUnit: 'seconds_per_mile',
- },
- })
- ));
-
- // Set workout calculator localStorage entries
- await page.evaluate(() => localStorage.setItem('running-tools.workout-calculator-input',
- JSON.stringify({
- distanceValue: 1,
- distanceUnit: 'miles',
- time: 301,
- })
- ));
- await page.evaluate(() => localStorage.setItem('running-tools.workout-calculator-options',
- JSON.stringify({
- // customTargetNames property added after v1.4.1
- model: 'VO2MaxModel',
- riegelExponent: 1.06,
- })
- ));
- await page.evaluate(() => localStorage.setItem('running-tools.workout-calculator-target-set',
- JSON.stringify('_workout_targets'))); // Property moved after v1.4.1
- await page.evaluate(() => localStorage.setItem('running-tools.workout-calculator-target-sets',
- JSON.stringify({
- _workout_targets: {
- name: 'Common Workout Targets',
- targets: [
- {
- splitValue: 400, splitUnit: 'meters',
- type: 'distance', distanceValue: 1, distanceUnit: 'miles',
- },
- {
- splitValue: 800, splitUnit: 'meters',
- type: 'distance', distanceValue: 5, distanceUnit: 'kilometers',
- },
- {
- splitValue: 1600, splitUnit: 'meters',
- type: 'time', time: 3600,
- },
- {
- splitValue: 1, splitUnit: 'miles',
- type: 'distance', distanceValue: 1, distanceUnit: 'marathons',
- },
- ],
- },
- })
- ));
-
- // Reload the app and assert general localStorage entries are correct
- await page.goto('/');
- expect(await page.evaluate(() => localStorage.length)).toEqual(16);
- expect(await page.evaluate(() => localStorage.getItem('running-tools.default-unit-system')))
- .toEqual(JSON.stringify('metric'));
-
- // Assert localStorage entries for the batch calculator are correct
- expect(await page.evaluate(() =>
- localStorage.getItem('running-tools.batch-calculator-input'))).toEqual(JSON.stringify({
- distanceValue: 2,
- distanceUnit: 'miles',
- time: 630,
- }));
- expect(await page.evaluate(() =>
- localStorage.getItem('running-tools.batch-calculator-options'))).toEqual(JSON.stringify({
- calculator: 'race',
- increment: 10,
- rows: 15,
- }));
-
- // Assert localStorage entries for the pace calculator are correct
- expect(await page.evaluate(() =>
- localStorage.getItem('running-tools.pace-calculator-input'))).toEqual(JSON.stringify({
- distanceValue: 2,
- distanceUnit: 'miles',
- time: 930,
- }));
- expect(await page.evaluate(() => localStorage.getItem('running-tools.pace-calculator-options')))
- .toEqual(JSON.stringify({
- selectedTargetSet: '123456789',
- }));
- expect(await page.evaluate(() =>
- localStorage.getItem('running-tools.pace-calculator-target-set'))).toBeNull();
- expect(await page.evaluate(() =>
- localStorage.getItem('running-tools.pace-calculator-target-sets'))).toEqual(JSON.stringify({
- _pace_targets: {
- name: 'Common Pace Targets',
- targets: [
- { type: 'distance', distanceValue: 100, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 200, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 300, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 400, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 600, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 800, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 1000, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 1200, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 1500, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 1, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 2, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 3, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 3200, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 2, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 4, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 3, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 6, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 8, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 5, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 6, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 10, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 8, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 10, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 0.5, distanceUnit: 'marathons' },
- { type: 'distance', distanceValue: 1, distanceUnit: 'marathons' },
- { type: 'time', time: 600 },
- { type: 'time', time: 1800 },
- { type: 'time', time: 3600 },
- ],
- },
- '123456789': {
- name: '800m Splits',
- targets: [
- { type: 'distance', distanceValue: 0.4, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 800, distanceUnit: 'meters' },
- { type: 'time', time: 600 },
- ],
- },
- }));
-
- // Assert localStorage entries for the race calculator are correct
- expect(await page.evaluate(() =>
- localStorage.getItem('running-tools.race-calculator-input'))).toEqual(JSON.stringify({
- distanceValue: 2,
- distanceUnit: 'miles',
- time: 630,
- }));
- expect(await page.evaluate(() => localStorage.getItem('running-tools.race-calculator-options')))
- .toEqual(JSON.stringify({
- model: 'RiegelModel',
- riegelExponent: 1.06,
- selectedTargetSet: '_race_targets',
- }));
- expect(await page.evaluate(() =>
- localStorage.getItem('running-tools.race-calculator-target-set'))).toBeNull();
- expect(await page.evaluate(() =>
- localStorage.getItem('running-tools.race-calculator-target-sets'))).toEqual(JSON.stringify({
- _race_targets: {
- name: 'Common Race Targets',
- targets: [
- { type: 'distance', distanceValue: 400, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 800, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 1500, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 1, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 3000, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 3200, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 2, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 3, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 6, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 8, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 10, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 15, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 0.5, distanceUnit: 'marathons' },
- { type: 'distance', distanceValue: 1, distanceUnit: 'marathons' },
- ],
- },
- }));
-
- // Assert localStorage entries for the split calculator are correct
- expect(await page.evaluate(() => localStorage.getItem('running-tools.split-calculator-options')))
- .toEqual(JSON.stringify({
- selectedTargetSet: '_split_targets',
- }));
- expect(await page.evaluate(() =>
- localStorage.getItem('running-tools.split-calculator-target-sets'))).toEqual(JSON.stringify({
- _split_targets: {
- name: '5K 1600m Splits',
- targets: [
- { type: 'distance', distanceValue: 1.6, distanceUnit: 'kilometers', splitTime: 420 },
- { type: 'distance', distanceValue: 3.2, distanceUnit: 'kilometers', splitTime: 390 },
- { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers', splitTime: 390 },
- ],
- },
- }));
- expect(await page.evaluate(() =>
- localStorage.getItem('running-tools.split-calculator-target-set'))).toBeNull();
-
- // Assert localStorage entries for the unit calculator are correct
- expect(await page.evaluate(() => localStorage.getItem('running-tools.unit-calculator-category')))
- .toEqual(JSON.stringify('speed_and_pace'));
- expect(await page.evaluate(() =>
- localStorage.getItem('running-tools.unit-calculator-inputs'))).toEqual(JSON.stringify({
- distance: {
- inputValue: 1,
- inputUnit: 'miles',
- outputUnit: 'kilometers',
- },
- time: {
- inputValue: 1,
- inputUnit: 'seconds',
- outputUnit: 'hh:mm:ss',
- },
- speed_and_pace: {
- inputValue: 10,
- inputUnit: 'kilometers_per_hour',
- outputUnit: 'seconds_per_mile',
- },
- }));
-
- // Assert localStorage entries for the workout calculator are correct
- expect(await page.evaluate(() =>
- localStorage.getItem('running-tools.workout-calculator-input'))).toEqual(JSON.stringify({
- distanceValue: 1,
- distanceUnit: 'miles',
- time: 301,
- }));
- expect(await page.evaluate(() =>
- localStorage.getItem('running-tools.workout-calculator-options'))).toEqual(JSON.stringify({
- model: 'VO2MaxModel',
- riegelExponent: 1.06,
- selectedTargetSet: '_workout_targets',
- customTargetNames: false,
- }));
- expect(await page.evaluate(() =>
- localStorage.getItem('running-tools.workout-calculator-target-set'))).toBeNull();
- expect(await page.evaluate(() =>
- localStorage.getItem('running-tools.workout-calculator-target-sets'))).toEqual(JSON.stringify({
- _workout_targets: {
- name: 'Common Workout Targets',
- targets: [
- {
- splitValue: 400, splitUnit: 'meters',
- type: 'distance', distanceValue: 1, distanceUnit: 'miles',
- },
- {
- splitValue: 800, splitUnit: 'meters',
- type: 'distance', distanceValue: 5, distanceUnit: 'kilometers',
- },
- {
- splitValue: 1600, splitUnit: 'meters',
- type: 'time', time: 3600,
- },
- {
- splitValue: 1, splitUnit: 'miles',
- type: 'distance', distanceValue: 1, distanceUnit: 'marathons',
- },
- ],
- },
- }));
-
- // Assert pace results are correct (inputs and options not reset)
- await page.getByRole('button', { name: 'Batch Calculator' }).click();
- await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi');
- await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m');
- await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(17);
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30');
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:24');
- await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(17);
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('2:56');
- await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(17);
- await expect(page.getByRole('row')).toHaveCount(16);
-
- // Assert pace results are correct (inputs and options not reset, new pace targets loaded)
- await page.getByLabel('Calculator').selectOption('Pace Calculator');
- await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi');
- await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m');
- await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(4);
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30');
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:37');
- await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(4);
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:11');
- await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(4);
- await expect(page.getByRole('row')).toHaveCount(16);
-
- // Assert workout results are correct (new workout options loaded)
- await page.getByLabel('Calculator').selectOption('Workout Calculator');
- await expect(page.getByLabel('Target name customization')).toHaveValue('false');
- await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi');
- await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m @ 5 km');
- await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(5);
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30');
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:42');
- await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(5);
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
- await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:17');
- await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(5);
- await expect(page.getByRole('row')).toHaveCount(16);
-
- // Reset selected calculator
- await page.getByLabel('Calculator').selectOption('Race Calculator');
-
- // Return to pace calculator
- await page.getByRole('link', { name: 'Back' }).click();
- await page.getByRole('button', { name: 'Pace Calculator' }).click();
-
- // Assert paces are correct (input pace not reset)
- await expect(page.getByRole('row').nth(1)).toHaveText('0.4 km' + '1:55.58');
- await expect(page.getByRole('row').nth(2)).toHaveText('800 m' + '3:51.15');
- await expect(page.getByRole('row').nth(3)).toHaveText('2.08 km' + '10:00');
- await expect(page.getByRole('row')).toHaveCount(4);
-
- // Return to race calculator
- await page.getByRole('link', { name: 'Back' }).click();
- await page.getByRole('button', { name: 'Race Calculator' }).click();
-
- // Assert race predictions are correct (input race not resset and new prediction model loaded)
- await expect(page.getByRole('row').nth(5)).toHaveText('1 mi' + '5:02.17' + '3:08 / km');
- await expect(page.getByRole('row').nth(10)).toHaveText('5 km' + '16:44.87' + '3:21 / km');
- await expect(page.getByRole('row')).toHaveCount(17);
-
- // Return to split calculator
- await page.getByRole('link', { name: 'Back' }).click();
- await page.getByRole('button', { name: 'Split Calculator' }).click();
-
- // Assert times and paces are correct (split times not reset)
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('7:00.00');
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('4:23 / km');
- await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('13:30.00');
- await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('4:04 / km');
- await expect(page.getByRole('row').nth(3).getByRole('cell').nth(1)).toHaveText('20:00.00');
- await expect(page.getByRole('row').nth(3).getByRole('cell').nth(3)).toHaveText('3:37 / km');
- await expect(page.getByRole('row')).toHaveCount(4);
-
- // Return to unit calculator
- await page.getByRole('link', { name: 'Back' }).click();
- await page.getByRole('button', { name: 'Unit Calculator' }).click();
-
- // Assert result is correct (state not reset)
- await expect(page.getByLabel('Output value')).toHaveText('00:09:39.364');
-
- // Return to workout calculator
- await page.getByRole('link', { name: 'Back' }).click();
- await page.getByRole('button', { name: 'Workout Calculator' }).click();
-
- // Assert workout splits are correct (input race and prediction model not reset)
- await expect(page.getByRole('row').nth(1)).toHaveText('400 m @ 1 mi' + '1:14.81');
- await expect(page.getByRole('row').nth(3)).toHaveText('1600 m @ 1:00:00' + '5:53.56');
- await expect(page.getByRole('row')).toHaveCount(5);
+
+ // Set v1.4.1 localStorage entries
+ {
+ // Set general localStorage entries
+ await page.evaluate(() => localStorage.setItem('running-tools.default-unit-system',
+ JSON.stringify('metric')));
+
+ // Set batch calculator localStorage entries
+ await page.evaluate(() => localStorage.setItem('running-tools.batch-calculator-input',
+ JSON.stringify({
+ distanceValue: 2,
+ distanceUnit: 'miles',
+ time: 630,
+ })
+ ));
+ await page.evaluate(() => localStorage.setItem('running-tools.batch-calculator-options',
+ JSON.stringify({
+ calculator: 'race',
+ increment: 10,
+ rows: 15,
+ })
+ ));
+
+ // Set pace calculator localStorage entries
+ await page.evaluate(() => localStorage.setItem('running-tools.pace-calculator-input',
+ JSON.stringify({
+ distanceValue: 2,
+ distanceUnit: 'miles',
+ time: 930,
+ })
+ ));
+ await page.evaluate(() => localStorage.setItem('running-tools.pace-calculator-target-set',
+ JSON.stringify('123456789'))); // Property moved after v1.4.1
+ await page.evaluate(() => localStorage.setItem('running-tools.pace-calculator-target-sets',
+ JSON.stringify({
+ _pace_targets: {
+ name: 'Common Pace Targets',
+ targets: [
+ { type: 'distance', distanceValue: 100, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 200, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 300, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 400, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 600, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 800, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 1000, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 1200, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 1500, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 1, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 2, distanceUnit: 'kilometers' },
+ { type: 'distance', distanceValue: 3, distanceUnit: 'kilometers' },
+ { type: 'distance', distanceValue: 3200, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 2, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 4, distanceUnit: 'kilometers' },
+ { type: 'distance', distanceValue: 3, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' },
+ { type: 'distance', distanceValue: 6, distanceUnit: 'kilometers' },
+ { type: 'distance', distanceValue: 8, distanceUnit: 'kilometers' },
+ { type: 'distance', distanceValue: 5, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 6, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 10, distanceUnit: 'kilometers' },
+ { type: 'distance', distanceValue: 8, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 10, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 0.5, distanceUnit: 'marathons' },
+ { type: 'distance', distanceValue: 1, distanceUnit: 'marathons' },
+ { type: 'time', time: 600 },
+ { type: 'time', time: 1800 },
+ { type: 'time', time: 3600 },
+ ],
+ },
+ '123456789': {
+ name: '800m Splits',
+ targets: [
+ { type: 'distance', distanceValue: 0.4, distanceUnit: 'kilometers' },
+ { type: 'distance', distanceValue: 800, distanceUnit: 'meters' },
+ { type: 'time', time: 600 },
+ ],
+ },
+ })
+ ));
+
+ // Set race calculator localStorage entries
+ await page.evaluate(() => localStorage.setItem('running-tools.race-calculator-input',
+ JSON.stringify({
+ distanceValue: 2,
+ distanceUnit: 'miles',
+ time: 630,
+ })
+ ));
+ await page.evaluate(() => localStorage.setItem('running-tools.race-calculator-options',
+ JSON.stringify({
+ model: 'RiegelModel',
+ riegelExponent: 1.06,
+ })
+ ));
+ await page.evaluate(() => localStorage.setItem('running-tools.race-calculator-target-set',
+ JSON.stringify('_race_targets'))); // Property moved after v1.4.1
+ await page.evaluate(() => localStorage.setItem('running-tools.race-calculator-target-sets',
+ JSON.stringify({
+ _race_targets: {
+ name: 'Common Race Targets',
+ targets: [
+ { type: 'distance', distanceValue: 400, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 800, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 1500, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 1, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 3000, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 3200, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 2, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 3, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' },
+ { type: 'distance', distanceValue: 6, distanceUnit: 'kilometers' },
+ { type: 'distance', distanceValue: 8, distanceUnit: 'kilometers' },
+ { type: 'distance', distanceValue: 10, distanceUnit: 'kilometers' },
+ { type: 'distance', distanceValue: 15, distanceUnit: 'kilometers' },
+ { type: 'distance', distanceValue: 0.5, distanceUnit: 'marathons' },
+ { type: 'distance', distanceValue: 1, distanceUnit: 'marathons' },
+ ],
+ },
+ })
+ ));
+
+ // Set split calculator localStorage entries
+ await page.evaluate(() => localStorage.setItem('running-tools.split-calculator-target-set',
+ JSON.stringify('_split_targets'))); // Property moved after v1.4.1
+ await page.evaluate(() => localStorage.setItem('running-tools.split-calculator-target-sets',
+ JSON.stringify({
+ _split_targets: {
+ name: '5K 1600m Splits',
+ targets: [
+ { type: 'distance', distanceValue: 1.6, distanceUnit: 'kilometers', splitTime: 420 },
+ { type: 'distance', distanceValue: 3.2, distanceUnit: 'kilometers', splitTime: 390 },
+ { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers', splitTime: 390 },
+ ],
+ },
+ })
+ ));
+
+ // Set unit calculator localStorage entries
+ await page.evaluate(() => localStorage.setItem('running-tools.unit-calculator-category',
+ JSON.stringify('speed_and_pace')));
+ await page.evaluate(() => localStorage.setItem('running-tools.unit-calculator-inputs',
+ JSON.stringify({
+ distance: {
+ inputValue: 1,
+ inputUnit: 'miles',
+ outputUnit: 'kilometers',
+ },
+ time: {
+ inputValue: 1,
+ inputUnit: 'seconds',
+ outputUnit: 'hh:mm:ss',
+ },
+ speed_and_pace: {
+ inputValue: 10,
+ inputUnit: 'kilometers_per_hour',
+ outputUnit: 'seconds_per_mile',
+ },
+ })
+ ));
+
+ // Set workout calculator localStorage entries
+ await page.evaluate(() => localStorage.setItem('running-tools.workout-calculator-input',
+ JSON.stringify({
+ distanceValue: 1,
+ distanceUnit: 'miles',
+ time: 301,
+ })
+ ));
+ await page.evaluate(() => localStorage.setItem('running-tools.workout-calculator-options',
+ JSON.stringify({
+ // customTargetNames property added after v1.4.1
+ model: 'VO2MaxModel',
+ riegelExponent: 1.06,
+ })
+ ));
+ await page.evaluate(() => localStorage.setItem('running-tools.workout-calculator-target-set',
+ JSON.stringify('_workout_targets'))); // Property moved after v1.4.1
+ await page.evaluate(() => localStorage.setItem('running-tools.workout-calculator-target-sets',
+ JSON.stringify({
+ _workout_targets: {
+ name: 'Common Workout Targets',
+ targets: [
+ {
+ splitValue: 400, splitUnit: 'meters',
+ type: 'distance', distanceValue: 1, distanceUnit: 'miles',
+ },
+ {
+ splitValue: 800, splitUnit: 'meters',
+ type: 'distance', distanceValue: 5, distanceUnit: 'kilometers',
+ },
+ {
+ splitValue: 1600, splitUnit: 'meters',
+ type: 'time', time: 3600,
+ },
+ {
+ splitValue: 1, splitUnit: 'miles',
+ type: 'distance', distanceValue: 1, distanceUnit: 'marathons',
+ },
+ ],
+ },
+ })
+ ));
+ }
+
+ // Reload app and assert the proper localStorage migrations were performed
+ {
+ // Reload the app and assert general localStorage entries are correct
+ await page.goto('/');
+ expect(await page.evaluate(() => localStorage.length)).toEqual(16);
+ expect(await page.evaluate(() => localStorage.getItem('running-tools.default-unit-system')))
+ .toEqual(JSON.stringify('metric'));
+
+ // Assert localStorage entries for the batch calculator are correct
+ expect(await page.evaluate(() =>
+ localStorage.getItem('running-tools.batch-calculator-input'))).toEqual(JSON.stringify({
+ distanceValue: 2,
+ distanceUnit: 'miles',
+ time: 630,
+ }));
+ expect(await page.evaluate(() =>
+ localStorage.getItem('running-tools.batch-calculator-options'))).toEqual(JSON.stringify({
+ calculator: 'race',
+ increment: 10,
+ rows: 15,
+ }));
+
+ // Assert localStorage entries for the pace calculator are correct
+ expect(await page.evaluate(() =>
+ localStorage.getItem('running-tools.pace-calculator-input'))).toEqual(JSON.stringify({
+ distanceValue: 2,
+ distanceUnit: 'miles',
+ time: 930,
+ }));
+ expect(await page.evaluate(() => localStorage.getItem('running-tools.pace-calculator-options')))
+ .toEqual(JSON.stringify({
+ selectedTargetSet: '123456789',
+ }));
+ expect(await page.evaluate(() =>
+ localStorage.getItem('running-tools.pace-calculator-target-set'))).toBeNull();
+ expect(await page.evaluate(() =>
+ localStorage.getItem('running-tools.pace-calculator-target-sets'))).toEqual(JSON.stringify({
+ _pace_targets: {
+ name: 'Common Pace Targets',
+ targets: [
+ { type: 'distance', distanceValue: 100, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 200, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 300, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 400, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 600, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 800, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 1000, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 1200, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 1500, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 1, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 2, distanceUnit: 'kilometers' },
+ { type: 'distance', distanceValue: 3, distanceUnit: 'kilometers' },
+ { type: 'distance', distanceValue: 3200, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 2, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 4, distanceUnit: 'kilometers' },
+ { type: 'distance', distanceValue: 3, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' },
+ { type: 'distance', distanceValue: 6, distanceUnit: 'kilometers' },
+ { type: 'distance', distanceValue: 8, distanceUnit: 'kilometers' },
+ { type: 'distance', distanceValue: 5, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 6, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 10, distanceUnit: 'kilometers' },
+ { type: 'distance', distanceValue: 8, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 10, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 0.5, distanceUnit: 'marathons' },
+ { type: 'distance', distanceValue: 1, distanceUnit: 'marathons' },
+ { type: 'time', time: 600 },
+ { type: 'time', time: 1800 },
+ { type: 'time', time: 3600 },
+ ],
+ },
+ '123456789': {
+ name: '800m Splits',
+ targets: [
+ { type: 'distance', distanceValue: 0.4, distanceUnit: 'kilometers' },
+ { type: 'distance', distanceValue: 800, distanceUnit: 'meters' },
+ { type: 'time', time: 600 },
+ ],
+ },
+ }));
+
+ // Assert localStorage entries for the race calculator are correct
+ expect(await page.evaluate(() =>
+ localStorage.getItem('running-tools.race-calculator-input'))).toEqual(JSON.stringify({
+ distanceValue: 2,
+ distanceUnit: 'miles',
+ time: 630,
+ }));
+ expect(await page.evaluate(() => localStorage.getItem('running-tools.race-calculator-options')))
+ .toEqual(JSON.stringify({
+ model: 'RiegelModel',
+ riegelExponent: 1.06,
+ selectedTargetSet: '_race_targets',
+ }));
+ expect(await page.evaluate(() =>
+ localStorage.getItem('running-tools.race-calculator-target-set'))).toBeNull();
+ expect(await page.evaluate(() =>
+ localStorage.getItem('running-tools.race-calculator-target-sets'))).toEqual(JSON.stringify({
+ _race_targets: {
+ name: 'Common Race Targets',
+ targets: [
+ { type: 'distance', distanceValue: 400, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 800, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 1500, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 1, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 3000, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 3200, distanceUnit: 'meters' },
+ { type: 'distance', distanceValue: 2, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 3, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' },
+ { type: 'distance', distanceValue: 6, distanceUnit: 'kilometers' },
+ { type: 'distance', distanceValue: 8, distanceUnit: 'kilometers' },
+ { type: 'distance', distanceValue: 10, distanceUnit: 'kilometers' },
+ { type: 'distance', distanceValue: 15, distanceUnit: 'kilometers' },
+ { type: 'distance', distanceValue: 0.5, distanceUnit: 'marathons' },
+ { type: 'distance', distanceValue: 1, distanceUnit: 'marathons' },
+ ],
+ },
+ }));
+
+ // Assert localStorage entries for the split calculator are correct
+ expect(await page.evaluate(() => localStorage.getItem('running-tools.split-calculator-options')))
+ .toEqual(JSON.stringify({
+ selectedTargetSet: '_split_targets',
+ }));
+ expect(await page.evaluate(() =>
+ localStorage.getItem('running-tools.split-calculator-target-sets'))).toEqual(JSON.stringify({
+ _split_targets: {
+ name: '5K 1600m Splits',
+ targets: [
+ { type: 'distance', distanceValue: 1.6, distanceUnit: 'kilometers', splitTime: 420 },
+ { type: 'distance', distanceValue: 3.2, distanceUnit: 'kilometers', splitTime: 390 },
+ { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers', splitTime: 390 },
+ ],
+ },
+ }));
+ expect(await page.evaluate(() =>
+ localStorage.getItem('running-tools.split-calculator-target-set'))).toBeNull();
+
+ // Assert localStorage entries for the unit calculator are correct
+ expect(await page.evaluate(() => localStorage.getItem('running-tools.unit-calculator-category')))
+ .toEqual(JSON.stringify('speed_and_pace'));
+ expect(await page.evaluate(() =>
+ localStorage.getItem('running-tools.unit-calculator-inputs'))).toEqual(JSON.stringify({
+ distance: {
+ inputValue: 1,
+ inputUnit: 'miles',
+ outputUnit: 'kilometers',
+ },
+ time: {
+ inputValue: 1,
+ inputUnit: 'seconds',
+ outputUnit: 'hh:mm:ss',
+ },
+ speed_and_pace: {
+ inputValue: 10,
+ inputUnit: 'kilometers_per_hour',
+ outputUnit: 'seconds_per_mile',
+ },
+ }));
+
+ // Assert localStorage entries for the workout calculator are correct
+ expect(await page.evaluate(() =>
+ localStorage.getItem('running-tools.workout-calculator-input'))).toEqual(JSON.stringify({
+ distanceValue: 1,
+ distanceUnit: 'miles',
+ time: 301,
+ }));
+ expect(await page.evaluate(() =>
+ localStorage.getItem('running-tools.workout-calculator-options'))).toEqual(JSON.stringify({
+ model: 'VO2MaxModel',
+ riegelExponent: 1.06,
+ selectedTargetSet: '_workout_targets',
+ customTargetNames: false,
+ }));
+ expect(await page.evaluate(() =>
+ localStorage.getItem('running-tools.workout-calculator-target-set'))).toBeNull();
+ expect(await page.evaluate(() =>
+ localStorage.getItem('running-tools.workout-calculator-target-sets'))).toEqual(JSON.stringify({
+ _workout_targets: {
+ name: 'Common Workout Targets',
+ targets: [
+ {
+ splitValue: 400, splitUnit: 'meters',
+ type: 'distance', distanceValue: 1, distanceUnit: 'miles',
+ },
+ {
+ splitValue: 800, splitUnit: 'meters',
+ type: 'distance', distanceValue: 5, distanceUnit: 'kilometers',
+ },
+ {
+ splitValue: 1600, splitUnit: 'meters',
+ type: 'time', time: 3600,
+ },
+ {
+ splitValue: 1, splitUnit: 'miles',
+ type: 'distance', distanceValue: 1, distanceUnit: 'marathons',
+ },
+ ],
+ },
+ }));
+ }
+
+ // Assert UI options are up to date
+ // Very similar to the previous "go back and assert the options are not resset" section
+ {
+ // Assert pace results are correct (inputs and options not reset)
+ await page.getByRole('button', { name: 'Batch Calculator' }).click();
+ await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi');
+ await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m');
+ await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(17);
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30');
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:24');
+ await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(17);
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('2:56');
+ await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(17);
+ await expect(page.getByRole('row')).toHaveCount(16);
+
+ // Assert pace results are correct (inputs and options not reset, new pace targets loaded)
+ await page.getByLabel('Calculator').selectOption('Pace Calculator');
+ await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi');
+ await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m');
+ await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(4);
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30');
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:37');
+ await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(4);
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:11');
+ await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(4);
+ await expect(page.getByRole('row')).toHaveCount(16);
+
+ // Assert workout results are correct (new workout options loaded)
+ await page.getByLabel('Calculator').selectOption('Workout Calculator');
+ await expect(page.getByLabel('Target name customization')).toHaveValue('false');
+ await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi');
+ await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m @ 5 km');
+ await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(5);
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30');
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:42');
+ await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(5);
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50');
+ await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:17');
+ await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(5);
+ await expect(page.getByRole('row')).toHaveCount(16);
+
+ // Reset selected calculator
+ await page.getByLabel('Calculator').selectOption('Race Calculator');
+
+ // Return to pace calculator
+ await page.getByRole('link', { name: 'Back' }).click();
+ await page.getByRole('button', { name: 'Pace Calculator' }).click();
+
+ // Assert paces are correct (input pace not reset)
+ await expect(page.getByRole('row').nth(1)).toHaveText('0.4 km' + '1:55.58');
+ await expect(page.getByRole('row').nth(2)).toHaveText('800 m' + '3:51.15');
+ await expect(page.getByRole('row').nth(3)).toHaveText('2.08 km' + '10:00');
+ await expect(page.getByRole('row')).toHaveCount(4);
+
+ // Return to race calculator
+ await page.getByRole('link', { name: 'Back' }).click();
+ await page.getByRole('button', { name: 'Race Calculator' }).click();
+
+ // Assert race predictions are correct (input race not resset and new prediction model loaded)
+ await expect(page.getByRole('row').nth(5)).toHaveText('1 mi' + '5:02.17' + '3:08 / km');
+ await expect(page.getByRole('row').nth(10)).toHaveText('5 km' + '16:44.87' + '3:21 / km');
+ await expect(page.getByRole('row')).toHaveCount(17);
+
+ // Return to split calculator
+ await page.getByRole('link', { name: 'Back' }).click();
+ await page.getByRole('button', { name: 'Split Calculator' }).click();
+
+ // Assert times and paces are correct (split times not reset)
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('7:00.00');
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('4:23 / km');
+ await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('13:30.00');
+ await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('4:04 / km');
+ await expect(page.getByRole('row').nth(3).getByRole('cell').nth(1)).toHaveText('20:00.00');
+ await expect(page.getByRole('row').nth(3).getByRole('cell').nth(3)).toHaveText('3:37 / km');
+ await expect(page.getByRole('row')).toHaveCount(4);
+
+ // Return to unit calculator
+ await page.getByRole('link', { name: 'Back' }).click();
+ await page.getByRole('button', { name: 'Unit Calculator' }).click();
+
+ // Assert result is correct (state not reset)
+ await expect(page.getByLabel('Output value')).toHaveText('00:09:39.364');
+
+ // Return to workout calculator
+ await page.getByRole('link', { name: 'Back' }).click();
+ await page.getByRole('button', { name: 'Workout Calculator' }).click();
+
+ // Assert workout splits are correct (input race and prediction model not reset)
+ await expect(page.getByRole('row').nth(1)).toHaveText('400 m @ 1 mi' + '1:14.81');
+ await expect(page.getByRole('row').nth(3)).toHaveText('1600 m @ 1:00:00' + '5:53.56');
+ await expect(page.getByRole('row')).toHaveCount(5);
+ }
});
diff --git a/tests/e2e/pace-calculator.spec.js b/tests/e2e/pace-calculator.spec.js
@@ -1,128 +1,134 @@
import { test, expect } from '@playwright/test';
test('Pace Calculator', async ({ page }) => {
- // Structure:
- // - Test standard pace results
- // - Test different default units
- // - Test modified default target set
- // - Test custom target set
- // - Reload page
- // - Assert outputs are still the same
- // - Test target set deletion and reversion
-
// Go to pace calculator
await page.goto('/');
await page.getByRole('button', { name: 'Pace Calculator' }).click();
await expect(page).toHaveTitle('Pace Calculator - Running Tools');
- // Enter input pace (2 mi in 15:30)
- await page.getByLabel('Input distance value').fill('2');
- await page.getByLabel('Input distance unit').selectOption('Miles');
- await page.getByLabel('Input duration hours').fill('0');
- await page.getByLabel('Input duration minutes').fill('15');
- await page.getByLabel('Input duration seconds').fill('30');
-
- // Assert paces are correct
- await expect(page.getByRole('row').nth(11)).toHaveText('1 mi' + '7:45.00');
- await expect(page.getByRole('row').nth(13)).toHaveText('1.29 mi' + '10:00');
- await expect(page.getByRole('row')).toHaveCount(31);
-
- // Change default units
- await page.getByText('Advanced Options').click();
- await page.getByLabel('Default units').selectOption('Kilometers');
-
- // Assert paces are correct
- await expect(page.getByRole('row').nth(11)).toHaveText('1 mi' + '7:45.00');
- await expect(page.getByRole('row').nth(13)).toHaveText('2.08 km' + '10:00');
- await expect(page.getByRole('row')).toHaveCount(31);
-
- // Edit default target set
- await page.getByRole('button', { name: 'Edit target set' }).click();
- await page.getByLabel('Target set label').fill('Less-common Pace Targets');
- await page.getByLabel('Target distance value').nth(10).fill('1.01');
- await page.getByLabel('Target distance unit').nth(10).selectOption('Miles');
- await page.getByLabel('Target duration second').nth(0).fill('1');
- await page.getByRole('button', { name: 'Add distance target' }).click();
- await page.getByLabel('Target distance value').last().fill('1.5');
- await page.getByLabel('Target distance unit').last().selectOption('Miles');
- await page.getByRole('button', { name: 'Add time target' }).click();
- await page.getByLabel('Target duration minutes').last().fill('19');
- await page.getByLabel('Target duration seconds').last().fill('0');
- await page.getByRole('button', { name: 'Close' }).click();
-
- // Assert paces are correct
- await expect(page.getByRole('row').nth(11)).toHaveText('1.01 mi' + '7:49.65');
- await expect(page.getByRole('row').nth(13)).toHaveText('2.08 km' + '10:01');
- await expect(page.getByRole('row').nth(14)).toHaveText('1.5 mi' + '11:37.50');
- await expect(page.getByRole('row').nth(18)).toHaveText('3.95 km' + '19:00');
- await expect(page.getByRole('row')).toHaveCount(33);
-
- // Create custom target set
- await page.getByLabel('Selected target set').selectOption('[ Create New Target Set ]');
- await expect(page.getByRole('row').nth(4)).toHaveText('There aren\'t any targets in this set yet.');
- await expect(page.getByRole('row')).toHaveCount(5);
-
- // Edit new target set
- await expect(page.getByRole('row').nth(1)).toHaveText('There aren\'t any targets in this set yet.');
- await expect(page.getByLabel('Target set label')).toHaveValue('New target set');
- await page.getByLabel('Target set label').fill('800m Splits');
- await page.getByRole('button', { name: 'Add distance target' }).click();
- await page.getByLabel('Target distance value').nth(0).fill('0.4');
- await page.getByLabel('Target distance unit').nth(0).selectOption('Kilometers');
- await page.getByRole('button', { name: 'Add distance target' }).click();
- await page.getByLabel('Target distance value').nth(1).fill('800');
- await page.getByLabel('Target distance unit').nth(1).selectOption('Meters');
- await page.getByRole('button', { name: 'Close' }).click();
-
- // Assert paces are correct
- await expect(page.getByRole('row').nth(1)).toHaveText('0.4 km' + '1:55.58');
- await expect(page.getByRole('row').nth(2)).toHaveText('800 m' + '3:51.15');
- await expect(page.getByRole('row')).toHaveCount(3);
+ // Test standard pace results
+ {
+ // Enter input pace (2 mi in 15:30)
+ await page.getByLabel('Input distance value').fill('2');
+ await page.getByLabel('Input distance unit').selectOption('Miles');
+ await page.getByLabel('Input duration hours').fill('0');
+ await page.getByLabel('Input duration minutes').fill('15');
+ await page.getByLabel('Input duration seconds').fill('30');
+
+ // Assert paces are correct
+ await expect(page.getByRole('row').nth(11)).toHaveText('1 mi' + '7:45.00');
+ await expect(page.getByRole('row').nth(13)).toHaveText('1.29 mi' + '10:00');
+ await expect(page.getByRole('row')).toHaveCount(31);
+
+ // Change default units
+ await page.getByText('Advanced Options').click();
+ await page.getByLabel('Default units').selectOption('Kilometers');
+
+ // Assert paces are correct
+ await expect(page.getByRole('row').nth(11)).toHaveText('1 mi' + '7:45.00');
+ await expect(page.getByRole('row').nth(13)).toHaveText('2.08 km' + '10:00');
+ await expect(page.getByRole('row')).toHaveCount(31);
+ }
+
+ // Test modified default target set
+ {
+ // Edit default target set
+ await page.getByRole('button', { name: 'Edit target set' }).click();
+ await page.getByLabel('Target set label').fill('Less-common Pace Targets');
+ await page.getByLabel('Target distance value').nth(10).fill('1.01');
+ await page.getByLabel('Target distance unit').nth(10).selectOption('Miles');
+ await page.getByLabel('Target duration second').nth(0).fill('1');
+ await page.getByRole('button', { name: 'Add distance target' }).click();
+ await page.getByLabel('Target distance value').last().fill('1.5');
+ await page.getByLabel('Target distance unit').last().selectOption('Miles');
+ await page.getByRole('button', { name: 'Add time target' }).click();
+ await page.getByLabel('Target duration minutes').last().fill('19');
+ await page.getByLabel('Target duration seconds').last().fill('0');
+ await page.getByRole('button', { name: 'Close' }).click();
+
+ // Assert paces are correct
+ await expect(page.getByRole('row').nth(11)).toHaveText('1.01 mi' + '7:49.65');
+ await expect(page.getByRole('row').nth(13)).toHaveText('2.08 km' + '10:01');
+ await expect(page.getByRole('row').nth(14)).toHaveText('1.5 mi' + '11:37.50');
+ await expect(page.getByRole('row').nth(18)).toHaveText('3.95 km' + '19:00');
+ await expect(page.getByRole('row')).toHaveCount(33);
+ }
+
+ // Test custom target set
+ {
+ // Create custom target set
+ await page.getByLabel('Selected target set').selectOption('[ Create New Target Set ]');
+ await expect(page.getByRole('row').nth(4)).toHaveText('There aren\'t any targets in this set yet.');
+ await expect(page.getByRole('row')).toHaveCount(5);
+
+ // Edit new target set
+ await expect(page.getByRole('row').nth(1)).toHaveText('There aren\'t any targets in this set yet.');
+ await expect(page.getByLabel('Target set label')).toHaveValue('New target set');
+ await page.getByLabel('Target set label').fill('800m Splits');
+ await page.getByRole('button', { name: 'Add distance target' }).click();
+ await page.getByLabel('Target distance value').nth(0).fill('0.4');
+ await page.getByLabel('Target distance unit').nth(0).selectOption('Kilometers');
+ await page.getByRole('button', { name: 'Add distance target' }).click();
+ await page.getByLabel('Target distance value').nth(1).fill('800');
+ await page.getByLabel('Target distance unit').nth(1).selectOption('Meters');
+ await page.getByRole('button', { name: 'Close' }).click();
+
+ // Assert paces are correct
+ await expect(page.getByRole('row').nth(1)).toHaveText('0.4 km' + '1:55.58');
+ await expect(page.getByRole('row').nth(2)).toHaveText('800 m' + '3:51.15');
+ await expect(page.getByRole('row')).toHaveCount(3);
+ }
// Reload page
await page.reload();
- // Assert paces are correct (custom targets and default units not reset)
- await expect(page.getByRole('row').nth(1)).toHaveText('0.4 km' + '1:55.58');
- await expect(page.getByRole('row').nth(2)).toHaveText('800 m' + '3:51.15');
- await expect(page.getByRole('row')).toHaveCount(3);
-
- // Switch target set
- await page.getByText('Advanced Options').click();
- await page.getByLabel('Selected target set').selectOption('Less-common Pace Targets');
-
- // Assert paces are correct
- await expect(page.getByRole('row').nth(11)).toHaveText('1.01 mi' + '7:49.65');
- await expect(page.getByRole('row').nth(13)).toHaveText('2.08 km' + '10:01');
- await expect(page.getByRole('row').nth(14)).toHaveText('1.5 mi' + '11:37.50');
- await expect(page.getByRole('row').nth(18)).toHaveText('3.95 km' + '19:00');
- await expect(page.getByRole('row')).toHaveCount(33);
-
- // Delete custom target set
- await page.getByLabel('Selected target set').selectOption('800m Splits');
- await page.getByRole('button', { name: 'Edit target set' }).click();
- await expect(page.getByLabel('Target set label')).toHaveValue('800m Splits');
- await page.getByRole('button', { name: 'Delete target set' }).click();
-
- // Assert paces are correct (back to default target set)
- await expect(page.getByRole('row').nth(11)).toHaveText('1.01 mi' + '7:49.65');
- await expect(page.getByRole('row').nth(13)).toHaveText('2.08 km' + '10:01');
- await expect(page.getByRole('row').nth(14)).toHaveText('1.5 mi' + '11:37.50');
- await expect(page.getByRole('row').nth(18)).toHaveText('3.95 km' + '19:00');
- await expect(page.getByRole('row')).toHaveCount(33);
-
- // Revert target set
- await page.getByRole('button', { name: 'Edit target set' }).click();
- await expect(page.getByLabel('Target set label')).toHaveValue('Less-common Pace Targets');
- await page.getByRole('button', { name: 'Revert target set' }).click();
- await page.getByRole('button', { name: 'Close' }).click();
-
- // Assert paces are correct
- await expect(page.getByRole('row').nth(11)).toHaveText('1 mi' + '7:45.00');
- await expect(page.getByRole('row').nth(13)).toHaveText('2.08 km' + '10:00');
- await expect(page.getByRole('row')).toHaveCount(31);
-
- // Assert title was reset
- await page.getByRole('button', { name: 'Edit target set' }).click();
- await expect(page.getByLabel('Target set label')).toHaveValue('Common Pace Targets');
+ // Assert outputs are still the same
+ {
+ // Assert paces are correct (custom targets and default units not reset)
+ await expect(page.getByRole('row').nth(1)).toHaveText('0.4 km' + '1:55.58');
+ await expect(page.getByRole('row').nth(2)).toHaveText('800 m' + '3:51.15');
+ await expect(page.getByRole('row')).toHaveCount(3);
+
+ // Switch target set
+ await page.getByText('Advanced Options').click();
+ await page.getByLabel('Selected target set').selectOption('Less-common Pace Targets');
+
+ // Assert paces are correct
+ await expect(page.getByRole('row').nth(11)).toHaveText('1.01 mi' + '7:49.65');
+ await expect(page.getByRole('row').nth(13)).toHaveText('2.08 km' + '10:01');
+ await expect(page.getByRole('row').nth(14)).toHaveText('1.5 mi' + '11:37.50');
+ await expect(page.getByRole('row').nth(18)).toHaveText('3.95 km' + '19:00');
+ await expect(page.getByRole('row')).toHaveCount(33);
+ }
+
+ // Test target set deletion and reversion
+ {
+ // Delete custom target set
+ await page.getByLabel('Selected target set').selectOption('800m Splits');
+ await page.getByRole('button', { name: 'Edit target set' }).click();
+ await expect(page.getByLabel('Target set label')).toHaveValue('800m Splits');
+ await page.getByRole('button', { name: 'Delete target set' }).click();
+
+ // Assert paces are correct (back to default target set)
+ await expect(page.getByRole('row').nth(11)).toHaveText('1.01 mi' + '7:49.65');
+ await expect(page.getByRole('row').nth(13)).toHaveText('2.08 km' + '10:01');
+ await expect(page.getByRole('row').nth(14)).toHaveText('1.5 mi' + '11:37.50');
+ await expect(page.getByRole('row').nth(18)).toHaveText('3.95 km' + '19:00');
+ await expect(page.getByRole('row')).toHaveCount(33);
+
+ // Revert target set
+ await page.getByRole('button', { name: 'Edit target set' }).click();
+ await expect(page.getByLabel('Target set label')).toHaveValue('Less-common Pace Targets');
+ await page.getByRole('button', { name: 'Revert target set' }).click();
+ await page.getByRole('button', { name: 'Close' }).click();
+
+ // Assert paces are correct
+ await expect(page.getByRole('row').nth(11)).toHaveText('1 mi' + '7:45.00');
+ await expect(page.getByRole('row').nth(13)).toHaveText('2.08 km' + '10:00');
+ await expect(page.getByRole('row')).toHaveCount(31);
+
+ // Assert title was reset
+ await page.getByRole('button', { name: 'Edit target set' }).click();
+ await expect(page.getByLabel('Target set label')).toHaveValue('Common Pace Targets');
+ }
});
diff --git a/tests/e2e/race-calculator.spec.js b/tests/e2e/race-calculator.spec.js
@@ -1,146 +1,154 @@
import { test, expect } from '@playwright/test';
test('Race Calculator', async ({ page }) => {
- // Structure:
- // - Test standard race results
- // - Test different default units
- // - Test different prediction options
- // - Test modified default target set
- // - Test custom target set
- // - Reload page
- // - Assert outputs are still the same
- // - Test target set deletion and reversion
-
// Go to race calculator
await page.goto('/');
await page.getByRole('button', { name: 'Race Calculator' }).click();
await expect(page).toHaveTitle('Race Calculator - Running Tools');
- // Enter input race (2 mi in 10:30)
- await page.getByLabel('Input race distance value').fill('2');
- await page.getByLabel('Input race distance unit').selectOption('Miles');
- await page.getByLabel('Input race duration hours').fill('0');
- await page.getByLabel('Input race duration minutes').fill('10');
- await page.getByLabel('Input race duration seconds').fill('30');
-
- // Assert race predictions are correct
- await expect(page.getByRole('row').nth(5)).toHaveText('1 mi' + '4:55.53' + '4:56 / mi');
- await expect(page.getByRole('row').nth(10)).toHaveText('5 km' + '16:47.57' + '5:24 / mi');
- await expect(page.getByRole('row')).toHaveCount(17);
-
- // Assert race statistics are correct
- await page.getByText('Race Statistics').click();
- await expect(page.getByText('Purdy Points:')).toContainText(': 680.1');
- await expect(page.getByText('V̇O₂:')).toContainText(': 61.0 ml/kg/min (100.5% of max)');
- await expect(page.getByText('V̇O₂ Max:')).toContainText(': 60.7 ml/kg/min');
-
- // Change default units
- await page.getByText('Advanced Options').click();
- await page.getByLabel('Default units').selectOption('Kilometers');
-
- // Assert race predictions are correct
- await expect(page.getByRole('row').nth(5)).toHaveText('1 mi' + '4:55.53' + '3:04 / km');
- await expect(page.getByRole('row').nth(10)).toHaveText('5 km' + '16:47.57' + '3:22 / km');
- await expect(page.getByRole('row')).toHaveCount(17);
-
- // Change prediction model
- await page.getByLabel('Prediction model').selectOption('Riegel\'s Model');
-
- // Assert race predictions are correct
- await expect(page.getByRole('row').nth(5)).toHaveText('1 mi' + '5:02.17' + '3:08 / km');
- await expect(page.getByRole('row').nth(10)).toHaveText('5 km' + '16:44.87' + '3:21 / km');
- await expect(page.getByRole('row')).toHaveCount(17);
-
- // Change Riegel exponent
- await page.getByLabel('Riegel Exponent').fill('1.12');
-
- // Assert race predictions are correct
- await expect(page.getByRole('row').nth(5)).toHaveText('1 mi' + '4:49.86' + '3:00 / km');
- await expect(page.getByRole('row').nth(10)).toHaveText('5 km' + '17:11.78' + '3:26 / km');
- await expect(page.getByRole('row')).toHaveCount(17);
-
- // Edit default target set
- await page.getByRole('button', { name: 'Edit target set' }).click();
- await page.getByLabel('Target set label').fill('Less-common Race Targets');
- await page.getByLabel('Target distance value').nth(4).fill('1.01');
- await page.getByRole('button', { name: 'Add distance target' }).click();
- await page.getByLabel('Target distance value').last().fill('1.5');
- await page.getByLabel('Target distance unit').last().selectOption('Miles');
- await page.getByRole('button', { name: 'Add time target' }).click();
- await page.getByLabel('Target duration minutes').last().fill('19');
- await page.getByLabel('Target duration seconds').last().fill('0');
- await page.getByRole('button', { name: 'Close' }).click();
-
- // Assert race predictions are correct
- await expect(page.getByRole('row').nth(5)).toHaveText('1.01 mi' + '4:53.11' + '3:00 / km');
- await expect(page.getByRole('row').nth(6)).toHaveText('1.5 mi' + '7:36.47' + '3:09 / km');
- await expect(page.getByRole('row').nth(12)).toHaveText('5.47 km' + '19:00' + '3:29 / km');
- await expect(page.getByRole('row')).toHaveCount(19);
-
- // Create custom target set
- await page.getByLabel('Selected target set').selectOption('[ Create New Target Set ]');
- await expect(page.getByRole('row').nth(4)).toHaveText('There aren\'t any targets in this set yet.');
- await expect(page.getByRole('row')).toHaveCount(5);
-
- // Edit new target set
- await expect(page.getByRole('row').nth(1)).toHaveText('There aren\'t any targets in this set yet.');
- await expect(page.getByLabel('Target set label')).toHaveValue('New target set');
- await page.getByLabel('Target set label').fill('XC Race Targets');
- await page.getByRole('button', { name: 'Add distance target' }).click();
- await page.getByLabel('Target distance value').nth(0).fill('5');
- await page.getByLabel('Target distance unit').nth(0).selectOption('Kilometers');
- await page.getByRole('button', { name: 'Add distance target' }).click();
- await page.getByLabel('Target distance value').nth(1).fill('10');
- await page.getByLabel('Target distance unit').nth(1).selectOption('Kilometers');
- await page.getByRole('button', { name: 'Close' }).click();
-
- // Assert race predictions are correct
- await expect(page.getByRole('row').nth(1)).toHaveText('5 km' + '17:11.78' + '3:26 / km');
- await expect(page.getByRole('row').nth(2)).toHaveText('10 km' + '37:22.54' + '3:44 / km');
- await expect(page.getByRole('row')).toHaveCount(3);
+ // Test standard race results
+ {
+ // Enter input race (2 mi in 10:30)
+ await page.getByLabel('Input race distance value').fill('2');
+ await page.getByLabel('Input race distance unit').selectOption('Miles');
+ await page.getByLabel('Input race duration hours').fill('0');
+ await page.getByLabel('Input race duration minutes').fill('10');
+ await page.getByLabel('Input race duration seconds').fill('30');
+
+ // Assert race predictions are correct
+ await expect(page.getByRole('row').nth(5)).toHaveText('1 mi' + '4:55.53' + '4:56 / mi');
+ await expect(page.getByRole('row').nth(10)).toHaveText('5 km' + '16:47.57' + '5:24 / mi');
+ await expect(page.getByRole('row')).toHaveCount(17);
+
+ // Assert race statistics are correct
+ await page.getByText('Race Statistics').click();
+ await expect(page.getByText('Purdy Points:')).toContainText(': 680.1');
+ await expect(page.getByText('V̇O₂:')).toContainText(': 61.0 ml/kg/min (100.5% of max)');
+ await expect(page.getByText('V̇O₂ Max:')).toContainText(': 60.7 ml/kg/min');
+ }
+
+ // Test different calculator options
+ {
+ // Change default units
+ await page.getByText('Advanced Options').click();
+ await page.getByLabel('Default units').selectOption('Kilometers');
+
+ // Assert race predictions are correct
+ await expect(page.getByRole('row').nth(5)).toHaveText('1 mi' + '4:55.53' + '3:04 / km');
+ await expect(page.getByRole('row').nth(10)).toHaveText('5 km' + '16:47.57' + '3:22 / km');
+ await expect(page.getByRole('row')).toHaveCount(17);
+
+ // Change prediction model
+ await page.getByLabel('Prediction model').selectOption('Riegel\'s Model');
+
+ // Assert race predictions are correct
+ await expect(page.getByRole('row').nth(5)).toHaveText('1 mi' + '5:02.17' + '3:08 / km');
+ await expect(page.getByRole('row').nth(10)).toHaveText('5 km' + '16:44.87' + '3:21 / km');
+ await expect(page.getByRole('row')).toHaveCount(17);
+
+ // Change Riegel exponent
+ await page.getByLabel('Riegel Exponent').fill('1.12');
+
+ // Assert race predictions are correct
+ await expect(page.getByRole('row').nth(5)).toHaveText('1 mi' + '4:49.86' + '3:00 / km');
+ await expect(page.getByRole('row').nth(10)).toHaveText('5 km' + '17:11.78' + '3:26 / km');
+ await expect(page.getByRole('row')).toHaveCount(17);
+ }
+
+ // Test modified default target set
+ {
+ // Edit default target set
+ await page.getByRole('button', { name: 'Edit target set' }).click();
+ await page.getByLabel('Target set label').fill('Less-common Race Targets');
+ await page.getByLabel('Target distance value').nth(4).fill('1.01');
+ await page.getByRole('button', { name: 'Add distance target' }).click();
+ await page.getByLabel('Target distance value').last().fill('1.5');
+ await page.getByLabel('Target distance unit').last().selectOption('Miles');
+ await page.getByRole('button', { name: 'Add time target' }).click();
+ await page.getByLabel('Target duration minutes').last().fill('19');
+ await page.getByLabel('Target duration seconds').last().fill('0');
+ await page.getByRole('button', { name: 'Close' }).click();
+
+ // Assert race predictions are correct
+ await expect(page.getByRole('row').nth(5)).toHaveText('1.01 mi' + '4:53.11' + '3:00 / km');
+ await expect(page.getByRole('row').nth(6)).toHaveText('1.5 mi' + '7:36.47' + '3:09 / km');
+ await expect(page.getByRole('row').nth(12)).toHaveText('5.47 km' + '19:00' + '3:29 / km');
+ await expect(page.getByRole('row')).toHaveCount(19);
+ }
+
+ // Test custom target set
+ {
+ // Create custom target set
+ await page.getByLabel('Selected target set').selectOption('[ Create New Target Set ]');
+ await expect(page.getByRole('row').nth(4)).toHaveText('There aren\'t any targets in this set yet.');
+ await expect(page.getByRole('row')).toHaveCount(5);
+
+ // Edit new target set
+ await expect(page.getByRole('row').nth(1)).toHaveText('There aren\'t any targets in this set yet.');
+ await expect(page.getByLabel('Target set label')).toHaveValue('New target set');
+ await page.getByLabel('Target set label').fill('XC Race Targets');
+ await page.getByRole('button', { name: 'Add distance target' }).click();
+ await page.getByLabel('Target distance value').nth(0).fill('5');
+ await page.getByLabel('Target distance unit').nth(0).selectOption('Kilometers');
+ await page.getByRole('button', { name: 'Add distance target' }).click();
+ await page.getByLabel('Target distance value').nth(1).fill('10');
+ await page.getByLabel('Target distance unit').nth(1).selectOption('Kilometers');
+ await page.getByRole('button', { name: 'Close' }).click();
+
+ // Assert race predictions are correct
+ await expect(page.getByRole('row').nth(1)).toHaveText('5 km' + '17:11.78' + '3:26 / km');
+ await expect(page.getByRole('row').nth(2)).toHaveText('10 km' + '37:22.54' + '3:44 / km');
+ await expect(page.getByRole('row')).toHaveCount(3);
+ }
// Reload page
await page.reload();
- // Assert race predictions are correct (custom targets, default units, and model settings not reset)
- await expect(page.getByRole('row').nth(1)).toHaveText('5 km' + '17:11.78' + '3:26 / km');
- await expect(page.getByRole('row').nth(2)).toHaveText('10 km' + '37:22.54' + '3:44 / km');
- await expect(page.getByRole('row')).toHaveCount(3);
-
- // Switch target set
- await page.getByText('Advanced Options').click();
- await page.getByLabel('Selected target set').selectOption('Less-common Race Targets');
-
- // Assert race predictions are correct
- await expect(page.getByRole('row').nth(5)).toHaveText('1.01 mi' + '4:53.11' + '3:00 / km');
- await expect(page.getByRole('row').nth(6)).toHaveText('1.5 mi' + '7:36.47' + '3:09 / km');
- await expect(page.getByRole('row').nth(12)).toHaveText('5.47 km' + '19:00' + '3:29 / km');
- await expect(page.getByRole('row')).toHaveCount(19);
-
- // Delete custom target set
- await page.getByLabel('Selected target set').selectOption('XC Race Targets');
- await page.getByRole('button', { name: 'Edit target set' }).click();
- await expect(page.getByLabel('Target set label')).toHaveValue('XC Race Targets');
- await page.getByRole('button', { name: 'Delete target set' }).click();
-
- // Assert race predictions are correct (back to default target set)
- await expect(page.getByRole('row').nth(5)).toHaveText('1.01 mi' + '4:53.11' + '3:00 / km');
- await expect(page.getByRole('row').nth(6)).toHaveText('1.5 mi' + '7:36.47' + '3:09 / km');
- await expect(page.getByRole('row').nth(12)).toHaveText('5.47 km' + '19:00' + '3:29 / km');
- await expect(page.getByRole('row')).toHaveCount(19);
-
- // Revert target set
- await page.getByRole('button', { name: 'Edit target set' }).click();
- await expect(page.getByLabel('Target set label')).toHaveValue('Less-common Race Targets');
- await page.getByRole('button', { name: 'Revert target set' }).click();
- await page.getByRole('button', { name: 'Close' }).click();
-
- // Assert paces are correct
- await expect(page.getByRole('row').nth(5)).toHaveText('1 mi' + '4:49.86' + '3:00 / km');
- await expect(page.getByRole('row').nth(10)).toHaveText('5 km' + '17:11.78' + '3:26 / km');
- await expect(page.getByRole('row')).toHaveCount(17);
-
- // Assert title was reset
- await page.getByRole('button', { name: 'Edit target set' }).click();
- await expect(page.getByLabel('Target set label')).toHaveValue('Common Race Targets');
+ // Assert outputs are still the same
+ {
+ // Assert race predictions are correct (custom targets, default units, and model settings not reset)
+ await expect(page.getByRole('row').nth(1)).toHaveText('5 km' + '17:11.78' + '3:26 / km');
+ await expect(page.getByRole('row').nth(2)).toHaveText('10 km' + '37:22.54' + '3:44 / km');
+ await expect(page.getByRole('row')).toHaveCount(3);
+
+ // Switch target set
+ await page.getByText('Advanced Options').click();
+ await page.getByLabel('Selected target set').selectOption('Less-common Race Targets');
+
+ // Assert race predictions are correct
+ await expect(page.getByRole('row').nth(5)).toHaveText('1.01 mi' + '4:53.11' + '3:00 / km');
+ await expect(page.getByRole('row').nth(6)).toHaveText('1.5 mi' + '7:36.47' + '3:09 / km');
+ await expect(page.getByRole('row').nth(12)).toHaveText('5.47 km' + '19:00' + '3:29 / km');
+ await expect(page.getByRole('row')).toHaveCount(19);
+ }
+
+ // Test target set deletion and reversion
+ {
+ // Delete custom target set
+ await page.getByLabel('Selected target set').selectOption('XC Race Targets');
+ await page.getByRole('button', { name: 'Edit target set' }).click();
+ await expect(page.getByLabel('Target set label')).toHaveValue('XC Race Targets');
+ await page.getByRole('button', { name: 'Delete target set' }).click();
+
+ // Assert race predictions are correct (back to default target set)
+ await expect(page.getByRole('row').nth(5)).toHaveText('1.01 mi' + '4:53.11' + '3:00 / km');
+ await expect(page.getByRole('row').nth(6)).toHaveText('1.5 mi' + '7:36.47' + '3:09 / km');
+ await expect(page.getByRole('row').nth(12)).toHaveText('5.47 km' + '19:00' + '3:29 / km');
+ await expect(page.getByRole('row')).toHaveCount(19);
+
+ // Revert target set
+ await page.getByRole('button', { name: 'Edit target set' }).click();
+ await expect(page.getByLabel('Target set label')).toHaveValue('Less-common Race Targets');
+ await page.getByRole('button', { name: 'Revert target set' }).click();
+ await page.getByRole('button', { name: 'Close' }).click();
+
+ // Assert paces are correct
+ await expect(page.getByRole('row').nth(5)).toHaveText('1 mi' + '4:49.86' + '3:00 / km');
+ await expect(page.getByRole('row').nth(10)).toHaveText('5 km' + '17:11.78' + '3:26 / km');
+ await expect(page.getByRole('row')).toHaveCount(17);
+
+ // Assert title was reset
+ await page.getByRole('button', { name: 'Edit target set' }).click();
+ await expect(page.getByLabel('Target set label')).toHaveValue('Common Race Targets');
+ }
});
diff --git a/tests/e2e/split-calculator.spec.js b/tests/e2e/split-calculator.spec.js
@@ -1,191 +1,200 @@
import { test, expect } from '@playwright/test';
test('Split Calculator', async ({ page }) => {
- // Structure:
- // - Test standard split results
- // - Test different default units
- // - Test modified default target set
- // - Test custom target set
- // - Reload page
- // - Assert outputs are still the same
- // - Test target set deletion and reversion
-
// Go to split calculator
await page.goto('/');
await page.getByRole('button', { name: 'Split Calculator' }).click();
await expect(page).toHaveTitle('Split Calculator - Running Tools');
- // Enter input 5K splits (7:00, 6:30, 6:30)
- await page.getByLabel('Split duration minutes').nth(0).fill('7');
- await page.getByLabel('Split duration seconds').nth(0).fill('0');
- await page.getByLabel('Split duration minutes').nth(1).fill('6');
- await page.getByLabel('Split duration seconds').nth(1).fill('30');
- await page.getByLabel('Split duration minutes').nth(2).fill('6');
- await page.getByLabel('Split duration seconds').nth(2).fill('30');
-
- // Assert times and paces are correct
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('7:00.00');
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('7:00 / mi');
- await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('13:30.00');
- await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('6:30 / mi');
- await expect(page.getByRole('row').nth(3).getByRole('cell').nth(1)).toHaveText('20:00.00');
- await expect(page.getByRole('row').nth(3).getByRole('cell').nth(3)).toHaveText('5:52 / mi');
- await expect(page.getByRole('row')).toHaveCount(4);
-
- // Change default units
- await page.getByLabel('Default units').selectOption('Kilometers');
-
- // Assert times and paces are correct
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('7:00.00');
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('4:21 / km');
- await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('13:30.00');
- await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('4:02 / km');
- await expect(page.getByRole('row').nth(3).getByRole('cell').nth(1)).toHaveText('20:00.00');
- await expect(page.getByRole('row').nth(3).getByRole('cell').nth(3)).toHaveText('3:39 / km');
- await expect(page.getByRole('row')).toHaveCount(4);
-
- // Edit target set
- await page.getByRole('button', { name: 'Edit target set' }).click();
- await page.getByLabel('Target set label').fill('5K 1600m Splits');
- await page.getByLabel('Target distance value').nth(0).fill('1.6');
- await page.getByLabel('Target distance unit').nth(0).selectOption('Kilometers');
- await page.getByLabel('Target distance value').nth(1).fill('3.2');
- await page.getByLabel('Target distance unit').nth(1).selectOption('Kilometers');
- await page.getByRole('button', { name: 'Add distance target' }).click();
- await page.getByLabel('Target distance value').nth(3).fill('4.8');
- await page.getByLabel('Target distance unit').nth(3).selectOption('Kilometers');
- await page.getByRole('button', { name: 'Close' }).click();
-
- // Assert times and paces are correct (new distances are processed)
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('7:00.00');
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('4:23 / km');
- await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('13:30.00');
- await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('4:04 / km');
- await expect(page.getByRole('row').nth(3).getByRole('cell').nth(1)).toHaveText('13:30.00');
- await expect(page.getByRole('row').nth(3).getByRole('cell').nth(3)).toHaveText('0:00 / km');
- await expect(page.getByRole('row').nth(4).getByRole('cell').nth(1)).toHaveText('20:00.00');
- await expect(page.getByRole('row').nth(4).getByRole('cell').nth(3)).toHaveText('32:30 / km');
- await expect(page.getByRole('row')).toHaveCount(5);
-
- // Update third and fourth splits
- await page.getByLabel('Split duration minutes').nth(2).fill('6');
- await page.getByLabel('Split duration seconds').nth(2).fill('0');
- await page.getByLabel('Split duration minutes').nth(3).fill('0');
- await page.getByLabel('Split duration seconds').nth(3).fill('30');
- await page.getByLabel('Split duration seconds').nth(3).blur();
-
- // Assert times and paces are correct (new input splits are processed)
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('7:00.00');
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('4:23 / km');
- await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('13:30.00');
- await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('4:04 / km');
- await expect(page.getByRole('row').nth(3).getByRole('cell').nth(1)).toHaveText('19:30.00');
- await expect(page.getByRole('row').nth(3).getByRole('cell').nth(3)).toHaveText('3:45 / km');
- await expect(page.getByRole('row').nth(4).getByRole('cell').nth(1)).toHaveText('20:00.00');
- await expect(page.getByRole('row').nth(4).getByRole('cell').nth(3)).toHaveText('2:30 / km');
- await expect(page.getByRole('row')).toHaveCount(5);
-
- // Create custom target set
- await page.getByLabel('Selected target set').selectOption('[ Create New Target Set ]');
- await expect(page.getByRole('row').nth(4)).toHaveText('There aren\'t any targets in this set yet.');
- await expect(page.getByRole('row')).toHaveCount(5);
-
- // Edit new target set
- await expect(page.getByRole('row').nth(1)).toHaveText('There aren\'t any targets in this set yet.');
- await expect(page.getByLabel('Target set label')).toHaveValue('New target set');
- await page.getByLabel('Target set label').fill('800m Splits');
- await page.getByRole('button', { name: 'Add distance target' }).click();
- await page.getByLabel('Target distance value').nth(0).fill('0.4');
- await page.getByLabel('Target distance unit').nth(0).selectOption('Kilometers');
- await page.getByRole('button', { name: 'Add distance target' }).click();
- await page.getByLabel('Target distance value').nth(1).fill('800');
- await page.getByLabel('Target distance unit').nth(1).selectOption('Meters');
- await page.getByRole('button', { name: 'Close' }).click();
-
- // Assert times and paces are correct (input splits initialized to zero)
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('0.4 km');
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('0:00.00');
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('0:00 / km');
- await expect(page.getByRole('row').nth(2).getByRole('cell').nth(0)).toHaveText('800 m');
- await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('0:00.00');
- await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('0:00 / km');
- await expect(page.getByRole('row')).toHaveCount(3);
-
- // Enter input 800m splits (0:55, 1:05)
- await page.getByLabel('Split duration minutes').nth(0).fill('0');
- await page.getByLabel('Split duration seconds').nth(0).fill('55');
- await page.getByLabel('Split duration minutes').nth(1).fill('1');
- await page.getByLabel('Split duration seconds').nth(1).fill('5');
-
- // Assert times and paces are correct
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('0:55.00');
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('2:18 / km');
- await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('2:00.00');
- await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('2:43 / km');
- await expect(page.getByRole('row')).toHaveCount(3);
+ // Test standard split results
+ {
+ // Enter input 5K splits (7:00, 6:30, 6:30)
+ await page.getByLabel('Split duration minutes').nth(0).fill('7');
+ await page.getByLabel('Split duration seconds').nth(0).fill('0');
+ await page.getByLabel('Split duration minutes').nth(1).fill('6');
+ await page.getByLabel('Split duration seconds').nth(1).fill('30');
+ await page.getByLabel('Split duration minutes').nth(2).fill('6');
+ await page.getByLabel('Split duration seconds').nth(2).fill('30');
+
+ // Assert times and paces are correct
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('7:00.00');
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('7:00 / mi');
+ await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('13:30.00');
+ await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('6:30 / mi');
+ await expect(page.getByRole('row').nth(3).getByRole('cell').nth(1)).toHaveText('20:00.00');
+ await expect(page.getByRole('row').nth(3).getByRole('cell').nth(3)).toHaveText('5:52 / mi');
+ await expect(page.getByRole('row')).toHaveCount(4);
+ }
+
+ // Test different calculator options
+ {
+ // Change default units
+ await page.getByLabel('Default units').selectOption('Kilometers');
+
+ // Assert times and paces are correct
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('7:00.00');
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('4:21 / km');
+ await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('13:30.00');
+ await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('4:02 / km');
+ await expect(page.getByRole('row').nth(3).getByRole('cell').nth(1)).toHaveText('20:00.00');
+ await expect(page.getByRole('row').nth(3).getByRole('cell').nth(3)).toHaveText('3:39 / km');
+ await expect(page.getByRole('row')).toHaveCount(4);
+ }
+
+ // Test modified default target set
+ {
+ // Edit target set
+ await page.getByRole('button', { name: 'Edit target set' }).click();
+ await page.getByLabel('Target set label').fill('5K 1600m Splits');
+ await page.getByLabel('Target distance value').nth(0).fill('1.6');
+ await page.getByLabel('Target distance unit').nth(0).selectOption('Kilometers');
+ await page.getByLabel('Target distance value').nth(1).fill('3.2');
+ await page.getByLabel('Target distance unit').nth(1).selectOption('Kilometers');
+ await page.getByRole('button', { name: 'Add distance target' }).click();
+ await page.getByLabel('Target distance value').nth(3).fill('4.8');
+ await page.getByLabel('Target distance unit').nth(3).selectOption('Kilometers');
+ await page.getByRole('button', { name: 'Close' }).click();
+
+ // Assert times and paces are correct (new distances are processed)
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('7:00.00');
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('4:23 / km');
+ await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('13:30.00');
+ await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('4:04 / km');
+ await expect(page.getByRole('row').nth(3).getByRole('cell').nth(1)).toHaveText('13:30.00');
+ await expect(page.getByRole('row').nth(3).getByRole('cell').nth(3)).toHaveText('0:00 / km');
+ await expect(page.getByRole('row').nth(4).getByRole('cell').nth(1)).toHaveText('20:00.00');
+ await expect(page.getByRole('row').nth(4).getByRole('cell').nth(3)).toHaveText('32:30 / km');
+ await expect(page.getByRole('row')).toHaveCount(5);
+
+ // Update third and fourth splits
+ await page.getByLabel('Split duration minutes').nth(2).fill('6');
+ await page.getByLabel('Split duration seconds').nth(2).fill('0');
+ await page.getByLabel('Split duration minutes').nth(3).fill('0');
+ await page.getByLabel('Split duration seconds').nth(3).fill('30');
+ await page.getByLabel('Split duration seconds').nth(3).blur();
+
+ // Assert times and paces are correct (new input splits are processed)
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('7:00.00');
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('4:23 / km');
+ await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('13:30.00');
+ await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('4:04 / km');
+ await expect(page.getByRole('row').nth(3).getByRole('cell').nth(1)).toHaveText('19:30.00');
+ await expect(page.getByRole('row').nth(3).getByRole('cell').nth(3)).toHaveText('3:45 / km');
+ await expect(page.getByRole('row').nth(4).getByRole('cell').nth(1)).toHaveText('20:00.00');
+ await expect(page.getByRole('row').nth(4).getByRole('cell').nth(3)).toHaveText('2:30 / km');
+ await expect(page.getByRole('row')).toHaveCount(5);
+ }
+
+ // Test custom target set
+ {
+ // Create custom target set
+ await page.getByLabel('Selected target set').selectOption('[ Create New Target Set ]');
+ await expect(page.getByRole('row').nth(4)).toHaveText('There aren\'t any targets in this set yet.');
+ await expect(page.getByRole('row')).toHaveCount(5);
+
+ // Edit new target set
+ await expect(page.getByRole('row').nth(1)).toHaveText('There aren\'t any targets in this set yet.');
+ await expect(page.getByLabel('Target set label')).toHaveValue('New target set');
+ await page.getByLabel('Target set label').fill('800m Splits');
+ await page.getByRole('button', { name: 'Add distance target' }).click();
+ await page.getByLabel('Target distance value').nth(0).fill('0.4');
+ await page.getByLabel('Target distance unit').nth(0).selectOption('Kilometers');
+ await page.getByRole('button', { name: 'Add distance target' }).click();
+ await page.getByLabel('Target distance value').nth(1).fill('800');
+ await page.getByLabel('Target distance unit').nth(1).selectOption('Meters');
+ await page.getByRole('button', { name: 'Close' }).click();
+
+ // Assert times and paces are correct (input splits initialized to zero)
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('0.4 km');
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('0:00.00');
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('0:00 / km');
+ await expect(page.getByRole('row').nth(2).getByRole('cell').nth(0)).toHaveText('800 m');
+ await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('0:00.00');
+ await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('0:00 / km');
+ await expect(page.getByRole('row')).toHaveCount(3);
+
+ // Enter input 800m splits (0:55, 1:05)
+ await page.getByLabel('Split duration minutes').nth(0).fill('0');
+ await page.getByLabel('Split duration seconds').nth(0).fill('55');
+ await page.getByLabel('Split duration minutes').nth(1).fill('1');
+ await page.getByLabel('Split duration seconds').nth(1).fill('5');
+
+ // Assert times and paces are correct
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('0:55.00');
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('2:18 / km');
+ await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('2:00.00');
+ await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('2:43 / km');
+ await expect(page.getByRole('row')).toHaveCount(3);
+ }
// Reload page
await page.reload();
- // Assert times and paces are correct (custom targets, split times, and default units not reset)
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('0.4 km');
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('0:55.00');
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('2:18 / km');
- await expect(page.getByRole('row').nth(2).getByRole('cell').nth(0)).toHaveText('800 m');
- await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('2:00.00');
- await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('2:43 / km');
- await expect(page.getByRole('row')).toHaveCount(3);
-
- // Switch target set
- await page.getByLabel('Selected target set').selectOption('5K 1600m Splits');
-
- // Assert times and paces are correct (input splits are not reset)
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('7:00.00');
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('4:23 / km');
- await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('13:30.00');
- await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('4:04 / km');
- await expect(page.getByRole('row').nth(3).getByRole('cell').nth(1)).toHaveText('19:30.00');
- await expect(page.getByRole('row').nth(3).getByRole('cell').nth(3)).toHaveText('3:45 / km');
- await expect(page.getByRole('row').nth(4).getByRole('cell').nth(1)).toHaveText('20:00.00');
- await expect(page.getByRole('row').nth(4).getByRole('cell').nth(3)).toHaveText('2:30 / km');
- await expect(page.getByRole('row')).toHaveCount(5);
-
- // Delete custom target set
- await page.getByLabel('Selected target set').selectOption('800m Splits');
- await page.getByRole('button', { name: 'Edit target set' }).click();
- await expect(page.getByLabel('Target set label')).toHaveValue('800m Splits');
- await page.getByRole('button', { name: 'Delete target set' }).click();
-
- // Assert times and paces are correct (back to default target set)
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('7:00.00');
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('4:23 / km');
- await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('13:30.00');
- await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('4:04 / km');
- await expect(page.getByRole('row').nth(3).getByRole('cell').nth(1)).toHaveText('19:30.00');
- await expect(page.getByRole('row').nth(3).getByRole('cell').nth(3)).toHaveText('3:45 / km');
- await expect(page.getByRole('row').nth(4).getByRole('cell').nth(1)).toHaveText('20:00.00');
- await expect(page.getByRole('row').nth(4).getByRole('cell').nth(3)).toHaveText('2:30 / km');
- await expect(page.getByRole('row')).toHaveCount(5);
-
- // Revert target set
- await page.getByRole('button', { name: 'Edit target set' }).click();
- await expect(page.getByLabel('Target set label')).toHaveValue('5K 1600m Splits');
- await page.getByRole('button', { name: 'Revert target set' }).click();
- await page.getByRole('button', { name: 'Close' }).click();
-
- // Assert times and paces are correct (split times are reverted)
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('1 mi');
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('0:00.00');
- await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('0:00 / km');
- await expect(page.getByRole('row').nth(2).getByRole('cell').nth(0)).toHaveText('2 mi');
- await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('0:00.00');
- await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('0:00 / km');
- await expect(page.getByRole('row').nth(3).getByRole('cell').nth(0)).toHaveText('5 km');
- await expect(page.getByRole('row').nth(3).getByRole('cell').nth(1)).toHaveText('0:00.00');
- await expect(page.getByRole('row').nth(3).getByRole('cell').nth(3)).toHaveText('0:00 / km');
- await expect(page.getByRole('row')).toHaveCount(4);
-
- // Assert title was reset
- await page.getByRole('button', { name: 'Edit target set' }).click();
- await expect(page.getByLabel('Target set label')).toHaveValue('5K Mile Splits');
+ // Assert outputs are still the same
+ {
+ // Assert times and paces are correct (custom targets, split times, and default units not reset)
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('0.4 km');
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('0:55.00');
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('2:18 / km');
+ await expect(page.getByRole('row').nth(2).getByRole('cell').nth(0)).toHaveText('800 m');
+ await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('2:00.00');
+ await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('2:43 / km');
+ await expect(page.getByRole('row')).toHaveCount(3);
+
+ // Switch target set
+ await page.getByLabel('Selected target set').selectOption('5K 1600m Splits');
+
+ // Assert times and paces are correct (input splits are not reset)
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('7:00.00');
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('4:23 / km');
+ await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('13:30.00');
+ await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('4:04 / km');
+ await expect(page.getByRole('row').nth(3).getByRole('cell').nth(1)).toHaveText('19:30.00');
+ await expect(page.getByRole('row').nth(3).getByRole('cell').nth(3)).toHaveText('3:45 / km');
+ await expect(page.getByRole('row').nth(4).getByRole('cell').nth(1)).toHaveText('20:00.00');
+ await expect(page.getByRole('row').nth(4).getByRole('cell').nth(3)).toHaveText('2:30 / km');
+ await expect(page.getByRole('row')).toHaveCount(5);
+ }
+
+ // Test target set deletion and reversion
+ {
+ // Delete custom target set
+ await page.getByLabel('Selected target set').selectOption('800m Splits');
+ await page.getByRole('button', { name: 'Edit target set' }).click();
+ await expect(page.getByLabel('Target set label')).toHaveValue('800m Splits');
+ await page.getByRole('button', { name: 'Delete target set' }).click();
+
+ // Assert times and paces are correct (back to default target set)
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('7:00.00');
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('4:23 / km');
+ await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('13:30.00');
+ await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('4:04 / km');
+ await expect(page.getByRole('row').nth(3).getByRole('cell').nth(1)).toHaveText('19:30.00');
+ await expect(page.getByRole('row').nth(3).getByRole('cell').nth(3)).toHaveText('3:45 / km');
+ await expect(page.getByRole('row').nth(4).getByRole('cell').nth(1)).toHaveText('20:00.00');
+ await expect(page.getByRole('row').nth(4).getByRole('cell').nth(3)).toHaveText('2:30 / km');
+ await expect(page.getByRole('row')).toHaveCount(5);
+
+ // Revert target set
+ await page.getByRole('button', { name: 'Edit target set' }).click();
+ await expect(page.getByLabel('Target set label')).toHaveValue('5K 1600m Splits');
+ await page.getByRole('button', { name: 'Revert target set' }).click();
+ await page.getByRole('button', { name: 'Close' }).click();
+
+ // Assert times and paces are correct (split times are reverted)
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('1 mi');
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('0:00.00');
+ await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('0:00 / km');
+ await expect(page.getByRole('row').nth(2).getByRole('cell').nth(0)).toHaveText('2 mi');
+ await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('0:00.00');
+ await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('0:00 / km');
+ await expect(page.getByRole('row').nth(3).getByRole('cell').nth(0)).toHaveText('5 km');
+ await expect(page.getByRole('row').nth(3).getByRole('cell').nth(1)).toHaveText('0:00.00');
+ await expect(page.getByRole('row').nth(3).getByRole('cell').nth(3)).toHaveText('0:00 / km');
+ await expect(page.getByRole('row')).toHaveCount(4);
+
+ // Assert title was reset
+ await page.getByRole('button', { name: 'Edit target set' }).click();
+ await expect(page.getByLabel('Target set label')).toHaveValue('5K Mile Splits');
+ }
});
diff --git a/tests/e2e/unit-calculator.spec.js b/tests/e2e/unit-calculator.spec.js
@@ -1,68 +1,72 @@
import { test, expect } from '@playwright/test';
test('Unit Calculator', async ({ page }) => {
- // Structure:
- // - Test distance unit conversion
- // - Test speed and pace unit conversion
- // - Test time unit conversion
- // - Reload page
- // - Assert distance inputs are still loaded
- // - Assert time inputs are still loaded
- // - Assert speed and pace inputs are still loaded
-
// Go to unit calculator
await page.goto('/');
await page.getByRole('button', { name: 'Unit Calculator' }).click();
await expect(page).toHaveTitle('Unit Calculator - Running Tools');
- // Convert distance units (5000m to mi)
- await page.getByLabel('Input units').selectOption('Meters');
- await page.getByLabel('Input value').fill('5000');
- await page.getByLabel('Output units').selectOption('Miles');
- await expect(page.getByLabel('Output value')).toHaveText('3.107');
+ // Test distance unit conversion
+ {
+ // Convert distance units (5000m to mi)
+ await page.getByLabel('Input units').selectOption('Meters');
+ await page.getByLabel('Input value').fill('5000');
+ await page.getByLabel('Output units').selectOption('Miles');
+ await expect(page.getByLabel('Output value')).toHaveText('3.107');
+ }
- // Convert speed and pace units (0:04:32/km to mph)
- await page.getByLabel('Selected unit category').selectOption('Speed & Pace');
- await page.getByLabel('Input units').selectOption('Time per Kilometer');
- await page.getByLabel('Input time hours').fill('0');
- await page.getByLabel('Input time minutes').fill('4');
- await page.getByLabel('Input time seconds').fill('32');
- await page.getByLabel('Output units').selectOption('Miles per Hour');
- await expect(page.getByLabel('Output value')).toHaveText('8.224');
+ // Test speed and pace unit conversion
+ {
+ // Convert speed and pace units (0:04:32/km to mph)
+ await page.getByLabel('Selected unit category').selectOption('Speed & Pace');
+ await page.getByLabel('Input units').selectOption('Time per Kilometer');
+ await page.getByLabel('Input time hours').fill('0');
+ await page.getByLabel('Input time minutes').fill('4');
+ await page.getByLabel('Input time seconds').fill('32');
+ await page.getByLabel('Output units').selectOption('Miles per Hour');
+ await expect(page.getByLabel('Output value')).toHaveText('8.224');
- // Convert speed and pace units (10 kph to time per mile)
- await page.getByLabel('Input units').selectOption('Kilometers per Hour');
- await page.getByLabel('Input value').fill('10');
- await page.getByLabel('Output units').selectOption('Time per Mile');
- await expect(page.getByLabel('Output value')).toHaveText('00:09:39.364');
+ // Convert speed and pace units (10 kph to time per mile)
+ await page.getByLabel('Input units').selectOption('Kilometers per Hour');
+ await page.getByLabel('Input value').fill('10');
+ await page.getByLabel('Output units').selectOption('Time per Mile');
+ await expect(page.getByLabel('Output value')).toHaveText('00:09:39.364');
+ }
- // Convert time units (83.76 min to hh:mm:ss)
- await page.getByLabel('Selected unit category').selectOption('Time');
- await page.getByLabel('Input units').selectOption('Minutes');
- await page.getByLabel('Input value').fill('83.76');
- await page.getByLabel('Output units').selectOption('hh:mm:ss');
- await expect(page.getByLabel('Output value')).toHaveText('01:23:45.600');
+ // Test time unit conversion
+ {
+ // Convert time units (83.76 min to hh:mm:ss)
+ await page.getByLabel('Selected unit category').selectOption('Time');
+ await page.getByLabel('Input units').selectOption('Minutes');
+ await page.getByLabel('Input value').fill('83.76');
+ await page.getByLabel('Output units').selectOption('hh:mm:ss');
+ await expect(page.getByLabel('Output value')).toHaveText('01:23:45.600');
- // Convert time units (6:54:32.100 to seconds)
- await page.getByLabel('Selected unit category').selectOption('Time');
- await page.getByLabel('Input units').selectOption('hh:mm:ss');
- await page.getByLabel('Input time hours').fill('6');
- await page.getByLabel('Input time minutes').fill('54');
- await page.getByLabel('Input time seconds').fill('32.1');
- await page.getByLabel('Output units').selectOption('seconds');
- await expect(page.getByLabel('Output value')).toHaveText('24872.100');
+ // Convert time units (6:54:32.100 to seconds)
+ await page.getByLabel('Selected unit category').selectOption('Time');
+ await page.getByLabel('Input units').selectOption('hh:mm:ss');
+ await page.getByLabel('Input time hours').fill('6');
+ await page.getByLabel('Input time minutes').fill('54');
+ await page.getByLabel('Input time seconds').fill('32.1');
+ await page.getByLabel('Output units').selectOption('seconds');
+ await expect(page.getByLabel('Output value')).toHaveText('24872.100');
+ }
// Reload page
await page.reload();
- // Assert time result is correct (state not reset)
- await expect(page.getByLabel('Output value')).toHaveText('24872.100');
+ // Assert inputs are still loaded
+ {
+ // Assert time result is correct (state not reset)
+ await expect(page.getByLabel('Selected unit category')).toHaveValue('time');
+ await expect(page.getByLabel('Output value')).toHaveText('24872.100');
- // Assert distance result is correct (state not reset)
- await page.getByLabel('Selected unit category').selectOption('Distance');
- await expect(page.getByLabel('Output value')).toHaveText('3.107');
+ // Assert distance result is correct (state not reset)
+ await page.getByLabel('Selected unit category').selectOption('Distance');
+ await expect(page.getByLabel('Output value')).toHaveText('3.107');
- // Assert speed & pace result is correct (state not reset)
- await page.getByLabel('Selected unit category').selectOption('Speed & Pace');
- await expect(page.getByLabel('Output value')).toHaveText('00:09:39.364');
+ // Assert speed & pace result is correct (state not reset)
+ await page.getByLabel('Selected unit category').selectOption('Speed & Pace');
+ await expect(page.getByLabel('Output value')).toHaveText('00:09:39.364');
+ }
});
diff --git a/tests/e2e/workout-calculator.spec.js b/tests/e2e/workout-calculator.spec.js
@@ -1,156 +1,165 @@
import { test, expect } from '@playwright/test';
test('Workout Calculator', async ({ page }) => {
- // Structure:
- // - Test standard workout results
- // - Test different prediction options
- // - Test modified default target set
- // - Test custom target set
- // - Reload page
- // - Assert outputs are still the same
- // - Test target set deletion and reversion
-
// Go to workout calculator
await page.goto('/');
await page.getByRole('button', { name: 'Workout Calculator' }).click();
await expect(page).toHaveTitle('Workout Calculator - Running Tools');
- // Enter input race (2 mi in 10:30)
- await page.getByLabel('Input race distance value').fill('2');
- await page.getByLabel('Input race distance unit').selectOption('Miles');
- await page.getByLabel('Input race duration hours').fill('0');
- await page.getByLabel('Input race duration minutes').fill('10');
- await page.getByLabel('Input race duration seconds').fill('30');
-
- // Assert workout splits are correct
- await expect(page.getByRole('row').nth(1)).toHaveText('400 m @ 1 mi' + '1:13.45');
- await expect(page.getByRole('row').nth(3)).toHaveText('1600 m @ 1:00:00' + '5:45.44');
- await expect(page.getByRole('row')).toHaveCount(5);
-
- // Change prediction model
- await page.getByText('Advanced Options').click();
- await page.getByLabel('Prediction model').selectOption('Riegel\'s Model');
-
- // Assert workout splits are correct
- await expect(page.getByRole('row').nth(1)).toHaveText('400 m @ 1 mi' + '1:15.10');
- await expect(page.getByRole('row').nth(3)).toHaveText('1600 m @ 1:00:00' + '5:45.64');
- await expect(page.getByRole('row')).toHaveCount(5);
-
- // Change Riegel exponent
- await page.getByLabel('Riegel Exponent').fill('1.12');
-
- // Assert workout splits are correct
- await expect(page.getByRole('row').nth(1)).toHaveText('400 m @ 1 mi' + '1:12.04');
- await expect(page.getByRole('row').nth(3)).toHaveText('1600 m @ 1:00:00' + '6:17.47');
- await expect(page.getByRole('row')).toHaveCount(5);
-
- // Edit default target set
- await page.getByRole('button', { name: 'Edit target set' }).click();
- await page.getByLabel('Target set label').fill('Less-common Workout Targets');
- await expect(page.getByLabel('Custom target name')).toHaveCount(0);
- await page.getByLabel('Split distance value').nth(0).fill('401');
- await page.getByLabel('Target distance value').nth(0).fill('2');
- await page.getByRole('button', { name: 'Add distance target' }).click();
- await page.getByLabel('Split distance value').last().fill('1');
- await page.getByLabel('Split distance unit').last().selectOption('Miles');
- await page.getByLabel('Target distance value').last().fill('10');
- await page.getByLabel('Target distance unit').last().selectOption('Kilometers');
- await page.getByRole('button', { name: 'Add time target' }).click();
- await page.getByLabel('Split distance value').last().fill('600');
- await page.getByLabel('Split distance unit').last().selectOption('Meters');
- await page.getByLabel('Target duration minutes').last().fill('19');
- await page.getByLabel('Target duration seconds').last().fill('0');
- await page.getByRole('button', { name: 'Add distance target' }).click();
- await page.getByLabel('Split distance value').last().fill('2');
- await page.getByLabel('Split distance unit').last().selectOption('Miles');
- await page.getByLabel('Target distance value').last().fill('2');
- await page.getByLabel('Target distance unit').last().selectOption('Miles');
- await page.getByRole('button', { name: 'Close' }).click();
-
- // Assert workout splits are correct
- await expect(page.getByRole('row').nth(1)).toHaveText('401 m @ 2 mi' + '1:18.49');
- await expect(page.getByRole('row').nth(2)).toHaveText('600 m @ 19:00' + '2:05.14');
- await expect(page.getByRole('row').nth(4)).toHaveText('1 mi @ 10 km' + '6:00.90');
- await expect(page.getByRole('row').nth(7)).toHaveText('2 mi' + '10:30.00');
- await expect(page.getByRole('row')).toHaveCount(8);
-
- // Enable target name customization
- await page.getByLabel('Target name customization').selectOption('Enabled');
-
- // Create custom target set
- await page.getByLabel('Selected target set').selectOption('[ Create New Target Set ]');
- await expect(page.getByRole('row').nth(4)).toHaveText('There aren\'t any targets in this set yet.');
- await expect(page.getByRole('row')).toHaveCount(5);
-
- // Edit new target set
- await expect(page.getByRole('row').nth(1)).toHaveText('There aren\'t any targets in this set yet.');
- await expect(page.getByLabel('Target set label')).toHaveValue('New target set');
- await page.getByLabel('Target set label').fill('Workout Target Set #2');
- await page.getByRole('button', { name: 'Add distance target' }).click();
- await page.getByLabel('Custom target name').last().fill('800m Interval');
- await page.getByLabel('Split distance value').last().fill('800');
- await page.getByLabel('Split distance unit').last().selectOption('Meters');
- await page.getByLabel('Target distance value').last().fill('5');
- await page.getByLabel('Target distance unit').last().selectOption('Kilometers');
- await page.getByRole('button', { name: 'Add distance target' }).click();
- await page.getByLabel('Split distance value').last().fill('1600');
- await page.getByLabel('Split distance unit').last().selectOption('Meters');
- await page.getByLabel('Target distance value').last().fill('10');
- await page.getByLabel('Target distance unit').last().selectOption('Kilometers');
- await page.getByRole('button', { name: 'Close' }).click();
-
- // Assert workout splits are correct
- await expect(page.getByRole('row').nth(1)).toHaveText('800m Interval' + '2:45.08');
- await expect(page.getByRole('row').nth(2)).toHaveText('1600 m @ 10 km' + '5:58.81');
- await expect(page.getByRole('row')).toHaveCount(3);
+ // Test standard workout results
+ {
+ // Enter input race (2 mi in 10:30)
+ await page.getByLabel('Input race distance value').fill('2');
+ await page.getByLabel('Input race distance unit').selectOption('Miles');
+ await page.getByLabel('Input race duration hours').fill('0');
+ await page.getByLabel('Input race duration minutes').fill('10');
+ await page.getByLabel('Input race duration seconds').fill('30');
+
+ // Assert workout splits are correct
+ await expect(page.getByRole('row').nth(1)).toHaveText('400 m @ 1 mi' + '1:13.45');
+ await expect(page.getByRole('row').nth(3)).toHaveText('1600 m @ 1:00:00' + '5:45.44');
+ await expect(page.getByRole('row')).toHaveCount(5);
+ }
+
+ // Test different calculator options options
+ {
+ // Change prediction model
+ await page.getByText('Advanced Options').click();
+ await page.getByLabel('Prediction model').selectOption('Riegel\'s Model');
+
+ // Assert workout splits are correct
+ await expect(page.getByRole('row').nth(1)).toHaveText('400 m @ 1 mi' + '1:15.10');
+ await expect(page.getByRole('row').nth(3)).toHaveText('1600 m @ 1:00:00' + '5:45.64');
+ await expect(page.getByRole('row')).toHaveCount(5);
+
+ // Change Riegel exponent
+ await page.getByLabel('Riegel Exponent').fill('1.12');
+
+ // Assert workout splits are correct
+ await expect(page.getByRole('row').nth(1)).toHaveText('400 m @ 1 mi' + '1:12.04');
+ await expect(page.getByRole('row').nth(3)).toHaveText('1600 m @ 1:00:00' + '6:17.47');
+ await expect(page.getByRole('row')).toHaveCount(5);
+ }
+
+ // Test modified default target set
+ {
+ // Edit default target set
+ await page.getByRole('button', { name: 'Edit target set' }).click();
+ await page.getByLabel('Target set label').fill('Less-common Workout Targets');
+ await expect(page.getByLabel('Custom target name')).toHaveCount(0);
+ await page.getByLabel('Split distance value').nth(0).fill('401');
+ await page.getByLabel('Target distance value').nth(0).fill('2');
+ await page.getByRole('button', { name: 'Add distance target' }).click();
+ await page.getByLabel('Split distance value').last().fill('1');
+ await page.getByLabel('Split distance unit').last().selectOption('Miles');
+ await page.getByLabel('Target distance value').last().fill('10');
+ await page.getByLabel('Target distance unit').last().selectOption('Kilometers');
+ await page.getByRole('button', { name: 'Add time target' }).click();
+ await page.getByLabel('Split distance value').last().fill('600');
+ await page.getByLabel('Split distance unit').last().selectOption('Meters');
+ await page.getByLabel('Target duration minutes').last().fill('19');
+ await page.getByLabel('Target duration seconds').last().fill('0');
+ await page.getByRole('button', { name: 'Add distance target' }).click();
+ await page.getByLabel('Split distance value').last().fill('2');
+ await page.getByLabel('Split distance unit').last().selectOption('Miles');
+ await page.getByLabel('Target distance value').last().fill('2');
+ await page.getByLabel('Target distance unit').last().selectOption('Miles');
+ await page.getByRole('button', { name: 'Close' }).click();
+
+ // Assert workout splits are correct
+ await expect(page.getByRole('row').nth(1)).toHaveText('401 m @ 2 mi' + '1:18.49');
+ await expect(page.getByRole('row').nth(2)).toHaveText('600 m @ 19:00' + '2:05.14');
+ await expect(page.getByRole('row').nth(4)).toHaveText('1 mi @ 10 km' + '6:00.90');
+ await expect(page.getByRole('row').nth(7)).toHaveText('2 mi' + '10:30.00');
+ await expect(page.getByRole('row')).toHaveCount(8);
+ }
+
+ // Test custom target set (with custom target names)
+ {
+ // Enable target name customization
+ await page.getByLabel('Target name customization').selectOption('Enabled');
+
+ // Create custom target set
+ await page.getByLabel('Selected target set').selectOption('[ Create New Target Set ]');
+ await expect(page.getByRole('row').nth(4)).toHaveText('There aren\'t any targets in this set yet.');
+ await expect(page.getByRole('row')).toHaveCount(5);
+
+ // Edit new target set
+ await expect(page.getByRole('row').nth(1)).toHaveText('There aren\'t any targets in this set yet.');
+ await expect(page.getByLabel('Target set label')).toHaveValue('New target set');
+ await page.getByLabel('Target set label').fill('Workout Target Set #2');
+ await page.getByRole('button', { name: 'Add distance target' }).click();
+ await page.getByLabel('Custom target name').last().fill('800m Interval');
+ await page.getByLabel('Split distance value').last().fill('800');
+ await page.getByLabel('Split distance unit').last().selectOption('Meters');
+ await page.getByLabel('Target distance value').last().fill('5');
+ await page.getByLabel('Target distance unit').last().selectOption('Kilometers');
+ await page.getByRole('button', { name: 'Add distance target' }).click();
+ await page.getByLabel('Split distance value').last().fill('1600');
+ await page.getByLabel('Split distance unit').last().selectOption('Meters');
+ await page.getByLabel('Target distance value').last().fill('10');
+ await page.getByLabel('Target distance unit').last().selectOption('Kilometers');
+ await page.getByRole('button', { name: 'Close' }).click();
+
+ // Assert workout splits are correct
+ await expect(page.getByRole('row').nth(1)).toHaveText('800m Interval' + '2:45.08');
+ await expect(page.getByRole('row').nth(2)).toHaveText('1600 m @ 10 km' + '5:58.81');
+ await expect(page.getByRole('row')).toHaveCount(3);
+ }
// Reload page
await page.reload();
- // Assert workout splits are correct (custom targets and model settings not reset)
- await expect(page.getByRole('row').nth(1)).toHaveText('800m Interval' + '2:45.08');
- await expect(page.getByRole('row').nth(2)).toHaveText('1600 m @ 10 km' + '5:58.81');
- await expect(page.getByRole('row')).toHaveCount(3);
-
- // Switch target set
- await page.getByText('Advanced Options').click();
- await page.getByLabel('Selected target set').selectOption('Less-common Workout Targets');
-
- // Assert workout splits are correct
- await expect(page.getByRole('row').nth(1)).toHaveText('401 m @ 2 mi' + '1:18.49');
- await expect(page.getByRole('row').nth(2)).toHaveText('600 m @ 19:00' + '2:05.14');
- await expect(page.getByRole('row').nth(4)).toHaveText('1 mi @ 10 km' + '6:00.90');
- await expect(page.getByRole('row').nth(7)).toHaveText('2 mi' + '10:30.00');
- await expect(page.getByRole('row')).toHaveCount(8);
-
- // Delete custom target set
- await page.getByLabel('Selected target set').selectOption('Workout Target Set #2');
- await page.getByRole('button', { name: 'Edit target set' }).click();
- await expect(page.getByLabel('Target set label')).toHaveValue('Workout Target Set #2');
- await page.getByRole('button', { name: 'Delete target set' }).click();
-
- // Switch to default target set
- await page.getByLabel('Selected target set').selectOption('Less-common Workout Targets');
-
- // Assert workout splits are correct (back to default target set)
- await expect(page.getByRole('row').nth(1)).toHaveText('401 m @ 2 mi' + '1:18.49');
- await expect(page.getByRole('row').nth(2)).toHaveText('600 m @ 19:00' + '2:05.14');
- await expect(page.getByRole('row').nth(4)).toHaveText('1 mi @ 10 km' + '6:00.90');
- await expect(page.getByRole('row').nth(7)).toHaveText('2 mi' + '10:30.00');
- await expect(page.getByRole('row')).toHaveCount(8);
-
- // Revert target set
- await page.getByRole('button', { name: 'Edit target set' }).click();
- await expect(page.getByLabel('Target set label')).toHaveValue('Less-common Workout Targets');
- await page.getByRole('button', { name: 'Revert target set' }).click();
- await page.getByRole('button', { name: 'Close' }).click();
-
- // Assert paces are correct
- await expect(page.getByRole('row').nth(1)).toHaveText('400 m @ 1 mi' + '1:12.04');
- await expect(page.getByRole('row').nth(3)).toHaveText('1600 m @ 1:00:00' + '6:17.47');
- await expect(page.getByRole('row')).toHaveCount(5);
-
- // Assert title was reset
- await page.getByRole('button', { name: 'Edit target set' }).click();
- await expect(page.getByLabel('Target set label')).toHaveValue('Common Workout Targets');
+ // Assert outputs are still the same
+ {
+ // Assert workout splits are correct (custom targets and model settings not reset)
+ await expect(page.getByRole('row').nth(1)).toHaveText('800m Interval' + '2:45.08');
+ await expect(page.getByRole('row').nth(2)).toHaveText('1600 m @ 10 km' + '5:58.81');
+ await expect(page.getByRole('row')).toHaveCount(3);
+
+ // Switch target set
+ await page.getByText('Advanced Options').click();
+ await page.getByLabel('Selected target set').selectOption('Less-common Workout Targets');
+
+ // Assert workout splits are correct
+ await expect(page.getByRole('row').nth(1)).toHaveText('401 m @ 2 mi' + '1:18.49');
+ await expect(page.getByRole('row').nth(2)).toHaveText('600 m @ 19:00' + '2:05.14');
+ await expect(page.getByRole('row').nth(4)).toHaveText('1 mi @ 10 km' + '6:00.90');
+ await expect(page.getByRole('row').nth(7)).toHaveText('2 mi' + '10:30.00');
+ await expect(page.getByRole('row')).toHaveCount(8);
+ }
+
+ // Test target set deletion and reversion
+ {
+ // Delete custom target set
+ await page.getByLabel('Selected target set').selectOption('Workout Target Set #2');
+ await page.getByRole('button', { name: 'Edit target set' }).click();
+ await expect(page.getByLabel('Target set label')).toHaveValue('Workout Target Set #2');
+ await page.getByRole('button', { name: 'Delete target set' }).click();
+
+ // Switch to default target set
+ await page.getByLabel('Selected target set').selectOption('Less-common Workout Targets');
+
+ // Assert workout splits are correct (back to default target set)
+ await expect(page.getByRole('row').nth(1)).toHaveText('401 m @ 2 mi' + '1:18.49');
+ await expect(page.getByRole('row').nth(2)).toHaveText('600 m @ 19:00' + '2:05.14');
+ await expect(page.getByRole('row').nth(4)).toHaveText('1 mi @ 10 km' + '6:00.90');
+ await expect(page.getByRole('row').nth(7)).toHaveText('2 mi' + '10:30.00');
+ await expect(page.getByRole('row')).toHaveCount(8);
+
+ // Revert target set
+ await page.getByRole('button', { name: 'Edit target set' }).click();
+ await expect(page.getByLabel('Target set label')).toHaveValue('Less-common Workout Targets');
+ await page.getByRole('button', { name: 'Revert target set' }).click();
+ await page.getByRole('button', { name: 'Close' }).click();
+
+ // Assert paces are correct
+ await expect(page.getByRole('row').nth(1)).toHaveText('400 m @ 1 mi' + '1:12.04');
+ await expect(page.getByRole('row').nth(3)).toHaveText('1600 m @ 1:00:00' + '6:17.47');
+ await expect(page.getByRole('row')).toHaveCount(5);
+
+ // Assert title was reset
+ await page.getByRole('button', { name: 'Edit target set' }).click();
+ await expect(page.getByLabel('Target set label')).toHaveValue('Common Workout Targets');
+ }
});