running-tools

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

BatchCalculator.spec.js (25553B)


      1 import { beforeEach, test, expect } from 'vitest';
      2 import { shallowMount } from '@vue/test-utils';
      3 import BatchCalculator from '@/views/BatchCalculator.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 regular options to default values', async () => {
     12   // Initialize component
     13   const wrapper = shallowMount(BatchCalculator);
     14 
     15   // Assert regular 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: 'time-input' }).vm.modelValue).to.equal(15);
     22   expect(wrapper.findComponent({ name: 'integer-input' }).vm.modelValue).to.equal(20);
     23   expect(wrapper.find('select[aria-label="Calculator"]').element.value).to.equal('workout');
     24   expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.globalOptions).to.deep.equal({
     25     defaultUnitSystem: detectDefaultUnitSystem(),
     26     racePredictionOptions: {
     27       model: 'AverageModel',
     28       riegelExponent: 1.06,
     29     },
     30   });
     31   expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.batchOptions).to.deep.equal({
     32     calculator: 'workout',
     33     input: {
     34       distanceValue: 5,
     35       distanceUnit: 'kilometers',
     36       time: 1200,
     37     },
     38     increment: 15,
     39     label: '',
     40     rows: 20,
     41   });
     42 });
     43 
     44 test('should initialize calculator options to default values', async () => {
     45   // Initialize component
     46   const wrapper = shallowMount(BatchCalculator);
     47 
     48   // Assert pace calculator options are initialized
     49   await wrapper.find('select[aria-label="Calculator"]').setValue('pace');
     50   expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({
     51     input: {
     52       distanceValue: 5,
     53       distanceUnit: 'kilometers',
     54       time: 1200,
     55     },
     56     selectedTargetSet: '_pace_targets',
     57   });
     58   expect(wrapper.findComponent({ name: 'double-output-table' }).vm.targets)
     59     .to.deep.equal(defaultTargetSets._pace_targets.targets);
     60 
     61   // Assert race calculator options are loaded
     62   await wrapper.find('select[aria-label="Calculator"]').setValue('race');
     63   expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({
     64     input: {
     65       distanceValue: 5,
     66       distanceUnit: 'kilometers',
     67       time: 1200,
     68     },
     69     selectedTargetSet: '_race_targets',
     70   });
     71   expect(wrapper.findComponent({ name: 'double-output-table' }).vm.targets)
     72     .to.deep.equal(defaultTargetSets._race_targets.targets);
     73 
     74   // Assert workout calculator options are loaded
     75   await wrapper.find('select[aria-label="Calculator"]').setValue('workout');
     76   expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({
     77     customTargetNames: false,
     78     input: {
     79       distanceValue: 5,
     80       distanceUnit: 'kilometers',
     81       time: 1200,
     82     },
     83     selectedTargetSet: '_workout_targets',
     84   });
     85   expect(wrapper.findComponent({ name: 'double-output-table' }).vm.targets)
     86     .to.deep.equal(defaultTargetSets._workout_targets.targets);
     87 });
     88 
     89 test('should load regular options from localStorage', async () => {
     90   // Initialize localStorage
     91   localStorage.setItem('running-tools.global-options', JSON.stringify({
     92     defaultUnitSystem: 'imperial',
     93     racePredictionOptions: {
     94       model: 'PurdyPointsModel',
     95       riegelExponent: 1.2,
     96     },
     97   }));
     98   localStorage.setItem('running-tools.batch-calculator-options', JSON.stringify({
     99     calculator: 'race',
    100     increment: 32,
    101     input: {
    102       distanceValue: 2,
    103       distanceUnit: 'miles',
    104       time: 600,
    105     },
    106     label: 'foo',
    107     rows: 15,
    108   }));
    109 
    110   // Initialize component
    111   const wrapper = shallowMount(BatchCalculator);
    112 
    113   // Assert data loaded
    114   expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.globalOptions).to.deep.equal({
    115     defaultUnitSystem: 'imperial',
    116     racePredictionOptions: {
    117       model: 'PurdyPointsModel',
    118       riegelExponent: 1.2,
    119     },
    120   });
    121 
    122   // Assert options loaded
    123   expect(wrapper.find('select[aria-label="Calculator"]').element.value).to.equal('race');
    124   expect(wrapper.findComponent({ name: 'pace-input' }).vm.modelValue).to.deep.equal({
    125     distanceValue: 2,
    126     distanceUnit: 'miles',
    127     time: 600,
    128   });
    129   expect(wrapper.findComponent({ name: 'time-input' }).vm.modelValue).to.equal(32);
    130   expect(wrapper.findComponent({ name: 'integer-input' }).vm.modelValue).to.equal(15);
    131   expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.batchOptions).to.deep.equal({
    132     calculator: 'race',
    133     input: {
    134       distanceValue: 2,
    135       distanceUnit: 'miles',
    136       time: 600,
    137     },
    138     increment: 32,
    139     label: 'foo',
    140     rows: 15,
    141   });
    142 });
    143 
    144 test('should load calculator options from localStorage', async () => {
    145   // Initialize localStorage
    146   const selectedTargetSets = [
    147     {
    148       name: 'Pace targets #1',
    149       targets: [
    150         { type: 'distance', distanceValue: 1, distanceUnit: 'miles' },
    151       ],
    152     },
    153     {
    154       name: 'Race targets #1',
    155       targets: [
    156         { type: 'distance', distanceValue: 3, distanceUnit: 'miles' },
    157       ],
    158     },
    159     {
    160       name: 'Workout targets #1',
    161       targets: [
    162         {
    163           type: 'distance', distanceValue: 5, distanceUnit: 'miles',
    164           splitValue: 1, splitUnit: 'miles'
    165         },
    166       ],
    167     },
    168   ];
    169   localStorage.setItem('running-tools.pace-calculator-target-sets', JSON.stringify({
    170     'A': selectedTargetSets[0],
    171     'B': {
    172       name: 'Pace targets #2',
    173       targets: [
    174         { type: 'distance', distanceValue: 2, distanceUnit: 'miles' },
    175       ],
    176     }
    177   }));
    178   localStorage.setItem('running-tools.race-calculator-target-sets', JSON.stringify({
    179     'C': selectedTargetSets[1],
    180     'D': {
    181       name: 'Race targets #2',
    182       targets: [
    183         { type: 'distance', distanceValue: 4, distanceUnit: 'miles' },
    184       ],
    185     }
    186   }));
    187   localStorage.setItem('running-tools.workout-calculator-target-sets', JSON.stringify({
    188     'E': selectedTargetSets[2],
    189     'F': {
    190       name: 'Workout targets #2',
    191       targets: [
    192         { type: 'distance', distanceValue: 6, distanceUnit: 'miles' },
    193       ],
    194     }
    195   }));
    196   localStorage.setItem('running-tools.pace-calculator-options', JSON.stringify({
    197     input: {
    198       distanceValue: 1,
    199       distanceUnit: 'miles',
    200       time: 300,
    201     },
    202     selectedTargetSet: 'A',
    203   }));
    204   localStorage.setItem('running-tools.race-calculator-options', JSON.stringify({
    205     input: {
    206       distanceValue: 1.1,
    207       distanceUnit: 'miles',
    208       time: 310,
    209     },
    210     selectedTargetSet: 'C',
    211   }));
    212   localStorage.setItem('running-tools.workout-calculator-options', JSON.stringify({
    213     customTargetNames: true,
    214     input: {
    215       distanceValue: 1.2,
    216       distanceUnit: 'miles',
    217       time: 320,
    218     },
    219     selectedTargetSet: 'E',
    220   }));
    221 
    222   // Initialize component
    223   const wrapper = shallowMount(BatchCalculator);
    224 
    225   // Assert pace calculator options are loaded
    226   await wrapper.find('select[aria-label="Calculator"]').setValue('pace');
    227   expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({
    228     input: {
    229       distanceValue: 1.0,
    230       distanceUnit: 'miles',
    231       time: 300,
    232     },
    233     selectedTargetSet: 'A',
    234   });
    235   expect(wrapper.findComponent({ name: 'double-output-table' }).vm.targets)
    236     .to.deep.equal(selectedTargetSets[0].targets);
    237 
    238   // Assert race calculator options are loaded
    239   await wrapper.find('select[aria-label="Calculator"]').setValue('race');
    240   expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({
    241     input: {
    242       distanceValue: 1.1,
    243       distanceUnit: 'miles',
    244       time: 310,
    245     },
    246     selectedTargetSet: 'C',
    247   });
    248   expect(wrapper.findComponent({ name: 'double-output-table' }).vm.targets)
    249     .to.deep.equal(selectedTargetSets[1].targets);
    250 
    251   // Assert workout calculator options are loaded
    252   await wrapper.find('select[aria-label="Calculator"]').setValue('workout');
    253   expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({
    254     customTargetNames: true,
    255     input: {
    256       distanceValue: 1.2,
    257       distanceUnit: 'miles',
    258       time: 320,
    259     },
    260     selectedTargetSet: 'E',
    261   });
    262   expect(wrapper.findComponent({ name: 'double-output-table' }).vm.targets)
    263     .to.deep.equal(selectedTargetSets[2].targets);
    264 });
    265 
    266 test('should save regular options to localStorage when modified', async () => {
    267   // Initialize localStorage
    268   localStorage.setItem('running-tools.default-unit-system', '"metric"');
    269 
    270   // Initialize component
    271   const wrapper = shallowMount(BatchCalculator);
    272 
    273   // Change default units setting
    274   await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
    275     defaultUnitSystem: 'imperial',
    276     racePredictionOptions: {
    277       model: 'CameronModel',
    278       riegelExponent: 1.30,
    279     },
    280   }, 'globalOptions');
    281 
    282   // New default units should be saved to localStorage
    283   expect(localStorage.getItem('running-tools.global-options')).to.equal(JSON.stringify({
    284     defaultUnitSystem: 'imperial',
    285     racePredictionOptions: {
    286       model: 'CameronModel',
    287       riegelExponent: 1.30,
    288     },
    289   }));
    290 
    291   // Update input pace
    292   await wrapper.findComponent({ name: 'pace-input' }).setValue({
    293     distanceValue: 2,
    294     distanceUnit: 'miles',
    295     time: 600,
    296   });
    297 
    298   // Assert options saved
    299   expect(localStorage.getItem('running-tools.batch-calculator-options')).to.equal(JSON.stringify({
    300     calculator: 'workout',
    301     increment: 15,
    302     input: {
    303       distanceValue: 2,
    304       distanceUnit: 'miles',
    305       time: 600,
    306     },
    307     label: '',
    308     rows: 20,
    309   }));
    310 
    311   // Update increment value
    312   await wrapper.findComponent({ name: 'time-input' }).setValue(32);
    313 
    314   // Assert options saved
    315   expect(localStorage.getItem('running-tools.batch-calculator-options')).to.equal(JSON.stringify({
    316     calculator: 'workout',
    317     increment: 32,
    318     input: {
    319       distanceValue: 2,
    320       distanceUnit: 'miles',
    321       time: 600,
    322     },
    323     label: '',
    324     rows: 20,
    325   }));
    326 
    327   // Update number of rows
    328   await wrapper.findComponent({ name: 'integer-input' }).setValue(15);
    329 
    330   // Assert options saved
    331   expect(localStorage.getItem('running-tools.batch-calculator-options')).to.equal(JSON.stringify({
    332     calculator: 'workout',
    333     increment: 32,
    334     input: {
    335       distanceValue: 2,
    336       distanceUnit: 'miles',
    337       time: 600,
    338     },
    339     label: '',
    340     rows: 15,
    341   }));
    342 
    343   // Update batch column label
    344   await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
    345     calculator: 'workout',
    346     increment: 32,
    347     input: {
    348       distanceValue: 2,
    349       distanceUnit: 'miles',
    350       time: 600,
    351     },
    352     label: 'foo',
    353     rows: 15,
    354   }, 'batch-options');
    355 
    356   // Assert options saved
    357   expect(localStorage.getItem('running-tools.batch-calculator-options')).to.equal(JSON.stringify({
    358     calculator: 'workout',
    359     increment: 32,
    360     input: {
    361       distanceValue: 2,
    362       distanceUnit: 'miles',
    363       time: 600,
    364     },
    365     label: 'foo',
    366     rows: 15,
    367   }));
    368 
    369   // Update active calculator
    370   await wrapper.find('select[aria-label="Calculator"]').setValue('race');
    371 
    372   // Assert options saved
    373   expect(localStorage.getItem('running-tools.batch-calculator-options')).to.equal(JSON.stringify({
    374     calculator: 'race',
    375     increment: 32,
    376     input: {
    377       distanceValue: 2,
    378       distanceUnit: 'miles',
    379       time: 600,
    380     },
    381     label: 'foo',
    382     rows: 15,
    383   }));
    384 });
    385 
    386 test('should save calculator options to localStorage when modified', async () => {
    387   // Initialize localStorage
    388   const selectedTargetSets = [
    389     {
    390       name: 'Pace targets #1',
    391       targets: [
    392         { type: 'distance', distanceValue: 1, distanceUnit: 'miles' },
    393       ],
    394     },
    395     {
    396       name: 'Race targets #1',
    397       targets: [
    398         { type: 'distance', distanceValue: 3, distanceUnit: 'miles' },
    399       ],
    400     },
    401     {
    402       name: 'Workout targets #1',
    403       targets: [
    404         {
    405           type: 'distance', distanceValue: 5, distanceUnit: 'miles',
    406           splitValue: 1, splitUnit: 'miles'
    407         },
    408       ],
    409     },
    410   ];
    411   localStorage.setItem('running-tools.pace-calculator-target-sets', JSON.stringify({
    412     'A': selectedTargetSets[0],
    413     'B': {
    414       name: 'Pace targets #2',
    415       targets: [
    416         { type: 'distance', distanceValue: 2, distanceUnit: 'miles' },
    417       ],
    418     }
    419   }));
    420   localStorage.setItem('running-tools.pace-calculator-options', JSON.stringify({
    421     input: {
    422       distanceValue: 1,
    423       distanceUnit: 'miles',
    424       time: 300,
    425     },
    426     selectedTargetSet: 'B',
    427   }));
    428   localStorage.setItem('running-tools.race-calculator-target-sets', JSON.stringify({
    429     'C': selectedTargetSets[1],
    430     'D': {
    431       name: 'Race targets #2',
    432       targets: [
    433         { type: 'distance', distanceValue: 4, distanceUnit: 'miles' },
    434       ],
    435     }
    436   }));
    437   localStorage.setItem('running-tools.race-calculator-options', JSON.stringify({
    438     input: {
    439       distanceValue: 1.1,
    440       distanceUnit: 'miles',
    441       time: 310,
    442     },
    443     selectedTargetSet: 'D',
    444   }));
    445   localStorage.setItem('running-tools.workout-calculator-target-sets', JSON.stringify({
    446     'E': selectedTargetSets[2],
    447     'F': {
    448       name: 'Workout targets #2',
    449       targets: [
    450         { type: 'distance', distanceValue: 6, distanceUnit: 'miles' },
    451       ],
    452     }
    453   }));
    454   localStorage.setItem('running-tools.workout-calculator-options', JSON.stringify({
    455     customWorkoutNames: false,
    456     input: {
    457       distanceValue: 1.2,
    458       distanceUnit: 'miles',
    459       time: 320,
    460     },
    461     selectedTargetSet: 'F',
    462   }));
    463 
    464   // Initialize component
    465   const wrapper = shallowMount(BatchCalculator);
    466 
    467   // Update pace calculator options X
    468   await wrapper.find('select[aria-label="Calculator"]').setValue('pace');
    469   await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
    470     input: {
    471       distanceValue: 1.0,
    472       distanceUnit: 'miles',
    473       time: 300,
    474     },
    475     selectedTargetSet: 'A',
    476   }, 'options');
    477   expect(wrapper.findComponent({ name: 'double-output-table' }).vm.targets)
    478     .to.deep.equal(selectedTargetSets[0].targets);
    479 
    480   // Update race calculator options
    481   await wrapper.find('select[aria-label="Calculator"]').setValue('race');
    482   await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
    483     input: {
    484       distanceValue: 1.1,
    485       distanceUnit: 'miles',
    486       time: 310,
    487     },
    488     selectedTargetSet: 'C',
    489   }, 'options');
    490   expect(wrapper.findComponent({ name: 'double-output-table' }).vm.targets)
    491     .to.deep.equal(selectedTargetSets[1].targets);
    492 
    493   // Update workout calculator options
    494   await wrapper.find('select[aria-label="Calculator"]').setValue('workout');
    495   await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
    496     customTargetNames: true,
    497     input: {
    498       distanceValue: 1.2,
    499       distanceUnit: 'miles',
    500       time: 320,
    501     },
    502     selectedTargetSet: 'E',
    503   }, 'options');
    504   expect(wrapper.findComponent({ name: 'double-output-table' }).vm.targets)
    505     .to.deep.equal(selectedTargetSets[2].targets);
    506 
    507   // Assert options saved to localStorage
    508   expect(localStorage.getItem('running-tools.pace-calculator-options')).to.equal(JSON.stringify({
    509     input: {
    510       distanceValue: 1.0,
    511       distanceUnit: 'miles',
    512       time: 300,
    513     },
    514     selectedTargetSet: 'A',
    515   }));
    516   expect(localStorage.getItem('running-tools.race-calculator-options')).to.equal(JSON.stringify({
    517     input: {
    518       distanceValue: 1.1,
    519       distanceUnit: 'miles',
    520       time: 310,
    521     },
    522     selectedTargetSet: 'C',
    523   }));
    524   expect(localStorage.getItem('running-tools.workout-calculator-options')).to.equal(JSON.stringify({
    525     customTargetNames: true,
    526     input: {
    527       distanceValue: 1.2,
    528       distanceUnit: 'miles',
    529       time: 320,
    530     },
    531     selectedTargetSet: 'E',
    532   }));
    533 });
    534 
    535 test('should pass correct input props to DoubleOutputTable', async () => {
    536   // Initialize component
    537   const wrapper = shallowMount(BatchCalculator);
    538 
    539   // Assert that initial props are correct
    540   expect(wrapper.findComponent({ name: 'double-output-table' }).vm.inputDistance).to.deep.equal({
    541     distanceValue: 5,
    542     distanceUnit: 'kilometers',
    543   });
    544   expect(wrapper.findComponent({ name: 'double-output-table' }).vm.inputTimes).to.deep.equal([
    545     1200, 1215, 1230, 1245, 1260, 1275, 1290, 1305, 1320, 1335,
    546     1350, 1365, 1380, 1395, 1410, 1425, 1440, 1455, 1470, 1485,
    547   ]);
    548   expect(wrapper.findComponent({ name: 'double-output-table' }).vm.label).to.equal('5 km');
    549 
    550   // Change input pace
    551   await wrapper.findComponent({ name: 'pace-input' }).setValue({
    552     distanceValue: 2,
    553     distanceUnit: 'miles',
    554     time: 600,
    555   });
    556 
    557   // Assert that the props are updated
    558   expect(wrapper.findComponent({ name: 'double-output-table' }).vm.inputDistance).to.deep.equal({
    559     distanceValue: 2,
    560     distanceUnit: 'miles',
    561   });
    562   expect(wrapper.findComponent({ name: 'double-output-table' }).vm.inputTimes).to.deep.equal([
    563     600, 615, 630, 645, 660, 675, 690, 705, 720, 735,
    564     750, 765, 780, 795, 810, 825, 840, 855, 870, 885,
    565   ]);
    566   expect(wrapper.findComponent({ name: 'double-output-table' }).vm.label).to.equal('2 mi');
    567 
    568   // Change increment value
    569   await wrapper.findComponent({ name: 'time-input' }).setValue(10);
    570 
    571   // Assert that the props are updated
    572   expect(wrapper.findComponent({ name: 'double-output-table' }).vm.inputDistance).to.deep.equal({
    573     distanceValue: 2,
    574     distanceUnit: 'miles',
    575   });
    576   expect(wrapper.findComponent({ name: 'double-output-table' }).vm.inputTimes).to.deep.equal([
    577     600, 610, 620, 630, 640, 650, 660, 670, 680, 690,
    578     700, 710, 720, 730, 740, 750, 760, 770, 780, 790,
    579   ]);
    580   expect(wrapper.findComponent({ name: 'double-output-table' }).vm.label).to.equal('2 mi');
    581 
    582   // Change number of rows
    583   await wrapper.findComponent({ name: 'integer-input' }).setValue(15);
    584 
    585   // Assert that the props are updated
    586   expect(wrapper.findComponent({ name: 'double-output-table' }).vm.inputDistance).to.deep.equal({
    587     distanceValue: 2,
    588     distanceUnit: 'miles',
    589   });
    590   expect(wrapper.findComponent({ name: 'double-output-table' }).vm.inputTimes).to.deep.equal([
    591     600, 610, 620, 630, 640, 650, 660, 670, 680, 690, 700, 710, 720, 730, 740,
    592   ]);
    593   expect(wrapper.findComponent({ name: 'double-output-table' }).vm.label).to.equal('2 mi');
    594 
    595   // Enter custom batch column label
    596   await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
    597     calculator: 'workout',
    598     increment: 10,
    599     input: {
    600       distanceValue: 2,
    601       distanceUnit: 'miles',
    602       time: 600,
    603     },
    604     label: 'foo',
    605     rows: 15,
    606   }, 'batchOptions');
    607 
    608   // Assert that the props are updated
    609   expect(wrapper.findComponent({ name: 'double-output-table' }).vm.inputDistance).to.deep.equal({
    610     distanceValue: 2,
    611     distanceUnit: 'miles',
    612   });
    613   expect(wrapper.findComponent({ name: 'double-output-table' }).vm.inputTimes).to.deep.equal([
    614     600, 610, 620, 630, 640, 650, 660, 670, 680, 690, 700, 710, 720, 730, 740,
    615   ]);
    616   expect(wrapper.findComponent({ name: 'double-output-table' }).vm.label).to.equal('2 mi');
    617 
    618   // Enable target name customization
    619   await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({
    620     customTargetNames: true,
    621     input: {
    622       distanceValue: 5,
    623       distanceUnit: 'kilometers',
    624       time: 1200,
    625     },
    626     selectedTargetSet: '_workout_targets',
    627   }, 'options');
    628 
    629   // Assert that the props are updated
    630   expect(wrapper.findComponent({ name: 'double-output-table' }).vm.inputDistance).to.deep.equal({
    631     distanceValue: 2,
    632     distanceUnit: 'miles',
    633   });
    634   expect(wrapper.findComponent({ name: 'double-output-table' }).vm.inputTimes).to.deep.equal([
    635     600, 610, 620, 630, 640, 650, 660, 670, 680, 690, 700, 710, 720, 730, 740,
    636   ]);
    637   expect(wrapper.findComponent({ name: 'double-output-table' }).vm.label).to.equal('foo');
    638 
    639   // Switch calculators
    640   await wrapper.find('select[aria-label="Calculator"]').setValue('race');
    641 
    642   // Assert that the props are updated
    643   expect(wrapper.findComponent({ name: 'double-output-table' }).vm.inputDistance).to.deep.equal({
    644     distanceValue: 2,
    645     distanceUnit: 'miles',
    646   });
    647   expect(wrapper.findComponent({ name: 'double-output-table' }).vm.inputTimes).to.deep.equal([
    648     600, 610, 620, 630, 640, 650, 660, 670, 680, 690, 700, 710, 720, 730, 740,
    649   ]);
    650   expect(wrapper.findComponent({ name: 'double-output-table' }).vm.label).to.equal('2 mi');
    651 });
    652 
    653 test('should correctly set AdvancedOptionsInput props', async () => {
    654   // Initialize component
    655   const wrapper = shallowMount(BatchCalculator);
    656 
    657   // Set input pace and batch options
    658   await wrapper.findComponent({ name: 'pace-input' }).setValue({
    659     distanceValue: 2,
    660     distanceUnit: 'miles',
    661     time: 600,
    662   });
    663   await wrapper.findComponent({ name: 'time-input' }).setValue(32);
    664   await wrapper.findComponent({ name: 'integer-input' }).setValue(15);
    665 
    666   // Assert batch props are correct
    667   expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.batchOptions).to.deep.equal({
    668     calculator: 'workout',
    669     increment: 32,
    670     input: {
    671       distanceValue: 2,
    672       distanceUnit: 'miles',
    673       time: 600,
    674     },
    675     label: '',
    676     rows: 15,
    677   });
    678 
    679   // Assert props are correct for pace calculator
    680   await wrapper.find('select[aria-label="Calculator"]').setValue('pace');
    681   expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.type).to.equal('pace');
    682   expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.globalOptions).to.deep.equal({
    683     defaultUnitSystem: 'imperial',
    684     racePredictionOptions: {
    685       model: 'AverageModel',
    686       riegelExponent: 1.06,
    687     },
    688   });
    689   expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({
    690     input: {
    691       distanceValue: 5,
    692       distanceUnit: 'kilometers',
    693       time: 1200,
    694     },
    695     selectedTargetSet: '_pace_targets',
    696   });
    697 
    698   // Assert props are correct for race calculator
    699   await wrapper.find('select[aria-label="Calculator"]').setValue('race');
    700   expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.type).to.equal('race');
    701   expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.globalOptions).to.deep.equal({
    702     defaultUnitSystem: 'imperial',
    703     racePredictionOptions: {
    704       model: 'AverageModel',
    705       riegelExponent: 1.06,
    706     },
    707   });
    708   expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({
    709     input: {
    710       distanceValue: 5,
    711       distanceUnit: 'kilometers',
    712       time: 1200,
    713     },
    714     selectedTargetSet: '_race_targets',
    715   });
    716 
    717   // Assert props are correct for workout calculator
    718   await wrapper.find('select[aria-label="Calculator"]').setValue('workout');
    719   expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.type).to.equal('workout');
    720   expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.globalOptions).to.deep.equal({
    721     defaultUnitSystem: 'imperial',
    722     racePredictionOptions: {
    723       model: 'AverageModel',
    724       riegelExponent: 1.06,
    725     },
    726   });
    727   expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({
    728     customTargetNames: false,
    729     input: {
    730       distanceValue: 5,
    731       distanceUnit: 'kilometers',
    732       time: 1200,
    733     },
    734     selectedTargetSet: '_workout_targets',
    735   });
    736 });
    737 
    738 test('should correctly calculate outputs', async () => {
    739   // Initialize localStorage
    740   localStorage.setItem('running-tools.global-options', JSON.stringify({
    741     defaultUnitSystem: 'imperial',
    742     racePredictionOptions: {
    743       model: 'PurdyPointsModel',
    744       riegelExponent: 1.2,
    745     },
    746   }));
    747   localStorage.setItem('running-tools.race-calculator-options', JSON.stringify({
    748     input: {
    749       distanceValue: 1.1,
    750       distanceUnit: 'miles',
    751       time: 310,
    752     },
    753     selectedTargetSet: '_race_targets',
    754   }));
    755   localStorage.setItem('running-tools.workout-calculator-options', JSON.stringify({
    756     customTargetNames: false,
    757     input: {
    758       distanceValue: 1.2,
    759       distanceUnit: 'miles',
    760       time: 320,
    761     },
    762     selectedTargetSet: '_workout_targets',
    763   }));
    764 
    765   // Initialize component
    766   const wrapper = shallowMount(BatchCalculator);
    767   const input = { distanceValue: 2, distanceUnit: 'miles', time: 600 };
    768 
    769   // Assert pace outputs are calculated correctly
    770   await wrapper.find('select[aria-label="Calculator"]').setValue('pace');
    771   let calculate = wrapper.findComponent({ name: 'double-output-table' }).vm.calculateResult;
    772   expect(calculate(input, { type: 'time', time: 3600 })).to.deep.equal({
    773     key: '12.00 mi',
    774     value: '1:00:00',
    775     pace: '5:00 / mi',
    776     sort: 3600,
    777     result: 'key',
    778   });
    779 
    780   // Assert race outputs are calculated correctly
    781   await wrapper.find('select[aria-label="Calculator"]').setValue('race');
    782   calculate = wrapper.findComponent({ name: 'double-output-table' }).vm.calculateResult;
    783   expect(calculate(input, { type: 'time', time: 3600 })).to.deep.equal({
    784     key: '10.93 mi',
    785     value: '1:00:00',
    786     pace: '5:29 / mi',
    787     sort: 3600,
    788     result: 'key',
    789   });
    790 
    791   // Assert workout outputs are calculated correctly
    792   await wrapper.find('select[aria-label="Calculator"]').setValue('workout');
    793   calculate = wrapper.findComponent({ name: 'double-output-table' }).vm.calculateResult;
    794   const workoutTarget = { type: 'time', time: 3600, splitValue: 1, splitUnit: 'miles' };
    795   const result = calculate(input, workoutTarget);
    796   expect(result.key).to.equal('1 mi @ 1:00:00');
    797   expect(result.value).to.equal('5:29');
    798   expect(result.pace).to.equal('');
    799   expect(result.sort).to.be.closeTo(329.48, 0.01);
    800   expect(result.result).to.equal('value');
    801 });