commit bebba6bd1ebdcd83b8cd20d80e3fbed51db5d616
parent 2bb3da51e0a295e2d450d8dbeef4cc8546253030
Author: ashermorgan <59518073+ashermorgan@users.noreply.github.com>
Date: Mon, 13 Nov 2023 20:52:05 -0800
Merge pull request #6 from ashermorgan/global-target-sets
Implement global target sets
Diffstat:
15 files changed, 925 insertions(+), 392 deletions(-)
diff --git a/src/App.vue b/src/App.vue
@@ -43,8 +43,6 @@ header a {
}
::v-deep(.feather-chevron-left) {
padding: 0em;
- height: 2em;
- width: 2em;
color: #000000;
}
h1 {
diff --git a/src/assets/global.css b/src/assets/global.css
@@ -40,14 +40,11 @@ table {
table th, table td {
padding: 0.2em;
}
-table button.icon {
- height: 2em;
- width: 2em;
-}
/* empty table message */
table .empty-message td {
text-align: center !important;
+ padding: 0.5em;
}
table .empty-message svg {
height: 1em;
@@ -64,8 +61,8 @@ table .empty-message svg {
vertical-align: middle;
}
.icon svg {
- width: 100%;
- height: 100%;
+ height: 2em;
+ width: 2em;
padding: 0.3em;
}
diff --git a/src/assets/target-calculator.css b/src/assets/target-calculator.css
@@ -0,0 +1,42 @@
+/* container */
+.calculator {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+/* headings */
+h2 {
+ font-size: 1.3em;
+ margin-bottom: 0.2em;
+}
+* + h2 {
+ margin-top: 0.5em;
+}
+
+/* calculator input */
+.input>* {
+ margin-bottom: 5px; /* adds space between wrapped lines */
+}
+.input select {
+ margin-left: 5px;
+}
+
+/* target set */
+.target-set {
+ margin-bottom: 5px;
+}
+.target-set button {
+ margin-left: 3px;
+}
+
+/* calculator output */
+.output {
+ min-width: 300px;
+}
+@media only screen and (max-width: 500px) {
+ .output {
+ width: 100%;
+ min-width: 0px;
+ }
+}
diff --git a/src/components/Modal.vue b/src/components/Modal.vue
@@ -0,0 +1,45 @@
+<template>
+ <div class="modal">
+ <div class="backdrop"></div>
+ <div class="content-container">
+ <div class="content">
+ <slot></slot>
+ </div>
+ </div>
+ </div>
+</template>
+
+<script>
+export default {
+ name: 'Modal',
+};
+</script>
+
+<style scoped>
+.modal .backdrop {
+ position: fixed;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ z-index: 1000;
+ background-color: #00000080;
+}
+.modal .content-container {
+ position: fixed;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ z-index: 1010;
+ overflow: scroll;
+}
+
+.modal .content {
+ max-width: 500px;
+ margin: 75px auto 1em;
+ padding: 1em;
+ background-color: var(--background1);
+ border-radius: 10px;
+}
+</style>
diff --git a/src/components/SimpleTargetTable.vue b/src/components/SimpleTargetTable.vue
@@ -1,6 +1,6 @@
<template>
<div class="simple-target-table">
- <table class="results" v-show="!inEditMode">
+ <table class="results">
<thead>
<tr>
<th>Distance</th>
@@ -8,12 +8,6 @@
<th>Time</th>
<th v-if="showPace">Pace</th>
-
- <th>
- <button class="icon" title="Edit Targets" @click="inEditMode=true" v-blur>
- <vue-feather type="edit"/>
- </button>
- </th>
</tr>
</thead>
@@ -24,11 +18,11 @@
{{ distanceUnits[item.distanceUnit].symbol }}
</td>
- <td :colspan="showPace ? 1 : 2" :class="item.result === 'time' ? 'result' : ''">
+ <td :class="item.result === 'time' ? 'result' : ''">
{{ formatDuration(item.time, 3, 2, item.result === 'time') }}
</td>
- <td v-if="showPace" colspan="2">
+ <td v-if="showPace">
{{ formatDuration(getPace(item), 3, 0, true) }}
/ {{ distanceUnits[getDefaultDistanceUnit()].symbol }}
</td>
@@ -36,17 +30,11 @@
<tr v-if="results.length === 0" class="empty-message">
<td colspan="4">
- There aren't any targets yet,<br>
- click
- <vue-feather type="edit"/>
- to edit the list of targets
+ There aren't any targets in this set yet.
</td>
</tr>
</tbody>
</table>
-
- <target-editor v-show="inEditMode" v-model="targets" @close="inEditMode=false"
- @reset="resetTargets"/>
</div>
</template>
@@ -54,19 +42,14 @@
import VueFeather from 'vue-feather';
import formatUtils from '@/utils/format';
-import storage from '@/utils/localStorage';
-import targetUtils from '@/utils/targets';
import unitUtils from '@/utils/units';
-import TargetEditor from '@/components/TargetEditor.vue';
-
import blur from '@/directives/blur';
export default {
name: 'SimpleTargetTable',
components: {
- TargetEditor,
VueFeather,
},
@@ -84,22 +67,14 @@ export default {
},
/**
- * The default targets
+ * The target set
*/
- defaultTargets: {
+ targets: {
type: Array,
default: () => [],
},
/**
- * The localStorage key for the list of targets
- */
- storageKey: {
- type: String,
- default: null,
- },
-
- /**
* Whether to show result paces
*/
showPace: {
@@ -129,16 +104,6 @@ export default {
* The getDefaultDistanceUnit method
*/
getDefaultDistanceUnit: unitUtils.getDefaultDistanceUnit,
-
- /**
- * Whether the table is in edit mode
- */
- inEditMode: false,
-
- /**
- * The target table targets
- */
- targets: storage.get(this.storageKey, this.defaultTargets),
};
},
@@ -162,40 +127,8 @@ export default {
},
},
- watch: {
- /**
- * Sort targets
- */
- inEditMode() {
- this.targets = targetUtils.sort(this.targets);
- },
-
- /**
- * Save targets
- */
- targets: {
- handler(newValue) {
- if (this.storageKey !== null) {
- storage.set(this.storageKey, newValue);
- }
- },
- deep: true,
- },
- },
-
methods: {
/**
- * Restore the default targets
- */
- resetTargets() {
- // Clone default targets array
- this.targets = JSON.parse(JSON.stringify(this.defaultTargets));
-
- // Sort targets
- this.targets = targetUtils.sort(this.targets);
- },
-
- /**
* Get the pace of a result
* @param {Object} result The result
*/
@@ -204,21 +137,11 @@ export default {
unitUtils.getDefaultDistanceUnit());
},
},
-
- /**
- * Close edit targets table
- */
- deactivated() {
- this.inEditMode = false;
- },
};
</script>
<style scoped>
/* target table */
-.results th:last-child {
- text-align: right;
-}
.results .result {
font-weight: bold;
}
diff --git a/src/components/TargetEditor.vue b/src/components/TargetEditor.vue
@@ -2,12 +2,12 @@
<table class="target-editor">
<thead>
<tr>
- <th>Edit Targets</th>
+ <th>
+ Edit
+ <input v-model="internalValue.name" placeholder="Target set name"/>
+ </th>
<th>
- <button class="icon" title="Reset Targets" @click="reset" v-blur>
- <vue-feather type="rotate-ccw"/>
- </button>
<button class="icon" title="Close" @click="close" v-blur>
<vue-feather type="x"/>
</button>
@@ -16,7 +16,7 @@
</thead>
<tbody>
- <tr v-for="(item, index) in internalValue" :key="index">
+ <tr v-for="(item, index) in internalValue.targets" :key="index">
<td v-if="item.result === 'time'">
<decimal-input v-model="item.distanceValue" aria-label="Distance Value"
:min="0" :digits="2"/>
@@ -38,9 +38,9 @@
</td>
</tr>
- <tr v-if="internalValue.length === 0" class="empty-message">
+ <tr v-if="internalValue.targets.length === 0" class="empty-message">
<td colspan="2">
- There aren't any targets yet
+ There aren't any targets in this set yet
</td>
</tr>
</tbody>
@@ -63,6 +63,7 @@
<script>
import VueFeather from 'vue-feather';
+import targetUtils from '@/utils/targets';
import unitUtils from '@/utils/units';
import DecimalInput from '@/components/DecimalInput.vue';
@@ -88,8 +89,8 @@ export default {
* The targets
*/
modelValue: {
- type: Array,
- default: () => [],
+ type: Object,
+ default: JSON.parse(JSON.stringify(targetUtils.defaultTargetSet)),
},
/**
@@ -141,14 +142,6 @@ export default {
methods: {
/**
- * Restore the default targets
- */
- reset() {
- // Emit reset event
- this.$emit('reset');
- },
-
- /**
* Close the target editor
*/
close() {
@@ -160,7 +153,7 @@ export default {
* Add a new distance based target
*/
addDistanceTarget() {
- this.internalValue.push({
+ this.internalValue.targets.push({
result: 'time',
distanceValue: 1,
distanceUnit: unitUtils.getDefaultDistanceUnit(),
@@ -171,7 +164,7 @@ export default {
* Add a new time based target
*/
addTimeTarget() {
- this.internalValue.push({
+ this.internalValue.targets.push({
result: 'distance',
time: 600,
});
@@ -182,7 +175,7 @@ export default {
* @param {Number} index The index of the target
*/
removeTarget(index) {
- this.internalValue.splice(index, 1);
+ this.internalValue.targets.splice(index, 1);
},
},
};
diff --git a/src/components/TargetSetEditor.vue b/src/components/TargetSetEditor.vue
@@ -0,0 +1,221 @@
+<template>
+ <div class="target-set-editor">
+ <table v-show="selectedTargetSet === null">
+ <thead>
+ <tr>
+ <th>
+ Edit Target Sets
+ </th>
+ <th>
+ <button class="icon" title="Restore Default Sets" @click="reset" v-blur>
+ <vue-feather type="rotate-ccw"/>
+ </button>
+ <button class="icon" title="Close" @click="close" v-blur>
+ <vue-feather type="x"/>
+ </button>
+ </th>
+ </tr>
+ </thead>
+
+ <tbody>
+ <tr v-for="(item, key) in internalValue" :key="key">
+ <td>
+ {{ item.name }}
+ </td>
+ <td>
+ <button class="icon" title="Edit Set" @click="editTargetSet(key)" v-blur>
+ <vue-feather type="edit"/>
+ </button>
+ <button class="icon" title="Delete Set" @click="removeTargetSet(key)" v-blur>
+ <vue-feather type="trash-2"/>
+ </button>
+ </td>
+ </tr>
+
+ <tr v-if="Object.keys(internalValue).length === 0" class="empty-message">
+ <td colspan="2">
+ There aren't any target sets yet
+ </td>
+ </tr>
+ </tbody>
+
+ <tfoot>
+ <tr>
+ <td colspan="2">
+ <button title="Add Target Set" @click="addTargetSet" v-blur>
+ Add Target Set
+ </button>
+ </td>
+ </tr>
+ </tfoot>
+ </table>
+
+ <target-editor v-model="internalValue[selectedTargetSet]" v-if="selectedTargetSet !== null"
+ @close="selectedTargetSet = null"/>
+
+ <p v-if="selectedTargetSet !== null">
+ Note: time targets are ignored by the Split Calculator
+ </p>
+ </div>
+</template>
+
+<script>
+import VueFeather from 'vue-feather';
+
+import storage from '@/utils/localStorage';
+import targetUtils from '@/utils/targets';
+import unitUtils from '@/utils/units';
+
+import DecimalInput from '@/components/DecimalInput.vue';
+import TargetEditor from '@/components/TargetEditor.vue';
+import TimeInput from '@/components/TimeInput.vue';
+
+import blur from '@/directives/blur';
+
+export default {
+ name: 'TargetSetEditor',
+
+ components: {
+ DecimalInput,
+ TargetEditor,
+ TimeInput,
+ VueFeather,
+ },
+
+ directives: {
+ blur,
+ },
+
+ data() {
+ return {
+ /**
+ * The internal value
+ */
+ internalValue: storage.get('target-sets', targetUtils.defaultTargetSets),
+
+ /**
+ * The target set currently being edited
+ */
+ selectedTargetSet: null,
+
+ /**
+ * The distance units
+ */
+ distanceUnits: unitUtils.DISTANCE_UNITS,
+ };
+ },
+
+ watch: {
+ /**
+ * Save target sets
+ */
+ internalValue: {
+ deep: true,
+ handler(newValue) {
+ storage.set('target-sets', newValue);
+ },
+ },
+
+ /**
+ * Sort current target set
+ */
+ selectedTargetSet(newValue, oldValue) {
+ let value = newValue !== null ? newValue : oldValue;
+ this.internalValue[value].targets = targetUtils.sort(this.internalValue[value].targets);
+ },
+ },
+
+ methods: {
+ /**
+ * Restore the default target sets
+ */
+ reset() {
+ let old_sets = this.internalValue;
+ this.internalValue = JSON.parse(JSON.stringify(targetUtils.defaultTargetSets));
+ for (let key in old_sets) {
+ if (!this.internalValue.hasOwnProperty(key)) {
+ this.internalValue[key] = old_sets[key];
+ }
+ }
+ },
+
+ /**
+ * Close the target editor
+ */
+ close() {
+ // Emit close event
+ this.$emit('close');
+ },
+
+ /**
+ * Add a new target set
+ */
+ addTargetSet() {
+ let key = Date.now().toString()
+ this.internalValue[key] = {
+ name: 'New target set',
+ targets: [],
+ }
+ this.editTargetSet(key);
+ },
+
+ /**
+ * Edit a target set
+ */
+ editTargetSet(key) {
+ this.selectedTargetSet = key;
+ },
+
+ /**
+ * Remove a target set
+ */
+ removeTargetSet(key) {
+ delete this.internalValue[key]
+ },
+ },
+
+ /**
+ * Close target editor
+ */
+ deactivated() {
+ this.selectedTargetSet = null;
+ },
+};
+</script>
+
+<style scoped>
+/* container */
+.target-set-editor {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+.target-set-editor table {
+ max-width: 500px;
+}
+h2 {
+ font-size: 1.3em;
+ margin-bottom: 0.2em;
+}
+
+/* tables */
+.target-set-editor table th:last-child, .target-set-editor table td:last-child {
+ text-align: right;
+}
+.target-set-editor table td select {
+ margin-left: 0.2em;
+ width: 8em;
+}
+.target-set-editor table tfoot td {
+ text-align: center !important;
+ padding: 0.5em 0.2em;
+}
+.target-set-editor table tfoot button {
+ margin: 0.5em;
+}
+
+/* note about split calculator */
+.target-set-editor p {
+ margin-top: 0.5em;
+}
+</style>
diff --git a/src/utils/targets.js b/src/utils/targets.js
@@ -16,6 +16,89 @@ function sort(targets) {
];
}
+const defaultTargetSets = {
+ '_pace_targets': {
+ name: 'Common Pace Targets',
+ targets: [
+ { result: 'time', distanceValue: 100, distanceUnit: 'meters' },
+ { result: 'time', distanceValue: 200, distanceUnit: 'meters' },
+ { result: 'time', distanceValue: 300, distanceUnit: 'meters' },
+ { result: 'time', distanceValue: 400, distanceUnit: 'meters' },
+ { result: 'time', distanceValue: 600, distanceUnit: 'meters' },
+ { result: 'time', distanceValue: 800, distanceUnit: 'meters' },
+ { result: 'time', distanceValue: 1000, distanceUnit: 'meters' },
+ { result: 'time', distanceValue: 1200, distanceUnit: 'meters' },
+ { result: 'time', distanceValue: 1500, distanceUnit: 'meters' },
+ { result: 'time', distanceValue: 1600, distanceUnit: 'meters' },
+ { result: 'time', distanceValue: 3200, distanceUnit: 'meters' },
+
+ { result: 'time', distanceValue: 2, distanceUnit: 'kilometers' },
+ { result: 'time', distanceValue: 3, distanceUnit: 'kilometers' },
+ { result: 'time', distanceValue: 4, distanceUnit: 'kilometers' },
+ { result: 'time', distanceValue: 5, distanceUnit: 'kilometers' },
+ { result: 'time', distanceValue: 6, distanceUnit: 'kilometers' },
+ { result: 'time', distanceValue: 8, distanceUnit: 'kilometers' },
+ { result: 'time', distanceValue: 10, distanceUnit: 'kilometers' },
+
+ { result: 'time', distanceValue: 1, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 2, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 3, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 5, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 6, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 8, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 10, distanceUnit: 'miles' },
+
+ { result: 'time', distanceValue: 0.5, distanceUnit: 'marathons' },
+ { result: 'time', distanceValue: 1, distanceUnit: 'marathons' },
+
+ { result: 'distance', time: 600 },
+ { result: 'distance', time: 1800 },
+ { result: 'distance', time: 3600 },
+ ],
+ },
+ '_race_targets': {
+ name: 'Common Race Targets',
+ targets: [
+ { result: 'time', distanceValue: 400, distanceUnit: 'meters' },
+ { result: 'time', distanceValue: 800, distanceUnit: 'meters' },
+ { result: 'time', distanceValue: 1500, distanceUnit: 'meters' },
+ { result: 'time', distanceValue: 1600, distanceUnit: 'meters' },
+ { result: 'time', distanceValue: 1, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 3000, distanceUnit: 'meters' },
+ { result: 'time', distanceValue: 3200, distanceUnit: 'meters' },
+ { result: 'time', distanceValue: 2, distanceUnit: 'miles' },
+
+ { result: 'time', distanceValue: 3, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 5, distanceUnit: 'kilometers' },
+ { result: 'time', distanceValue: 6, distanceUnit: 'kilometers' },
+ { result: 'time', distanceValue: 8, distanceUnit: 'kilometers' },
+ { result: 'time', distanceValue: 10, distanceUnit: 'kilometers' },
+ { result: 'time', distanceValue: 15, distanceUnit: 'kilometers' },
+
+ { result: 'time', distanceValue: 0.5, distanceUnit: 'marathons' },
+ { result: 'time', distanceValue: 1, distanceUnit: 'marathons' },
+
+ { result: 'distance', time: 600 },
+ { result: 'distance', time: 3600 },
+ ],
+ },
+ '_split_targets': {
+ name: '5K Mile Splits',
+ targets: [
+ { result: 'time', distanceValue: 1, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 2, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 5, distanceUnit: 'kilometers' },
+ ],
+ },
+};
+
+const defaultTargetSet = {
+ name: 'New target set',
+ targets: [],
+};
+
export default {
sort,
+ defaultTargetSets,
+ defaultTargetSet,
};
diff --git a/src/views/PaceCalculator.vue b/src/views/PaceCalculator.vue
@@ -1,5 +1,5 @@
<template>
- <div class="pace-calculator">
+ <div class="calculator">
<h2>Input Pace</h2>
<div class="input">
<div>
@@ -19,28 +19,57 @@
</div>
<h2>Equivalent Paces</h2>
+ <div class="target-set">
+ Target Set:
+ <select v-model="selectedTargetSet">
+ <option v-for="(item, index) in targetSets" :key="index" :value="index">
+ {{ item.name }}
+ </option>
+ </select>
+ <button class="icon" title="Edit Target Sets" @click="editingTargetSets = true" v-blur>
+ <vue-feather type="edit"/>
+ </button>
+ </div>
<simple-target-table class="output" :calculate-result="calculatePace"
- :default-targets="defaultTargets" storage-key="pace-calculator-targets-v2"/>
+ :targets="targetSets[selectedTargetSet] ? targetSets[selectedTargetSet].targets : []"/>
+
+ <Modal v-show="editingTargetSets">
+ <target-set-editor @close="editingTargetSets = false"/>
+ </Modal>
</div>
</template>
<script>
+import VueFeather from 'vue-feather';
+
import paceUtils from '@/utils/paces';
import storage from '@/utils/localStorage';
+import targetUtils from '@/utils/targets';
import unitUtils from '@/utils/units';
import DecimalInput from '@/components/DecimalInput.vue';
-import TimeInput from '@/components/TimeInput.vue';
+import Modal from '@/components/Modal.vue';
import SimpleTargetTable from '@/components/SimpleTargetTable.vue';
+import TargetSetEditor from '@/components/TargetSetEditor.vue';
+import TimeInput from '@/components/TimeInput.vue';
+
+import blur from '@/directives/blur';
export default {
name: 'PaceCalculator',
components: {
DecimalInput,
- TimeInput,
+ Modal,
SimpleTargetTable,
+ TargetSetEditor,
+ TimeInput,
+ VueFeather,
+ },
+
+ directives: {
+ blur,
},
data() {
@@ -66,44 +95,19 @@ export default {
distanceUnits: unitUtils.DISTANCE_UNITS,
/**
- * The default output targets
+ * The current selected target set
+ */
+ selectedTargetSet: storage.get('pace-calculator-target-set', '_pace_targets'),
+
+ /**
+ * The target sets
+ */
+ targetSets: storage.get('target-sets', targetUtils.defaultTargetSets),
+
+ /**
+ * Whether the target set is being edited
*/
- defaultTargets: [
- { result: 'time', distanceValue: 100, distanceUnit: 'meters' },
- { result: 'time', distanceValue: 200, distanceUnit: 'meters' },
- { result: 'time', distanceValue: 300, distanceUnit: 'meters' },
- { result: 'time', distanceValue: 400, distanceUnit: 'meters' },
- { result: 'time', distanceValue: 600, distanceUnit: 'meters' },
- { result: 'time', distanceValue: 800, distanceUnit: 'meters' },
- { result: 'time', distanceValue: 1000, distanceUnit: 'meters' },
- { result: 'time', distanceValue: 1200, distanceUnit: 'meters' },
- { result: 'time', distanceValue: 1500, distanceUnit: 'meters' },
- { result: 'time', distanceValue: 1600, distanceUnit: 'meters' },
- { result: 'time', distanceValue: 3200, distanceUnit: 'meters' },
-
- { result: 'time', distanceValue: 2, distanceUnit: 'kilometers' },
- { result: 'time', distanceValue: 3, distanceUnit: 'kilometers' },
- { result: 'time', distanceValue: 4, distanceUnit: 'kilometers' },
- { result: 'time', distanceValue: 5, distanceUnit: 'kilometers' },
- { result: 'time', distanceValue: 6, distanceUnit: 'kilometers' },
- { result: 'time', distanceValue: 8, distanceUnit: 'kilometers' },
- { result: 'time', distanceValue: 10, distanceUnit: 'kilometers' },
-
- { result: 'time', distanceValue: 1, distanceUnit: 'miles' },
- { result: 'time', distanceValue: 2, distanceUnit: 'miles' },
- { result: 'time', distanceValue: 3, distanceUnit: 'miles' },
- { result: 'time', distanceValue: 5, distanceUnit: 'miles' },
- { result: 'time', distanceValue: 6, distanceUnit: 'miles' },
- { result: 'time', distanceValue: 8, distanceUnit: 'miles' },
- { result: 'time', distanceValue: 10, distanceUnit: 'miles' },
-
- { result: 'time', distanceValue: 0.5, distanceUnit: 'marathons' },
- { result: 'time', distanceValue: 1, distanceUnit: 'marathons' },
-
- { result: 'distance', time: 600 },
- { result: 'distance', time: 1800 },
- { result: 'distance', time: 3600 },
- ],
+ editingTargetSets: false,
};
},
@@ -128,6 +132,22 @@ export default {
inputTime(newValue) {
storage.set('pace-calculator-input-time', newValue);
},
+
+ /**
+ * Save the current selected target set
+ */
+ selectedTargetSet(newValue) {
+ storage.set('pace-calculator-target-set', newValue);
+ },
+
+ /**
+ * Refresh the target sets
+ */
+ editingTargetSets(newValue) {
+ if (!newValue) {
+ this.targetSets = storage.get('target-sets', targetUtils.defaultTargetSets);
+ }
+ }
},
computed: {
@@ -142,6 +162,14 @@ export default {
methods: {
/**
+ * Restore the default target set
+ */
+ resetTargetSet() {
+ this.targetSets[this.selectedTargetSet] =
+ JSON.parse(JSON.stringify(targetUtils.defaultTargetSets[this.selectedTargetSet]));
+ },
+
+ /**
* Calculate paces from a target
* @param {Object} target The target
* @returns {Object} The result
@@ -181,42 +209,13 @@ export default {
return result;
},
},
+
+ activated() {
+ this.targetSets = storage.get('target-sets', targetUtils.defaultTargetSets);
+ },
};
</script>
<style scoped>
-/* container */
-.pace-calculator {
- display: flex;
- flex-direction: column;
- align-items: center;
-}
-
-/* headings */
-h2 {
- font-size: 1.3em;
- margin-bottom: 0.2em;
-}
-* + h2 {
- margin-top: 0.5em;
-}
-
-/* calculator input */
-.input>* {
- margin-bottom: 5px; /* adds space between wrapped lines */
-}
-.input select {
- margin-left: 5px;
-}
-
-/* calculator output */
-.output {
- min-width: 300px;
-}
-@media only screen and (max-width: 500px) {
- .output {
- width: 100%;
- min-width: 0px;
- }
-}
+@import '@/assets/target-calculator.css';
</style>
diff --git a/src/views/RaceCalculator.vue b/src/views/RaceCalculator.vue
@@ -1,5 +1,5 @@
<template>
- <div class="race-calculator">
+ <div class="calculator">
<h2>Input Race Result</h2>
<div class="input">
<div>
@@ -19,7 +19,7 @@
<h2>
Advanced
- <button class="link" @click="showAdvancedOptions=!showAdvancedOptions">
+ <button class="link" @click="showAdvancedOptions=!showAdvancedOptions" v-blur>
{{ showAdvancedOptions ? '[hide]' : '[show]' }}
</button>
</h2>
@@ -53,29 +53,58 @@
</div>
<h2>Equivalent Race Results</h2>
+ <div class="target-set">
+ Target Set:
+ <select v-model="selectedTargetSet">
+ <option v-for="(item, index) in targetSets" :key="index" :value="index">
+ {{ item.name }}
+ </option>
+ </select>
+ <button class="icon" title="Edit Target Sets" @click="editingTargetSets = true" v-blur>
+ <vue-feather type="edit"/>
+ </button>
+ </div>
<simple-target-table class="output" :calculate-result="predictResult"
- :default-targets="defaultTargets" storage-key="race-calculator-targets-v2" show-pace/>
+ :targets="targetSets[selectedTargetSet] ? targetSets[selectedTargetSet].targets : []" show-pace/>
+
+ <Modal v-show="editingTargetSets">
+ <target-set-editor @close="editingTargetSets = false"/>
+ </Modal>
</div>
</template>
<script>
+import VueFeather from 'vue-feather';
+
import formatUtils from '@/utils/format';
import raceUtils from '@/utils/races';
import storage from '@/utils/localStorage';
+import targetUtils from '@/utils/targets';
import unitUtils from '@/utils/units';
import DecimalInput from '@/components/DecimalInput.vue';
-import TimeInput from '@/components/TimeInput.vue';
+import Modal from '@/components/Modal.vue';
import SimpleTargetTable from '@/components/SimpleTargetTable.vue';
+import TargetSetEditor from '@/components/TargetSetEditor.vue';
+import TimeInput from '@/components/TimeInput.vue';
+
+import blur from '@/directives/blur';
export default {
name: 'RaceCalculator',
components: {
DecimalInput,
- TimeInput,
+ Modal,
SimpleTargetTable,
+ TargetSetEditor,
+ TimeInput,
+ VueFeather,
+ },
+
+ directives: {
+ blur,
},
data() {
@@ -121,40 +150,32 @@ export default {
formatNumber: formatUtils.formatNumber,
/**
- * The default output targets
+ * The current selected target set
*/
- defaultTargets: [
- { result: 'time', distanceValue: 400, distanceUnit: 'meters' },
- { result: 'time', distanceValue: 800, distanceUnit: 'meters' },
- { result: 'time', distanceValue: 1000, distanceUnit: 'meters' },
- { result: 'time', distanceValue: 1200, distanceUnit: 'meters' },
- { result: 'time', distanceValue: 1500, distanceUnit: 'meters' },
- { result: 'time', distanceValue: 1600, distanceUnit: 'meters' },
- { result: 'time', distanceValue: 3200, distanceUnit: 'meters' },
-
- { result: 'time', distanceValue: 3, distanceUnit: 'kilometers' },
- { result: 'time', distanceValue: 5, distanceUnit: 'kilometers' },
- { result: 'time', distanceValue: 8, distanceUnit: 'kilometers' },
- { result: 'time', distanceValue: 10, distanceUnit: 'kilometers' },
- { result: 'time', distanceValue: 15, distanceUnit: 'kilometers' },
-
- { result: 'time', distanceValue: 1, distanceUnit: 'miles' },
- { result: 'time', distanceValue: 2, distanceUnit: 'miles' },
- { result: 'time', distanceValue: 3, distanceUnit: 'miles' },
- { result: 'time', distanceValue: 5, distanceUnit: 'miles' },
- { result: 'time', distanceValue: 10, distanceUnit: 'miles' },
-
- { result: 'time', distanceValue: 0.5, distanceUnit: 'marathons' },
- { result: 'time', distanceValue: 1, distanceUnit: 'marathons' },
-
- { result: 'distance', time: 600 },
- { result: 'distance', time: 3600 },
- ],
+ selectedTargetSet: storage.get('race-calculator-target-set', '_race_targets'),
+
+ /**
+ * The target sets
+ */
+ targetSets: storage.get('target-sets', targetUtils.defaultTargetSets),
+
+ /**
+ * Whether the target set is being edited
+ */
+ editingTargetSets: false,
};
},
methods: {
/**
+ * Restore the default target set
+ */
+ resetTargetSet() {
+ this.targetSets[this.selectedTargetSet].targets =
+ JSON.parse(JSON.stringify(targetUtils.defaultTargetSets[this.selectedTargetSet].targets));
+ },
+
+ /**
* Predict race results from a target
* @param {Object} target The target
* @returns {Object} The result
@@ -319,48 +340,35 @@ export default {
showAdvancedOptions(newValue) {
storage.set('race-calculator-show-advanced-options', newValue);
},
+
+ /**
+ * Save the current selected target set
+ */
+ selectedTargetSet(newValue) {
+ storage.set('pace-calculator-target-set', newValue);
+ },
+
+ /**
+ * Refresh the target sets
+ */
+ editingTargetSets(newValue) {
+ if (!newValue) {
+ this.targetSets = storage.get('target-sets', targetUtils.defaultTargetSets);
+ }
+ }
+ },
+
+ activated() {
+ this.targetSets = storage.get('target-sets', targetUtils.defaultTargetSets);
},
};
</script>
<style scoped>
-/* container */
-.race-calculator {
- display: flex;
- flex-direction: column;
- align-items: center;
-}
-
-/* headings */
-h2 {
- font-size: 1.3em;
- margin-bottom: 0.2em;
-}
-* + h2 {
- margin-top: 0.5em;
-}
-
-/* calculator input */
-.input>* {
- margin-bottom: 5px; /* adds space between wrapped lines */
-}
-.input select {
- margin-left: 5px;
-}
+@import '@/assets/target-calculator.css';
/* advanced options */
.advanced-options>* {
margin-bottom: 5px;
}
-
-/* calculator output */
-.output {
- min-width: 300px;
-}
-@media only screen and (max-width: 500px) {
- .output {
- width: 100%;
- min-width: 0px;
- }
-}
</style>
diff --git a/src/views/SplitCalculator.vue b/src/views/SplitCalculator.vue
@@ -1,7 +1,19 @@
<template>
- <div class="split-calculator">
+ <div class="calculator">
+ <div class="target-set">
+ Target Set:
+ <select v-model="selectedTargetSet">
+ <option v-for="(item, index) in targetSets" :key="index" :value="index">
+ {{ item.name }}
+ </option>
+ </select>
+ <button class="icon" title="Edit Target Sets" @click="editingTargetSets = true" v-blur>
+ <vue-feather type="edit"/>
+ </button>
+ </div>
+
<div class="output">
- <table class="results" v-show="!inEditMode">
+ <table class="results">
<thead>
<tr>
<th>
@@ -14,12 +26,6 @@
<th>Split</th>
<th>Pace</th>
-
- <th>
- <button class="icon" title="Edit Targets" @click="inEditMode=true" v-blur>
- <vue-feather type="edit"/>
- </button>
- </th>
</tr>
</thead>
@@ -34,30 +40,28 @@
{{ formatDuration(item.totalTime, 3, 2, true) }}
</td>
- <td>
- <time-input v-model="targets[index].split" :showHours="false"/>
+ <td v-if="targetSets[selectedTargetSet]">
+ <time-input v-model="targetSets[selectedTargetSet].targets[index].split" :showHours="false"/>
</td>
- <td colspan="2">
+ <td>
{{ formatDuration(item.pace, 3, 0, true) }}
/ {{ distanceUnits[getDefaultDistanceUnit()].symbol }}
</td>
</tr>
- <tr v-if="targets.length === 0" class="empty-message">
+ <tr v-if="!targetSets[selectedTargetSet] || targetSets[selectedTargetSet].targets.length === 0" class="empty-message">
<td colspan="5">
- There aren't any targets yet,<br>
- click
- <vue-feather type="edit"/>
- to edit the list of targets
+ There aren't any targets in this set yet.
</td>
</tr>
</tbody>
</table>
-
- <target-editor v-model="targets" :time-targets="false" v-show="inEditMode"
- @close="inEditMode=false" @reset="resetTargets"/>
</div>
+
+ <Modal v-show="editingTargetSets">
+ <target-set-editor @close="editingTargetSets = false"/>
+ </Modal>
</div>
</template>
@@ -69,24 +73,19 @@ import storage from '@/utils/localStorage';
import targetUtils from '@/utils/targets';
import unitUtils from '@/utils/units';
+import Modal from '@/components/Modal.vue';
+import TargetSetEditor from '@/components/TargetSetEditor.vue';
import TimeInput from '@/components/TimeInput.vue';
-import TargetEditor from '@/components/TargetEditor.vue';
import blur from '@/directives/blur';
-const defaultTargets = [
- { result: 'time', distanceValue: 1, distanceUnit: 'miles' },
- { result: 'time', distanceValue: 2, distanceUnit: 'miles' },
- { result: 'time', distanceValue: 5, distanceUnit: 'kilometers' },
-];
-const storageKey = 'split-calculator-targets-v2';
-
export default {
name: 'SplitCalculator',
components: {
+ Modal,
+ TargetSetEditor,
TimeInput,
- TargetEditor,
VueFeather,
},
@@ -117,17 +116,40 @@ export default {
getDefaultDistanceUnit: unitUtils.getDefaultDistanceUnit,
/**
- * Whether the table is in edit mode
+ * The current selected target set
+ */
+ selectedTargetSet: storage.get('split-calculator-target-set', '_split_targets'),
+
+ /**
+ * The default output targets
*/
- inEditMode: false,
+ targetSets: storage.get('target-sets', targetUtils.defaultTargetSets),
/**
- * The target table targets
+ * Whether the target set is being edited
*/
- targets: storage.get(storageKey, defaultTargets),
+ editingTargetSets: false,
};
},
+ watch: {
+ /**
+ * Save the current selected target set
+ */
+ selectedTargetSet(newValue) {
+ storage.set('split-calculator-target-set', newValue);
+ },
+
+ /**
+ * Refresh the target sets
+ */
+ editingTargetSets(newValue) {
+ if (!newValue) {
+ this.targetSets = storage.get('target-sets', targetUtils.defaultTargetSets);
+ }
+ }
+ },
+
computed: {
/**
* The target table results
@@ -136,29 +158,36 @@ export default {
// Initialize results array
const results = [];
- for (let i = 0; i < this.targets.length; i += 1) {
- // Calculate split and total times
- const splitTime = this.targets[i].split || 0;
- const totalTime = i === 0 ? splitTime : results[i - 1].totalTime + splitTime;
-
- // Calculate split and total distances
- const totalDistance = unitUtils.convertDistance(this.targets[i].distanceValue,
- this.targets[i].distanceUnit, 'meters');
- const splitDistance = i === 0 ? totalDistance : totalDistance - results[i - 1].distance;
-
- // Calculate pace
- const pace = splitTime / unitUtils.convertDistance(splitDistance, 'meters',
- unitUtils.getDefaultDistanceUnit());
-
- // Add row to results array
- results.push({
- distance: totalDistance,
- distanceValue: this.targets[i].distanceValue,
- distanceUnit: this.targets[i].distanceUnit,
- totalTime,
- splitTime,
- pace,
- });
+ // Check for missing target set
+ if (!this.targetSets[this.selectedTargetSet]) return [];
+
+ for (let i = 0; i < this.targetSets[this.selectedTargetSet].targets.length; i += 1) {
+ if (this.targetSets[this.selectedTargetSet].targets[i].result === 'time') {
+ // Calculate split and total times
+ const splitTime = this.targetSets[this.selectedTargetSet].targets[i].split || 0;
+ const totalTime = i === 0 ? splitTime : results[i - 1].totalTime + splitTime;
+
+ // Calculate split and total distances
+ const totalDistance = unitUtils.convertDistance(
+ this.targetSets[this.selectedTargetSet].targets[i].distanceValue,
+ this.targetSets[this.selectedTargetSet].targets[i].distanceUnit, 'meters',
+ );
+ const splitDistance = i === 0 ? totalDistance : totalDistance - results[i - 1].distance;
+
+ // Calculate pace
+ const pace = splitTime / unitUtils.convertDistance(splitDistance, 'meters',
+ unitUtils.getDefaultDistanceUnit());
+
+ // Add row to results array
+ results.push({
+ distance: totalDistance,
+ distanceValue: this.targetSets[this.selectedTargetSet].targets[i].distanceValue,
+ distanceUnit: this.targetSets[this.selectedTargetSet].targets[i].distanceUnit,
+ totalTime,
+ splitTime,
+ pace,
+ });
+ }
}
// Return results array
@@ -166,74 +195,37 @@ export default {
},
},
- watch: {
- /**
- * Sort targets
- */
- inEditMode() {
- this.targets = targetUtils.sort(this.targets);
- },
-
- /**
- * Save targets
- */
- targets: {
- handler(newValue) {
- if (storageKey !== null) {
- storage.set(storageKey, newValue);
- }
- },
- deep: true,
- },
- },
-
methods: {
/**
- * Restore the default targets
+ * Restore the default target set
*/
- resetTargets() {
- // Clone default targets array
- this.targets = JSON.parse(JSON.stringify(defaultTargets));
-
- // Sort targets
- this.targets = targetUtils.sort(this.targets);
+ resetTargetSet() {
+ this.targetSets[this.selectedTargetSet] =
+ JSON.parse(JSON.stringify(targetUtils.defaultTargetSets[this.selectedTargetSet]));
},
},
- /**
- * Close edit targets table
- */
- deactivated() {
- this.inEditMode = false;
+ activated() {
+ this.targetSets = storage.get('target-sets', targetUtils.defaultTargetSets);
},
};
</script>
<style scoped>
-/* container */
-.split-calculator {
- display: flex;
- flex-direction: column;
- align-items: center;
-}
+@import '@/assets/target-calculator.css';
-/* target table */
-.results th:last-child {
- text-align: right;
+/* Widen default calculator output */
+@media only screen and (min-width: 501px) {
+ .output {
+ min-width: 400px;
+ }
}
+
+/* Show/hide mobile abbreviations */
.results th:first-child span.mobile-abbreviation {
display: none;
}
-
-/* calculator output */
-.output {
- min-width: 400px;
-}
@media only screen and (max-width: 500px) {
- .output {
- width: 100%;
- min-width: 0px;
- }
.results th:first-child span:not(.mobile-abbreviation) {
display: none;
}
diff --git a/tests/unit/components/SimpleTargetTable.spec.js b/tests/unit/components/SimpleTargetTable.spec.js
@@ -13,7 +13,7 @@ test('results should be correct and sorted by time', () => {
distanceUnit: row.distanceUnit,
time: row.distanceValue + 1,
}),
- defaultTargets: [
+ targets: [
{ distanceValue: 20, distanceUnit: 'meters' },
{ distanceValue: 100, distanceUnit: 'meters' },
{ distanceValue: 1, distanceUnit: 'kilometers' },
diff --git a/tests/unit/components/TargetEditor.spec.js b/tests/unit/components/TargetEditor.spec.js
@@ -6,31 +6,60 @@ import TargetEditor from '@/components/TargetEditor.vue';
test('addDistanceTarget method should correctly add distance target', async () => {
// Initialize component
- const wrapper = shallowMount(TargetEditor);
+ const wrapper = mount(TargetEditor, {
+ propsData: {
+ modelValue: {
+ name: 'My target set',
+ targets: [
+ { distanceUnit: 'miles', distanceValue: 0, result: 'time' },
+ { time: 0, result: 'distance' },
+ ],
+ },
+ },
+ });
// Add distance target
await wrapper.vm.addDistanceTarget();
// Assert input event was emitted
expect(wrapper.emitted()['update:modelValue']).to.deep.equal([
- [[
- { distanceUnit: 'miles', distanceValue: 1, result: 'time' },
- ]],
+ [{
+ name: 'My target set',
+ targets: [
+ { distanceUnit: 'miles', distanceValue: 0, result: 'time' },
+ { time: 0, result: 'distance' },
+ { distanceUnit: 'miles', distanceValue: 1, result: 'time'},
+ ],
+ }],
]);
});
test('addTimeTarget method should correctly add time target', async () => {
// Initialize component
- const wrapper = shallowMount(TargetEditor);
+ const wrapper = mount(TargetEditor, {
+ propsData: {
+ modelValue: {
+ name: 'My target set',
+ targets: [
+ { distanceUnit: 'miles', distanceValue: 0, result: 'time' },
+ { time: 0, result: 'distance' },
+ ],
+ },
+ },
+ });
// Add time target
await wrapper.vm.addTimeTarget();
// Assert input event was emitted
expect(wrapper.emitted()['update:modelValue']).to.deep.equal([
- [[
- { time: 600, result: 'distance' },
- ]],
+ [{ name: 'My target set',
+ targets: [
+ { distanceUnit: 'miles', distanceValue: 0, result: 'time' },
+ { time: 0, result: 'distance' },
+ { time: 600, result: 'distance' },
+ ],
+ }],
]);
});
@@ -38,9 +67,12 @@ test('should emit input event when targets are updated', async () => {
// Initialize component
const wrapper = mount(TargetEditor, {
propsData: {
- modelValue: [
- { distanceUnit: 'miles', distanceValue: 2, result: 'time' },
- ],
+ modelValue: {
+ name: 'My target set',
+ targets: [
+ { distanceUnit: 'miles', distanceValue: 2, result: 'time' },
+ ],
+ },
},
});
@@ -49,9 +81,43 @@ test('should emit input event when targets are updated', async () => {
// Assert input event was emitted
expect(wrapper.emitted()['update:modelValue']).to.deep.equal([
- [[
- { distanceUnit: 'miles', distanceValue: 3, result: 'time' },
- ]],
+ [
+ {
+ name: 'My target set',
+ targets: [
+ { distanceUnit: 'miles', distanceValue: 3, result: 'time' },
+ ],
+ },
+ ],
+ ]);
+});
+
+test('should emit input event when target set name is updated', async () => {
+ // Initialize component
+ const wrapper = mount(TargetEditor, {
+ propsData: {
+ modelValue: {
+ name: 'My target set',
+ targets: [
+ { distanceUnit: 'miles', distanceValue: 2, result: 'time' },
+ ],
+ },
+ },
+ });
+
+ // Update distance value
+ await wrapper.find('thead input').setValue('My target set #2');
+
+ // Assert input event was emitted
+ expect(wrapper.emitted()['update:modelValue']).to.deep.equal([
+ [
+ {
+ name: 'My target set #2',
+ targets: [
+ { distanceUnit: 'miles', distanceValue: 2, result: 'time' },
+ ],
+ },
+ ],
]);
});
@@ -59,11 +125,14 @@ test('removeTarget method should correctly remove target', async () => {
// Initialize component
const wrapper = shallowMount(TargetEditor, {
propsData: {
- modelValue: [
- { distanceUnit: 'miles', distanceValue: 1, result: 'time' },
- { distanceUnit: 'miles', distanceValue: 2, result: 'time' },
- { distanceUnit: 'miles', distanceValue: 3, result: 'time' },
- ],
+ modelValue: {
+ name: 'My target set',
+ targets: [
+ { distanceUnit: 'miles', distanceValue: 1, result: 'time' },
+ { distanceUnit: 'miles', distanceValue: 2, result: 'time' },
+ { distanceUnit: 'miles', distanceValue: 3, result: 'time' },
+ ],
+ },
},
});
@@ -72,9 +141,12 @@ test('removeTarget method should correctly remove target', async () => {
// Assert input event was emitted
expect(wrapper.emitted()['update:modelValue']).to.deep.equal([
- [[
- { distanceUnit: 'miles', distanceValue: 1, result: 'time' },
- { distanceUnit: 'miles', distanceValue: 3, result: 'time' },
- ]],
+ [{
+ name: 'My target set',
+ targets: [
+ { distanceUnit: 'miles', distanceValue: 1, result: 'time' },
+ { distanceUnit: 'miles', distanceValue: 3, result: 'time' },
+ ],
+ }],
]);
});
diff --git a/tests/unit/components/TargetSetEditor.spec.js b/tests/unit/components/TargetSetEditor.spec.js
@@ -0,0 +1,154 @@
+/* eslint-disable no-underscore-dangle */
+
+import { test, expect } from 'vitest';
+import { shallowMount, mount } from '@vue/test-utils';
+import TargetSetEditor from '@/components/TargetSetEditor.vue';
+import targetUtils from '@/utils/targets';
+
+test('addTargetSet method should correctly add target set', async () => {
+ // Initialize component
+ const wrapper = mount(TargetSetEditor, {
+ data() {
+ return {
+ internalValue: {
+ 'A': {
+ name: '1st target set',
+ targets: [
+ { result: 'time', distanceValue: 1, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 2, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 3, distanceUnit: 'miles' },
+ ],
+ },
+ },
+ };
+ },
+ });
+
+ // Add target set
+ await wrapper.vm.addTargetSet();
+
+ // Assert target set was added
+ expect(wrapper.vm.internalValue['A']).to.deep.equal({
+ name: '1st target set',
+ targets: [
+ { result: 'time', distanceValue: 1, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 2, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 3, distanceUnit: 'miles' },
+ ],
+ });
+ const keys = Object.keys(wrapper.vm.internalValue)
+ expect(keys.length).to.equal(2);
+ expect(wrapper.vm.internalValue[keys[1]]).to.deep.equal({
+ name: 'New target set',
+ targets: [],
+ });
+
+ // Assert new target set was selected
+ expect(wrapper.vm.selectedTargetSet).to.equal(keys[1]);
+});
+
+test('reset method should correctly reset target sets', async () => {
+ // Initialize component
+ const wrapper = mount(TargetSetEditor, {
+ data() {
+ return {
+ internalValue: {
+ 'A': {
+ name: '1st target set',
+ targets: [
+ { result: 'time', distanceValue: 1, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 2, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 3, distanceUnit: 'miles' },
+ ],
+ },
+ '_split_targets': {
+ name: '5K Kilometer Splits',
+ targets: [
+ { result: 'time', distanceValue: 2, distanceUnit: 'Kilometer' },
+ { result: 'time', distanceValue: 4, distanceUnit: 'Kilometer' },
+ { result: 'time', distanceValue: 5, distanceUnit: 'kilometers' },
+ ],
+ },
+ },
+ };
+ },
+ });
+
+ // Reset target sets
+ await wrapper.vm.reset();
+
+ // Assert target sets were reset
+ expect(wrapper.vm.internalValue).to.deep.equal({
+ // Default target sets should be restored
+ ...targetUtils.defaultTargetSets,
+
+ // Custom target sets should be kept
+ 'A': {
+ name: '1st target set',
+ targets: [
+ { result: 'time', distanceValue: 1, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 2, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 3, distanceUnit: 'miles' },
+ ],
+ },
+ });
+});
+
+test('removeTargetSet method should correctly remove target set', async () => {
+ // Initialize component
+ const wrapper = mount(TargetSetEditor, {
+ data() {
+ return {
+ internalValue: {
+ 'A': {
+ name: '1st target set',
+ targets: [
+ { result: 'time', distanceValue: 1, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 2, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 3, distanceUnit: 'miles' },
+ ],
+ },
+ 'B': {
+ name: '2nd target set',
+ targets: [
+ { result: 'time', distanceValue: 4, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 5, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 6, distanceUnit: 'miles' },
+ ],
+ },
+ 'C': {
+ name: '3rd target set',
+ targets: [
+ { result: 'time', distanceValue: 7, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 8, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 9, distanceUnit: 'miles' },
+ ],
+ },
+ },
+ };
+ },
+ });
+
+ // Remove 2nd target set
+ await wrapper.vm.removeTargetSet('B');
+
+ // Assert target set was removed
+ expect(wrapper.vm.internalValue).to.deep.equal({
+ 'A': {
+ name: '1st target set',
+ targets: [
+ { result: 'time', distanceValue: 1, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 2, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 3, distanceUnit: 'miles' },
+ ],
+ },
+ 'C': {
+ name: '3rd target set',
+ targets: [
+ { result: 'time', distanceValue: 7, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 8, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 9, distanceUnit: 'miles' },
+ ],
+ },
+ });
+});
diff --git a/tests/unit/views/SplitCalculator.spec.js b/tests/unit/views/SplitCalculator.spec.js
@@ -7,15 +7,21 @@ import unitUtils from '@/utils/units';
test('should correctly calculate split paces and total times', async () => {
// Initialize component
- const wrapper = shallowMount(SplitCalculator);
-
- // Override input values
- await wrapper.setData({
- targets: [
- { result: 'time', distanceValue: 2, distanceUnit: 'miles', split: 60 },
- { result: 'time', distanceValue: 4, distanceUnit: 'miles', split: 70 },
- { result: 'time', distanceValue: 10, distanceUnit: 'kilometers', split: 80 },
- ],
+ const wrapper = shallowMount(SplitCalculator, {
+ data() {
+ return {
+ targetSets: {
+ '_split_targets': {
+ name: 'Split targets',
+ targets: [
+ { result: 'time', distanceValue: 2, distanceUnit: 'miles', split: 60 },
+ { result: 'time', distanceValue: 4, distanceUnit: 'miles', split: 70 },
+ { result: 'time', distanceValue: 10, distanceUnit: 'kilometers', split: 80 },
+ ],
+ },
+ },
+ };
+ },
});
// Assert results are correct