1(function () {
2 'use strict';
3
4 const converter = typeof require === 'function' ? require('./convert.js') : window.UnitConverter;
5 const tests = [];
6
7 function test(name, fn) {
8 tests.push({ name: name, fn: fn });
9 }
10
11 function approx(actual, expected, tolerance, message) {
12 const delta = Math.abs(actual - expected);
13 if (delta > tolerance) {
14 throw new Error((message || 'Values differ') + ': expected ' + expected + ', got ' + actual + ', delta ' + delta);
15 }
16 }
17
18 function equal(actual, expected, message) {
19 if (actual !== expected) {
20 throw new Error((message || 'Values differ') + ': expected ' + expected + ', got ' + actual);
21 }
22 }
23
24 function throws(fn, ErrorType, messageIncludes) {
25 let thrown = null;
26 try { fn(); } catch (error) { thrown = error; }
27 if (!thrown) throw new Error('Expected an exception');
28 if (ErrorType && !(thrown instanceof ErrorType)) {
29 throw new Error('Expected ' + ErrorType.name + ', got ' + thrown.constructor.name);
30 }
31 if (messageIncludes && !String(thrown.message).includes(messageIncludes)) {
32 throw new Error('Expected message including "' + messageIncludes + '", got "' + thrown.message + '"');
33 }
34 }
35
36 test('length: meters, feet, miles', function () {
37 approx(converter.convertValue(1, 'm', 'cm', 'length'), 100, 1e-12);
38 approx(converter.convertValue(1, 'ft', 'in', 'length'), 12, 1e-12);
39 approx(converter.convertValue(1, 'mi', 'ft', 'length'), 5280, 1e-9);
40 approx(converter.convertValue(5, 'km', 'mi', 'length'), 3.106855961, 1e-9);
41 });
42
43 test('mass: kilograms, pounds, ounces', function () {
44 approx(converter.convertValue(1, 'kg', 'g', 'mass'), 1000, 1e-12);
45 approx(converter.convertValue(1, 'lb', 'oz', 'mass'), 16, 1e-12);
46 approx(converter.convertValue(10, 'st', 'lb', 'mass'), 140, 1e-7);
47 });
48
49 test('temperature: common reference points', function () {
50 approx(converter.convertValue(0, 'C', 'F', 'temperature'), 32, 1e-12);
51 approx(converter.convertValue(100, 'C', 'F', 'temperature'), 212, 1e-12);
52 approx(converter.convertValue(32, 'F', 'C', 'temperature'), 0, 1e-12);
53 approx(converter.convertValue(212, 'F', 'C', 'temperature'), 100, 1e-12);
54 approx(converter.convertValue(273.15, 'K', 'C', 'temperature'), 0, 1e-12);
55 });
56
57 test('temperature: absolute zero edge cases', function () {
58 approx(converter.convertValue(-273.15, 'C', 'K', 'temperature'), 0, 1e-12);
59 approx(converter.convertValue(0, 'K', 'C', 'temperature'), -273.15, 1e-12);
60 approx(converter.convertValue(0, 'K', 'F', 'temperature'), -459.67, 1e-10);
61 approx(converter.convertValue(-459.67, 'F', 'K', 'temperature'), 0, 1e-10);
62 throws(function () { converter.convertValue(-273.1500001, 'C', 'K', 'temperature'); }, RangeError, 'absolute zero');
63 throws(function () { converter.convertValue(-1, 'K', 'C', 'temperature'); }, RangeError, 'absolute zero');
64 throws(function () { converter.convertValue(-459.670001, 'F', 'C', 'temperature'); }, RangeError, 'absolute zero');
65 });
66
67 test('temperature: negative values above absolute zero', function () {
68 approx(converter.convertValue(-40, 'C', 'F', 'temperature'), -40, 1e-12);
69 approx(converter.convertValue(-40, 'F', 'C', 'temperature'), -40, 1e-12);
70 approx(converter.convertValue(233.15, 'K', 'F', 'temperature'), -40, 1e-10);
71 });
72
73 test('data size: decimal and binary units', function () {
74 approx(converter.convertValue(8, 'b', 'B', 'data'), 1, 1e-12);
75 approx(converter.convertValue(1, 'KB', 'B', 'data'), 1000, 1e-12);
76 approx(converter.convertValue(1, 'KiB', 'B', 'data'), 1024, 1e-12);
77 approx(converter.convertValue(1, 'MiB', 'KB', 'data'), 1048.576, 1e-12);
78 approx(converter.convertValue(1, 'GB', 'GiB', 'data'), 0.9313225746154785, 1e-15);
79 });
80
81 test('time: seconds, days, years', function () {
82 approx(converter.convertValue(2, 'h', 'min', 'time'), 120, 1e-12);
83 approx(converter.convertValue(1, 'd', 'h', 'time'), 24, 1e-12);
84 approx(converter.convertValue(1, 'wk', 'd', 'time'), 7, 1e-12);
85 approx(converter.convertValue(1, 'yr', 'd', 'time'), 365.25, 1e-12);
86 approx(converter.convertValue(1500, 'ms', 's', 'time'), 1.5, 1e-12);
87 });
88
89 test('round trips retain value across every unit in each category', function () {
90 Object.keys(converter.CATEGORIES).forEach(function (category) {
91 const units = Object.keys(converter.CATEGORIES[category].units);
92 units.forEach(function (from) {
93 units.forEach(function (to) {
94 const value = category === 'temperature' ? 25 : 123.456;
95 const converted = converter.convertValue(value, from, to, category);
96 const roundTrip = converter.convertValue(converted, to, from, category);
97 approx(roundTrip, value, category === 'temperature' ? 1e-9 : 1e-8, category + ' ' + from + ' -> ' + to + ' -> ' + from);
98 });
99 });
100 });
101 });
102
103 test('formatNumber trims trailing zeros and respects precision', function () {
104 equal(converter.formatNumber(12.3400, 4), '12.34');
105 equal(converter.formatNumber(12.3456, 2), '12.35');
106 equal(converter.formatNumber(-0, 4), '0');
107 equal(converter.formatNumber(42, 0), '42');
108 });
109
110 test('input validation reports invalid categories, units, and values', function () {
111 throws(function () { converter.convertValue(1, 'm', 'cm', 'speed'); }, Error, 'Unknown category');
112 throws(function () { converter.convertValue(1, 'meter', 'cm', 'length'); }, Error, 'Unknown unit');
113 throws(function () { converter.convertValue(Number.POSITIVE_INFINITY, 'm', 'cm', 'length'); }, TypeError, 'finite number');
114 });
115
116 function report(results) {
117 if (typeof document === 'undefined') return;
118 const output = document.getElementById('results');
119 const failed = results.filter(function (result) { return !result.ok; });
120 output.className = failed.length ? 'fail' : 'pass';
121 output.innerHTML = '<h2>' + (failed.length ? 'Failed' : 'Passed') + ': ' + (results.length - failed.length) + '/' + results.length + '</h2>' +
122 '<ol>' + results.map(function (result) {
123 return '<li><strong>' + (result.ok ? '✓' : '✗') + ' ' + result.name + '</strong>' +
124 (result.ok ? '' : '<pre>' + result.error.stack.replace(/[<>&]/g, function (c) { return { '<': '<', '>': '>', '&': '&' }[c]; }) + '</pre>') +
125 '</li>';
126 }).join('') + '</ol>';
127 }
128
129 function run() {
130 const results = tests.map(function (item) {
131 try {
132 item.fn();
133 return { name: item.name, ok: true };
134 } catch (error) {
135 return { name: item.name, ok: false, error: error };
136 }
137 });
138
139 report(results);
140
141 const failed = results.filter(function (result) { return !result.ok; });
142 if (typeof process !== 'undefined' && process.versions && process.versions.node) {
143 results.forEach(function (result) {
144 const mark = result.ok ? '✓' : '✗';
145 console.log(mark + ' ' + result.name);
146 if (!result.ok) console.error(result.error.stack);
147 });
148 if (failed.length) process.exitCode = 1;
149 }
150 }
151
152 if (typeof document !== 'undefined' && document.readyState === 'loading') {
153 document.addEventListener('DOMContentLoaded', run);
154 } else {
155 run();
156 }
157})();
158
Discussion
No comments yet. Start the discussion. Recorded by @patrick-toulme.