commit 2cfdf69b011235bd699364f996ee967b5a15a60e
parent 379e7974ad448b3ad2d1b8ab41bed2566a78ecbc
Author: ashermorgan <59518073+ashermorgan@users.noreply.github.com>
Date: Wed, 17 Nov 2021 18:00:28 -0800
Implement formatNumber method
Diffstat:
9 files changed, 341 insertions(+), 141 deletions(-)
diff --git a/src/components/DecimalInput.vue b/src/components/DecimalInput.vue
@@ -8,6 +8,8 @@
</template>
<script>
+import formatUtils from '@/utils/format';
+
export default {
name: 'DecimalInput',
@@ -216,8 +218,7 @@ export default {
* @returns {String} The formated string
*/
format(value) {
- const result = value.toFixed(this.digits);
- return result.padStart(this.padding + this.digits + 1, '0');
+ return formatUtils.formatNumber(value, this.padding, this.digits, true);
},
},
};
diff --git a/src/components/SimpleTargetTable.vue b/src/components/SimpleTargetTable.vue
@@ -20,16 +20,16 @@
<tbody>
<tr v-for="(item, index) in results" :key="index">
<td :class="item.result === 'distance' ? 'result' : ''">
- {{ item.distanceValue.toFixed(2) }}
+ {{ formatNumber(item.distanceValue, 0, 2, true) }}
{{ distanceUnits[item.distanceUnit].symbol }}
</td>
<td :colspan="showPace ? 1 : 2" :class="item.result === 'time' ? 'result' : ''">
- {{ formatDuration(item.time, 0, 2) }}
+ {{ formatDuration(item.time, 0, 2, true) }}
</td>
<td v-if="showPace" colspan="2">
- {{ formatDuration(getPace(item), 3, 0) }}
+ {{ formatDuration(getPace(item), 3, 0, true) }}
/ {{ distanceUnits[getDefaultDistanceUnit()].symbol }}
</td>
</tr>
@@ -55,6 +55,7 @@ import {
EditIcon,
} from 'vue-feather-icons';
+import formatUtils from '@/utils/format';
import storage from '@/utils/localStorage';
import targetUtils from '@/utils/targets';
import unitUtils from '@/utils/units';
@@ -120,7 +121,12 @@ export default {
/**
* The formatDuration method
*/
- formatDuration: unitUtils.formatDuration,
+ formatDuration: formatUtils.formatDuration,
+
+ /**
+ * The formatNumber method
+ */
+ formatNumber: formatUtils.formatNumber,
/**
* The getDefaultDistanceUnit method
diff --git a/src/utils/format.js b/src/utils/format.js
@@ -0,0 +1,104 @@
+/**
+ * Format a number as a string
+ * @param {Number} value The number
+ * @param {Number} minPadding The minimum number of digits to show before the decimal point
+ * @param {Number} maxDigits The maximum number of digits to show after the decimal point
+ * @param {Boolean} extraDigits Whether to show extra zeros after the decimal point
+ * @returns {String} The formatted value
+ */
+function formatNumber(value, minPadding = 0, maxDigits = 2, extraDigits = true) {
+ // Initialize result
+ let result = '';
+
+ // Remove sign
+ const negative = value < 0;
+ const fixedValue = Math.abs(value);
+
+ // Address edge cases
+ if (Number.isNaN(fixedValue)) {
+ return 'NaN';
+ }
+ if (fixedValue === Infinity) {
+ return negative ? '-Infinity' : 'Infinity';
+ }
+
+ // Convert number to string
+ if (extraDigits) {
+ result = fixedValue.toFixed(maxDigits);
+ } else {
+ const power = 10 ** maxDigits;
+ result = (Math.round((fixedValue + Number.EPSILON) * power) / power).toString();
+ }
+
+ // Add padding
+ const currentPadding = result.split('.')[0].length;
+ result = result.padStart(result.length - currentPadding + minPadding, '0');
+
+ // Add negative sign
+ if (negative) {
+ result = `-${result}`;
+ }
+
+ // Return result
+ return result;
+}
+
+/**
+ * Format a duration as a string
+ * @param {Number} value The duration (in seconds)
+ * @param {Number} minPadding The minimum number of digits to show before the decimal point
+ * @param {Number} maxDigits The maximum number of digits to show after the decimal point
+ * @param {Boolean} extraDigits Whether to show extra zeros after the decimal point
+ * @returns {String} The formatted value
+ */
+function formatDuration(value, minPadding = 6, maxDigits = 2, extraDigits = true) {
+ // Check if value is NaN
+ if (Number.isNaN(value)) {
+ return 'NaN';
+ }
+
+ // Initialize result
+ let result = '';
+
+ // Check value sign
+ if (value < 0) {
+ result += '-';
+ }
+
+ // Check if value is valid
+ if (Math.abs(value) === Infinity) {
+ return `${result}Infinity`;
+ }
+
+ // Validate padding
+ let fixedPadding = Math.min(minPadding, 6);
+
+ // Prevent rounding errors
+ const fixedValue = parseFloat(Math.abs(value).toFixed(maxDigits));
+
+ // Calculate parts
+ const hours = Math.floor(fixedValue / 3600);
+ const minutes = Math.floor((fixedValue % 3600) / 60);
+ const seconds = fixedValue % 60;
+
+ // Format parts
+ if (hours !== 0 || fixedPadding >= 5) {
+ result += hours.toString().padStart(fixedPadding - 4, '0');
+ result += ':';
+ fixedPadding = 4;
+ }
+ if (minutes !== 0 || fixedPadding >= 3) {
+ result += minutes.toString().padStart(fixedPadding - 2, '0');
+ result += ':';
+ fixedPadding = 2;
+ }
+ result += formatNumber(seconds, fixedPadding, maxDigits, extraDigits);
+
+ // Return result
+ return result;
+}
+
+export default {
+ formatNumber,
+ formatDuration,
+};
diff --git a/src/utils/units.js b/src/utils/units.js
@@ -160,62 +160,6 @@ function convertSpeedPace(inputValue, inputUnit, outputUnit) {
}
/**
- * Format a duration as a string
- * @param {Number} value The duration (in seconds)
- * @param {Number} padding The number of digits to show before the decimal point
- * @param {Number} digits The number of digits to show after the decimal point
- * @returns {String} The formatted value
- */
-function formatDuration(value, padding = 6, digits = 2) {
- // Check if value is NaN
- if (Number.isNaN(value)) {
- return 'NaN';
- }
-
- // Initialize result
- let result = '';
-
- // Check value sign
- if (value < 0) {
- result += '-';
- }
-
- // Check if value is valid
- if (Math.abs(value) === Infinity) {
- return `${result}Infinity`;
- }
-
- // Validate padding
- let fixedPadding = Math.min(padding, 6);
-
- // Prevent rounding errors
- const fixedValue = parseFloat(Math.abs(value).toFixed(digits));
-
- // Calculate parts
- const hours = Math.floor(fixedValue / 3600);
- const minutes = Math.floor((fixedValue % 3600) / 60);
- const seconds = fixedValue % 60;
-
- // Format parts
- if (hours !== 0 || fixedPadding >= 5) {
- result += hours.toString().padStart(fixedPadding - 4, '0');
- result += ':';
- fixedPadding = 4;
- }
- if (minutes !== 0 || fixedPadding >= 3) {
- result += minutes.toString().padStart(fixedPadding - 2, '0');
- result += ':';
- fixedPadding = 2;
- }
- if (digits === 0) {
- result += seconds.toFixed(digits).padStart(fixedPadding, '0');
- } else {
- result += seconds.toFixed(digits).padStart(fixedPadding + digits + 1, '0');
- }
- return result;
-}
-
-/**
* Get the default unit system
* @returns {String} The default unit system
*/
@@ -263,8 +207,6 @@ export default {
convertPace,
convertSpeedPace,
- formatDuration,
-
getDefaultUnitSystem,
getDefaultDistanceUnit,
getDefaultSpeedUnit,
diff --git a/src/views/RaceCalculator.vue b/src/views/RaceCalculator.vue
@@ -41,14 +41,14 @@
(default: 1.06)
</div>
<div>
- Purdy Points: <b>{{ purdyPoints.toFixed(1) }}</b>
+ Purdy Points: <b>{{ formatNumber(purdyPoints, 0, 1, true) }}</b>
</div>
<div>
- V̇O₂: <b>{{ vo2.toFixed(1) }}</b> ml/kg/min
- (<b>{{ vo2Percentage.toFixed(1) }}%</b> of max)
+ V̇O₂: <b>{{ formatNumber(vo2, 0, 1, true) }}</b> ml/kg/min
+ (<b>{{ formatNumber(vo2Percentage, 0, 1, true) }}%</b> of max)
</div>
<div>
- V̇O₂ Max: <b>{{ vo2Max.toFixed(1) }}</b> ml/kg/min
+ V̇O₂ Max: <b>{{ formatNumber(vo2Max, 0, 1, true) }}</b> ml/kg/min
</div>
</div>
@@ -60,6 +60,7 @@
</template>
<script>
+import formatUtils from '@/utils/format';
import raceUtils from '@/utils/races';
import storage from '@/utils/localStorage';
import unitUtils from '@/utils/units';
@@ -115,6 +116,11 @@ export default {
distanceUnits: unitUtils.DISTANCE_UNITS,
/**
+ * The formatNumber method
+ */
+ formatNumber: formatUtils.formatNumber,
+
+ /**
* The default output targets
*/
defaultTargets: [
diff --git a/src/views/SplitCalculator.vue b/src/views/SplitCalculator.vue
@@ -23,12 +23,12 @@
<tbody>
<tr v-for="(item, index) in results" :key="index">
<td>
- {{ item.distanceValue.toFixed(2) }}
+ {{ formatNumber(item.distanceValue, 0, 2, true) }}
{{ distanceUnits[item.distanceUnit].symbol }}
</td>
<td>
- {{ formatDuration(item.totalTime, 3, 2) }}
+ {{ formatDuration(item.totalTime, 3, 2, true) }}
</td>
<td>
@@ -36,7 +36,7 @@
</td>
<td colspan="2">
- {{ formatDuration(item.pace, 3, 0) }}
+ {{ formatDuration(item.pace, 3, 0, true) }}
/ {{ distanceUnits[getDefaultDistanceUnit()].symbol }}
</td>
</tr>
@@ -63,6 +63,7 @@ import {
EditIcon,
} from 'vue-feather-icons';
+import formatUtils from '@/utils/format';
import storage from '@/utils/localStorage';
import targetUtils from '@/utils/targets';
import unitUtils from '@/utils/units';
@@ -103,7 +104,12 @@ export default {
/**
* The formatDuration method
*/
- formatDuration: unitUtils.formatDuration,
+ formatDuration: formatUtils.formatDuration,
+
+ /**
+ * The formatNumber method
+ */
+ formatNumber: formatUtils.formatNumber,
/**
* The getDefaultDistanceUnit method
diff --git a/src/views/UnitCalculator.vue b/src/views/UnitCalculator.vue
@@ -20,10 +20,10 @@
<span class="equals"> = </span>
<span v-if="getUnitType(outputUnit) === 'time'" class="output-value">
- {{ formatDuration(outputValue, 6, 3) }}
+ {{ formatDuration(outputValue, 6, 3, true) }}
</span>
<span v-else class="output-value">
- {{ outputValue.toFixed(3) }}
+ {{ formatNumber(outputValue, 0, 3, true) }}
</span>
<select v-model="outputUnit" class="output-units" aria-label="output units">
@@ -35,6 +35,7 @@
</template>
<script>
+import formatUtils from '@/utils/format';
import storage from '@/utils/localStorage';
import unitUtils from '@/utils/units';
@@ -74,7 +75,12 @@ export default {
/**
* The formatDuration method
*/
- formatDuration: unitUtils.formatDuration,
+ formatDuration: formatUtils.formatDuration,
+
+ /**
+ * The formatNumber method
+ */
+ formatNumber: formatUtils.formatNumber,
};
},
diff --git a/tests/unit/utils/format.spec.js b/tests/unit/utils/format.spec.js
@@ -0,0 +1,195 @@
+import { expect } from 'chai';
+import formatUtils from '@/utils/format';
+
+describe('utils/format.js', () => {
+ describe('formatNumber method', () => {
+ it('should correctly format number when padding is not 0', () => {
+ let result = formatUtils.formatNumber(12.3, 3, 0);
+ expect(result).to.equal('012');
+
+ result = formatUtils.formatNumber(12.3, 3, 2);
+ expect(result).to.equal('012.30');
+
+ result = formatUtils.formatNumber(123, 2, 0);
+ expect(result).to.equal('123');
+
+ result = formatUtils.formatNumber(-12, 3, 0);
+ expect(result).to.equal('-012');
+ });
+
+ it('should correctly format number when extraDigits is true', () => {
+ let result = formatUtils.formatNumber(1234, 0, 2);
+ expect(result).to.equal('1234.00');
+
+ result = formatUtils.formatNumber(1234.5, 0, 2);
+ expect(result).to.equal('1234.50');
+
+ result = formatUtils.formatNumber(1234.56, 0, 2);
+ expect(result).to.equal('1234.56');
+
+ result = formatUtils.formatNumber(1234.567, 0, 2);
+ expect(result).to.equal('1234.57');
+
+ result = formatUtils.formatNumber(1234.56, 0, 0);
+ expect(result).to.equal('1235');
+ });
+
+ it('should correctly format number when extraDigits is false', () => {
+ let result = formatUtils.formatNumber(1234, 0, 2, false);
+ expect(result).to.equal('1234');
+
+ result = formatUtils.formatNumber(1234.5, 0, 2, false);
+ expect(result).to.equal('1234.5');
+
+ result = formatUtils.formatNumber(1234.56, 0, 2, false);
+ expect(result).to.equal('1234.56');
+
+ result = formatUtils.formatNumber(1234.567, 0, 2, false);
+ expect(result).to.equal('1234.57');
+
+ result = formatUtils.formatNumber(1234.56, 0, 0, false);
+ expect(result).to.equal('1235');
+ });
+
+ it('should correctly format undefined', () => {
+ let result = formatUtils.formatNumber(undefined, 0, 2);
+ expect(result).to.equal('NaN');
+
+ result = formatUtils.formatNumber(undefined, 0, 2, false);
+ expect(result).to.equal('NaN');
+
+ result = formatUtils.formatNumber(undefined, 5, 2);
+ expect(result).to.equal('NaN');
+ });
+
+ it('should correctly format NaN', () => {
+ let result = formatUtils.formatNumber(NaN, 0, 0);
+ expect(result).to.equal('NaN');
+
+ result = formatUtils.formatNumber(NaN, 0, 2, false);
+ expect(result).to.equal('NaN');
+
+ result = formatUtils.formatNumber(NaN, 5, 2);
+ expect(result).to.equal('NaN');
+ });
+
+ it('should correctly format +/- Infinity', () => {
+ let result = formatUtils.formatNumber(Infinity);
+ expect(result).to.equal('Infinity');
+
+ result = formatUtils.formatNumber(Infinity, 10, 2);
+ expect(result).to.equal('Infinity');
+
+ result = formatUtils.formatNumber(-Infinity);
+ expect(result).to.equal('-Infinity');
+ });
+
+ it('should correctly format numbers less than 1', () => {
+ let result = formatUtils.formatNumber(0.123, 0, 0);
+ expect(result).to.equal('0');
+
+ result = formatUtils.formatNumber(0.123, 0, 2);
+ expect(result).to.equal('0.12');
+ });
+
+ it('should correctly format negative numbers', () => {
+ let result = formatUtils.formatNumber(-12, 0, 2, false);
+ expect(result).to.equal('-12');
+
+ result = formatUtils.formatNumber(-12, 0, 2);
+ expect(result).to.equal('-12.00');
+
+ result = formatUtils.formatNumber(-12.34, 0, 2);
+ expect(result).to.equal('-12.34');
+
+ result = formatUtils.formatNumber(-12.34, 3, 2);
+ expect(result).to.equal('-012.34');
+
+ result = formatUtils.formatNumber(-0.12, 0, 2);
+ expect(result).to.equal('-0.12');
+ });
+ });
+
+ describe('formatDuration method', () => {
+ it('should correctly divide durations into parts', () => {
+ const result = formatUtils.formatDuration(3600 + 120 + 3 + 0.4);
+ expect(result).to.equal('01:02:03.40');
+ });
+
+ it('should correctly format duration when padding is 7', () => {
+ const result = formatUtils.formatDuration(3600 + 120 + 3 + 0.4, 7);
+ expect(result).to.equal('01:02:03.40');
+ });
+
+ it('should correctly format duration when padding is 3', () => {
+ let result = formatUtils.formatDuration(3600 + 120 + 3 + 0.4, 3);
+ expect(result).to.equal('1:02:03.40');
+
+ result = formatUtils.formatDuration(120 + 3 + 0.4, 3);
+ expect(result).to.equal('2:03.40');
+
+ result = formatUtils.formatDuration(3 + 0.4, 3);
+ expect(result).to.equal('0:03.40');
+ });
+
+ it('should correctly format duration when padding is 0', () => {
+ const result = formatUtils.formatDuration(0.4, 0);
+ expect(result).to.equal('0.40');
+ });
+
+ it('should correctly format duration when digits is 3', () => {
+ const result = formatUtils.formatDuration(3600 + 120 + 3 + 0.4567, 0, 3);
+ expect(result).to.equal('1:02:03.457');
+ });
+
+ it('should correctly format duration when digits is 0', () => {
+ const result = formatUtils.formatDuration(3600 + 120 + 3 + 0.456, 0, 0);
+ expect(result).to.equal('1:02:03');
+ });
+
+ it('should correctly format NaN', () => {
+ const result = formatUtils.formatDuration(NaN);
+ expect(result).to.equal('NaN');
+ });
+
+ it('should correctly format +/- Infinity', () => {
+ let result = formatUtils.formatDuration(Infinity);
+ expect(result).to.equal('Infinity');
+
+ result = formatUtils.formatDuration(-Infinity);
+ expect(result).to.equal('-Infinity');
+ });
+
+ it('should correctly format 0 when padding is 0', () => {
+ const result = formatUtils.formatDuration(0, 0);
+ expect(result).to.equal('0.00');
+ });
+
+ it('should correctly format negative durations', () => {
+ const result = formatUtils.formatDuration(-3600 - 120 - 3 - 0.4);
+ expect(result).to.equal('-01:02:03.40');
+ });
+
+ it('should correctly format 59.9999', () => {
+ const result = formatUtils.formatDuration(59.9999);
+ expect(result).to.equal('00:01:00.00');
+ });
+
+ it('should correctly format duration when extraDigits is false', () => {
+ let result = formatUtils.formatDuration(83, 0, 2, false);
+ expect(result).to.equal('1:23');
+
+ result = formatUtils.formatDuration(83.4, 0, 2, false);
+ expect(result).to.equal('1:23.4');
+
+ result = formatUtils.formatDuration(83.45, 0, 2, false);
+ expect(result).to.equal('1:23.45');
+
+ result = formatUtils.formatDuration(83.456, 0, 2, false);
+ expect(result).to.equal('1:23.46');
+
+ result = formatUtils.formatDuration(83.45, 0, 0, false);
+ expect(result).to.equal('1:23');
+ });
+ });
+});
diff --git a/tests/unit/utils/units.spec.js b/tests/unit/utils/units.spec.js
@@ -61,70 +61,4 @@ describe('utils/units.js', () => {
expect(result).to.equal(1);
});
});
-
- describe('formatDuration method', () => {
- it('should correctly divide durations into parts', () => {
- const result = units.formatDuration(3600 + 120 + 3 + 0.4);
- expect(result).to.equal('01:02:03.40');
- });
-
- it('should correctly format duration when padding is 7', () => {
- const result = units.formatDuration(3600 + 120 + 3 + 0.4, 7);
- expect(result).to.equal('01:02:03.40');
- });
-
- it('should correctly format duration when padding is 3', () => {
- let result = units.formatDuration(3600 + 120 + 3 + 0.4, 3);
- expect(result).to.equal('1:02:03.40');
-
- result = units.formatDuration(120 + 3 + 0.4, 3);
- expect(result).to.equal('2:03.40');
-
- result = units.formatDuration(3 + 0.4, 3);
- expect(result).to.equal('0:03.40');
- });
-
- it('should correctly format duration when padding is 0', () => {
- const result = units.formatDuration(0.4, 0);
- expect(result).to.equal('0.40');
- });
-
- it('should correctly format duration when digits is 3', () => {
- const result = units.formatDuration(3600 + 120 + 3 + 0.4567, 0, 3);
- expect(result).to.equal('1:02:03.457');
- });
-
- it('should correctly format duration when digits is 0', () => {
- const result = units.formatDuration(3600 + 120 + 3 + 0.456, 0, 0);
- expect(result).to.equal('1:02:03');
- });
-
- it('should correctly format NaN', () => {
- const result = units.formatDuration(NaN);
- expect(result).to.equal('NaN');
- });
-
- it('should correctly format +/- Infinity', () => {
- let result = units.formatDuration(Infinity);
- expect(result).to.equal('Infinity');
-
- result = units.formatDuration(-Infinity);
- expect(result).to.equal('-Infinity');
- });
-
- it('should correctly format 0 when padding is 0', () => {
- const result = units.formatDuration(0, 0);
- expect(result).to.equal('0.00');
- });
-
- it('should correctly format negative durations', () => {
- const result = units.formatDuration(-3600 - 120 - 3 - 0.4);
- expect(result).to.equal('-01:02:03.40');
- });
-
- it('should correctly format 59.9999', () => {
- const result = units.formatDuration(59.9999);
- expect(result).to.equal('00:01:00.00');
- });
- });
});