commit 2f27b105dedb31218d0d649b2548c9dbf2a6814b
parent 454a7aa31f47169c9a4027affc5e7cb572023f3a
Author: ashermorgan <59518073+ashermorgan@users.noreply.github.com>
Date: Wed, 27 Dec 2023 11:04:12 -0800
Implement TargetSetSelector component
Diffstat:
9 files changed, 388 insertions(+), 476 deletions(-)
diff --git a/src/assets/global.css b/src/assets/global.css
@@ -54,6 +54,7 @@ table .empty-message svg {
/* styles for icons */
.icon {
+ height: 2em;
border: none;
padding: 0em;
background-color: #00000000;
diff --git a/src/components/TargetEditor.vue b/src/components/TargetEditor.vue
@@ -5,6 +5,10 @@
<th>
Edit
<input v-model="internalValue.name" placeholder="Target set name"/>
+ <button class="icon" :title="isCustomSet ? 'Delete Target Set' : 'Revert Target Set'"
+ @click="revert" v-blur>
+ <vue-feather :type="isCustomSet ? 'trash-2' : 'rotate-ccw'"/>
+ </button>
</th>
<th>
@@ -54,6 +58,8 @@
<button v-if="timeTargets" title="Add Time Target" @click="addTimeTarget" v-blur>
Add time target
</button>
+ <br/>
+ <p>Note: time targets are ignored by the Split Calculator</p>
</td>
</tr>
</tfoot>
@@ -100,6 +106,14 @@ export default {
type: Boolean,
default: true,
},
+
+ /**
+ * Whether the target set is a custom or default set
+ */
+ isCustomSet: {
+ type: Boolean,
+ default: false,
+ },
},
data() {
@@ -142,6 +156,14 @@ export default {
methods: {
/**
+ * Revert the target set
+ */
+ revert() {
+ // Emit revert event
+ this.$emit('revert');
+ },
+
+ /**
* Close the target editor
*/
close() {
@@ -183,6 +205,9 @@ export default {
<style scoped>
/* edit targets table */
+.target-editor th .icon {
+ margin-left: 0.3em;
+}
.target-editor th:last-child, .target-editor td:last-child {
text-align: right;
}
@@ -197,4 +222,7 @@ export default {
.target-editor tfoot button {
margin: 0.5em;
}
+.target-editor tfoot p {
+ margin-top: 0.5em;
+}
</style>
diff --git a/src/components/TargetSetEditor.vue b/src/components/TargetSetEditor.vue
@@ -1,217 +0,0 @@
-<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 TargetEditor from '@/components/TargetEditor.vue';
-
-import blur from '@/directives/blur';
-
-export default {
- name: 'TargetSetEditor',
-
- components: {
- TargetEditor,
- 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 (!Object.keys(this.internalValue).includes(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/components/TargetSetSelector.vue b/src/components/TargetSetSelector.vue
@@ -0,0 +1,149 @@
+<template>
+ <span class="target-set-selector">
+ <select v-model="internalValue">
+ <option v-for="(item, index) in targetSets" :key="index" :value="index">
+ {{ item.name }}
+ </option>
+ <option value="_new">[ Create New Target Set ]</option>
+ </select>
+
+ <button class="icon" title="Edit Target Set" @click="editingTargetSets = true" v-blur>
+ <vue-feather type="edit"/>
+ </button>
+
+ <fullscreen-modal v-show="editingTargetSets">
+ <target-editor @close="editingTargetSets = false" v-model="targetSets[internalValue]"
+ @revert="revertTargetSet(); editingTargetSets = false"
+ :isCustomSet="!internalValue.startsWith('_')"/>
+ </fullscreen-modal>
+ </span>
+</template>
+
+<script>
+import VueFeather from 'vue-feather';
+
+import storage from '@/utils/localStorage';
+import targetUtils from '@/utils/targets';
+
+import FullscreenModal from '@/components/FullscreenModal.vue';
+import TargetEditor from '@/components/TargetEditor.vue';
+
+import blur from '@/directives/blur';
+
+export default {
+ name: 'TargetSetSelector',
+
+ components: {
+ FullscreenModal,
+ TargetEditor,
+ VueFeather,
+ },
+
+ directives: {
+ blur,
+ },
+
+ props: {
+ /**
+ * The selected target set
+ */
+ modelValue: {
+ type: String,
+ default: '_new',
+ },
+ },
+
+ data() {
+ return {
+ /**
+ * The internal value
+ */
+ internalValue: this.modelValue,
+
+ /**
+ * The target sets
+ */
+ targetSets: storage.get('target-sets', targetUtils.defaultTargetSets),
+
+ /**
+ * Whether the target set is being edited
+ */
+ editingTargetSets: false,
+ };
+ },
+
+ watch: {
+ /**
+ * Update the component value when the modelValue prop changes
+ */
+ modelValue(newValue) {
+ if (newValue !== this.internalValue) {
+ this.internalValue = newValue;
+ }
+ },
+
+ /**
+ * Emit the input event when the component value changes and create a new set if necessary
+ */
+ internalValue: {
+ immediate: true,
+ handler(newValue) {
+ if (newValue == '_new') {
+ let key = Date.now().toString();
+ this.targetSets[key] = {
+ name: 'New target set',
+ targets: [],
+ };
+ this.internalValue = key;
+ } else {
+ this.$emit('update:modelValue', newValue);
+ }
+ },
+ },
+
+ /**
+ * Save target sets
+ */
+ targetSets: {
+ deep: true,
+ handler(newValue) {
+ storage.set('target-sets', newValue);
+ this.$emit('targets-updated');
+ },
+ },
+
+ /**
+ * Sort target set
+ */
+ editingTargetSets(newValue) {
+ if (!newValue) {
+ this.targetSets[this.internalValue].targets =
+ targetUtils.sort(this.targetSets[this.internalValue].targets);
+ }
+ },
+ },
+
+ methods: {
+ /**
+ * Revert or remove the current target set
+ */
+ revertTargetSet() {
+ if (this.internalValue.startsWith('_')) {
+ // Revert default set
+ this.targetSets[this.internalValue] =
+ JSON.parse(JSON.stringify(targetUtils.defaultTargetSets[this.internalValue]));
+ } else {
+ // Remove custom set
+ delete this.targetSets[this.internalValue];
+ this.internalValue = [...Object.keys(this.targetSets), '_new'][0];
+ }
+ },
+ },
+};
+</script>
+
+<style scoped>
+.target-set-selector .icon {
+ margin-left: 0.3em;
+}
+</style>
diff --git a/src/views/PaceCalculator.vue b/src/views/PaceCalculator.vue
@@ -21,37 +21,23 @@
<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>
+ <target-set-selector v-model="selectedTargetSet" @targets-updated="reloadTargets"/>
</div>
<simple-target-table class="output" :calculate-result="calculatePace"
:targets="targetSets[selectedTargetSet] ? targetSets[selectedTargetSet].targets : []"/>
-
- <fullscreen-modal v-show="editingTargetSets">
- <target-set-editor @close="editingTargetSets = false"/>
- </fullscreen-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 FullscreenModal from '@/components/FullscreenModal.vue';
import SimpleTargetTable from '@/components/SimpleTargetTable.vue';
-import TargetSetEditor from '@/components/TargetSetEditor.vue';
+import TargetSetSelector from '@/components/TargetSetSelector.vue';
import TimeInput from '@/components/TimeInput.vue';
import blur from '@/directives/blur';
@@ -61,11 +47,9 @@ export default {
components: {
DecimalInput,
- FullscreenModal,
SimpleTargetTable,
- TargetSetEditor,
+ TargetSetSelector,
TimeInput,
- VueFeather,
},
directives: {
@@ -139,15 +123,6 @@ export default {
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: {
@@ -162,11 +137,10 @@ export default {
methods: {
/**
- * Restore the default target set
+ * Reload the target sets
*/
- resetTargetSet() {
- this.targetSets[this.selectedTargetSet] =
- JSON.parse(JSON.stringify(targetUtils.defaultTargetSets[this.selectedTargetSet]));
+ reloadTargets() {
+ this.targetSets = storage.get('target-sets', targetUtils.defaultTargetSets);
},
/**
diff --git a/src/views/RaceCalculator.vue b/src/views/RaceCalculator.vue
@@ -55,28 +55,15 @@
<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>
+ <target-set-selector v-model="selectedTargetSet" @targets-updated="reloadTargets"/>
</div>
<simple-target-table class="output" :calculate-result="predictResult"
:targets="targetSets[selectedTargetSet] ? targetSets[selectedTargetSet].targets : []" show-pace/>
-
- <fullscreen-modal v-show="editingTargetSets">
- <target-set-editor @close="editingTargetSets = false"/>
- </fullscreen-modal>
</div>
</template>
<script>
-import VueFeather from 'vue-feather';
-
import formatUtils from '@/utils/format';
import raceUtils from '@/utils/races';
import storage from '@/utils/localStorage';
@@ -84,9 +71,8 @@ import targetUtils from '@/utils/targets';
import unitUtils from '@/utils/units';
import DecimalInput from '@/components/DecimalInput.vue';
-import FullscreenModal from '@/components/FullscreenModal.vue';
import SimpleTargetTable from '@/components/SimpleTargetTable.vue';
-import TargetSetEditor from '@/components/TargetSetEditor.vue';
+import TargetSetSelector from '@/components/TargetSetSelector.vue';
import TimeInput from '@/components/TimeInput.vue';
import blur from '@/directives/blur';
@@ -96,11 +82,9 @@ export default {
components: {
DecimalInput,
- FullscreenModal,
SimpleTargetTable,
- TargetSetEditor,
+ TargetSetSelector,
TimeInput,
- VueFeather,
},
directives: {
@@ -168,11 +152,10 @@ export default {
methods: {
/**
- * Restore the default target set
+ * Reload the target sets
*/
- resetTargetSet() {
- this.targetSets[this.selectedTargetSet].targets =
- JSON.parse(JSON.stringify(targetUtils.defaultTargetSets[this.selectedTargetSet].targets));
+ reloadTargets() {
+ this.targetSets = storage.get('target-sets', targetUtils.defaultTargetSets);
},
/**
diff --git a/src/views/SplitCalculator.vue b/src/views/SplitCalculator.vue
@@ -2,14 +2,7 @@
<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>
+ <target-set-selector v-model="selectedTargetSet" @targets-updated="reloadTargets"/>
</div>
<div class="output">
@@ -58,23 +51,16 @@
</tbody>
</table>
</div>
-
- <fullscreen-modal v-show="editingTargetSets">
- <target-set-editor @close="editingTargetSets = false"/>
- </fullscreen-modal>
</div>
</template>
<script>
-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 FullscreenModal from '@/components/FullscreenModal.vue';
-import TargetSetEditor from '@/components/TargetSetEditor.vue';
+import TargetSetSelector from '@/components/TargetSetSelector.vue';
import TimeInput from '@/components/TimeInput.vue';
import blur from '@/directives/blur';
@@ -83,10 +69,8 @@ export default {
name: 'SplitCalculator',
components: {
- FullscreenModal,
- TargetSetEditor,
+ TargetSetSelector,
TimeInput,
- VueFeather,
},
directives: {
@@ -161,33 +145,34 @@ export default {
// 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,
- });
- }
+ let targets = this.targetSets[this.selectedTargetSet].targets.filter(x => x.result ===
+ 'time');
+
+ for (let i = 0; i < targets.length; i += 1) {
+ // Calculate split and total times
+ const splitTime = targets[i].split || 0;
+ const totalTime = i === 0 ? splitTime : results[i - 1].totalTime + splitTime;
+
+ // Calculate split and total distances
+ const totalDistance = unitUtils.convertDistance(
+ targets[i].distanceValue,
+ 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: targets[i].distanceValue,
+ distanceUnit: targets[i].distanceUnit,
+ totalTime,
+ splitTime,
+ pace,
+ });
}
// Return results array
@@ -197,11 +182,10 @@ export default {
methods: {
/**
- * Restore the default target set
+ * Reload the target sets
*/
- resetTargetSet() {
- this.targetSets[this.selectedTargetSet] =
- JSON.parse(JSON.stringify(targetUtils.defaultTargetSets[this.selectedTargetSet]));
+ reloadTargets() {
+ this.targetSets = storage.get('target-sets', targetUtils.defaultTargetSets);
},
},
diff --git a/tests/unit/components/TargetSetEditor.spec.js b/tests/unit/components/TargetSetEditor.spec.js
@@ -1,154 +0,0 @@
-/* eslint-disable no-underscore-dangle */
-
-import { test, expect } from 'vitest';
-import { 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/components/TargetSetSelector.spec.js b/tests/unit/components/TargetSetSelector.spec.js
@@ -0,0 +1,164 @@
+/* eslint-disable no-underscore-dangle */
+
+import { test, expect } from 'vitest';
+import { mount } from '@vue/test-utils';
+import TargetSetSelector from '@/components/TargetSetSelector.vue';
+
+test('Create New Target Set option should correctly add target set', async () => {
+ // Initialize component
+ const wrapper = mount(TargetSetSelector, {
+ data() {
+ return {
+ internalValue: 'A',
+ editingTargetSets: false,
+ targetSets: {
+ '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
+ wrapper.vm.internalValue = '_new';
+ await wrapper.vm.$nextTick();
+
+ // Assert target set was added
+ expect(wrapper.vm.targetSets['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.targetSets)
+ expect(keys.length).to.equal(2);
+ expect(wrapper.vm.targetSets[keys[1]]).to.deep.equal({
+ name: 'New target set',
+ targets: [],
+ });
+
+ // Assert new target set was selected
+ expect(wrapper.vm.internalValue).to.equal(keys[1]);
+});
+
+test('revertTargetSet method should correctly reset target sets', async () => {
+ // Initialize component
+ const wrapper = mount(TargetSetSelector, {
+ data() {
+ return {
+ internalValue: 'A',
+ editingTargetSets: false,
+ targetSets: {
+ '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' },
+ ],
+ },
+ },
+ };
+ },
+ });
+
+ // Revert first target set
+ await wrapper.vm.revertTargetSet();
+
+ // Assert first target set was removed
+ expect(wrapper.vm.targetSets).to.deep.equal({
+ '_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' },
+ ],
+ },
+ });
+ expect(wrapper.vm.internalValue).to.equal('_split_targets');
+
+ // Revert second target set
+ await wrapper.vm.revertTargetSet();
+
+ // Assert second target set was reset
+ expect(wrapper.vm.targetSets).to.deep.equal({
+ '_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' },
+ ],
+ },
+ });
+ expect(wrapper.vm.internalValue).to.equal('_split_targets');
+});
+
+test('Target sets should be correctly sorted', async () => {
+ // Initialize component
+ const wrapper = mount(TargetSetSelector, {
+ data() {
+ return {
+ internalValue: '_split_targets',
+ editingTargetSets: false,
+ targetSets: {
+ '_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' },
+ ],
+ },
+ },
+ };
+ },
+ });
+
+ // Edit target set
+ wrapper.vm.editingTargetSets = true;
+ await wrapper.vm.$nextTick();
+ wrapper.vm.targetSets['_split_targets'] = {
+ name: '5K Mile Splits',
+ targets: [
+ { result: 'distance', timeValue: 60 },
+ { result: 'time', distanceValue: 1, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 2, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 5, distanceUnit: 'kilometers' },
+ { result: 'time', distanceValue: 3, distanceUnit: 'miles' },
+ ],
+ };
+ wrapper.vm.editingTargetSets = false;
+ await wrapper.vm.$nextTick();
+
+ // Assert target set was sorted
+ expect(wrapper.vm.targetSets).to.deep.equal({
+ '_split_targets': {
+ name: '5K Mile Splits',
+ targets: [
+ { result: 'time', distanceValue: 1, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 2, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 3, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 5, distanceUnit: 'kilometers' },
+ { result: 'distance', timeValue: 60 },
+ ],
+ },
+ });
+});