shared: phone number normalize, format and reserved check
Task: M0-T2
This commit is contained in:
1 parent
aa99622a24
commit
0247137765
3 files changed
+104
No files matched your search
@@ -1,2 +1,3 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
export const APP_NAME = 'SelfPhoneNG';
|
||||
export * from './phone.ts';
|
||||
@@ -0,0 +1,72 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { formatNumber, isReservedNumber, normalizeNumber } from './phone.ts';
|
||||
|
||||
describe('normalizeNumber', () => {
|
||||
it.each([
|
||||
['+1 (312) 555-1234', '13125551234'],
|
||||
['+13125551234', '13125551234'],
|
||||
['3125551234', '13125551234'],
|
||||
['13125551234', '13125551234'],
|
||||
['(312) 555-1234', '13125551234'],
|
||||
['312.555.1234', '13125551234'],
|
||||
['312-555-1234', '13125551234'],
|
||||
[' 312 555 1234 ', '13125551234'],
|
||||
['8005551234', '18005551234'],
|
||||
['+44 20 7946 0958', '011442079460958'],
|
||||
['+33 1 23 45 67 89', '01133123456789'],
|
||||
['011442079460958', '011442079460958'],
|
||||
['*97', '*97'],
|
||||
['*72101', '*72101'],
|
||||
['**101', '**101'],
|
||||
['#', '#'],
|
||||
['911', '911'],
|
||||
['101', '101'],
|
||||
['0', '0'],
|
||||
['5551234', '5551234'],
|
||||
['1234567890', '1234567890'], // 10 digits starting with 1 is not NANP: left as-is
|
||||
])('%j → %s', (input, expected) => {
|
||||
expect(normalizeNumber(input)).toBe(expected);
|
||||
});
|
||||
|
||||
it.each([
|
||||
'',
|
||||
' ',
|
||||
'abc',
|
||||
'312-555-CALL',
|
||||
'+',
|
||||
'+1',
|
||||
'+1312555123',
|
||||
'+131255512345',
|
||||
'+44abc',
|
||||
'12+34',
|
||||
'101); System(rm -rf /',
|
||||
'१२३',
|
||||
])('rejects %j', (input) => {
|
||||
expect(() => normalizeNumber(input)).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatNumber', () => {
|
||||
it.each([
|
||||
['13125551234', '(312) 555-1234'],
|
||||
['18005551234', '(800) 555-1234'],
|
||||
['101', '101'],
|
||||
['011442079460958', '011442079460958'],
|
||||
['*97', '*97'],
|
||||
])('%s → %s', (input, expected) => {
|
||||
expect(formatNumber(input)).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isReservedNumber', () => {
|
||||
it.each(['211', '311', '411', '511', '611', '711', '811', '911', '933', '0123', '1000', '10000'])(
|
||||
'%s is reserved',
|
||||
(n) => {
|
||||
expect(isReservedNumber(n)).toBe(true);
|
||||
},
|
||||
);
|
||||
it.each(['100', '101', '499', '500', '700', '912', '011', '2000'])('%s is free', (n) => {
|
||||
expect(isReservedNumber(n)).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
// The only place that normalizes phone numbers (spec 00 §8).
|
||||
|
||||
const RESERVED = new Set(['211', '311', '411', '511', '611', '711', '811', '911', '933']);
|
||||
|
||||
export function normalizeNumber(input: string, defaultCountry = '1'): string {
|
||||
const n = input.replace(/[\s\-.()]/g, '');
|
||||
if (n.startsWith('+')) {
|
||||
const rest = n.slice(1);
|
||||
if (!/^\d+$/.test(rest)) throw new Error(`Invalid phone number: ${input}`);
|
||||
if (defaultCountry === '1' && rest.startsWith('1')) {
|
||||
// TODO(spec): 00 §8 only defines +1 followed by 10 digits; other lengths are rejected.
|
||||
if (rest.length !== 11) throw new Error(`Invalid phone number: ${input}`);
|
||||
return rest;
|
||||
}
|
||||
return `011${rest}`;
|
||||
}
|
||||
if (/^[2-9]\d{9}$/.test(n)) return defaultCountry + n;
|
||||
if (/^[0-9*#]+$/.test(n)) return n;
|
||||
throw new Error(`Invalid phone number: ${input}`);
|
||||
}
|
||||
|
||||
export function formatNumber(n: string): string {
|
||||
const m = /^1(\d{3})(\d{3})(\d{4})$/.exec(n);
|
||||
return m ? `(${m[1]}) ${m[2]}-${m[3]}` : n;
|
||||
}
|
||||
|
||||
/** N11 service codes, 933, and anything starting with 0 or 1 followed by more than 2 digits. */
|
||||
export function isReservedNumber(n: string): boolean {
|
||||
return RESERVED.has(n) || /^[01]\d{3,}$/.test(n);
|
||||
}
|
||||
Reference in new issue
Block a user