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:
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')
});