backend v4 half
This commit is contained in:
+12
@@ -0,0 +1,12 @@
|
||||
import PhoneNumberMatcher from '../PhoneNumberMatcher.js'
|
||||
import normalizeArguments from '../normalizeArguments.js'
|
||||
|
||||
export default function findNumbers() {
|
||||
const { text, options, metadata } = normalizeArguments(arguments)
|
||||
const matcher = new PhoneNumberMatcher(text, options, metadata)
|
||||
const results = []
|
||||
while (matcher.hasNext()) {
|
||||
results.push(matcher.next())
|
||||
}
|
||||
return results
|
||||
}
|
||||
Generated
Vendored
+204
@@ -0,0 +1,204 @@
|
||||
import findNumbers from './findNumbers.js'
|
||||
import metadata from '../../metadata.max.json' assert { type: 'json' }
|
||||
|
||||
describe('findNumbers', () => {
|
||||
it('should find numbers', () => {
|
||||
findNumbers('2133734253', 'US', metadata).should.deep.equal([{
|
||||
phone : '2133734253',
|
||||
country : 'US',
|
||||
startsAt : 0,
|
||||
endsAt : 10
|
||||
}])
|
||||
|
||||
findNumbers('(213) 373-4253', 'US', metadata).should.deep.equal([{
|
||||
phone : '2133734253',
|
||||
country : 'US',
|
||||
startsAt : 0,
|
||||
endsAt : 14
|
||||
}])
|
||||
|
||||
findNumbers('The number is +7 (800) 555-35-35 and not (213) 373-4253 as written in the document.', 'US', metadata).should.deep.equal([{
|
||||
phone : '8005553535',
|
||||
country : 'RU',
|
||||
startsAt : 14,
|
||||
endsAt : 32
|
||||
}, {
|
||||
phone : '2133734253',
|
||||
country : 'US',
|
||||
startsAt : 41,
|
||||
endsAt : 55
|
||||
}])
|
||||
|
||||
// Opening parenthesis issue.
|
||||
// https://github.com/catamphetamine/libphonenumber-js/issues/252
|
||||
findNumbers('The number is +7 (800) 555-35-35 and not (213) 373-4253 (that\'s not even in the same country!) as written in the document.', 'US', metadata).should.deep.equal([{
|
||||
phone : '8005553535',
|
||||
country : 'RU',
|
||||
startsAt : 14,
|
||||
endsAt : 32
|
||||
}, {
|
||||
phone : '2133734253',
|
||||
country : 'US',
|
||||
startsAt : 41,
|
||||
endsAt : 55
|
||||
}])
|
||||
|
||||
// No default country.
|
||||
findNumbers('The number is +7 (800) 555-35-35 as written in the document.', metadata).should.deep.equal([{
|
||||
phone : '8005553535',
|
||||
country : 'RU',
|
||||
startsAt : 14,
|
||||
endsAt : 32
|
||||
}])
|
||||
|
||||
// Passing `options` and default country.
|
||||
findNumbers('The number is +7 (800) 555-35-35 as written in the document.', 'US', { leniency: 'VALID' }, metadata).should.deep.equal([{
|
||||
phone : '8005553535',
|
||||
country : 'RU',
|
||||
startsAt : 14,
|
||||
endsAt : 32
|
||||
}])
|
||||
|
||||
// Passing `options`.
|
||||
findNumbers('The number is +7 (800) 555-35-35 as written in the document.', { leniency: 'VALID' }, metadata).should.deep.equal([{
|
||||
phone : '8005553535',
|
||||
country : 'RU',
|
||||
startsAt : 14,
|
||||
endsAt : 32
|
||||
}])
|
||||
|
||||
// Not a phone number and a phone number.
|
||||
findNumbers('Digits 12 are not a number, but +7 (800) 555-35-35 is.', { leniency: 'VALID' }, metadata).should.deep.equal([{
|
||||
phone : '8005553535',
|
||||
country : 'RU',
|
||||
startsAt : 32,
|
||||
endsAt : 50
|
||||
}])
|
||||
|
||||
// Phone number extension.
|
||||
findNumbers('Date 02/17/2018 is not a number, but +7 (800) 555-35-35 ext. 123 is.', { leniency: 'VALID' }, metadata).should.deep.equal([{
|
||||
phone : '8005553535',
|
||||
country : 'RU',
|
||||
ext : '123',
|
||||
startsAt : 37,
|
||||
endsAt : 64
|
||||
}])
|
||||
})
|
||||
|
||||
it('should find numbers (v2)', () => {
|
||||
const phoneNumbers = findNumbers('The number is +7 (800) 555-35-35 ext. 1234 and not (213) 373-4253 as written in the document.', 'US', { v2: true }, metadata)
|
||||
|
||||
phoneNumbers.length.should.equal(2)
|
||||
|
||||
phoneNumbers[0].startsAt.should.equal(14)
|
||||
phoneNumbers[0].endsAt.should.equal(42)
|
||||
|
||||
phoneNumbers[0].number.number.should.equal('+78005553535')
|
||||
phoneNumbers[0].number.nationalNumber.should.equal('8005553535')
|
||||
phoneNumbers[0].number.country.should.equal('RU')
|
||||
phoneNumbers[0].number.countryCallingCode.should.equal('7')
|
||||
phoneNumbers[0].number.ext.should.equal('1234')
|
||||
|
||||
phoneNumbers[1].startsAt.should.equal(51)
|
||||
phoneNumbers[1].endsAt.should.equal(65)
|
||||
|
||||
phoneNumbers[1].number.number.should.equal('+12133734253')
|
||||
phoneNumbers[1].number.nationalNumber.should.equal('2133734253')
|
||||
phoneNumbers[1].number.country.should.equal('US')
|
||||
phoneNumbers[1].number.countryCallingCode.should.equal('1')
|
||||
})
|
||||
|
||||
it('shouldn\'t find non-valid numbers', () => {
|
||||
// Not a valid phone number for US.
|
||||
findNumbers('1111111111', 'US', metadata).should.deep.equal([])
|
||||
})
|
||||
|
||||
it('should find non-European digits', () => {
|
||||
// E.g. in Iraq they don't write `+442323234` but rather `+٤٤٢٣٢٣٢٣٤`.
|
||||
findNumbers('العَرَبِيَّة +٤٤٣٣٣٣٣٣٣٣٣٣عَرَبِيّ', metadata).should.deep.equal([{
|
||||
country : 'GB',
|
||||
phone : '3333333333',
|
||||
startsAt : 14,
|
||||
endsAt : 27
|
||||
}])
|
||||
})
|
||||
|
||||
it('should work in edge cases', () => {
|
||||
let thrower
|
||||
|
||||
// No input
|
||||
findNumbers('', metadata).should.deep.equal([])
|
||||
|
||||
// // No country metadata for this `require` country code
|
||||
// thrower = () => findNumbers('123', 'ZZ', metadata)
|
||||
// thrower.should.throw('Unknown country')
|
||||
|
||||
// Numerical `value`
|
||||
thrower = () => findNumbers(2141111111, 'US')
|
||||
thrower.should.throw('A text for parsing must be a string.')
|
||||
|
||||
// // No metadata
|
||||
// thrower = () => findNumbers('')
|
||||
// thrower.should.throw('`metadata` argument not passed')
|
||||
|
||||
// No metadata, no default country, no phone numbers.
|
||||
findNumbers('').should.deep.equal([])
|
||||
})
|
||||
|
||||
it('should find international numbers when passed a non-existent default country', () => {
|
||||
const numbers = findNumbers('Phone: +7 (800) 555 35 35. National: 8 (800) 555-55-55', { defaultCountry: 'XX', v2: true }, metadata)
|
||||
numbers.length.should.equal(1)
|
||||
numbers[0].number.nationalNumber.should.equal('8005553535')
|
||||
})
|
||||
|
||||
it('shouldn\'t find phone numbers which are not phone numbers', () => {
|
||||
// A timestamp.
|
||||
findNumbers('2012-01-02 08:00', 'US', metadata).should.deep.equal([])
|
||||
|
||||
// A valid number (not a complete timestamp).
|
||||
findNumbers('2012-01-02 08', 'US', metadata).should.deep.equal([{
|
||||
country : 'US',
|
||||
phone : '2012010208',
|
||||
startsAt : 0,
|
||||
endsAt : 13
|
||||
}])
|
||||
|
||||
// Invalid parens.
|
||||
findNumbers('213(3734253', 'US', metadata).should.deep.equal([])
|
||||
|
||||
// Letters after phone number.
|
||||
findNumbers('2133734253a', 'US', metadata).should.deep.equal([])
|
||||
|
||||
// Valid phone (same as the one found in the UUID below).
|
||||
findNumbers('The phone number is 231354125.', 'FR', metadata).should.deep.equal([{
|
||||
country : 'FR',
|
||||
phone : '231354125',
|
||||
startsAt : 20,
|
||||
endsAt : 29
|
||||
}])
|
||||
|
||||
// Not a phone number (part of a UUID).
|
||||
// Should parse in `{ extended: true }` mode.
|
||||
const possibleNumbers = findNumbers('The UUID is CA801c26f98cd16e231354125ad046e40b.', 'FR', { extended: true }, metadata)
|
||||
possibleNumbers.length.should.equal(1)
|
||||
possibleNumbers[0].country.should.equal('FR')
|
||||
possibleNumbers[0].phone.should.equal('231354125')
|
||||
|
||||
// Not a phone number (part of a UUID).
|
||||
// Shouldn't parse by default.
|
||||
findNumbers('The UUID is CA801c26f98cd16e231354125ad046e40b.', 'FR', metadata).should.deep.equal([])
|
||||
})
|
||||
|
||||
// https://gitlab.com/catamphetamine/libphonenumber-js/-/merge_requests/4
|
||||
it('should return correct `startsAt` and `endsAt` when matching "inner" candidates in a could-be-a-candidate substring', () => {
|
||||
findNumbers('39945926 77200596 16533084', 'ID', metadata)
|
||||
.should
|
||||
.deep
|
||||
.equal([{
|
||||
country: 'ID',
|
||||
phone: '77200596',
|
||||
startsAt: 9,
|
||||
endsAt: 17
|
||||
}])
|
||||
})
|
||||
})
|
||||
Generated
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
// This is a legacy function.
|
||||
// Use `findNumbers()` instead.
|
||||
|
||||
import _findPhoneNumbers, { searchPhoneNumbers as _searchPhoneNumbers } from './findPhoneNumbersInitialImplementation.js'
|
||||
import normalizeArguments from '../normalizeArguments.js'
|
||||
|
||||
export default function findPhoneNumbers()
|
||||
{
|
||||
const { text, options, metadata } = normalizeArguments(arguments)
|
||||
return _findPhoneNumbers(text, options, metadata)
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ES6 `for ... of` iterator.
|
||||
*/
|
||||
export function searchPhoneNumbers()
|
||||
{
|
||||
const { text, options, metadata } = normalizeArguments(arguments)
|
||||
return _searchPhoneNumbers(text, options, metadata)
|
||||
}
|
||||
Generated
Vendored
+233
@@ -0,0 +1,233 @@
|
||||
// This is a legacy function.
|
||||
// Use `findNumbers()` instead.
|
||||
|
||||
import findNumbers, { searchPhoneNumbers } from './findPhoneNumbers.js'
|
||||
import { PhoneNumberSearch } from './findPhoneNumbersInitialImplementation.js'
|
||||
import metadata from '../../metadata.min.json' assert { type: 'json' }
|
||||
|
||||
describe('findPhoneNumbers', () => {
|
||||
it('should find numbers', () => {
|
||||
findNumbers('2133734253', 'US', metadata).should.deep.equal([{
|
||||
phone : '2133734253',
|
||||
country : 'US',
|
||||
startsAt : 0,
|
||||
endsAt : 10
|
||||
}])
|
||||
|
||||
findNumbers('(213) 373-4253', 'US', metadata).should.deep.equal([{
|
||||
phone : '2133734253',
|
||||
country : 'US',
|
||||
startsAt : 0,
|
||||
endsAt : 14
|
||||
}])
|
||||
|
||||
findNumbers('The number is +7 (800) 555-35-35 and not (213) 373-4253 as written in the document.', 'US', metadata).should.deep.equal([{
|
||||
phone : '8005553535',
|
||||
country : 'RU',
|
||||
startsAt : 14,
|
||||
endsAt : 32
|
||||
}, {
|
||||
phone : '2133734253',
|
||||
country : 'US',
|
||||
startsAt : 41,
|
||||
endsAt : 55
|
||||
}])
|
||||
|
||||
// Opening parenthesis issue.
|
||||
// https://github.com/catamphetamine/libphonenumber-js/issues/252
|
||||
findNumbers('The number is +7 (800) 555-35-35 and not (213) 373-4253 (that\'s not even in the same country!) as written in the document.', 'US', metadata).should.deep.equal([{
|
||||
phone : '8005553535',
|
||||
country : 'RU',
|
||||
startsAt : 14,
|
||||
endsAt : 32
|
||||
}, {
|
||||
phone : '2133734253',
|
||||
country : 'US',
|
||||
startsAt : 41,
|
||||
endsAt : 55
|
||||
}])
|
||||
|
||||
// No default country.
|
||||
findNumbers('The number is +7 (800) 555-35-35 as written in the document.', metadata).should.deep.equal([{
|
||||
phone : '8005553535',
|
||||
country : 'RU',
|
||||
startsAt : 14,
|
||||
endsAt : 32
|
||||
}])
|
||||
|
||||
// Passing `options` and default country.
|
||||
findNumbers('The number is +7 (800) 555-35-35 as written in the document.', 'US', { leniency: 'VALID' }, metadata).should.deep.equal([{
|
||||
phone : '8005553535',
|
||||
country : 'RU',
|
||||
startsAt : 14,
|
||||
endsAt : 32
|
||||
}])
|
||||
|
||||
// Passing `options`.
|
||||
findNumbers('The number is +7 (800) 555-35-35 as written in the document.', { leniency: 'VALID' }, metadata).should.deep.equal([{
|
||||
phone : '8005553535',
|
||||
country : 'RU',
|
||||
startsAt : 14,
|
||||
endsAt : 32
|
||||
}])
|
||||
|
||||
// Not a phone number and a phone number.
|
||||
findNumbers('Digits 12 are not a number, but +7 (800) 555-35-35 is.', { leniency: 'VALID' }, metadata).should.deep.equal([{
|
||||
phone : '8005553535',
|
||||
country : 'RU',
|
||||
startsAt : 32,
|
||||
endsAt : 50
|
||||
}])
|
||||
|
||||
// Phone number extension.
|
||||
findNumbers('Date 02/17/2018 is not a number, but +7 (800) 555-35-35 ext. 123 is.', { leniency: 'VALID' }, metadata).should.deep.equal([{
|
||||
phone : '8005553535',
|
||||
country : 'RU',
|
||||
ext : '123',
|
||||
startsAt : 37,
|
||||
endsAt : 64
|
||||
}])
|
||||
})
|
||||
|
||||
it('shouldn\'t find non-valid numbers', () => {
|
||||
// Not a valid phone number for US.
|
||||
findNumbers('1111111111', 'US', metadata).should.deep.equal([])
|
||||
})
|
||||
|
||||
it('should find non-European digits', () => {
|
||||
// E.g. in Iraq they don't write `+442323234` but rather `+٤٤٢٣٢٣٢٣٤`.
|
||||
findNumbers('العَرَبِيَّة +٤٤٣٣٣٣٣٣٣٣٣٣عَرَبِيّ', metadata).should.deep.equal([{
|
||||
country : 'GB',
|
||||
phone : '3333333333',
|
||||
startsAt : 14,
|
||||
endsAt : 27
|
||||
}])
|
||||
})
|
||||
|
||||
it('should iterate', () => {
|
||||
const expected_numbers = [{
|
||||
country : 'RU',
|
||||
phone : '8005553535',
|
||||
// number : '+7 (800) 555-35-35',
|
||||
startsAt : 14,
|
||||
endsAt : 32
|
||||
}, {
|
||||
country : 'US',
|
||||
phone : '2133734253',
|
||||
// number : '(213) 373-4253',
|
||||
startsAt : 41,
|
||||
endsAt : 55
|
||||
}]
|
||||
|
||||
for (const number of searchPhoneNumbers('The number is +7 (800) 555-35-35 and not (213) 373-4253 as written in the document.', 'US', metadata)) {
|
||||
number.should.deep.equal(expected_numbers.shift())
|
||||
}
|
||||
|
||||
expected_numbers.length.should.equal(0)
|
||||
})
|
||||
|
||||
it('should work in edge cases', () => {
|
||||
let thrower
|
||||
|
||||
// No input
|
||||
findNumbers('', metadata).should.deep.equal([])
|
||||
|
||||
// No country metadata for this `require` country code
|
||||
thrower = () => findNumbers('123', 'ZZ', metadata)
|
||||
thrower.should.throw('Unknown country')
|
||||
|
||||
// Numerical `value`
|
||||
thrower = () => findNumbers(2141111111, 'US')
|
||||
thrower.should.throw('A text for parsing must be a string.')
|
||||
|
||||
// // No metadata
|
||||
// thrower = () => findNumbers('')
|
||||
// thrower.should.throw('`metadata` argument not passed')
|
||||
})
|
||||
|
||||
it('shouldn\'t find phone numbers which are not phone numbers', () => {
|
||||
// A timestamp.
|
||||
findNumbers('2012-01-02 08:00', 'US', metadata).should.deep.equal([])
|
||||
|
||||
// A valid number (not a complete timestamp).
|
||||
findNumbers('2012-01-02 08', 'US', metadata).should.deep.equal([{
|
||||
country : 'US',
|
||||
phone : '2012010208',
|
||||
startsAt : 0,
|
||||
endsAt : 13
|
||||
}])
|
||||
|
||||
// Invalid parens.
|
||||
findNumbers('213(3734253', 'US', metadata).should.deep.equal([])
|
||||
|
||||
// Letters after phone number.
|
||||
findNumbers('2133734253a', 'US', metadata).should.deep.equal([])
|
||||
|
||||
// Valid phone (same as the one found in the UUID below).
|
||||
findNumbers('The phone number is 231354125.', 'FR', metadata).should.deep.equal([{
|
||||
country : 'FR',
|
||||
phone : '231354125',
|
||||
startsAt : 20,
|
||||
endsAt : 29
|
||||
}])
|
||||
|
||||
// Not a phone number (part of a UUID).
|
||||
// Should parse in `{ extended: true }` mode.
|
||||
const possibleNumbers = findNumbers('The UUID is CA801c26f98cd16e231354125ad046e40b.', 'FR', { extended: true }, metadata)
|
||||
possibleNumbers.length.should.equal(3)
|
||||
possibleNumbers[1].country.should.equal('FR')
|
||||
possibleNumbers[1].phone.should.equal('231354125')
|
||||
|
||||
// Not a phone number (part of a UUID).
|
||||
// Shouldn't parse by default.
|
||||
findNumbers('The UUID is CA801c26f98cd16e231354125ad046e40b.', 'FR', metadata).should.deep.equal([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('PhoneNumberSearch', () => {
|
||||
it('should search for phone numbers', () => {
|
||||
const finder = new PhoneNumberSearch('The number is +7 (800) 555-35-35 and not (213) 373-4253 as written in the document.', { defaultCountry: 'US' }, metadata)
|
||||
|
||||
finder.hasNext().should.equal(true)
|
||||
finder.next().should.deep.equal({
|
||||
country : 'RU',
|
||||
phone : '8005553535',
|
||||
// number : '+7 (800) 555-35-35',
|
||||
startsAt : 14,
|
||||
endsAt : 32
|
||||
})
|
||||
|
||||
finder.hasNext().should.equal(true)
|
||||
finder.next().should.deep.equal({
|
||||
country : 'US',
|
||||
phone : '2133734253',
|
||||
// number : '(213) 373-4253',
|
||||
startsAt : 41,
|
||||
endsAt : 55
|
||||
})
|
||||
|
||||
finder.hasNext().should.equal(false)
|
||||
})
|
||||
|
||||
it('should search for phone numbers (no options)', () => {
|
||||
const finder = new PhoneNumberSearch('The number is +7 (800) 555-35-35', undefined, metadata)
|
||||
finder.hasNext().should.equal(true)
|
||||
finder.next().should.deep.equal({
|
||||
country : 'RU',
|
||||
phone : '8005553535',
|
||||
// number : '+7 (800) 555-35-35',
|
||||
startsAt : 14,
|
||||
endsAt : 32
|
||||
})
|
||||
finder.hasNext().should.equal(false)
|
||||
})
|
||||
|
||||
it('should work in edge cases', () => {
|
||||
// No options
|
||||
const search = new PhoneNumberSearch('', undefined, metadata)
|
||||
|
||||
// No next element
|
||||
let thrower = () => search.next()
|
||||
thrower.should.throw('No next element')
|
||||
})
|
||||
})
|
||||
Generated
Vendored
+191
@@ -0,0 +1,191 @@
|
||||
// This is a legacy function.
|
||||
// Use `findNumbers()` instead.
|
||||
|
||||
import {
|
||||
PLUS_CHARS,
|
||||
VALID_PUNCTUATION,
|
||||
VALID_DIGITS,
|
||||
WHITESPACE
|
||||
} from '../constants.js'
|
||||
|
||||
import parse from '../parse.js'
|
||||
import { VALID_PHONE_NUMBER_WITH_EXTENSION } from '../helpers/isViablePhoneNumber.js'
|
||||
import createExtensionPattern from '../helpers/extension/createExtensionPattern.js'
|
||||
|
||||
import parsePreCandidate from '../findNumbers/parsePreCandidate.js'
|
||||
import isValidPreCandidate from '../findNumbers/isValidPreCandidate.js'
|
||||
import isValidCandidate from '../findNumbers/isValidCandidate.js'
|
||||
|
||||
/**
|
||||
* Regexp of all possible ways to write extensions, for use when parsing. This
|
||||
* will be run as a case-insensitive regexp match. Wide character versions are
|
||||
* also provided after each ASCII version. There are three regular expressions
|
||||
* here. The first covers RFC 3966 format, where the extension is added using
|
||||
* ';ext='. The second more generic one starts with optional white space and
|
||||
* ends with an optional full stop (.), followed by zero or more spaces/tabs
|
||||
* /commas and then the numbers themselves. The other one covers the special
|
||||
* case of American numbers where the extension is written with a hash at the
|
||||
* end, such as '- 503#'. Note that the only capturing groups should be around
|
||||
* the digits that you want to capture as part of the extension, or else parsing
|
||||
* will fail! We allow two options for representing the accented o - the
|
||||
* character itself, and one in the unicode decomposed form with the combining
|
||||
* acute accent.
|
||||
*/
|
||||
export const EXTN_PATTERNS_FOR_PARSING = createExtensionPattern('parsing')
|
||||
|
||||
const WHITESPACE_IN_THE_BEGINNING_PATTERN = new RegExp('^[' + WHITESPACE + ']+')
|
||||
const PUNCTUATION_IN_THE_END_PATTERN = new RegExp('[' + VALID_PUNCTUATION + ']+$')
|
||||
|
||||
// // Regular expression for getting opening brackets for a valid number
|
||||
// // found using `PHONE_NUMBER_START_PATTERN` for prepending those brackets to the number.
|
||||
// const BEFORE_NUMBER_DIGITS_PUNCTUATION = new RegExp('[' + OPENING_BRACKETS + ']+' + '[' + WHITESPACE + ']*' + '$')
|
||||
|
||||
const VALID_PRECEDING_CHARACTER_PATTERN = /[^a-zA-Z0-9]/
|
||||
|
||||
export default function findPhoneNumbers(text, options, metadata) {
|
||||
/* istanbul ignore if */
|
||||
if (options === undefined) {
|
||||
options = {}
|
||||
}
|
||||
const search = new PhoneNumberSearch(text, options, metadata)
|
||||
const phones = []
|
||||
while (search.hasNext()) {
|
||||
phones.push(search.next())
|
||||
}
|
||||
return phones
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ES6 `for ... of` iterator.
|
||||
*/
|
||||
export function searchPhoneNumbers(text, options, metadata) {
|
||||
/* istanbul ignore if */
|
||||
if (options === undefined) {
|
||||
options = {}
|
||||
}
|
||||
const search = new PhoneNumberSearch(text, options, metadata)
|
||||
return {
|
||||
[Symbol.iterator]() {
|
||||
return {
|
||||
next: () => {
|
||||
if (search.hasNext()) {
|
||||
return {
|
||||
done: false,
|
||||
value: search.next()
|
||||
}
|
||||
}
|
||||
return {
|
||||
done: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a parseable phone number including any opening brackets, etc.
|
||||
* @param {string} text - Input.
|
||||
* @return {object} `{ ?number, ?startsAt, ?endsAt }`.
|
||||
*/
|
||||
export class PhoneNumberSearch {
|
||||
constructor(text, options, metadata) {
|
||||
this.text = text
|
||||
// If assigning the `{}` default value is moved to the arguments above,
|
||||
// code coverage would decrease for some weird reason.
|
||||
this.options = options || {}
|
||||
this.metadata = metadata
|
||||
|
||||
// Iteration tristate.
|
||||
this.state = 'NOT_READY'
|
||||
|
||||
this.regexp = new RegExp(VALID_PHONE_NUMBER_WITH_EXTENSION, 'ig')
|
||||
}
|
||||
|
||||
find() {
|
||||
const matches = this.regexp.exec(this.text)
|
||||
if (!matches) {
|
||||
return
|
||||
}
|
||||
|
||||
let number = matches[0]
|
||||
let startsAt = matches.index
|
||||
|
||||
number = number.replace(WHITESPACE_IN_THE_BEGINNING_PATTERN, '')
|
||||
startsAt += matches[0].length - number.length
|
||||
// Fixes not parsing numbers with whitespace in the end.
|
||||
// Also fixes not parsing numbers with opening parentheses in the end.
|
||||
// https://github.com/catamphetamine/libphonenumber-js/issues/252
|
||||
number = number.replace(PUNCTUATION_IN_THE_END_PATTERN, '')
|
||||
|
||||
number = parsePreCandidate(number)
|
||||
|
||||
const result = this.parseCandidate(number, startsAt)
|
||||
if (result) {
|
||||
return result
|
||||
}
|
||||
|
||||
// Tail recursion.
|
||||
// Try the next one if this one is not a valid phone number.
|
||||
return this.find()
|
||||
}
|
||||
|
||||
parseCandidate(number, startsAt) {
|
||||
if (!isValidPreCandidate(number, startsAt, this.text)) {
|
||||
return
|
||||
}
|
||||
|
||||
// Don't parse phone numbers which are non-phone numbers
|
||||
// due to being part of something else (e.g. a UUID).
|
||||
// https://github.com/catamphetamine/libphonenumber-js/issues/213
|
||||
// Copy-pasted from Google's `PhoneNumberMatcher.js` (`.parseAndValidate()`).
|
||||
if (!isValidCandidate(number, startsAt, this.text, this.options.extended ? 'POSSIBLE' : 'VALID')) {
|
||||
return
|
||||
}
|
||||
|
||||
// // Prepend any opening brackets left behind by the
|
||||
// // `PHONE_NUMBER_START_PATTERN` regexp.
|
||||
// const text_before_number = text.slice(this.searching_from, startsAt)
|
||||
// const full_number_starts_at = text_before_number.search(BEFORE_NUMBER_DIGITS_PUNCTUATION)
|
||||
// if (full_number_starts_at >= 0)
|
||||
// {
|
||||
// number = text_before_number.slice(full_number_starts_at) + number
|
||||
// startsAt = full_number_starts_at
|
||||
// }
|
||||
//
|
||||
// this.searching_from = matches.lastIndex
|
||||
|
||||
const result = parse(number, this.options, this.metadata)
|
||||
if (!result.phone) {
|
||||
return
|
||||
}
|
||||
|
||||
result.startsAt = startsAt
|
||||
result.endsAt = startsAt + number.length
|
||||
return result
|
||||
}
|
||||
|
||||
hasNext() {
|
||||
if (this.state === 'NOT_READY') {
|
||||
this.last_match = this.find()
|
||||
if (this.last_match) {
|
||||
this.state = 'READY'
|
||||
} else {
|
||||
this.state = 'DONE'
|
||||
}
|
||||
}
|
||||
return this.state === 'READY'
|
||||
}
|
||||
|
||||
next() {
|
||||
// Check the state and find the next match as a side-effect if necessary.
|
||||
if (!this.hasNext()) {
|
||||
throw new Error('No next element')
|
||||
}
|
||||
// Don't retain that memory any longer than necessary.
|
||||
const result = this.last_match
|
||||
this.last_match = null
|
||||
this.state = 'NOT_READY'
|
||||
return result
|
||||
}
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
import _formatNumber from '../format.js'
|
||||
import parse from '../parse.js'
|
||||
import isObject from '../helpers/isObject.js'
|
||||
|
||||
export default function formatNumber() {
|
||||
const {
|
||||
input,
|
||||
format,
|
||||
options,
|
||||
metadata
|
||||
} = normalizeArguments(arguments)
|
||||
|
||||
return _formatNumber(input, format, options, metadata)
|
||||
}
|
||||
|
||||
// Sort out arguments
|
||||
function normalizeArguments(args)
|
||||
{
|
||||
const [arg_1, arg_2, arg_3, arg_4, arg_5] = Array.prototype.slice.call(args)
|
||||
|
||||
let input
|
||||
let format
|
||||
let options
|
||||
let metadata
|
||||
|
||||
// Sort out arguments.
|
||||
|
||||
// If the phone number is passed as a string.
|
||||
// `format('8005553535', ...)`.
|
||||
if (typeof arg_1 === 'string')
|
||||
{
|
||||
// If country code is supplied.
|
||||
// `format('8005553535', 'RU', 'NATIONAL', [options], metadata)`.
|
||||
if (typeof arg_3 === 'string')
|
||||
{
|
||||
format = arg_3
|
||||
|
||||
if (arg_5)
|
||||
{
|
||||
options = arg_4
|
||||
metadata = arg_5
|
||||
}
|
||||
else
|
||||
{
|
||||
metadata = arg_4
|
||||
}
|
||||
|
||||
input = parse(arg_1, { defaultCountry: arg_2, extended: true }, metadata)
|
||||
}
|
||||
// Just an international phone number is supplied
|
||||
// `format('+78005553535', 'NATIONAL', [options], metadata)`.
|
||||
else
|
||||
{
|
||||
if (typeof arg_2 !== 'string')
|
||||
{
|
||||
throw new Error('`format` argument not passed to `formatNumber(number, format)`')
|
||||
}
|
||||
|
||||
format = arg_2
|
||||
|
||||
if (arg_4)
|
||||
{
|
||||
options = arg_3
|
||||
metadata = arg_4
|
||||
}
|
||||
else
|
||||
{
|
||||
metadata = arg_3
|
||||
}
|
||||
|
||||
input = parse(arg_1, { extended: true }, metadata)
|
||||
}
|
||||
}
|
||||
// If the phone number is passed as a parsed number object.
|
||||
// `format({ phone: '8005553535', country: 'RU' }, 'NATIONAL', [options], metadata)`.
|
||||
else if (isObject(arg_1))
|
||||
{
|
||||
input = arg_1
|
||||
format = arg_2
|
||||
|
||||
if (arg_4)
|
||||
{
|
||||
options = arg_3
|
||||
metadata = arg_4
|
||||
}
|
||||
else
|
||||
{
|
||||
metadata = arg_3
|
||||
}
|
||||
}
|
||||
else throw new TypeError('A phone number must either be a string or an object of shape { phone, [country] }.')
|
||||
|
||||
// Legacy lowercase formats.
|
||||
if (format === 'International') {
|
||||
format = 'INTERNATIONAL'
|
||||
} else if (format === 'National') {
|
||||
format = 'NATIONAL'
|
||||
}
|
||||
|
||||
return {
|
||||
input,
|
||||
format,
|
||||
options,
|
||||
metadata
|
||||
}
|
||||
}
|
||||
+240
@@ -0,0 +1,240 @@
|
||||
import metadata from '../../metadata.min.json' assert { type: 'json' }
|
||||
import _formatNumber from './format.js'
|
||||
|
||||
function formatNumber(...parameters) {
|
||||
parameters.push(metadata)
|
||||
return _formatNumber.apply(this, parameters)
|
||||
}
|
||||
|
||||
describe('format', () => {
|
||||
it('should work with the first argument being a E.164 number', () => {
|
||||
formatNumber('+12133734253', 'NATIONAL').should.equal('(213) 373-4253')
|
||||
formatNumber('+12133734253', 'INTERNATIONAL').should.equal('+1 213 373 4253')
|
||||
|
||||
// Invalid number.
|
||||
formatNumber('+12111111111', 'NATIONAL').should.equal('(211) 111-1111')
|
||||
|
||||
// Formatting invalid E.164 numbers.
|
||||
formatNumber('+11111', 'INTERNATIONAL').should.equal('+1 1111')
|
||||
formatNumber('+11111', 'NATIONAL').should.equal('1111')
|
||||
})
|
||||
|
||||
it('should work with the first object argument expanded', () => {
|
||||
formatNumber('2133734253', 'US', 'NATIONAL').should.equal('(213) 373-4253')
|
||||
formatNumber('2133734253', 'US', 'INTERNATIONAL').should.equal('+1 213 373 4253')
|
||||
})
|
||||
|
||||
it('should support legacy "National" / "International" formats', () => {
|
||||
formatNumber('2133734253', 'US', 'National').should.equal('(213) 373-4253')
|
||||
formatNumber('2133734253', 'US', 'International').should.equal('+1 213 373 4253')
|
||||
})
|
||||
|
||||
it('should format using formats with no leading digits (`format.leadingDigitsPatterns().length === 0`)', () => {
|
||||
formatNumber({ phone: '12345678901', countryCallingCode: 888 }, 'INTERNATIONAL').should.equal('+888 123 456 78901')
|
||||
})
|
||||
|
||||
it('should sort out the arguments', () => {
|
||||
const options = {
|
||||
formatExtension: (number, extension) => `${number} доб. ${extension}`
|
||||
}
|
||||
|
||||
formatNumber({
|
||||
phone : '8005553535',
|
||||
country : 'RU',
|
||||
ext : '123'
|
||||
},
|
||||
'NATIONAL', options).should.equal('8 (800) 555-35-35 доб. 123')
|
||||
|
||||
// Parse number from string.
|
||||
formatNumber('+78005553535', 'NATIONAL', options).should.equal('8 (800) 555-35-35')
|
||||
formatNumber('8005553535', 'RU', 'NATIONAL', options).should.equal('8 (800) 555-35-35')
|
||||
})
|
||||
|
||||
it('should format with national prefix when specifically instructed', () => {
|
||||
// With national prefix.
|
||||
formatNumber('88005553535', 'RU', 'NATIONAL').should.equal('8 (800) 555-35-35')
|
||||
// Without national prefix via an explicitly set option.
|
||||
formatNumber('88005553535', 'RU', 'NATIONAL', { nationalPrefix: false }).should.equal('800 555-35-35')
|
||||
})
|
||||
|
||||
it('should format valid phone numbers', () => {
|
||||
// Switzerland
|
||||
formatNumber({ country: 'CH', phone: '446681800' }, 'INTERNATIONAL').should.equal('+41 44 668 18 00')
|
||||
formatNumber({ country: 'CH', phone: '446681800' }, 'E.164').should.equal('+41446681800')
|
||||
formatNumber({ country: 'CH', phone: '446681800' }, 'RFC3966').should.equal('tel:+41446681800')
|
||||
formatNumber({ country: 'CH', phone: '446681800' }, 'NATIONAL').should.equal('044 668 18 00')
|
||||
|
||||
// France
|
||||
formatNumber({ country: 'FR', phone: '169454850' }, 'NATIONAL').should.equal('01 69 45 48 50')
|
||||
|
||||
// Kazakhstan
|
||||
formatNumber('+7 702 211 1111', 'NATIONAL').should.deep.equal('8 (702) 211 1111')
|
||||
})
|
||||
|
||||
it('should format national numbers with national prefix even if it\'s optional', () => {
|
||||
// Russia
|
||||
formatNumber({ country: 'RU', phone: '9991234567' }, 'NATIONAL').should.equal('8 (999) 123-45-67')
|
||||
})
|
||||
|
||||
it('should work in edge cases', () => {
|
||||
let thrower
|
||||
|
||||
// No phone number
|
||||
formatNumber('', 'RU', 'INTERNATIONAL').should.equal('')
|
||||
formatNumber('', 'RU', 'NATIONAL').should.equal('')
|
||||
|
||||
formatNumber({ country: 'RU', phone: '' }, 'INTERNATIONAL').should.equal('+7')
|
||||
formatNumber({ country: 'RU', phone: '' }, 'NATIONAL').should.equal('')
|
||||
|
||||
// No suitable format
|
||||
formatNumber('+121337342530', 'US', 'NATIONAL').should.equal('21337342530')
|
||||
// No suitable format (leading digits mismatch)
|
||||
formatNumber('28199999', 'AD', 'NATIONAL').should.equal('28199999')
|
||||
|
||||
// Numerical `value`
|
||||
thrower = () => formatNumber(89150000000, 'RU', 'NATIONAL')
|
||||
thrower.should.throw('A phone number must either be a string or an object of shape { phone, [country] }.')
|
||||
|
||||
// No metadata for country
|
||||
expect(() => formatNumber('+121337342530', 'USA', 'NATIONAL')).to.throw('Unknown country')
|
||||
expect(() => formatNumber('21337342530', 'USA', 'NATIONAL')).to.throw('Unknown country')
|
||||
|
||||
// No format type
|
||||
thrower = () => formatNumber('+123')
|
||||
thrower.should.throw('`format` argument not passed')
|
||||
|
||||
// Unknown format type
|
||||
thrower = () => formatNumber('123', 'US', 'Gay')
|
||||
thrower.should.throw('Unknown "format" argument')
|
||||
|
||||
// No metadata
|
||||
thrower = () => _formatNumber('123', 'US', 'E.164')
|
||||
thrower.should.throw('`metadata`')
|
||||
|
||||
// No formats
|
||||
formatNumber('012345', 'AC', 'NATIONAL').should.equal('012345')
|
||||
|
||||
// No `fromCountry` for `IDD` format.
|
||||
expect(formatNumber('+78005553535', 'IDD')).to.be.undefined
|
||||
|
||||
// `fromCountry` has no default IDD prefix.
|
||||
expect(formatNumber('+78005553535', 'IDD', { fromCountry: 'BO' })).to.be.undefined
|
||||
|
||||
// No such country.
|
||||
expect(() => formatNumber({ phone: '123', country: 'USA' }, 'NATIONAL')).to.throw('Unknown country')
|
||||
})
|
||||
|
||||
it('should format phone number extensions', () => {
|
||||
// National
|
||||
formatNumber({
|
||||
country: 'US',
|
||||
phone: '2133734253',
|
||||
ext: '123'
|
||||
},
|
||||
'NATIONAL').should.equal('(213) 373-4253 ext. 123')
|
||||
|
||||
// International
|
||||
formatNumber({
|
||||
country : 'US',
|
||||
phone : '2133734253',
|
||||
ext : '123'
|
||||
},
|
||||
'INTERNATIONAL').should.equal('+1 213 373 4253 ext. 123')
|
||||
|
||||
// International
|
||||
formatNumber({
|
||||
country : 'US',
|
||||
phone : '2133734253',
|
||||
ext : '123'
|
||||
},
|
||||
'INTERNATIONAL').should.equal('+1 213 373 4253 ext. 123')
|
||||
|
||||
// E.164
|
||||
formatNumber({
|
||||
country : 'US',
|
||||
phone : '2133734253',
|
||||
ext : '123'
|
||||
},
|
||||
'E.164').should.equal('+12133734253')
|
||||
|
||||
// RFC3966
|
||||
formatNumber({
|
||||
country : 'US',
|
||||
phone : '2133734253',
|
||||
ext : '123'
|
||||
},
|
||||
'RFC3966').should.equal('tel:+12133734253;ext=123')
|
||||
|
||||
// Custom ext prefix.
|
||||
formatNumber({
|
||||
country : 'GB',
|
||||
phone : '7912345678',
|
||||
ext : '123'
|
||||
},
|
||||
'INTERNATIONAL').should.equal('+44 7912 345678 x123')
|
||||
})
|
||||
|
||||
it('should work with Argentina numbers', () => {
|
||||
// The same mobile number is written differently
|
||||
// in different formats in Argentina:
|
||||
// `9` gets prepended in international format.
|
||||
formatNumber({ country: 'AR', phone: '3435551212' }, 'INTERNATIONAL')
|
||||
.should.equal('+54 3435 55 1212')
|
||||
formatNumber({ country: 'AR', phone: '3435551212' }, 'NATIONAL')
|
||||
.should.equal('03435 55-1212')
|
||||
})
|
||||
|
||||
it('should work with Mexico numbers', () => {
|
||||
// Fixed line.
|
||||
formatNumber({ country: 'MX', phone: '4499780001' }, 'INTERNATIONAL')
|
||||
.should.equal('+52 449 978 0001')
|
||||
formatNumber({ country: 'MX', phone: '4499780001' }, 'NATIONAL')
|
||||
.should.equal('449 978 0001')
|
||||
// or '(449)978-0001'.
|
||||
// Mobile.
|
||||
// `1` is prepended before area code to mobile numbers in international format.
|
||||
formatNumber({ country: 'MX', phone: '3312345678' }, 'INTERNATIONAL')
|
||||
.should.equal('+52 33 1234 5678')
|
||||
formatNumber({ country: 'MX', phone: '3312345678' }, 'NATIONAL')
|
||||
.should.equal('33 1234 5678')
|
||||
// or '045 33 1234-5678'.
|
||||
})
|
||||
|
||||
it('should format possible numbers', () => {
|
||||
formatNumber({ countryCallingCode: '7', phone: '1111111111' }, 'E.164')
|
||||
.should.equal('+71111111111')
|
||||
|
||||
formatNumber({ countryCallingCode: '7', phone: '1111111111' }, 'NATIONAL')
|
||||
.should.equal('1111111111')
|
||||
|
||||
formatNumber({ countryCallingCode: '7', phone: '1111111111' }, 'INTERNATIONAL')
|
||||
.should.equal('+7 1111111111')
|
||||
})
|
||||
|
||||
it('should format IDD-prefixed number', () => {
|
||||
// No `fromCountry`.
|
||||
expect(formatNumber('+78005553535', 'IDD')).to.be.undefined
|
||||
|
||||
// No default IDD prefix.
|
||||
expect(formatNumber('+78005553535', 'IDD', { fromCountry: 'BO' })).to.be.undefined
|
||||
|
||||
// Same country calling code.
|
||||
formatNumber('+12133734253', 'IDD', { fromCountry: 'CA', humanReadable: true }).should.equal('1 (213) 373-4253')
|
||||
formatNumber('+78005553535', 'IDD', { fromCountry: 'KZ', humanReadable: true }).should.equal('8 (800) 555-35-35')
|
||||
|
||||
// formatNumber('+78005553535', 'IDD', { fromCountry: 'US' }).should.equal('01178005553535')
|
||||
formatNumber('+78005553535', 'IDD', { fromCountry: 'US', humanReadable: true }).should.equal('011 7 800 555 35 35')
|
||||
})
|
||||
|
||||
it('should format non-geographic numbering plan phone numbers', () => {
|
||||
// https://github.com/catamphetamine/libphonenumber-js/issues/323
|
||||
formatNumber('+870773111632', 'INTERNATIONAL').should.equal('+870 773 111 632')
|
||||
formatNumber('+870773111632', 'NATIONAL').should.equal('773 111 632')
|
||||
})
|
||||
|
||||
it('should use the default IDD prefix when formatting a phone number', () => {
|
||||
// Testing preferred international prefixes with ~ are supported.
|
||||
// ("~" designates waiting on a line until proceeding with the input).
|
||||
formatNumber('+390236618300', 'IDD', { fromCountry: 'BY' }).should.equal('8~10 39 02 3661 8300')
|
||||
})
|
||||
})
|
||||
Generated
Vendored
+111
@@ -0,0 +1,111 @@
|
||||
import isViablePhoneNumber from '../helpers/isViablePhoneNumber.js'
|
||||
import _getNumberType from '../helpers/getNumberType.js'
|
||||
import isObject from '../helpers/isObject.js'
|
||||
import parse from '../parse.js'
|
||||
|
||||
// Finds out national phone number type (fixed line, mobile, etc)
|
||||
export default function getNumberType() {
|
||||
const { input, options, metadata } = normalizeArguments(arguments)
|
||||
// `parseNumber()` would return `{}` when no phone number could be parsed from the input.
|
||||
if (!input.phone) {
|
||||
return
|
||||
}
|
||||
return _getNumberType(input, options, metadata)
|
||||
}
|
||||
|
||||
// Sort out arguments
|
||||
export function normalizeArguments(args)
|
||||
{
|
||||
const [arg_1, arg_2, arg_3, arg_4] = Array.prototype.slice.call(args)
|
||||
|
||||
let input
|
||||
let options = {}
|
||||
let metadata
|
||||
|
||||
// If the phone number is passed as a string.
|
||||
// `getNumberType('88005553535', ...)`.
|
||||
if (typeof arg_1 === 'string')
|
||||
{
|
||||
// If "default country" argument is being passed
|
||||
// then convert it to an `options` object.
|
||||
// `getNumberType('88005553535', 'RU', metadata)`.
|
||||
if (!isObject(arg_2))
|
||||
{
|
||||
if (arg_4)
|
||||
{
|
||||
options = arg_3
|
||||
metadata = arg_4
|
||||
}
|
||||
else
|
||||
{
|
||||
metadata = arg_3
|
||||
}
|
||||
|
||||
// `parse` extracts phone numbers from raw text,
|
||||
// therefore it will cut off all "garbage" characters,
|
||||
// while this `validate` function needs to verify
|
||||
// that the phone number contains no "garbage"
|
||||
// therefore the explicit `isViablePhoneNumber` check.
|
||||
if (isViablePhoneNumber(arg_1))
|
||||
{
|
||||
input = parse(arg_1, { defaultCountry: arg_2 }, metadata)
|
||||
}
|
||||
else
|
||||
{
|
||||
input = {}
|
||||
}
|
||||
}
|
||||
// No "resrict country" argument is being passed.
|
||||
// International phone number is passed.
|
||||
// `getNumberType('+78005553535', metadata)`.
|
||||
else
|
||||
{
|
||||
if (arg_3)
|
||||
{
|
||||
options = arg_2
|
||||
metadata = arg_3
|
||||
}
|
||||
else
|
||||
{
|
||||
metadata = arg_2
|
||||
}
|
||||
|
||||
// `parse` extracts phone numbers from raw text,
|
||||
// therefore it will cut off all "garbage" characters,
|
||||
// while this `validate` function needs to verify
|
||||
// that the phone number contains no "garbage"
|
||||
// therefore the explicit `isViablePhoneNumber` check.
|
||||
if (isViablePhoneNumber(arg_1))
|
||||
{
|
||||
input = parse(arg_1, undefined, metadata)
|
||||
}
|
||||
else
|
||||
{
|
||||
input = {}
|
||||
}
|
||||
}
|
||||
}
|
||||
// If the phone number is passed as a parsed phone number.
|
||||
// `getNumberType({ phone: '88005553535', country: 'RU' }, ...)`.
|
||||
else if (isObject(arg_1))
|
||||
{
|
||||
input = arg_1
|
||||
|
||||
if (arg_3)
|
||||
{
|
||||
options = arg_2
|
||||
metadata = arg_3
|
||||
}
|
||||
else
|
||||
{
|
||||
metadata = arg_2
|
||||
}
|
||||
}
|
||||
else throw new TypeError('A phone number must either be a string or an object of shape { phone, [country] }.')
|
||||
|
||||
return {
|
||||
input,
|
||||
options,
|
||||
metadata
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+65
@@ -0,0 +1,65 @@
|
||||
import metadata from '../../metadata.max.json' assert { type: 'json' }
|
||||
import Metadata from '../metadata.js'
|
||||
import _getNumberType from './getNumberType.js'
|
||||
|
||||
function getNumberType(...parameters) {
|
||||
parameters.push(metadata)
|
||||
return _getNumberType.apply(this, parameters)
|
||||
}
|
||||
|
||||
describe('getNumberType', () => {
|
||||
it('should infer phone number type MOBILE', () => {
|
||||
getNumberType('9150000000', 'RU').should.equal('MOBILE')
|
||||
getNumberType('7912345678', 'GB').should.equal('MOBILE')
|
||||
getNumberType('51234567', 'EE').should.equal('MOBILE')
|
||||
})
|
||||
|
||||
it('should infer phone number types', () => {
|
||||
getNumberType('88005553535', 'RU').should.equal('TOLL_FREE')
|
||||
getNumberType('8005553535', 'RU').should.equal('TOLL_FREE')
|
||||
getNumberType('4957777777', 'RU').should.equal('FIXED_LINE')
|
||||
getNumberType('8030000000', 'RU').should.equal('PREMIUM_RATE')
|
||||
|
||||
getNumberType('2133734253', 'US').should.equal('FIXED_LINE_OR_MOBILE')
|
||||
getNumberType('5002345678', 'US').should.equal('PERSONAL_NUMBER')
|
||||
})
|
||||
|
||||
it('should work when no country is passed', () => {
|
||||
getNumberType('+79150000000').should.equal('MOBILE')
|
||||
})
|
||||
|
||||
it('should return FIXED_LINE_OR_MOBILE when there is ambiguity', () => {
|
||||
// (no such country in the metadata, therefore no unit test for this `if`)
|
||||
})
|
||||
|
||||
it('should work in edge cases', function() {
|
||||
let thrower
|
||||
|
||||
// // No metadata
|
||||
// thrower = () => _getNumberType({ phone: '+78005553535' })
|
||||
// thrower.should.throw('`metadata` argument not passed')
|
||||
|
||||
// Parsed phone number
|
||||
getNumberType({ phone: '8005553535', country: 'RU' }).should.equal('TOLL_FREE')
|
||||
|
||||
// Invalid phone number
|
||||
type(getNumberType('123', 'RU')).should.equal('undefined')
|
||||
|
||||
// Invalid country
|
||||
thrower = () => getNumberType({ phone: '8005553535', country: 'RUS' })
|
||||
thrower.should.throw('Unknown country')
|
||||
|
||||
// Numerical `value`
|
||||
thrower = () => getNumberType(89150000000, 'RU')
|
||||
thrower.should.throw('A phone number must either be a string or an object of shape { phone, [country] }.')
|
||||
|
||||
// When `options` argument is passed.
|
||||
getNumberType('8005553535', 'RU', {}).should.equal('TOLL_FREE')
|
||||
getNumberType('+78005553535', {}).should.equal('TOLL_FREE')
|
||||
getNumberType({ phone: '8005553535', country: 'RU' }, {}).should.equal('TOLL_FREE')
|
||||
})
|
||||
})
|
||||
|
||||
function type(something) {
|
||||
return typeof something
|
||||
}
|
||||
Generated
Vendored
+25
@@ -0,0 +1,25 @@
|
||||
import { normalizeArguments } from './getNumberType.js'
|
||||
import _isPossibleNumber from '../isPossible.js'
|
||||
|
||||
/**
|
||||
* Checks if a given phone number is possible.
|
||||
* Which means it only checks phone number length
|
||||
* and doesn't test any regular expressions.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* ```js
|
||||
* isPossibleNumber('+78005553535', metadata)
|
||||
* isPossibleNumber('8005553535', 'RU', metadata)
|
||||
* isPossibleNumber('88005553535', 'RU', metadata)
|
||||
* isPossibleNumber({ phone: '8005553535', country: 'RU' }, metadata)
|
||||
* ```
|
||||
*/
|
||||
export default function isPossibleNumber() {
|
||||
const { input, options, metadata } = normalizeArguments(arguments)
|
||||
// `parseNumber()` would return `{}` when no phone number could be parsed from the input.
|
||||
if (!input.phone && !(options && options.v2)) {
|
||||
return false
|
||||
}
|
||||
return _isPossibleNumber(input, options, metadata)
|
||||
}
|
||||
Generated
Vendored
+40
@@ -0,0 +1,40 @@
|
||||
import metadata from '../../metadata.min.json' assert { type: 'json' }
|
||||
import _isPossibleNumber from './isPossibleNumber.js'
|
||||
|
||||
function isPossibleNumber(...parameters) {
|
||||
parameters.push(metadata)
|
||||
return _isPossibleNumber.apply(this, parameters)
|
||||
}
|
||||
|
||||
describe('isPossibleNumber', () => {
|
||||
it('should work', function()
|
||||
{
|
||||
isPossibleNumber('+79992223344').should.equal(true)
|
||||
|
||||
isPossibleNumber({ phone: '1112223344', country: 'RU' }).should.equal(true)
|
||||
isPossibleNumber({ phone: '111222334', country: 'RU' }).should.equal(false)
|
||||
isPossibleNumber({ phone: '11122233445', country: 'RU' }).should.equal(false)
|
||||
|
||||
isPossibleNumber({ phone: '1112223344', countryCallingCode: 7 }).should.equal(true)
|
||||
})
|
||||
|
||||
it('should work v2', () => {
|
||||
isPossibleNumber({ nationalNumber: '111222334', countryCallingCode: 7 }, { v2: true }).should.equal(false)
|
||||
isPossibleNumber({ nationalNumber: '1112223344', countryCallingCode: 7 }, { v2: true }).should.equal(true)
|
||||
isPossibleNumber({ nationalNumber: '11122233445', countryCallingCode: 7 }, { v2: true }).should.equal(false)
|
||||
})
|
||||
|
||||
it('should work in edge cases', () => {
|
||||
// Invalid `PhoneNumber` argument.
|
||||
expect(() => isPossibleNumber({}, { v2: true })).to.throw('Invalid phone number object passed')
|
||||
|
||||
// Empty input is passed.
|
||||
// This is just to support `isValidNumber({})`
|
||||
// for cases when `parseNumber()` returns `{}`.
|
||||
isPossibleNumber({}).should.equal(false)
|
||||
expect(() => isPossibleNumber({ phone: '1112223344' })).to.throw('Invalid phone number object passed')
|
||||
|
||||
// Incorrect country.
|
||||
expect(() => isPossibleNumber({ phone: '1112223344', country: 'XX' })).to.throw('Unknown country')
|
||||
})
|
||||
})
|
||||
Generated
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
import _isValidNumber from '../isValid.js'
|
||||
import { normalizeArguments } from './getNumberType.js'
|
||||
|
||||
// Finds out national phone number type (fixed line, mobile, etc)
|
||||
export default function isValidNumber() {
|
||||
const { input, options, metadata } = normalizeArguments(arguments)
|
||||
// `parseNumber()` would return `{}` when no phone number could be parsed from the input.
|
||||
if (!input.phone) {
|
||||
return false
|
||||
}
|
||||
return _isValidNumber(input, options, metadata)
|
||||
}
|
||||
Generated
Vendored
+91
@@ -0,0 +1,91 @@
|
||||
import metadata from '../../metadata.min.json' assert { type: 'json' }
|
||||
import _isValidNumber from './isValidNumber.js'
|
||||
|
||||
function isValidNumber(...parameters) {
|
||||
parameters.push(metadata)
|
||||
return _isValidNumber.apply(this, parameters)
|
||||
}
|
||||
|
||||
describe('isValidNumber', () => {
|
||||
it('should validate phone numbers', () => {
|
||||
isValidNumber('+1-213-373-4253').should.equal(true)
|
||||
isValidNumber('+1-213-373').should.equal(false)
|
||||
|
||||
isValidNumber('+1-213-373-4253', undefined).should.equal(true)
|
||||
|
||||
isValidNumber('(213) 373-4253', 'US').should.equal(true)
|
||||
isValidNumber('(213) 37', 'US').should.equal(false)
|
||||
|
||||
isValidNumber({ country: 'US', phone: '2133734253' }).should.equal(true)
|
||||
|
||||
// No "types" info: should return `true`.
|
||||
isValidNumber('+380972423740').should.equal(true)
|
||||
|
||||
isValidNumber('0912345678', 'TW').should.equal(true)
|
||||
|
||||
// Moible numbers starting 07624* are Isle of Man
|
||||
// which has its own "country code" "IM"
|
||||
// which is in the "GB" "country calling code" zone.
|
||||
// So while this number is for "IM" it's still supposed to
|
||||
// be valid when passed "GB" as a default country.
|
||||
isValidNumber('07624369230', 'GB').should.equal(true)
|
||||
})
|
||||
|
||||
it('should refine phone number validation in case extended regular expressions are set for a country', () => {
|
||||
// Germany general validation must pass
|
||||
console.log('--------------------------')
|
||||
isValidNumber('961111111', 'UZ').should.equal(true)
|
||||
|
||||
const phoneNumberTypePatterns = metadata.countries.UZ[11]
|
||||
|
||||
// Different regular expressions for precise national number validation.
|
||||
// `types` index in compressed array is `9` for v1.
|
||||
// For v2 it's 10.
|
||||
// For v3 it's 11.
|
||||
metadata.countries.UZ[11] =
|
||||
[
|
||||
["(?:6(?:1(?:22|3[124]|4[1-4]|5[123578]|64)|2(?:22|3[0-57-9]|41)|5(?:22|3[3-7]|5[024-8])|6\\d{2}|7(?:[23]\\d|7[69])|9(?:22|4[1-8]|6[135]))|7(?:0(?:5[4-9]|6[0146]|7[12456]|9[135-8])|1[12]\\d|2(?:22|3[1345789]|4[123579]|5[14])|3(?:2\\d|3[1578]|4[1-35-7]|5[1-57]|61)|4(?:2\\d|3[1-579]|7[1-79])|5(?:22|5[1-9]|6[1457])|6(?:22|3[12457]|4[13-8])|9(?:22|5[1-9])))\\d{5}"],
|
||||
["6(?:1(?:2(?:98|2[01])|35[0-4]|50\\d|61[23]|7(?:[01][017]|4\\d|55|9[5-9]))|2(?:11\\d|2(?:[12]1|9[01379])|5(?:[126]\\d|3[0-4])|7\\d{2})|5(?:19[01]|2(?:27|9[26])|30\\d|59\\d|7\\d{2})|6(?:2(?:1[5-9]|2[0367]|38|41|52|60)|3[79]\\d|4(?:56|83)|7(?:[07]\\d|1[017]|3[07]|4[047]|5[057]|67|8[0178]|9[79])|9[0-3]\\d)|7(?:2(?:24|3[237]|4[5-9]|7[15-8])|5(?:7[12]|8[0589])|7(?:0\\d|[39][07])|9(?:0\\d|7[079]))|9(?:2(?:1[1267]|5\\d|3[01]|7[0-4])|5[67]\\d|6(?:2[0-26]|8\\d)|7\\d{2}))\\d{4}|7(?:0\\d{3}|1(?:13[01]|6(?:0[47]|1[67]|66)|71[3-69]|98\\d)|2(?:2(?:2[79]|95)|3(?:2[5-9]|6[0-6])|57\\d|7(?:0\\d|1[17]|2[27]|3[37]|44|5[057]|66|88))|3(?:2(?:1[0-6]|21|3[469]|7[159])|33\\d|5(?:0[0-4]|5[579]|9\\d)|7(?:[0-3579]\\d|4[0467]|6[67]|8[078])|9[4-6]\\d)|4(?:2(?:29|5[0257]|6[0-7]|7[1-57])|5(?:1[0-4]|8\\d|9[5-9])|7(?:0\\d|1[024589]|2[0127]|3[0137]|[46][07]|5[01]|7[5-9]|9[079])|9(?:7[015-9]|[89]\\d))|5(?:112|2(?:0\\d|2[29]|[49]4)|3[1568]\\d|52[6-9]|7(?:0[01578]|1[017]|[23]7|4[047]|[5-7]\\d|8[78]|9[079]))|6(?:2(?:2[1245]|4[2-4])|39\\d|41[179]|5(?:[349]\\d|5[0-2])|7(?:0[017]|[13]\\d|22|44|55|67|88))|9(?:22[128]|3(?:2[0-4]|7\\d)|57[05629]|7(?:2[05-9]|3[37]|4\\d|60|7[2579]|87|9[07])))\\d{4}|9[0-57-9]\\d{7}"]
|
||||
]
|
||||
|
||||
// Extended validation must not pass for an invalid phone number
|
||||
isValidNumber('961111111', 'UZ').should.equal(false)
|
||||
|
||||
// Extended validation must pass for a valid phone number
|
||||
isValidNumber('912345678', 'UZ').should.equal(true)
|
||||
|
||||
metadata.countries.UZ[11] = phoneNumberTypePatterns
|
||||
})
|
||||
|
||||
it('should work in edge cases', () => {
|
||||
// No metadata
|
||||
let thrower = () => _isValidNumber('+78005553535')
|
||||
thrower.should.throw('`metadata` argument not passed')
|
||||
|
||||
// Non-phone-number characters in a phone number
|
||||
isValidNumber('+499821958a').should.equal(false)
|
||||
isValidNumber('88005553535x', 'RU').should.equal(false)
|
||||
|
||||
// Doesn't have `types` regexps in default metadata.
|
||||
isValidNumber({ country: 'UA', phone: '300000000' }).should.equal(true)
|
||||
isValidNumber({ country: 'UA', phone: '200000000' }).should.equal(false)
|
||||
|
||||
// Numerical `value`
|
||||
thrower = () => isValidNumber(88005553535, 'RU')
|
||||
thrower.should.throw('A phone number must either be a string or an object of shape { phone, [country] }.')
|
||||
|
||||
// Long country phone code
|
||||
isValidNumber('+3725555555').should.equal(true)
|
||||
|
||||
// Invalid country
|
||||
thrower = () => isValidNumber({ phone: '8005553535', country: 'RUS' })
|
||||
thrower.should.throw('Unknown country')
|
||||
})
|
||||
|
||||
it('should accept phone number extensions', () => {
|
||||
// International
|
||||
isValidNumber('+12133734253 ext. 123').should.equal(true)
|
||||
// National
|
||||
isValidNumber('88005553535 x123', 'RU').should.equal(true)
|
||||
})
|
||||
})
|
||||
Generated
Vendored
+62
@@ -0,0 +1,62 @@
|
||||
import isViablePhoneNumber from '../helpers/isViablePhoneNumber.js'
|
||||
import parseNumber from '../parse.js'
|
||||
import _isValidNumberForRegion from './isValidNumberForRegion_.js'
|
||||
|
||||
// This function has been deprecated and is not exported as
|
||||
// `isValidPhoneNumberForCountry()` or `isValidPhoneNumberForRegion()`.
|
||||
//
|
||||
// The rationale is:
|
||||
//
|
||||
// * We don't use the "region" word, so "country" would be better.
|
||||
//
|
||||
// * It could be substituted with:
|
||||
//
|
||||
// ```js
|
||||
// export default function isValidPhoneNumberForCountry(phoneNumberString, country) {
|
||||
// const phoneNumber = parsePhoneNumber(phoneNumberString, {
|
||||
// defaultCountry: country,
|
||||
// // Demand that the entire input string must be a phone number.
|
||||
// // Otherwise, it would "extract" a phone number from an input string.
|
||||
// extract: false
|
||||
// })
|
||||
// if (!phoneNumber) {
|
||||
// return false
|
||||
// }
|
||||
// if (phoneNumber.country !== country) {
|
||||
// return false
|
||||
// }
|
||||
// return phoneNumber.isValid()
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// * Same function could be used for `isPossiblePhoneNumberForCountry()`
|
||||
// by replacing `isValid()` with `isPossible()`.
|
||||
//
|
||||
// * The reason why this function is not exported is because its result is ambiguous.
|
||||
// Suppose `false` is returned. It could mean any of:
|
||||
// * Not a phone number.
|
||||
// * The phone number is valid but belongs to another country or another calling code.
|
||||
// * The phone number belongs to the correct country but is not valid digit-wise.
|
||||
// All those three cases should be handled separately from a "User Experience" standpoint.
|
||||
// Simply showing "Invalid phone number" error in all of those cases would be lazy UX.
|
||||
|
||||
export default function isValidNumberForRegion(number, country, metadata) {
|
||||
if (typeof number !== 'string') {
|
||||
throw new TypeError('number must be a string')
|
||||
}
|
||||
if (typeof country !== 'string') {
|
||||
throw new TypeError('country must be a string')
|
||||
}
|
||||
// `parse` extracts phone numbers from raw text,
|
||||
// therefore it will cut off all "garbage" characters,
|
||||
// while this `validate` function needs to verify
|
||||
// that the phone number contains no "garbage"
|
||||
// therefore the explicit `isViablePhoneNumber` check.
|
||||
let input
|
||||
if (isViablePhoneNumber(number)) {
|
||||
input = parseNumber(number, { defaultCountry: country }, metadata)
|
||||
} else {
|
||||
input = {}
|
||||
}
|
||||
return _isValidNumberForRegion(input, country, undefined, metadata)
|
||||
}
|
||||
Generated
Vendored
+28
@@ -0,0 +1,28 @@
|
||||
import metadata from '../../metadata.min.json' assert { type: 'json' }
|
||||
import isValidNumberForRegionCustom from './isValidNumberForRegion.js'
|
||||
import _isValidNumberForRegion from './isValidNumberForRegion_.js'
|
||||
|
||||
function isValidNumberForRegion(...parameters) {
|
||||
parameters.push(metadata)
|
||||
return isValidNumberForRegionCustom.apply(this, parameters)
|
||||
}
|
||||
|
||||
describe('isValidNumberForRegion', () => {
|
||||
it('should detect if is valid number for region', () => {
|
||||
isValidNumberForRegion('07624369230', 'GB').should.equal(false)
|
||||
isValidNumberForRegion('07624369230', 'IM').should.equal(true)
|
||||
})
|
||||
|
||||
it('should validate arguments', () => {
|
||||
expect(() => isValidNumberForRegion({ phone: '7624369230', country: 'GB' })).to.throw('number must be a string')
|
||||
expect(() => isValidNumberForRegion('7624369230')).to.throw('country must be a string')
|
||||
})
|
||||
|
||||
it('should work in edge cases', () => {
|
||||
// Not a "viable" phone number.
|
||||
isValidNumberForRegion('7', 'GB').should.equal(false)
|
||||
|
||||
// `options` argument `if/else` coverage.
|
||||
_isValidNumberForRegion('07624369230', 'GB', {}, metadata).should.equal(false)
|
||||
})
|
||||
})
|
||||
Generated
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
import isValidNumber from '../isValid.js'
|
||||
|
||||
/**
|
||||
* Checks if a given phone number is valid within a given region.
|
||||
* Is just an alias for `phoneNumber.isValid() && phoneNumber.country === country`.
|
||||
* https://github.com/googlei18n/libphonenumber/blob/master/FAQ.md#when-should-i-use-isvalidnumberforregion
|
||||
*/
|
||||
export default function isValidNumberForRegion(input, country, options, metadata) {
|
||||
// If assigning the `{}` default value is moved to the arguments above,
|
||||
// code coverage would decrease for some weird reason.
|
||||
options = options || {}
|
||||
return input.country === country && isValidNumber(input, options, metadata)
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import _parseNumber from '../parse.js'
|
||||
import normalizeArguments from '../normalizeArguments.js'
|
||||
|
||||
export default function parseNumber() {
|
||||
const { text, options, metadata } = normalizeArguments(arguments)
|
||||
return _parseNumber(text, options, metadata)
|
||||
}
|
||||
+533
@@ -0,0 +1,533 @@
|
||||
import metadata from '../../metadata.min.json' assert { type: 'json' }
|
||||
import _parseNumber from './parse.js'
|
||||
import Metadata from '../metadata.js'
|
||||
|
||||
function parseNumber(...parameters) {
|
||||
parameters.push(metadata)
|
||||
return _parseNumber.apply(this, parameters)
|
||||
}
|
||||
|
||||
const USE_NON_GEOGRAPHIC_COUNTRY_CODE = false
|
||||
|
||||
describe('parse', () => {
|
||||
it('should not parse invalid phone numbers', () => {
|
||||
// Too short.
|
||||
parseNumber('+7 (800) 55-35-35').should.deep.equal({})
|
||||
// Too long.
|
||||
parseNumber('+7 (800) 55-35-35-55').should.deep.equal({})
|
||||
|
||||
parseNumber('+7 (800) 55-35-35', 'US').should.deep.equal({})
|
||||
parseNumber('(800) 55 35 35', { defaultCountry: 'RU' }).should.deep.equal({})
|
||||
parseNumber('+1 187 215 5230', 'US').should.deep.equal({})
|
||||
|
||||
parseNumber('911231231', 'BE').should.deep.equal({})
|
||||
})
|
||||
|
||||
it('should parse valid phone numbers', () => {
|
||||
// Instant loans
|
||||
// https://www.youtube.com/watch?v=6e1pMrYH5jI
|
||||
//
|
||||
// Restrict to RU
|
||||
parseNumber('Phone: 8 (800) 555 35 35.', 'RU').should.deep.equal({ country: 'RU', phone: '8005553535' })
|
||||
// International format
|
||||
parseNumber('Phone: +7 (800) 555-35-35.').should.deep.equal({ country: 'RU', phone: '8005553535' })
|
||||
// // Restrict to US, but not a US country phone code supplied
|
||||
// parseNumber('+7 (800) 555-35-35', 'US').should.deep.equal({})
|
||||
// Restrict to RU
|
||||
parseNumber('(800) 555 35 35', 'RU').should.deep.equal({ country: 'RU', phone: '8005553535' })
|
||||
// Default to RU
|
||||
parseNumber('8 (800) 555 35 35', { defaultCountry: 'RU' }).should.deep.equal({ country: 'RU', phone: '8005553535' })
|
||||
|
||||
// Gangster partyline
|
||||
parseNumber('+1-213-373-4253').should.deep.equal({ country: 'US', phone: '2133734253' })
|
||||
|
||||
// Switzerland (just in case)
|
||||
parseNumber('044 668 18 00', 'CH').should.deep.equal({ country: 'CH', phone: '446681800' })
|
||||
|
||||
// China, Beijing
|
||||
parseNumber('010-852644821', 'CN').should.deep.equal({ country: 'CN', phone: '10852644821' })
|
||||
|
||||
// France
|
||||
parseNumber('+33169454850').should.deep.equal({ country: 'FR', phone: '169454850' })
|
||||
|
||||
// UK (Jersey)
|
||||
parseNumber('+44 7700 300000').should.deep.equal({ country: 'JE', phone: '7700300000' })
|
||||
|
||||
// KZ
|
||||
parseNumber('+7 702 211 1111').should.deep.equal({ country: 'KZ', phone: '7022111111' })
|
||||
|
||||
// Brazil
|
||||
parseNumber('11987654321', 'BR').should.deep.equal({ country: 'BR', phone: '11987654321' })
|
||||
|
||||
// Long country phone code.
|
||||
parseNumber('+212659777777').should.deep.equal({ country: 'MA', phone: '659777777' })
|
||||
|
||||
// No country could be derived.
|
||||
// parseNumber('+212569887076').should.deep.equal({ countryPhoneCode: '212', phone: '569887076' })
|
||||
|
||||
// GB. Moible numbers starting 07624* are Isle of Man.
|
||||
parseNumber('07624369230', 'GB').should.deep.equal({ country: 'IM', phone: '7624369230' })
|
||||
})
|
||||
|
||||
it('should parse possible numbers', () => {
|
||||
// Invalid phone number for a given country.
|
||||
parseNumber('1112223344', 'RU', { extended: true }).should.deep.equal({
|
||||
country : 'RU',
|
||||
countryCallingCode : '7',
|
||||
phone : '1112223344',
|
||||
carrierCode : undefined,
|
||||
ext : undefined,
|
||||
valid : false,
|
||||
possible : true
|
||||
})
|
||||
|
||||
// International phone number.
|
||||
// Several countries with the same country phone code.
|
||||
parseNumber('+71112223344').should.deep.equal({})
|
||||
parseNumber('+71112223344', { extended: true }).should.deep.equal({
|
||||
country : undefined,
|
||||
countryCallingCode : '7',
|
||||
phone : '1112223344',
|
||||
carrierCode : undefined,
|
||||
ext : undefined,
|
||||
valid : false,
|
||||
possible : true
|
||||
})
|
||||
|
||||
// International phone number.
|
||||
// Single country with the given country phone code.
|
||||
parseNumber('+33011222333', { extended: true }).should.deep.equal({
|
||||
country : 'FR',
|
||||
countryCallingCode : '33',
|
||||
phone : '011222333',
|
||||
carrierCode : undefined,
|
||||
ext : undefined,
|
||||
valid : false,
|
||||
possible : true
|
||||
})
|
||||
|
||||
// Too short.
|
||||
// Won't strip national prefix `8` because otherwise the number would be too short.
|
||||
parseNumber('+7 (800) 55-35-35', { extended: true }).should.deep.equal({
|
||||
country : undefined,
|
||||
countryCallingCode : '7',
|
||||
phone : '800553535',
|
||||
carrierCode : undefined,
|
||||
ext : undefined,
|
||||
valid : false,
|
||||
possible : false
|
||||
})
|
||||
|
||||
// Too long.
|
||||
parseNumber('+1 213 37342530', { extended: true }).should.deep.equal({
|
||||
country : undefined,
|
||||
countryCallingCode : '1',
|
||||
phone : '21337342530',
|
||||
carrierCode : undefined,
|
||||
ext : undefined,
|
||||
valid : false,
|
||||
possible : false
|
||||
})
|
||||
|
||||
// No national number to be parsed.
|
||||
parseNumber('+996', { extended: true }).should.deep.equal({
|
||||
// countryCallingCode : '996'
|
||||
})
|
||||
|
||||
// Valid number.
|
||||
parseNumber('+78005553535', { extended: true }).should.deep.equal({
|
||||
country : 'RU',
|
||||
countryCallingCode : '7',
|
||||
phone : '8005553535',
|
||||
carrierCode : undefined,
|
||||
ext : undefined,
|
||||
valid : true,
|
||||
possible : true
|
||||
})
|
||||
|
||||
// https://github.com/catamphetamine/libphonenumber-js/issues/211
|
||||
parseNumber('+966', { extended: true }).should.deep.equal({})
|
||||
parseNumber('+9664', { extended: true }).should.deep.equal({})
|
||||
parseNumber('+96645', { extended: true }).should.deep.equal({
|
||||
carrierCode : undefined,
|
||||
phone : '45',
|
||||
ext : undefined,
|
||||
country : 'SA',
|
||||
countryCallingCode : '966',
|
||||
possible : false,
|
||||
valid : false
|
||||
})
|
||||
})
|
||||
|
||||
it('should parse non-European digits', () => {
|
||||
parseNumber('+١٢١٢٢٣٢٣٢٣٢').should.deep.equal({ country: 'US', phone: '2122323232' })
|
||||
})
|
||||
|
||||
it('should work in edge cases', () => {
|
||||
let thrower
|
||||
|
||||
// No input
|
||||
parseNumber('').should.deep.equal({})
|
||||
|
||||
// No country phone code
|
||||
parseNumber('+').should.deep.equal({})
|
||||
|
||||
// No country at all (non international number and no explicit country code)
|
||||
parseNumber('123').should.deep.equal({})
|
||||
|
||||
// No country metadata for this `require` country code
|
||||
thrower = () => parseNumber('123', 'ZZ')
|
||||
thrower.should.throw('Unknown country')
|
||||
|
||||
// No country metadata for this `default` country code
|
||||
thrower = () => parseNumber('123', { defaultCountry: 'ZZ' })
|
||||
thrower.should.throw('Unknown country')
|
||||
|
||||
// Invalid country phone code
|
||||
parseNumber('+210').should.deep.equal({})
|
||||
|
||||
// Invalid country phone code (extended parsing mode)
|
||||
parseNumber('+210', { extended: true }).should.deep.equal({})
|
||||
|
||||
// Too short of a number.
|
||||
parseNumber('1', 'US', { extended: true }).should.deep.equal({})
|
||||
|
||||
// Too long of a number.
|
||||
parseNumber('1111111111111111111', 'RU', { extended: true }).should.deep.equal({})
|
||||
|
||||
// Not a number.
|
||||
parseNumber('abcdefg', 'US', { extended: true }).should.deep.equal({})
|
||||
|
||||
// Country phone code beginning with a '0'
|
||||
parseNumber('+0123').should.deep.equal({})
|
||||
|
||||
// Barbados NANPA phone number
|
||||
parseNumber('+12460000000').should.deep.equal({ country: 'BB', phone: '2460000000' })
|
||||
|
||||
// // A case when country (restricted to) is not equal
|
||||
// // to the one parsed out of an international number.
|
||||
// parseNumber('+1-213-373-4253', 'RU').should.deep.equal({})
|
||||
|
||||
// National (significant) number too short
|
||||
parseNumber('2', 'US').should.deep.equal({})
|
||||
|
||||
// National (significant) number too long
|
||||
parseNumber('222222222222222222', 'US').should.deep.equal({})
|
||||
|
||||
// No `national_prefix_for_parsing`
|
||||
parseNumber('41111', 'AC').should.deep.equal({ country: 'AC', phone: '41111'})
|
||||
|
||||
// https://github.com/catamphetamine/libphonenumber-js/issues/235
|
||||
// `matchesEntirely()` bug fix.
|
||||
parseNumber('+4915784846111').should.deep.equal({ country: 'DE', phone: '15784846111' })
|
||||
|
||||
// No metadata
|
||||
thrower = () => _parseNumber('')
|
||||
thrower.should.throw('`metadata` argument not passed')
|
||||
|
||||
// Numerical `value`
|
||||
thrower = () => parseNumber(2141111111, 'US')
|
||||
thrower.should.throw('A text for parsing must be a string.')
|
||||
|
||||
// Input string too long.
|
||||
parseNumber('8005553535 ', 'RU').should.deep.equal({})
|
||||
})
|
||||
|
||||
it('should parse phone number extensions', () => {
|
||||
// "ext"
|
||||
parseNumber('2134567890 ext 123', 'US').should.deep.equal({
|
||||
country : 'US',
|
||||
phone : '2134567890',
|
||||
ext : '123'
|
||||
})
|
||||
|
||||
// "ext."
|
||||
parseNumber('+12134567890 ext. 12345', 'US').should.deep.equal({
|
||||
country : 'US',
|
||||
phone : '2134567890',
|
||||
ext : '12345'
|
||||
})
|
||||
|
||||
// "доб."
|
||||
parseNumber('+78005553535 доб. 1234', 'RU').should.deep.equal({
|
||||
country : 'RU',
|
||||
phone : '8005553535',
|
||||
ext : '1234'
|
||||
})
|
||||
|
||||
// "#"
|
||||
parseNumber('+12134567890#1234').should.deep.equal({
|
||||
country : 'US',
|
||||
phone : '2134567890',
|
||||
ext : '1234'
|
||||
})
|
||||
|
||||
// "x"
|
||||
parseNumber('+78005553535 x1234').should.deep.equal({
|
||||
country : 'RU',
|
||||
phone : '8005553535',
|
||||
ext : '1234'
|
||||
})
|
||||
|
||||
// Not a valid extension
|
||||
parseNumber('2134567890 ext. abc', 'US').should.deep.equal({
|
||||
country : 'US',
|
||||
phone : '2134567890'
|
||||
})
|
||||
})
|
||||
|
||||
it('should parse RFC 3966 phone numbers', () => {
|
||||
parseNumber('tel:+78005553535;ext=123').should.deep.equal({
|
||||
country : 'RU',
|
||||
phone : '8005553535',
|
||||
ext : '123'
|
||||
})
|
||||
|
||||
// Should parse "visual separators".
|
||||
parseNumber('tel:+7(800)555-35.35;ext=123').should.deep.equal({
|
||||
country : 'RU',
|
||||
phone : '8005553535',
|
||||
ext : '123'
|
||||
})
|
||||
|
||||
// Invalid number.
|
||||
parseNumber('tel:+7x8005553535;ext=123').should.deep.equal({})
|
||||
})
|
||||
|
||||
it('should parse invalid international numbers even if they are invalid', () => {
|
||||
parseNumber('+7(8)8005553535', 'RU').should.deep.equal({
|
||||
country : 'RU',
|
||||
phone : '8005553535'
|
||||
})
|
||||
})
|
||||
|
||||
it('should parse carrier codes', () => {
|
||||
parseNumber('0 15 21 5555-5555', 'BR', { extended: true }).should.deep.equal({
|
||||
country : 'BR',
|
||||
countryCallingCode : '55',
|
||||
phone : '2155555555',
|
||||
carrierCode : '15',
|
||||
ext : undefined,
|
||||
valid : true,
|
||||
possible : true
|
||||
})
|
||||
})
|
||||
|
||||
it('should parse IDD prefixes', () => {
|
||||
parseNumber('011 61 2 3456 7890', 'US').should.deep.equal({
|
||||
phone : '234567890',
|
||||
country : 'AU'
|
||||
})
|
||||
|
||||
parseNumber('011 61 2 3456 7890', 'FR').should.deep.equal({})
|
||||
|
||||
parseNumber('00 61 2 3456 7890', 'US').should.deep.equal({})
|
||||
|
||||
parseNumber('810 61 2 3456 7890', 'RU').should.deep.equal({
|
||||
phone : '234567890',
|
||||
country : 'AU'
|
||||
})
|
||||
})
|
||||
|
||||
it('should work with v2 API', () => {
|
||||
parseNumber('+99989160151539')
|
||||
})
|
||||
|
||||
it('should work with Argentina numbers', () => {
|
||||
// The same mobile number is written differently
|
||||
// in different formats in Argentina:
|
||||
// `9` gets prepended in international format.
|
||||
parseNumber('+54 9 3435 55 1212').should.deep.equal({
|
||||
country: 'AR',
|
||||
phone: '93435551212'
|
||||
})
|
||||
parseNumber('0343 15-555-1212', 'AR').should.deep.equal({
|
||||
country: 'AR',
|
||||
phone: '93435551212'
|
||||
})
|
||||
})
|
||||
|
||||
it('should work with Mexico numbers', () => {
|
||||
// Fixed line.
|
||||
parseNumber('+52 449 978 0001').should.deep.equal({
|
||||
country: 'MX',
|
||||
phone: '4499780001'
|
||||
})
|
||||
// "Dialling tokens 01, 02, 044, 045 and 1 are removed as they are
|
||||
// no longer valid since August 2019."
|
||||
//
|
||||
// parseNumber('01 (449)978-0001', 'MX').should.deep.equal({
|
||||
// country: 'MX',
|
||||
// phone: '4499780001'
|
||||
// })
|
||||
parseNumber('(449)978-0001', 'MX').should.deep.equal({
|
||||
country: 'MX',
|
||||
phone: '4499780001'
|
||||
})
|
||||
// "Dialling tokens 01, 02, 044, 045 and 1 are removed as they are
|
||||
// no longer valid since August 2019."
|
||||
//
|
||||
// // Mobile.
|
||||
// // `1` is prepended before area code to mobile numbers in international format.
|
||||
// parseNumber('+52 1 33 1234-5678', 'MX').should.deep.equal({
|
||||
// country: 'MX',
|
||||
// phone: '3312345678'
|
||||
// })
|
||||
parseNumber('+52 33 1234-5678', 'MX').should.deep.equal({
|
||||
country: 'MX',
|
||||
phone: '3312345678'
|
||||
})
|
||||
// "Dialling tokens 01, 02, 044, 045 and 1 are removed as they are
|
||||
// no longer valid since August 2019."
|
||||
//
|
||||
// parseNumber('044 (33) 1234-5678', 'MX').should.deep.equal({
|
||||
// country: 'MX',
|
||||
// phone: '3312345678'
|
||||
// })
|
||||
// parseNumber('045 33 1234-5678', 'MX').should.deep.equal({
|
||||
// country: 'MX',
|
||||
// phone: '3312345678'
|
||||
// })
|
||||
})
|
||||
|
||||
it('should parse non-geographic numbering plan phone numbers', () => {
|
||||
parseNumber('+870773111632').should.deep.equal(
|
||||
USE_NON_GEOGRAPHIC_COUNTRY_CODE ?
|
||||
{
|
||||
country: '001',
|
||||
phone: '773111632'
|
||||
} :
|
||||
{}
|
||||
)
|
||||
})
|
||||
|
||||
it('should parse non-geographic numbering plan phone numbers (default country code)', () => {
|
||||
parseNumber('773111632', { defaultCallingCode: '870' }).should.deep.equal(
|
||||
USE_NON_GEOGRAPHIC_COUNTRY_CODE ?
|
||||
{
|
||||
country: '001',
|
||||
phone: '773111632'
|
||||
} :
|
||||
{}
|
||||
)
|
||||
})
|
||||
|
||||
it('should parse non-geographic numbering plan phone numbers (extended)', () => {
|
||||
parseNumber('+870773111632', { extended: true }).should.deep.equal({
|
||||
country: USE_NON_GEOGRAPHIC_COUNTRY_CODE ? '001' : undefined,
|
||||
countryCallingCode: '870',
|
||||
phone: '773111632',
|
||||
carrierCode: undefined,
|
||||
ext: undefined,
|
||||
possible: true,
|
||||
valid: true
|
||||
})
|
||||
})
|
||||
|
||||
it('should parse non-geographic numbering plan phone numbers (default country code) (extended)', () => {
|
||||
parseNumber('773111632', { defaultCallingCode: '870', extended: true }).should.deep.equal({
|
||||
country: USE_NON_GEOGRAPHIC_COUNTRY_CODE ? '001' : undefined,
|
||||
countryCallingCode: '870',
|
||||
phone: '773111632',
|
||||
carrierCode: undefined,
|
||||
ext: undefined,
|
||||
possible: true,
|
||||
valid: true
|
||||
})
|
||||
})
|
||||
|
||||
it('shouldn\'t crash when invalid `defaultCallingCode` is passed', () => {
|
||||
expect(() => parseNumber('773111632', { defaultCallingCode: '999' }))
|
||||
.to.throw('Unknown calling code')
|
||||
})
|
||||
|
||||
it('shouldn\'t set `country` when there\'s no `defaultCountry` and `defaultCallingCode` is not of a "non-geographic entity"', () => {
|
||||
parseNumber('88005553535', { defaultCallingCode: '7' }).should.deep.equal({
|
||||
country: 'RU',
|
||||
phone: '8005553535'
|
||||
})
|
||||
})
|
||||
|
||||
it('should correctly parse numbers starting with the same digit as the national prefix', () => {
|
||||
// https://github.com/catamphetamine/libphonenumber-js/issues/373
|
||||
// `BY`'s `national_prefix` is `8`.
|
||||
parseNumber('+37582004910060').should.deep.equal({
|
||||
country: 'BY',
|
||||
phone: '82004910060'
|
||||
});
|
||||
})
|
||||
|
||||
it('should autocorrect numbers without a leading +', () => {
|
||||
// https://github.com/catamphetamine/libphonenumber-js/issues/376
|
||||
parseNumber('375447521111', 'BY').should.deep.equal({
|
||||
country: 'BY',
|
||||
phone: '447521111'
|
||||
});
|
||||
// https://github.com/catamphetamine/libphonenumber-js/issues/316
|
||||
parseNumber('33612902554', 'FR').should.deep.equal({
|
||||
country: 'FR',
|
||||
phone: '612902554'
|
||||
});
|
||||
// https://github.com/catamphetamine/libphonenumber-js/issues/375
|
||||
parseNumber('61438331999', 'AU').should.deep.equal({
|
||||
country: 'AU',
|
||||
phone: '438331999'
|
||||
});
|
||||
// A case when `49` is a country calling code of a number without a leading `+`.
|
||||
parseNumber('4930123456', 'DE').should.deep.equal({
|
||||
country: 'DE',
|
||||
phone: '30123456'
|
||||
});
|
||||
// A case when `49` is a valid area code.
|
||||
parseNumber('4951234567890', 'DE').should.deep.equal({
|
||||
country: 'DE',
|
||||
phone: '4951234567890'
|
||||
});
|
||||
})
|
||||
|
||||
it('should parse extensions (long extensions with explicitl abels)', () => {
|
||||
// Test lower and upper limits of extension lengths for each type of label.
|
||||
|
||||
// Firstly, when in RFC format: PhoneNumberUtil.extLimitAfterExplicitLabel
|
||||
parseNumber('33316005 ext 0', 'NZ').ext.should.equal('0')
|
||||
parseNumber('33316005 ext 01234567890123456789', 'NZ').ext.should.equal('01234567890123456789')
|
||||
// Extension too long.
|
||||
expect(parseNumber('33316005 ext 012345678901234567890', 'NZ').ext).to.be.undefined
|
||||
|
||||
// Explicit extension label.
|
||||
parseNumber('03 3316005ext:1', 'NZ').ext.should.equal('1')
|
||||
parseNumber('03 3316005 xtn:12345678901234567890', 'NZ').ext.should.equal('12345678901234567890')
|
||||
parseNumber('03 3316005 extension\t12345678901234567890', 'NZ').ext.should.equal('12345678901234567890')
|
||||
parseNumber('03 3316005 xtensio:12345678901234567890', 'NZ').ext.should.equal('12345678901234567890')
|
||||
parseNumber('03 3316005 xtensión, 12345678901234567890#', 'NZ').ext.should.equal('12345678901234567890')
|
||||
parseNumber('03 3316005extension.12345678901234567890', 'NZ').ext.should.equal('12345678901234567890')
|
||||
parseNumber('03 3316005 доб:12345678901234567890', 'NZ').ext.should.equal('12345678901234567890')
|
||||
|
||||
// Extension too long.
|
||||
expect(parseNumber('03 3316005 extension 123456789012345678901', 'NZ').ext).to.be.undefined
|
||||
})
|
||||
|
||||
it('should parse extensions (long extensions with auto dialling labels)', () => {
|
||||
parseNumber('+12679000000,,123456789012345#').ext.should.equal('123456789012345')
|
||||
parseNumber('+12679000000;123456789012345#').ext.should.equal('123456789012345')
|
||||
parseNumber('+442034000000,,123456789#').ext.should.equal('123456789')
|
||||
// Extension too long.
|
||||
expect(parseNumber('+12679000000,,1234567890123456#').ext).to.be.undefined
|
||||
})
|
||||
|
||||
it('should parse extensions (short extensions with ambiguous characters)', () => {
|
||||
parseNumber('03 3316005 x 123456789', 'NZ').ext.should.equal('123456789')
|
||||
parseNumber('03 3316005 x. 123456789', 'NZ').ext.should.equal('123456789')
|
||||
parseNumber('03 3316005 #123456789#', 'NZ').ext.should.equal('123456789')
|
||||
parseNumber('03 3316005 ~ 123456789', 'NZ').ext.should.equal('123456789')
|
||||
// Extension too long.
|
||||
expect(parseNumber('03 3316005 ~ 1234567890', 'NZ').ext).to.be.undefined
|
||||
})
|
||||
|
||||
it('should parse extensions (short extensions when not sure of label)', () => {
|
||||
parseNumber('+1123-456-7890 666666#', { v2: true }).ext.should.equal('666666')
|
||||
parseNumber('+11234567890-6#', { v2: true }).ext.should.equal('6')
|
||||
// Extension too long.
|
||||
expect(() => parseNumber('+1123-456-7890 7777777#', { v2: true })).to.throw('NOT_A_NUMBER')
|
||||
})
|
||||
})
|
||||
Generated
Vendored
+30
@@ -0,0 +1,30 @@
|
||||
import normalizeArguments from '../normalizeArguments.js'
|
||||
import PhoneNumberMatcher from '../PhoneNumberMatcher.js'
|
||||
|
||||
/**
|
||||
* @return ES6 `for ... of` iterator.
|
||||
*/
|
||||
export default function searchNumbers()
|
||||
{
|
||||
const { text, options, metadata } = normalizeArguments(arguments)
|
||||
|
||||
const matcher = new PhoneNumberMatcher(text, options, metadata)
|
||||
|
||||
return {
|
||||
[Symbol.iterator]() {
|
||||
return {
|
||||
next: () => {
|
||||
if (matcher.hasNext()) {
|
||||
return {
|
||||
done: false,
|
||||
value: matcher.next()
|
||||
}
|
||||
}
|
||||
return {
|
||||
done: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+26
@@ -0,0 +1,26 @@
|
||||
import searchNumbers from './searchNumbers.js'
|
||||
import metadata from '../../metadata.min.json' assert { type: 'json' }
|
||||
|
||||
describe('searchNumbers', () => {
|
||||
it('should iterate', () => {
|
||||
const expectedNumbers =[{
|
||||
country : 'RU',
|
||||
phone : '8005553535',
|
||||
// number : '+7 (800) 555-35-35',
|
||||
startsAt : 14,
|
||||
endsAt : 32
|
||||
}, {
|
||||
country : 'US',
|
||||
phone : '2133734253',
|
||||
// number : '(213) 373-4253',
|
||||
startsAt : 41,
|
||||
endsAt : 55
|
||||
}]
|
||||
|
||||
for (const number of searchNumbers('The number is +7 (800) 555-35-35 and not (213) 373-4253 as written in the document.', 'US', metadata)) {
|
||||
number.should.deep.equal(expectedNumbers.shift())
|
||||
}
|
||||
|
||||
expectedNumbers.length.should.equal(0)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user