commit e4fc78a1aa40d710bb01979e611992af15d5e422
parent 583c500e154d0a25e26c579e4760bdadae4235ba
Author: ashermorgan <59518073+ashermorgan@users.noreply.github.com>
Date: Sun, 22 Aug 2021 14:22:42 -0700
Implement race calculator
Diffstat:
4 files changed, 199 insertions(+), 2 deletions(-)
diff --git a/src/router/index.js b/src/router/index.js
@@ -2,6 +2,7 @@ import Vue from 'vue';
import VueRouter from 'vue-router';
import Home from '../views/Home.vue';
import PaceCalculator from '../views/PaceCalculator.vue';
+import RaceCalculator from '../views/RaceCalculator.vue';
import UnitCalculator from '../views/UnitCalculator.vue';
Vue.use(VueRouter);
@@ -34,6 +35,15 @@ const routes = [
},
},
{
+ path: '/calculate/races',
+ name: 'calculate-races',
+ component: RaceCalculator,
+ meta: {
+ title: 'Race Calculator',
+ back: 'home',
+ },
+ },
+ {
path: '/calculate/units',
name: 'calculate-units',
component: UnitCalculator,
diff --git a/src/views/Home.vue b/src/views/Home.vue
@@ -4,12 +4,17 @@
A collection of tools for runners that calculate splits, convert units, and more
</p>
<div class="calculators">
- <router-link :to="{ name: 'calculate-paces' }" v-slot="{ navigate }">
+ <router-link :to="{ name: 'calculate-paces' }" v-slot="{ navigate }" custom>
<button @click="navigate">
Pace Calculator
</button>
</router-link>
- <router-link :to="{ name: 'calculate-units' }" v-slot="{ navigate }">
+ <router-link :to="{ name: 'calculate-races' }" v-slot="{ navigate }" custom>
+ <button @click="navigate">
+ Race Calculator
+ </button>
+ </router-link>
+ <router-link :to="{ name: 'calculate-units' }" v-slot="{ navigate }" custom>
<button @click="navigate">
Unit Calculator
</button>
diff --git a/src/views/RaceCalculator.vue b/src/views/RaceCalculator.vue
@@ -0,0 +1,147 @@
+<template>
+ <div class="race-calculator">
+ <div class="input">
+ Running
+ <decimal-input v-model="inputDistance" aria-label="Distance value" :min="0" :digits="2"/>
+ <select v-model="inputUnit" aria-label="Distance unit">
+ <option v-for="(value, key) in distanceUnits" :key="key" :value="key">
+ {{ value }}
+ </option>
+ </select>
+ in
+ <time-input v-model="inputTime"/>
+ </div>
+
+ <p>is approximately equivalent to running</p>
+
+ <time-table class="output" :calculate-result="predictTime" :default-targets="defaultTargets"/>
+ </div>
+</template>
+
+<script>
+import raceUtils from '@/utils/races';
+import unitUtils from '@/utils/units';
+
+import DecimalInput from '@/components/DecimalInput.vue';
+import TimeInput from '@/components/TimeInput.vue';
+import TimeTable from '@/components/TimeTable.vue';
+
+export default {
+ name: 'RaceCalculator',
+
+ components: {
+ DecimalInput,
+ TimeInput,
+ TimeTable,
+ },
+
+ data() {
+ return {
+ /**
+ * The input distance value
+ */
+ inputDistance: 5,
+
+ /**
+ * The input distance unit
+ */
+ inputUnit: 'kilometers',
+
+ /**
+ * The input time value
+ */
+ inputTime: 20 * 60,
+
+ /**
+ * The names of the distance units
+ */
+ distanceUnits: unitUtils.DISTANCE_UNIT_NAMES,
+
+ /**
+ * The default output targets
+ */
+ defaultTargets: [
+ { distanceValue: 400, distanceUnit: 'meters' },
+ { distanceValue: 800, distanceUnit: 'meters' },
+ { distanceValue: 1000, distanceUnit: 'meters' },
+ { distanceValue: 1200, distanceUnit: 'meters' },
+ { distanceValue: 1500, distanceUnit: 'meters' },
+ { distanceValue: 1600, distanceUnit: 'meters' },
+ { distanceValue: 3200, distanceUnit: 'meters' },
+
+ { distanceValue: 1, distanceUnit: 'miles' },
+ { distanceValue: 2, distanceUnit: 'miles' },
+ { distanceValue: 3, distanceUnit: 'miles' },
+ { distanceValue: 5, distanceUnit: 'miles' },
+ { distanceValue: 10, distanceUnit: 'miles' },
+
+ { distanceValue: 3, distanceUnit: 'kilometers' },
+ { distanceValue: 5, distanceUnit: 'kilometers' },
+ { distanceValue: 8, distanceUnit: 'kilometers' },
+ { distanceValue: 10, distanceUnit: 'kilometers' },
+ { distanceValue: 15, distanceUnit: 'kilometers' },
+
+ { distanceValue: 0.5, distanceUnit: 'marathons' },
+ { distanceValue: 1, distanceUnit: 'marathons' },
+ ],
+ };
+ },
+
+ methods: {
+ /**
+ * Predict race times from a target
+ * @param {Object} target The target
+ * @returns {Object} The result
+ */
+ predictTime(target) {
+ // Convert distances into meters
+ const d1 = unitUtils.convertDistance(this.inputDistance, this.inputUnit,
+ unitUtils.DISTANCE_UNITS.meters);
+ const d2 = unitUtils.convertDistance(target.distanceValue, target.distanceUnit,
+ unitUtils.DISTANCE_UNITS.meters);
+
+ // Get prediction
+ const time = raceUtils.AverageFormula(d1, this.inputTime, d2);
+
+ // Return result
+ return {
+ distanceValue: target.distanceValue,
+ distanceUnit: target.distanceUnit,
+ time,
+ };
+ },
+ },
+};
+</script>
+
+<style scoped>
+/* container */
+.race-calculator {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+/* calculator input */
+.input {
+ text-align: center;
+ margin-bottom: 5px;
+}
+.input>* {
+ margin-bottom: 5px; /* adds space between wrapped lines */
+}
+.input select {
+ margin-left: 5px;
+}
+
+/* calculator output */
+.output {
+ margin-top: 10px;
+}
+@media only screen and (max-width: 500px) {
+ .output {
+ width: 100%;
+ min-width: 0px;
+ }
+}
+</style>
diff --git a/tests/unit/RaceCalculator.spec.js b/tests/unit/RaceCalculator.spec.js
@@ -0,0 +1,35 @@
+/* eslint-disable no-underscore-dangle */
+
+import { expect } from 'chai';
+import { shallowMount } from '@vue/test-utils';
+import raceUtils from '@/utils/races';
+import RaceCalculator from '@/views/RaceCalculator.vue';
+
+describe('RaceCalculator.vue', () => {
+ it('should correctly predict race times', async () => {
+ // Initialize component
+ const wrapper = shallowMount(RaceCalculator);
+
+ // Override input values
+ await wrapper.setData({
+ inputDistance: 5,
+ inputUnit: 'kilometers',
+ inputTime: 20 * 60,
+ });
+
+ // Predict race times
+ const result = wrapper.vm.predictTime({
+ distanceValue: 10,
+ distanceUnit: 'kilometers',
+ });
+
+ // Assert result is correct
+ const riegel = raceUtils.RiegelFormula(5000, 20 * 60, 10000);
+ const cameron = raceUtils.CameronFormula(5000, 20 * 60, 10000);
+ expect(result).to.deep.equal({
+ distanceValue: 10,
+ distanceUnit: 'kilometers',
+ time: (riegel + cameron) / 2,
+ });
+ });
+});