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 });