/ tests / utils / phone-normalizer.test.js
phone-normalizer.test.js
  1  /**
  2   * Tests for Phone Number Normalization Utility
  3   */
  4  
  5  import { describe, test } from 'node:test';
  6  import assert from 'node:assert';
  7  import {
  8    normalizePhoneNumber,
  9    normalizePhoneNumbers,
 10    addCountryCode,
 11  } from '../../src/utils/phone-normalizer.js';
 12  
 13  describe('Phone Normalizer Module', () => {
 14    describe('normalizePhoneNumber', () => {
 15      test('should normalize US phone number with dashes', () => {
 16        const result = normalizePhoneNumber('+1-609-619-7151');
 17        assert.strictEqual(result, '+16096197151');
 18      });
 19  
 20      test('should normalize US phone number with parentheses and spaces', () => {
 21        const result = normalizePhoneNumber('+1 (609) 619-7151');
 22        assert.strictEqual(result, '+16096197151');
 23      });
 24  
 25      test('should normalize phone number without country code', () => {
 26        const result = normalizePhoneNumber('609-619-7151');
 27        assert.strictEqual(result, '6096197151');
 28      });
 29  
 30      test('should normalize Australian phone number', () => {
 31        const result = normalizePhoneNumber('+61 (424) 713 418');
 32        assert.strictEqual(result, '+61424713418');
 33      });
 34  
 35      test('should handle phone number with only spaces', () => {
 36        const result = normalizePhoneNumber('+1 234 567 8900');
 37        assert.strictEqual(result, '+12345678900');
 38      });
 39  
 40      test('should handle phone number with dots', () => {
 41        const result = normalizePhoneNumber('+1.609.619.7151');
 42        assert.strictEqual(result, '+16096197151');
 43      });
 44  
 45      test('should handle phone number with multiple formatting characters', () => {
 46        const result = normalizePhoneNumber('+1-(609).619-7151');
 47        assert.strictEqual(result, '+16096197151');
 48      });
 49  
 50      test('should remove multiple plus signs', () => {
 51        const result = normalizePhoneNumber('++1-609-619-7151');
 52        assert.strictEqual(result, '+16096197151');
 53      });
 54  
 55      test('should handle plus sign in the middle', () => {
 56        const result = normalizePhoneNumber('+1-609+619-7151');
 57        assert.strictEqual(result, '+16096197151');
 58      });
 59  
 60      test('should return unchanged if no formatting needed', () => {
 61        const result = normalizePhoneNumber('+16096197151');
 62        assert.strictEqual(result, '+16096197151');
 63      });
 64  
 65      test('should return empty string for empty input', () => {
 66        const result = normalizePhoneNumber('');
 67        assert.strictEqual(result, '');
 68      });
 69  
 70      test('should return null for null input', () => {
 71        const result = normalizePhoneNumber(null);
 72        assert.strictEqual(result, null);
 73      });
 74  
 75      test('should return undefined for undefined input', () => {
 76        const result = normalizePhoneNumber(undefined);
 77        assert.strictEqual(result, undefined);
 78      });
 79  
 80      test('should return non-string input unchanged', () => {
 81        const result = normalizePhoneNumber(123456);
 82        assert.strictEqual(result, 123456);
 83      });
 84  
 85      test('should handle UK phone number', () => {
 86        const result = normalizePhoneNumber('+44 20 7946 0958');
 87        assert.strictEqual(result, '+442079460958');
 88      });
 89  
 90      test('should handle phone number with extension markers removed', () => {
 91        const result = normalizePhoneNumber('+1-609-619-7151 ext 123');
 92        assert.strictEqual(result, '+16096197151123');
 93      });
 94  
 95      test('should handle international format with leading zeros', () => {
 96        const result = normalizePhoneNumber('+61 (0)424 713 418');
 97        assert.strictEqual(result, '+61424713418'); // (0) trunk prefix removed after country code
 98      });
 99  
100      test('should handle phone number starting without plus', () => {
101        const result = normalizePhoneNumber('1-609-619-7151');
102        assert.strictEqual(result, '16096197151');
103      });
104    });
105  
106    describe('normalizePhoneNumbers', () => {
107      test('should normalize array of phone number strings', () => {
108        const input = ['+1-609-619-7151', '+61 424 713 418', '555-1234'];
109        const result = normalizePhoneNumbers(input);
110  
111        assert.strictEqual(result.length, 3);
112        assert.strictEqual(result[0], '+16096197151');
113        assert.strictEqual(result[1], '+61424713418');
114        assert.strictEqual(result[2], '5551234');
115      });
116  
117      test('should normalize array of phone number objects', () => {
118        const input = [
119          { number: '+1-609-619-7151', label: 'Office' },
120          { number: '+61 424 713 418', label: 'Mobile' },
121        ];
122        const result = normalizePhoneNumbers(input);
123  
124        assert.strictEqual(result.length, 2);
125        assert.strictEqual(result[0].number, '+16096197151');
126        assert.strictEqual(result[0].label, 'Office');
127        assert.strictEqual(result[1].number, '+61424713418');
128        assert.strictEqual(result[1].label, 'Mobile');
129      });
130  
131      test('should handle mixed array of strings and objects', () => {
132        const input = ['+1-609-619-7151', { number: '+61 424 713 418', label: 'Mobile' }, '555-1234'];
133        const result = normalizePhoneNumbers(input);
134  
135        assert.strictEqual(result.length, 3);
136        assert.strictEqual(result[0], '+16096197151');
137        assert.strictEqual(result[1].number, '+61424713418');
138        assert.strictEqual(result[2], '5551234');
139      });
140  
141      test('should preserve additional object properties', () => {
142        const input = [
143          { number: '+1-609-619-7151', label: 'Office', primary: true, verified: false },
144        ];
145        const result = normalizePhoneNumbers(input);
146  
147        assert.strictEqual(result[0].number, '+16096197151');
148        assert.strictEqual(result[0].label, 'Office');
149        assert.strictEqual(result[0].primary, true);
150        assert.strictEqual(result[0].verified, false);
151      });
152  
153      test('should return unchanged if not an array', () => {
154        const result = normalizePhoneNumbers('not-an-array');
155        assert.strictEqual(result, 'not-an-array');
156      });
157  
158      test('should return null for null input', () => {
159        const result = normalizePhoneNumbers(null);
160        assert.strictEqual(result, null);
161      });
162  
163      test('should return undefined for undefined input', () => {
164        const result = normalizePhoneNumbers(undefined);
165        assert.strictEqual(result, undefined);
166      });
167  
168      test('should handle empty array', () => {
169        const result = normalizePhoneNumbers([]);
170        assert.deepStrictEqual(result, []);
171      });
172  
173      test('should handle array with null values', () => {
174        const input = ['+1-609-619-7151', null, '+61 424 713 418'];
175        const result = normalizePhoneNumbers(input);
176  
177        assert.strictEqual(result.length, 3);
178        assert.strictEqual(result[0], '+16096197151');
179        assert.strictEqual(result[1], null);
180        assert.strictEqual(result[2], '+61424713418');
181      });
182  
183      test('should handle array with undefined values', () => {
184        const input = ['+1-609-619-7151', undefined, '+61 424 713 418'];
185        const result = normalizePhoneNumbers(input);
186  
187        assert.strictEqual(result.length, 3);
188        assert.strictEqual(result[0], '+16096197151');
189        assert.strictEqual(result[1], undefined);
190        assert.strictEqual(result[2], '+61424713418');
191      });
192  
193      test('should handle objects without number property', () => {
194        const input = [{ label: 'Office' }, { number: '+1-609-619-7151', label: 'Mobile' }];
195        const result = normalizePhoneNumbers(input);
196  
197        assert.strictEqual(result.length, 2);
198        assert.deepStrictEqual(result[0], { label: 'Office' });
199        assert.strictEqual(result[1].number, '+16096197151');
200      });
201  
202      test('should handle objects with empty number property', () => {
203        const input = [{ number: '', label: 'Office' }];
204        const result = normalizePhoneNumbers(input);
205  
206        assert.strictEqual(result.length, 1);
207        assert.strictEqual(result[0].number, '');
208        assert.strictEqual(result[0].label, 'Office');
209      });
210  
211      test('should handle large array', () => {
212        const input = Array.from({ length: 100 }, (_, i) => `+1-555-${String(i).padStart(4, '0')}`);
213        const result = normalizePhoneNumbers(input);
214  
215        assert.strictEqual(result.length, 100);
216        assert.strictEqual(result[0], '+15550000');
217        assert.strictEqual(result[99], '+15550099');
218      });
219  
220      test('should preserve object reference integrity', () => {
221        const input = [{ number: '+1-609-619-7151', label: 'Office', metadata: { verified: true } }];
222        const result = normalizePhoneNumbers(input);
223  
224        assert.deepStrictEqual(result[0].metadata, { verified: true });
225      });
226    });
227  
228    describe('Edge Cases', () => {
229      test('should handle phone number with letters', () => {
230        const result = normalizePhoneNumber('+1-ABC-DEF-GHIJ');
231        assert.strictEqual(result, '+1');
232      });
233  
234      test('should handle phone number with only special characters', () => {
235        const result = normalizePhoneNumber('---()()---');
236        assert.strictEqual(result, '');
237      });
238  
239      test('should handle very long phone number', () => {
240        const result = normalizePhoneNumber('+1-234-567-8900-1234-5678');
241        assert.strictEqual(result, '+1234567890012345678');
242      });
243  
244      test('should handle phone number with unicode characters', () => {
245        const result = normalizePhoneNumber('+1📞609📱619🔢7151');
246        assert.strictEqual(result, '+16096197151');
247      });
248    });
249  
250    describe('addCountryCode', () => {
251      test('should add AU country code and remove leading 0', () => {
252        const result = addCountryCode('0412345678', 'AU');
253        assert.strictEqual(result, '+61412345678');
254      });
255  
256      test('should return unchanged if already has country code', () => {
257        const result = addCountryCode('+61412345678', 'AU');
258        assert.strictEqual(result, '+61412345678');
259      });
260  
261      test('should add US country code', () => {
262        const result = addCountryCode('6096197151', 'US');
263        assert.strictEqual(result, '+16096197151');
264      });
265  
266      test('should normalize formatted input before adding country code', () => {
267        const result = addCountryCode('(609) 619-7151', 'US');
268        assert.strictEqual(result, '+16096197151');
269      });
270  
271      test('should return normalized number for unknown country code', () => {
272        // Unknown country code returns normalized number without country prefix
273        const result = addCountryCode('1234567', 'ZZ');
274        assert.strictEqual(result, '1234567');
275      });
276  
277      test('should return normalized number when country code is null', () => {
278        const result = addCountryCode('1234567', null);
279        assert.strictEqual(result, '1234567');
280      });
281  
282      test('should return null for null phone number', () => {
283        const result = addCountryCode(null, 'AU');
284        assert.strictEqual(result, null);
285      });
286  
287      test('should return non-string phone number unchanged', () => {
288        const result = addCountryCode(123, 'AU');
289        assert.strictEqual(result, 123);
290      });
291  
292      test('should handle lowercase country code', () => {
293        const result = addCountryCode('0412345678', 'au');
294        assert.strictEqual(result, '+61412345678');
295      });
296    });
297  
298    describe('Real-world Examples', () => {
299      test('should normalize Twilio test number', () => {
300        const result = normalizePhoneNumber('+1 (500) 555-0006');
301        assert.strictEqual(result, '+15005550006');
302      });
303  
304      test('should normalize Australian landline', () => {
305        const result = normalizePhoneNumber('+61 (02) 1234 5678');
306        assert.strictEqual(result, '+61212345678'); // trunk prefix 0 in area code (02) is stripped
307      });
308  
309      test('should normalize Australian mobile', () => {
310        const result = normalizePhoneNumber('+61 412 345 678');
311        assert.strictEqual(result, '+61412345678');
312      });
313  
314      test('should normalize US toll-free number', () => {
315        const result = normalizePhoneNumber('+1-800-555-1234');
316        assert.strictEqual(result, '+18005551234');
317      });
318    });
319  });