running-tools

A collection of tools for runners and their coaches
git clone https://git.ashermorgan.net/running-tools/
Log | Files | Refs | README

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:
Mtests/e2e/batch-calculator.spec.js | 326++++++++++++++++++++++++++++++++++++++++---------------------------------------
Mtests/e2e/cross-calculator.spec.js | 2009++++++++++++++++++++++++++++++++++++++++---------------------------------------
Mtests/e2e/pace-calculator.spec.js | 236++++++++++++++++++++++++++++++++++++++++---------------------------------------
Mtests/e2e/race-calculator.spec.js | 274+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Mtests/e2e/split-calculator.spec.js | 365++++++++++++++++++++++++++++++++++++++++---------------------------------------
Mtests/e2e/unit-calculator.spec.js | 102+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Mtests/e2e/workout-calculator.spec.js | 295+++++++++++++++++++++++++++++++++++++++++--------------------------------------
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'); + } });