commit 78feba7fc21b6777e9e70fe0350ebaa3536a4c09
parent db2f7a77fc6fc5cfb2a888bceccb0d1709df9130
Author: Asher Morgan <59518073+ashermorgan@users.noreply.github.com>
Date: Wed, 12 Jun 2024 17:34:03 -0700
Implement TargetEditor setType prop
Diffstat:
10 files changed, 83 insertions(+), 121 deletions(-)
diff --git a/src/components/TargetEditor.vue b/src/components/TargetEditor.vue
@@ -56,11 +56,9 @@
<button title="Add distance target" @click="addDistanceTarget">
Add distance target
</button>
- <button title="Add time target" @click="addTimeTarget">
+ <button title="Add time target" @click="addTimeTarget" v-if="setType !== 'split'">
Add time target
</button>
- <br/>
- <p>Note: time targets are ignored by the Split Calculator</p>
</td>
</tr>
</tfoot>
@@ -72,7 +70,6 @@ import { watch, ref } from 'vue';
import VueFeather from 'vue-feather';
-import targetUtils from '@/utils/targets';
import unitUtils from '@/utils/units';
import DecimalInput from '@/components/DecimalInput.vue';
@@ -83,7 +80,10 @@ import TimeInput from '@/components/TimeInput.vue';
*/
const model = defineModel({
type: Object,
- default: JSON.parse(JSON.stringify(targetUtils.defaultTargetSet)),
+ default: {
+ name: 'New target set',
+ targets: [],
+ }
});
const props = defineProps({
@@ -102,6 +102,14 @@ const props = defineProps({
type: String,
default: 'metric',
},
+
+ /**
+ * The target set type ('standard' or 'split')
+ */
+ setType: {
+ type: String,
+ default: 'standard'
+ },
});
// Declare emitted events
diff --git a/src/components/TargetSetSelector.vue b/src/components/TargetSetSelector.vue
@@ -7,15 +7,14 @@
<option value="_new">[ Create New Target Set ]</option>
</select>
- <button class="icon" title="Edit target set"
- @click="sortTargetSet(); dialogElement.showModal()">
+ <button class="icon" title="Edit target set" @click="dialogElement.showModal()">
<vue-feather type="edit" aria-hidden="true"/>
</button>
<dialog ref="dialogElement" class="target-set-editor-dialog" aria-label="Edit target set">
- <target-editor @close="sortTargetSet(); dialogElement.close()" v-model="targetSets[internalValue]"
- @revert="revertTargetSet" :default-unit-system="defaultUnitSystem"
- :isCustomSet="!internalValue.startsWith('_')"/>
+ <target-editor @close="sortTargetSet(); dialogElement.close()"
+ @revert="revertTargetSet" :default-unit-system="defaultUnitSystem" :setType="setType"
+ v-model="targetSets[internalValue]" :isCustomSet="!internalValue.startsWith('_')"/>
</dialog>
</span>
</template>
@@ -53,6 +52,14 @@ defineProps({
type: String,
default: 'metric',
},
+
+ /**
+ * The target set type ('standard' or 'split')
+ */
+ setType: {
+ type: String,
+ default: 'standard'
+ },
});
/**
diff --git a/src/utils/targets.js b/src/utils/targets.js
@@ -19,7 +19,7 @@ function sort(targets) {
const defaultTargetSets = {
'_pace_targets': {
name: 'Common Pace Targets',
- targets: [
+ targets: sort([
{ type: 'distance', distanceValue: 100, distanceUnit: 'meters' },
{ type: 'distance', distanceValue: 200, distanceUnit: 'meters' },
{ type: 'distance', distanceValue: 300, distanceUnit: 'meters' },
@@ -54,11 +54,11 @@ const defaultTargetSets = {
{ type: 'time', time: 600 },
{ type: 'time', time: 1800 },
{ type: 'time', time: 3600 },
- ],
+ ]),
},
'_race_targets': {
name: 'Common Race Targets',
- targets: [
+ targets: sort([
{ type: 'distance', distanceValue: 400, distanceUnit: 'meters' },
{ type: 'distance', distanceValue: 800, distanceUnit: 'meters' },
{ type: 'distance', distanceValue: 1500, distanceUnit: 'meters' },
@@ -77,7 +77,7 @@ const defaultTargetSets = {
{ type: 'distance', distanceValue: 0.5, distanceUnit: 'marathons' },
{ type: 'distance', distanceValue: 1, distanceUnit: 'marathons' },
- ],
+ ]),
},
'_split_targets': {
name: '5K Mile Splits',
@@ -89,13 +89,7 @@ const defaultTargetSets = {
},
};
-const defaultTargetSet = {
- name: 'New target set',
- targets: [],
-};
-
export default {
sort,
defaultTargetSets,
- defaultTargetSet,
};
diff --git a/src/views/AboutPage.vue b/src/views/AboutPage.vue
@@ -87,10 +87,6 @@
<li>If I finished a 5K in 20:00 and ran the first 2 miles in 13:00, how fast was the last ~1.1
miles? (6:19 per mile pace)</li>
</ul>
- <p>
- <strong>Note:</strong> The split calculator only works with distance targets and ignores all
- time targets.
- </p>
<h3>Unit Calculator</h3>
<p>
@@ -108,17 +104,16 @@
<h2>Target Sets</h2>
<p>
- A target set is a collection of distances and times that the Pace, Race, and Split Calculators
- will calculate results for.
+ A target set is a collection of distances and/or times that the Pace, Race, or Split
+ Calculators will calculate results for.
These calculators will output a duration for each distance target and a distance for each time
target.
- Running Tools comes with three default target sets.
- You can switch between these sets, modify the targets they contain, and add new targets sets
- from within each supporting calculator.
+ Each of these calculators comes with a default target set and allows you to add new target
+ sets, modify existing target sets, and switch between sets that belong to the same
+ calculator.
</p>
<p>
- <strong>Note:</strong> The split calculator only works with distance targets and ignores all
- time targets.
+ <strong>Note:</strong> The split calculator only supports distance targets.
</p>
</div>
</template>
diff --git a/src/views/SplitCalculator.vue b/src/views/SplitCalculator.vue
@@ -10,7 +10,7 @@
<div class="target-set">
Target Set:
- <target-set-selector v-model:selectedTargetSet="selectedTargetSet"
+ <target-set-selector v-model:selectedTargetSet="selectedTargetSet" setType="split"
v-model:targetSets="targetSets" :default-unit-system="defaultUnitSystem"/>
</div>
@@ -104,8 +104,7 @@ const results = computed(() => {
// Check for missing target set
if (!targetSets.value[selectedTargetSet.value]) return [];
- let targets = targetUtils.sort(targetSets.value[selectedTargetSet.value].targets.filter(x =>
- x.type === 'distance'));
+ let targets = targetSets.value[selectedTargetSet.value].targets;
for (let i = 0; i < targets.length; i += 1) {
// Calculate split and total times
diff --git a/tests/e2e/cross-calculator.spec.js b/tests/e2e/cross-calculator.spec.js
@@ -59,7 +59,6 @@ test('Save and update state when navigating between calculators', async ({ page
await page.getByLabel('Target distance unit').nth(0).selectOption('Kilometers');
await page.getByLabel('Target distance value').nth(1).fill('3.2');
await page.getByLabel('Target distance unit').nth(1).selectOption('Kilometers');
- await page.getByRole('button', { name: 'Add time target' }).click();
await page.getByRole('button', { name: 'Close' }).click();
// Enter input 5K splits (7:00, 6:30, 6:30)
diff --git a/tests/e2e/split-calculator.spec.js b/tests/e2e/split-calculator.spec.js
@@ -55,7 +55,6 @@ test('Customize target sets', async ({ page }) => {
await page.getByRole('button', { name: 'Add distance target' }).click();
await page.getByLabel('Target distance value').nth(3).fill('4.8');
await page.getByLabel('Target distance unit').nth(3).selectOption('Kilometers');
- await page.getByRole('button', { name: 'Add time target' }).click();
await page.getByRole('button', { name: 'Close' }).click();
// Assert times and paces are correct (new distances are processed)
@@ -102,7 +101,6 @@ test('Customize target sets', async ({ page }) => {
await page.getByRole('button', { name: 'Add distance target' }).click();
await page.getByLabel('Target distance value').nth(1).fill('800');
await page.getByLabel('Target distance unit').nth(1).selectOption('Meters');
- await page.getByRole('button', { name: 'Add time target' }).click();
await page.getByRole('button', { name: 'Close' }).click();
// Assert times and paces are correct (input splits initialized to zero)
diff --git a/tests/unit/components/TargetEditor.spec.js b/tests/unit/components/TargetEditor.spec.js
@@ -156,6 +156,25 @@ test('add time target button should correctly add time target', async () => {
]);
});
+test('add time target button should be hidden for split target sets', async () => {
+ // Initialize component
+ const wrapper = shallowMount(TargetEditor, {
+ propsData: {
+ modelValue: {
+ name: 'My target set',
+ targets: [
+ { distanceUnit: 'miles', distanceValue: 1, type: 'distance' },
+ { distanceUnit: 'miles', distanceValue: 2, type: 'distance' },
+ ],
+ },
+ setType: 'split',
+ },
+ });
+
+ // Add time target
+ expect(wrapper.findAll('button[title="Add time target"]')).toHaveLength(0);
+});
+
test('Should emit input event when targets are updated', async () => {
// Initialize component
const wrapper = shallowMount(TargetEditor, {
diff --git a/tests/unit/components/TargetSetSelector.spec.js b/tests/unit/components/TargetSetSelector.spec.js
@@ -255,18 +255,12 @@ test('edit button should open target editor with the correct props for custom se
expect(targetEditor.vm.defaultUnitSystem).to.equal('fake-unit-system');
});
-test('should sort target set before target editor is opened', async () => {
+test('should sort target set after target editor is closed', async () => {
// Initialize component
let targetSets = {
'_split_targets': {
name: '5K Mile Splits',
- targets: [
- { type: 'time', timeValue: 60 },
- { type: 'distance', distanceValue: 1, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 2, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 3, distanceUnit: 'miles' },
- ],
+ targets: [],
},
};
const wrapper = shallowMount(TargetSetSelector, {
@@ -276,11 +270,21 @@ test('should sort target set before target editor is opened', async () => {
}
});
- // Mock showModal function
- wrapper.vm.dialogElement.showModal = vi.fn();
+ // Mock modal close function
+ wrapper.vm.dialogElement.close = vi.fn();
- // Click edit button
- await wrapper.find('button').trigger('click');
+ // Update targets and trigger close event
+ await wrapper.findComponent({ name: 'target-editor' }).setValue({
+ name: '5K Mile Splits',
+ targets: [
+ { type: 'time', timeValue: 60 },
+ { type: 'distance', distanceValue: 1, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 2, distanceUnit: 'miles' },
+ { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' },
+ { type: 'distance', distanceValue: 3, distanceUnit: 'miles' },
+ ],
+ });
+ await wrapper.findComponent({ name: 'target-editor' }).vm.$emit('close');
// Assert target set was sorted
expect(wrapper.findComponent({ name: 'target-editor' }).vm.modelValue).to.deep.equal({
@@ -294,3 +298,14 @@ test('should sort target set before target editor is opened', async () => {
],
});
});
+
+test('should correctly pass setType prop to TargetEditor', async () => {
+ const wrapper = shallowMount(TargetSetSelector, {
+ propsData: {
+ setType: 'foo'
+ }
+ });
+
+ // Assert target editor props are correct
+ expect(wrapper.findComponent({ name: 'target-editor' }).vm.setType).to.equal('foo');
+});
diff --git a/tests/unit/views/SplitCalculator.spec.js b/tests/unit/views/SplitCalculator.spec.js
@@ -128,78 +128,6 @@ test('should correctly calculate paces and cumulative times from entered split t
expect(rows.length).to.equal(3);
});
-test('should correctly sort split targets', async () => {
- // Initialize localStorage (targets are mis-ordered)
- localStorage.setItem('running-tools.split-calculator-target-sets', JSON.stringify({
- '_split_targets': {
- name: 'Split targets',
- targets: [
- { type: 'distance', distanceValue: 2, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 1, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 2, distanceUnit: 'kilometers' },
- ],
- },
- }));
-
- // Initialize component
- const wrapper = shallowMount(SplitCalculator)
-
- // Assert results are correct
- const rows = wrapper.findAll('tbody tr');
- expect(rows[0].findAll('td')[0].element.textContent).to.equal('1 km');
- expect(rows[0].findAll('td')[1].element.textContent).to.equal('0:00.00');
- expect(rows[0].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(0);
- expect(rows[0].findAll('td')[3].element.textContent).to.equal('0:00 / mi');
- expect(rows[0].findAll('td').length).to.equal(4);
- expect(rows[1].findAll('td')[0].element.textContent).to.equal('2 km');
- expect(rows[1].findAll('td')[1].element.textContent).to.equal('0:00.00');
- expect(rows[1].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(0);
- expect(rows[1].findAll('td')[3].element.textContent).to.equal('0:00 / mi');
- expect(rows[1].findAll('td').length).to.equal(4);
- expect(rows[2].findAll('td')[0].element.textContent).to.equal('2 mi');
- expect(rows[2].findAll('td')[1].element.textContent).to.equal('0:00.00');
- expect(rows[2].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(0);
- expect(rows[2].findAll('td')[3].element.textContent).to.equal('0:00 / mi');
- expect(rows[2].findAll('td').length).to.equal(4);
- expect(rows.length).to.equal(3);
-});
-
-test('should ignore time based targets', async () => {
- // Initialize localStorage
- localStorage.setItem('running-tools.split-calculator-target-sets', JSON.stringify({
- '_split_targets': {
- name: 'Split targets',
- targets: [
- { type: 'distance', distanceValue: 1, distanceUnit: 'kilometers' },
- { type: 'time', time: 600 },
- { type: 'distance', distanceValue: 2, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 3000, distanceUnit: 'meters' },
- ],
- },
- }));
- // Initialize component
- const wrapper = shallowMount(SplitCalculator);
-
- // Assert results are correct
- const rows = wrapper.findAll('tbody tr');
- expect(rows[0].findAll('td')[0].element.textContent).to.equal('1 km');
- expect(rows[0].findAll('td')[1].element.textContent).to.equal('0:00.00');
- expect(rows[0].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(0);
- expect(rows[0].findAll('td')[3].element.textContent).to.equal('0:00 / mi');
- expect(rows[0].findAll('td').length).to.equal(4);
- expect(rows[1].findAll('td')[0].element.textContent).to.equal('2 km');
- expect(rows[1].findAll('td')[1].element.textContent).to.equal('0:00.00');
- expect(rows[1].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(0);
- expect(rows[1].findAll('td')[3].element.textContent).to.equal('0:00 / mi');
- expect(rows[1].findAll('td').length).to.equal(4);
- expect(rows[2].findAll('td')[0].element.textContent).to.equal('3000 m');
- expect(rows[2].findAll('td')[1].element.textContent).to.equal('0:00.00');
- expect(rows[2].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(0);
- expect(rows[2].findAll('td')[3].element.textContent).to.equal('0:00 / mi');
- expect(rows[2].findAll('td').length).to.equal(4);
- expect(rows.length).to.equal(3);
-});
-
test('should correctly save split times with split targets in localStorage', async () => {
// Initialize localStorage
localStorage.setItem('running-tools.split-calculator-target-sets', JSON.stringify({