running-tools

A collection of tools for runners and their coaches
git clone https://git.ashermorgan.net/running-tools/
Log | Files | Refs | README

PaceCalculator.spec.js (9538B)


      1 import { beforeEach, test, expect } from 'vitest';
      2 import { shallowMount } from '@vue/test-utils';
      3 import PaceCalculator from '@/views/PaceCalculator.vue';
      4 import { defaultTargetSets } from '@/core/targets';
      5 import { detectDefaultUnitSystem } from '@/core/units';
      6 
      7 beforeEach(() => {
      8   localStorage.clear();
      9 });
     10 
     11 test('should initialize options to default values', async () => {
     12   // Initialize component
     13   const wrapper = shallowMount(PaceCalculator);
     14 
     15   // Assert options are initialized
     16   expect(wrapper.findComponent({ name: 'pace-input' }).vm.modelValue).to.deep.equal({
     17     distanceValue: 5,
     18     distanceUnit: 'kilometers',
     19     time: 1200,
     20   });
     21   expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.globalOptions
     22     .defaultUnitSystem).to.equal(detectDefaultUnitSystem());
     23   expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({
     24     input: {
     25       distanceValue: 5,
     26       distanceUnit: 'kilometers',
     27       time: 1200,
     28     },
     29     selectedTargetSet: '_pace_targets',
     30   });
     31   expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.targetSets)
     32     .to.deep.equal({ _pace_targets: defaultTargetSets._pace_targets });
     33   expect(wrapper.findComponent({ name: 'single-output-table' }).vm.targets)
     34     .to.deep.equal(defaultTargetSets._pace_targets.targets);
     35 });
     36 
     37 test('should load options from localStorage', async () => {
     38   const targetSets = {
     39     '_pace_targets': {
     40       name: 'Pace targets #1',
     41       targets: [
     42         { type: 'distance', distanceValue: 400, distanceUnit: 'meters' },
     43         { type: 'distance', distanceValue: 800, distanceUnit: 'meters' },
     44         { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' },
     45       ],
     46     },
     47     'B': {
     48       name: 'Pace targets #2',
     49       targets: [
     50         { type: 'distance', distanceValue: 1, distanceUnit: 'miles' },
     51         { type: 'distance', distanceValue: 2, distanceUnit: 'miles' },
     52         { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' },
     53       ],
     54     },
     55   };
     56 
     57   // Initialize localStorage
     58   localStorage.setItem('running-tools.global-options', JSON.stringify({
     59     defaultUnitSystem: 'imperial',
     60     racePredictionOptions: {
     61       model: 'PurdyPointsModel',
     62       riegelExponent: 1.2,
     63     },
     64   }));
     65   localStorage.setItem('running-tools.pace-calculator-target-sets', JSON.stringify(targetSets));
     66   localStorage.setItem('running-tools.pace-calculator-options', JSON.stringify({
     67     input: {
     68       distanceValue: 1,
     69       distanceUnit: 'miles',
     70       time: 600,
     71     },
     72     selectedTargetSet: 'B',
     73   }));
     74 
     75   // Initialize component
     76   const wrapper = shallowMount(PaceCalculator);
     77 
     78   // Assert options are loaded
     79   expect(wrapper.findComponent({ name: 'pace-input' }).vm.modelValue).to.deep.equal({
     80     distanceValue: 1,
     81     distanceUnit: 'miles',
     82     time: 600,
     83   });
     84   expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.globalOptions
     85     .defaultUnitSystem).to.equal('imperial');
     86   expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({
     87     input: {
     88       distanceValue: 1,
     89       distanceUnit: 'miles',
     90       time: 600,
     91     },
     92     selectedTargetSet: 'B',
     93   });
     94   expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.targetSets)
     95     .to.deep.equal(targetSets);
     96   expect(wrapper.findComponent({ name: 'single-output-table' }).vm.targets)
     97     .to.deep.equal(targetSets.B.targets);
     98 });
     99 
    100 test('should save options to localStorage when modified', async () => {
    101   const targetSets = {
    102     '_pace_targets': {
    103       name: 'Pace targets #1',
    104       targets: [
    105         { type: 'distance', distanceValue: 400, distanceUnit: 'meters' },
    106         { type: 'distance', distanceValue: 800, distanceUnit: 'meters' },
    107         { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' },
    108       ],
    109     },
    110     'B': {
    111       name: 'Pace targets #2',
    112       targets: [
    113         { type: 'distance', distanceValue: 1, distanceUnit: 'miles' },
    114         { type: 'distance', distanceValue: 2, distanceUnit: 'miles' },
    115         { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' },
    116       ],
    117     },
    118   };
    119 
    120   // Initialize component
    121   const wrapper = shallowMount(PaceCalculator);
    122 
    123   // Change default units
    124   await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
    125     defaultUnitSystem: 'imperial',
    126     racePredictionOptions: {
    127       model: 'AverageModel',
    128       riegelExponent: 1.06,
    129     },
    130   }, 'globalOptions');
    131 
    132   // New default units should be saved to localStorage
    133   expect(localStorage.getItem('running-tools.global-options')).to.equal(JSON.stringify({
    134     defaultUnitSystem: 'imperial',
    135     racePredictionOptions: {
    136       model: 'AverageModel',
    137       riegelExponent: 1.06,
    138     },
    139   }));
    140 
    141   // Update input pace
    142   await wrapper.findComponent({ name: 'pace-input' }).setValue({
    143     distanceValue: 1,
    144     distanceUnit: 'miles',
    145     time: 600,
    146   });
    147 
    148   // New input pace should be saved to localStorage
    149   expect(localStorage.getItem('running-tools.pace-calculator-options')).to.equal(JSON.stringify({
    150     input: {
    151       distanceValue: 1,
    152       distanceUnit: 'miles',
    153       time: 600,
    154     },
    155     selectedTargetSet: '_pace_targets',
    156   }));
    157 
    158   // Update target sets and selected target set
    159   await wrapper.findComponent({ name: 'advanced-options-input' }).setValue(targetSets,
    160     'targetSets');
    161   await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
    162     input: {
    163       distanceValue: 1,
    164       distanceUnit: 'miles',
    165       time: 600,
    166     },
    167     selectedTargetSet: 'B',
    168   }, 'options');
    169 
    170   // New selected target set should be saved to localStorage
    171   expect(localStorage.getItem('running-tools.pace-calculator-target-sets'))
    172     .to.equal(JSON.stringify(targetSets));
    173   expect(localStorage.getItem('running-tools.pace-calculator-options')).to.equal(JSON.stringify({
    174     input: {
    175       distanceValue: 1,
    176       distanceUnit: 'miles',
    177       time: 600,
    178     },
    179     selectedTargetSet: 'B',
    180   }));
    181 });
    182 
    183 test('should correctly calculate time results', async () => {
    184   // Initialize component
    185   const wrapper = shallowMount(PaceCalculator);
    186 
    187   // Enter input pace data
    188   await wrapper.findComponent({ name: 'pace-input' }).setValue({
    189     distanceValue: 1,
    190     distanceUnit: 'kilometers',
    191     time: 100,
    192   });
    193 
    194   // Calculate result
    195   const calculateResult = wrapper.findComponent({ name: 'single-output-table' }).vm.calculateResult;
    196   const result = calculateResult({
    197     distanceValue: 20,
    198     distanceUnit: 'meters',
    199     type: 'distance',
    200   });
    201 
    202   // Assert result is correct
    203   expect(result).to.deep.equal({
    204     key: '20 m',
    205     value: '0:02.00',
    206     pace: '2:41 / mi',
    207     result: 'value',
    208     sort: 2,
    209   });
    210 });
    211 
    212 test('should correctly calculate distance results according to global options', async () => {
    213   // Initialize component
    214   const wrapper = shallowMount(PaceCalculator);
    215 
    216   // Enter input pace data
    217   await wrapper.findComponent({ name: 'pace-input' }).setValue({
    218     distanceValue: 2,
    219     distanceUnit: 'miles',
    220     time: 1200,
    221   });
    222 
    223   // Set default units
    224   await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
    225     defaultUnitSystem: 'metric',
    226     racePredictionOptions: {
    227       model: 'AverageModel',
    228       riegelExponent: 1.06,
    229     },
    230   }, 'globalOptions');
    231 
    232   // Get calculate result function
    233   const calculateResult = wrapper.findComponent({ name: 'single-output-table' }).vm.calculateResult;
    234 
    235   // Assert result is correct
    236   let result = calculateResult({ type: 'time', time: 600 });
    237   expect(result.key).to.equal('1.61 km');
    238 
    239   // Change default units
    240   await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
    241     defaultUnitSystem: 'imperial',
    242     racePredictionOptions: {
    243       model: 'AverageModel',
    244       riegelExponent: 1.06,
    245     },
    246   }, 'globalOptions');
    247 
    248   // Assert result is correct
    249   result = calculateResult({ type: 'time', time: 600 });
    250   expect(result.key).to.equal('1.00 mi');
    251 });
    252 
    253 test('should not show paces in results table', async () => {
    254   // Initialize component
    255   const wrapper = shallowMount(PaceCalculator);
    256 
    257   // Assert paces are not shown in results table
    258   expect(wrapper.findComponent({ name: 'single-output-table' }).vm.showPace).to.equal(false);
    259 });
    260 
    261 test('should correctly handle null target set', async () => {
    262   // Initialize component
    263   const wrapper = shallowMount(PaceCalculator);
    264 
    265   // Switch to invalid target set
    266   await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
    267     input: {
    268       distanceValue: 5,
    269       distanceUnit: 'kilometers',
    270       time: 1200,
    271     },
    272     selectedTargetSet: 'does_not_exist'
    273   }, 'options');
    274 
    275   // Assert empty array passed to SingleOutputTable component
    276   expect(wrapper.findComponent({ name: 'single-output-table' }).vm.targets).to.deep.equal([]);
    277 
    278   // Switch to valid target set
    279   await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
    280     input: {
    281       distanceValue: 5,
    282       distanceUnit: 'kilometers',
    283       time: 1200,
    284     },
    285     selectedTargetSet: '_pace_targets'
    286   }, 'options');
    287 
    288   // Assert valid targets passed to SingleOutputTable component
    289   const paceTargets = defaultTargetSets._pace_targets.targets;
    290   expect(wrapper.findComponent({ name: 'single-output-table' }).vm.targets)
    291     .to.deep.equal(paceTargets);
    292 });
    293 
    294 test('should correctly set AdvancedOptionsInput type prop', async () => {
    295   // Initialize component
    296   const wrapper = shallowMount(PaceCalculator);
    297 
    298   // Assert type prop is correctly set
    299   expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.type).to.equal('pace');
    300 });