running-tools

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

commit a5d8a40f7441b55f79d00a901c6014f9780e309e
parent 870d74eef70abe41c1ff39407bf97afaeb112fb6
Author: Asher Morgan <59518073+ashermorgan@users.noreply.github.com>
Date:   Sat,  7 Sep 2024 08:52:12 -0700

Merge branch 'dev'

Diffstat:
A.env | 2++
M404.html | 42+++++++++++-------------------------------
MCHANGELOG.md | 8++++++++
MREADME.md | 20++++++++++++--------
Mindex.html | 37++++++++++++-------------------------
Mpackage-lock.json | 26+++++++++++++-------------
Mpackage.json | 2+-
Apublic/img/icons/apple-splash-1488x2266.png | 0
Apublic/img/icons/apple-splash-1640x2360.png | 0
Apublic/img/icons/apple-splash-2266x1488.png | 0
Apublic/img/icons/apple-splash-2360x1640.png | 0
Dscripts/deploy.sh | 17-----------------
Msrc/App.vue | 22+---------------------
Msrc/assets/global.css | 22++++++++++++++++++++++
Msrc/utils/races.js | 16++++++++--------
Msrc/utils/targets.js | 2+-
Msrc/views/AboutPage.vue | 4++--
Msrc/views/BatchCalculator.vue | 1+
Mtests/e2e/batch-calculator.spec.js | 4++--
Mtests/e2e/cross-calculator.spec.js | 6+++---
Mtests/e2e/race-calculator.spec.js | 4++--
Mtests/e2e/workout-calculator.spec.js | 2+-
Mtests/unit/views/RaceCalculator.spec.js | 2+-
23 files changed, 103 insertions(+), 136 deletions(-)

diff --git a/.env b/.env @@ -0,0 +1,2 @@ +# No specific domain by default +VITE_API_DOMAIN= diff --git a/404.html b/404.html @@ -9,40 +9,20 @@ <title>404 Not Found - Running Tools</title> <meta name="description" content="A collection of tools for runners and their coaches that calculate splits, predict race times, convert units, and more"> - - <style> - * { - margin: 0px; - padding: 0px; - box-sizing: border-box; - } - body { - font-family: Segoe UI, sans-serif; - text-align: center; - } - header { - background-color: hsl(30, 100%, 50%); - padding: 0.25em; - font-size: 2em; - font-weight: bold; - } - main { - margin: 1em; - } - h1 { - font-size: 1.5em; - } - p { - margin-top: 0.5em; - } - </style> </head> <body> - <header>Running Tools</header> - <main> - <h1>404 Not Found</h1> - <p><a href="%BASE_URL%">Return home</a></p> + <header> + <h1>Running Tools</h1> + </header> + <main style="margin: 1em; text-align: center"> + <h2 style="font-size: 1.5em">404 Not Found</h2> + <p style="margin-top: 0.5em"> + <a href="%BASE_URL%">Return home</a> + </p> </main> + + <!-- no #app div, so styles will be loaded but app will not be mounted --> + <script type="module" src="/src/main.js"></script> </body> </html> diff --git a/CHANGELOG.md b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 1.4.1 - 2024-09-07 + +### Fixed +- Bug that prevented workout split distances from being edited through the Batch + Calculator +- Improved accuracy of the VO2 Max model +- Miscellaneous dark mode issues + ## 1.4.0 - 2024-07-11 ### Added diff --git a/README.md b/README.md @@ -1,33 +1,36 @@ # Running Tools A collection of tools for runners and their coaches. -Try it out [here](https://ashermorgan.github.io/running-tools/). +Try it out [here](https://apps.ashermorgan.net/running-tools/). ## Features -- [Batch Calculator](https://ashermorgan.github.io/running-tools/#/calculate/batch): +- [Batch Calculator](https://apps.ashermorgan.net/running-tools/#/calculate/batch): Create tables of the results of the other calculators over a range of inputs -- [Pace Calculator](https://ashermorgan.github.io/running-tools/#/calculate/paces): +- [Pace Calculator](https://apps.ashermorgan.net/running-tools/#/calculate/paces): Calculate distances and times that are at the same pace -- [Race Calculator](https://ashermorgan.github.io/running-tools/#/calculate/races): +- [Race Calculator](https://apps.ashermorgan.net/running-tools/#/calculate/races): Estimate equivalent results for races of different distances and/or times -- [Split Calculator](https://ashermorgan.github.io/running-tools/#/calculate/splits): +- [Split Calculator](https://apps.ashermorgan.net/running-tools/#/calculate/splits): Find splits, paces, and cumulative times for the segments of a race -- [Unit Calculator](https://ashermorgan.github.io/running-tools/#/calculate/units): +- [Unit Calculator](https://apps.ashermorgan.net/running-tools/#/calculate/units): Convert between different distance, time, speed, and pace units -- [Workout Calculator](https://ashermorgan.github.io/running-tools/#/calculate/workouts): +- [Workout Calculator](https://apps.ashermorgan.net/running-tools/#/calculate/workouts): Estimate target workout splits using previous race results ## Setup Install dependencies + ``` npm install ``` Run development server + ``` npm run dev ``` Run linter and tests + ``` npm run lint npm run test:unit @@ -35,6 +38,7 @@ npm run test:e2e ``` Build for production + ``` -npm run build +VITE_API_DOMAIN=https://example.com BASE_URL=/running-tools/ npm run build ``` diff --git a/index.html b/index.html @@ -12,8 +12,8 @@ <meta property="og:title" content="Running Tools"/> <meta property="og:description" content="A collection of tools for runners and their coaches that calculate splits, predict race times, convert units, and more"/> <meta property="og:type" content="website"/> - <meta property="og:url" content="https://ashermorgan.github.io/running-tools/"/> - <meta property="og:image" content="https://ashermorgan.github.io/running-tools/img/icons/open-graph-1280x640.png"/> + <meta property="og:url" content="%VITE_API_DOMAIN%%BASE_URL%"/> + <meta property="og:image" content="%VITE_API_DOMAIN%%BASE_URL%img/icons/open-graph-1280x640.png"/> <!-- Microsoft tags --> <meta name="msapplication-square70x70logo" content="%BASE_URL%img/icons/mstile-icon-128x128.png"> @@ -32,6 +32,10 @@ <link rel="apple-touch-startup-image" href="/img/icons/apple-splash-2388x1668.png" media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"> <link rel="apple-touch-startup-image" href="/img/icons/apple-splash-1536x2048.png" media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"> <link rel="apple-touch-startup-image" href="/img/icons/apple-splash-2048x1536.png" media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"> + <link rel="apple-touch-startup-image" href="/img/icons/apple-splash-1488x2266.png" media="(device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"> + <link rel="apple-touch-startup-image" href="/img/icons/apple-splash-2266x1488.png" media="(device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"> + <link rel="apple-touch-startup-image" href="/img/icons/apple-splash-1640x2360.png" media="(device-width: 820px) and (device-height: 1180px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"> + <link rel="apple-touch-startup-image" href="/img/icons/apple-splash-2360x1640.png" media="(device-width: 820px) and (device-height: 1180px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"> <link rel="apple-touch-startup-image" href="/img/icons/apple-splash-1668x2224.png" media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"> <link rel="apple-touch-startup-image" href="/img/icons/apple-splash-2224x1668.png" media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"> <link rel="apple-touch-startup-image" href="/img/icons/apple-splash-1620x2160.png" media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"> @@ -59,29 +63,12 @@ </head> <body> <noscript> - <style> - * { - margin: 0px; - padding: 0px; - box-sizing: border-box; - } - body { - font-family: Segoe UI, sans-serif; - text-align: center; - } - header { - background-color: hsl(30, 100%, 50%); - padding: 0.25em; - font-size: 2em; - font-weight: bold; - } - p { - margin: 1em; - font-weight: bold; - } - </style> - <header>Running Tools</header> - <p>Running Tools requires JavaScript. Please enable it to continue.</p> + <header> + <h1>Running Tools</h1> + </header> + <p style="margin: 1em; font-weight: bold; text-align: center"> + Running Tools requires JavaScript. Please enable it to continue. + </p> </noscript> <!-- built files will be auto injected --> diff --git a/package-lock.json b/package-lock.json @@ -1,12 +1,12 @@ { "name": "running-tools", - "version": "1.4.0", + "version": "1.4.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "running-tools", - "version": "1.4.0", + "version": "1.4.1", "dependencies": { "feather-icons": "^4.29.2", "vue": "^3.4.27", @@ -3296,9 +3296,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001596", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001596.tgz", - "integrity": "sha512-zpkZ+kEr6We7w63ORkoJ2pOfBwBkY/bJrG/UZ90qNb45Isblu8wzDgevEOrRL1r9dWayHjYiiyCMEXPn4DweGQ==", + "version": "1.0.30001658", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001658.tgz", + "integrity": "sha512-N2YVqWbJELVdrnsW5p+apoQyYt51aBMSsBZki1XZEfeBCexcM/sf4xiAHcXQBkuOwJBXtWF7aW1sYX6tKebPHw==", "dev": true, "funding": [ { @@ -5894,12 +5894,12 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -6663,13 +6663,13 @@ } }, "node_modules/pwa-asset-generator": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/pwa-asset-generator/-/pwa-asset-generator-6.3.1.tgz", - "integrity": "sha512-iGbUvBH1T+yysr/31OsJyIbqrDUPOHZPOvCODgQmiBkbfALxF9Wqmzc9ddYYe0x07/qAurc54IAt4rDR+vaQDQ==", + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/pwa-asset-generator/-/pwa-asset-generator-6.3.2.tgz", + "integrity": "sha512-mLdzO3+R79/t/j0Ng6thZR8JDsJug/4jAXeOpgV40uEQb0ZSOFT6d6AYsSCRs0Y4eI8ywjnrkXgz/qxbVLvXJw==", "dev": true, "dependencies": { "chalk": "^4.1.2", - "cheerio": "^1.0.0-rc.12", + "cheerio": "1.0.0-rc.12", "chrome-launcher": "^0.15.0", "find-process": "^1.4.7", "lodash.isequal": "^4.5.0", diff --git a/package.json b/package.json @@ -1,6 +1,6 @@ { "name": "running-tools", - "version": "1.4.0", + "version": "1.4.1", "description": "A collection of tools for runners and their coaches that calculate splits, predict race times, convert units, and more", "private": true, "type": "module", diff --git a/public/img/icons/apple-splash-1488x2266.png b/public/img/icons/apple-splash-1488x2266.png Binary files differ. diff --git a/public/img/icons/apple-splash-1640x2360.png b/public/img/icons/apple-splash-1640x2360.png Binary files differ. diff --git a/public/img/icons/apple-splash-2266x1488.png b/public/img/icons/apple-splash-2266x1488.png Binary files differ. diff --git a/public/img/icons/apple-splash-2360x1640.png b/public/img/icons/apple-splash-2360x1640.png Binary files differ. diff --git a/scripts/deploy.sh b/scripts/deploy.sh @@ -1,17 +0,0 @@ -#!/usr/bin/env sh - -# https://cli.vuejs.org/guide/deployment.html#github-pages - -# abort on errors -set -e - -BASE_URL=/running-tools/ npm run build - -cd dist - -git init -git add -A -git commit -m 'deploy' -git push -f git@github.com:ashermorgan/running-tools.git master:gh-pages - -cd - diff --git a/src/App.vue b/src/App.vue @@ -28,37 +28,17 @@ import VueFeather from 'vue-feather'; </template> <style scoped> -header { - background-color: var(--theme); - padding: 0.5em; - display: grid; - grid-template-columns: 2em 1fr auto 1fr 2em; - grid-template-rows: auto; -} header a { grid-column: 1; margin: auto; height: 2em; width: 2em; } -::v-deep(.feather-chevron-left) { +header ::v-deep(.feather-chevron-left) { padding: 0em; color: #000000; } -h1 { - grid-column: 3; - font-size: 2em; - font-weight: bold; - text-decoration: none; - color: #000000; -} #route-content { margin: 1em; } -@media only screen and (max-width: 450px) { - /* adjust title size to fit small devices */ - h1 { - font-size: 7vw; - } -} </style> diff --git a/src/assets/global.css b/src/assets/global.css @@ -122,6 +122,28 @@ input:invalid:not(:focus) { border: 1px solid var(--error); } +/* basic page header style */ +header { + background-color: var(--theme); + padding: 0.5em; + display: grid; + grid-template-columns: 2em 1fr auto 1fr 2em; + grid-template-rows: auto; +} +header h1 { + grid-column: 3; + font-size: 2em; + font-weight: bold; + text-decoration: none; + color: #000000; +} +@media only screen and (max-width: 450px) { + /* adjust title size to fit small devices */ + header h1 { + font-size: 7vw; + } +} + /* light/default theme */ :root { /* The theme color of the app */ diff --git a/src/utils/races.js b/src/utils/races.js @@ -5,19 +5,18 @@ * @param {Function} method The function * @param {Function} derivative The function derivative * @param {Number} precision The acceptable precision - * @param {Number} iterations The maximum number of iterations * @returns {Number} The refined estimate */ -function NewtonsMethod(initialEstimate, target, method, derivative, precision, iterations = 500) { +function NewtonsMethod(initialEstimate, target, method, derivative, precision) { // Initialize estimate let estimate = initialEstimate; let estimateValue; - for (let i = 0; i < iterations; i += 1) { + for (let i = 0; i < 500; i += 1) { // Evaluate function at estimate estimateValue = method(estimate); - // Check if estimate is close enough + // Check if estimate is close enough (usually occurs way before i = 500) if (Math.abs(target - estimateValue) < precision) { break; } @@ -174,6 +173,7 @@ const PurdyPointsModel = { /* * Methods that implement the VO2 Max race prediction model * http://run-down.com/statistics/calcs_explained.php + * https://vdoto2.com/Calculator */ const VO2MaxModel = { /** @@ -185,7 +185,7 @@ const VO2MaxModel = { getVO2(d, t) { const minutes = t / 60; const v = d / minutes; - const result = -4.6 + (0.182 * v) + (0.000104 * (v ** 2)); + const result = -4.6 + (0.182258 * v) + (0.000104 * (v ** 2)); return result; }, @@ -196,7 +196,7 @@ const VO2MaxModel = { */ getVO2Percentage(t) { const minutes = t / 60; - const result = 0.8 + (0.189 * Math.exp(-0.0128 * minutes)) + (0.299 * Math.exp(-0.193 + const result = 0.8 + (0.189439 * Math.exp(-0.012778 * minutes)) + (0.298956 * Math.exp(-0.193261 * minutes)); return result; }, @@ -237,7 +237,7 @@ const VO2MaxModel = { */ predictTime(d1, t1, d2) { // Calculate input VO2 max - const inputVO2 = VO2MaxModel.getVO2Max(d1, t1); + const inputVO2Max = VO2MaxModel.getVO2Max(d1, t1); // Initialize estimate let estimate = (t1 * d2) / d1; @@ -245,7 +245,7 @@ const VO2MaxModel = { // Refine estimate const method = (x) => VO2MaxModel.getVO2Max(d2, x); const derivative = (x) => VO2MaxModel.VO2MaxTimeDerivative(d2, x); - estimate = NewtonsMethod(estimate, inputVO2, method, derivative, 0.0001); + estimate = NewtonsMethod(estimate, inputVO2Max, method, derivative, 0.0001); // Return estimate return estimate; diff --git a/src/utils/targets.js b/src/utils/targets.js @@ -104,7 +104,7 @@ export const defaultTargetSets = { }, { splitValue: 1, splitUnit: 'miles', - type: 'time', time: 7200, + type: 'distance', distanceValue: 1, distanceUnit: 'marathons', }, ], }, diff --git a/src/views/AboutPage.vue b/src/views/AboutPage.vue @@ -133,8 +133,8 @@ The Workout Calculator is useful for answering questions like: </p> <ul class="questions"> - <li>If I raced a 5K in 20:00, how fast should I run 400m intervals at mile pace? (about 1:27)</li> - <li>If I raced a mile in 5:00, what is my "threshold" (~1 hr race) pace? (about 5:50 / mi)</li> + <li>If I raced a 5K in 20:00, how fast should I run 400m reps at mile pace? (about 1:27)</li> + <li>If I raced a mile in 5:00, what is my "threshold" (~1 hr race) pace? (about 5:52 / mi)</li> </ul> <p> <strong>Note:</strong> Results are just estimated race splits that are helpful for estimating diff --git a/src/views/BatchCalculator.vue b/src/views/BatchCalculator.vue @@ -37,6 +37,7 @@ <div> Target Set: <target-set-selector v-model:selectedTargetSet="selectedTargetSet" + :setType="options.calculator === 'workout' ? 'workout' : 'standard'" v-model:targetSets="targetSets" :default-unit-system="defaultUnitSystem"/> </div> <race-options v-if="options.calculator !== 'pace'" v-model="advancedOptions"/> diff --git a/tests/e2e/batch-calculator.spec.js b/tests/e2e/batch-calculator.spec.js @@ -36,7 +36,7 @@ test('Batch calculator', async ({ page }) => { await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:41.21'); 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:16.91'); + await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:16.90'); await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(5); await expect(page.getByRole('row')).toHaveCount(16); @@ -107,7 +107,7 @@ test('Batch calculator', async ({ page }) => { await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:14.60'); 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:43.61'); + await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('2:43.62'); await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(17); await expect(page.getByRole('row')).toHaveCount(16); diff --git a/tests/e2e/cross-calculator.spec.js b/tests/e2e/cross-calculator.spec.js @@ -149,10 +149,10 @@ test('Cross-calculator', async ({ page }) => { 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.93'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:41.92'); 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:16.98'); + await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:16.97'); await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(5); await expect(page.getByRole('row')).toHaveCount(16); @@ -201,6 +201,6 @@ test('Cross-calculator', async ({ page }) => { // 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.58'); + 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/race-calculator.spec.js b/tests/e2e/race-calculator.spec.js @@ -25,7 +25,7 @@ test('Race Calculator', async ({ page }) => { // 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.58' + '5:24 / 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 @@ -40,7 +40,7 @@ test('Race Calculator', async ({ page }) => { // 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.58' + '3:22 / 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 diff --git a/tests/e2e/workout-calculator.spec.js b/tests/e2e/workout-calculator.spec.js @@ -24,7 +24,7 @@ test('Workout Calculator', async ({ page }) => { // 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').nth(3)).toHaveText('1600 m @ 1:00:00' + '5:45.43'); await expect(page.getByRole('row')).toHaveCount(5); // Change prediction model diff --git a/tests/unit/views/RaceCalculator.spec.js b/tests/unit/views/RaceCalculator.spec.js @@ -119,7 +119,7 @@ test('should correctly calculate race statistics', async () => { // Assert race statistics are correct expect(purdyPoints).to.equal('Purdy Points: 454.5'); - expect(vo2).to.equal('V̇O₂: 47.4 ml/kg/min (95.3% of max)') + expect(vo2).to.equal('V̇O₂: 47.5 ml/kg/min (95.3% of max)') expect(vo2Max).to.equal('V̇O₂ Max: 49.8 ml/kg/min') });