Skip to content

Commit

Permalink
fix(NumberFormat): improve regex for parsing phone numbers with count…
Browse files Browse the repository at this point in the history
…ry codes (#4340)

Fixes #4337
  • Loading branch information
tujoworker authored Nov 26, 2024
1 parent d02a0af commit 96613ed
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 56 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import ComponentBox from '../../../../../../shared/tags/ComponentBox'
import { P } from '@dnb/eufemia/src'
import { Flex, P } from '@dnb/eufemia/src'
import { Value } from '@dnb/eufemia/src/extensions/forms'

export const Empty = () => {
Expand Down Expand Up @@ -45,9 +45,11 @@ export const LabelAndValue = () => {
export const InternationalSuffix = () => {
return (
<ComponentBox>
<Value.PhoneNumber label="Label text" value="+47 98712345" />
<Value.PhoneNumber label="Label text" value="+886 0998472751" />
<Value.PhoneNumber label="Label text" value="+1-868 6758288" />
<Flex.Stack>
<Value.PhoneNumber label="Label text" value="+47 98712345" />
<Value.PhoneNumber label="Label text" value="+886 0998472751" />
<Value.PhoneNumber label="Label text" value="+1-868 6758288" />
</Flex.Stack>
</ComponentBox>
)
}
Expand Down
64 changes: 31 additions & 33 deletions packages/dnb-eufemia/src/components/number-format/NumberUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -261,11 +261,6 @@ export const format = (
currencyDisplay: 'name',
})
aria = enhanceSR(cleanedNumber, aria, locale) // also calls prepareMinus

// get only the currency name
// const num = aria.replace(/([^0-9])+$/g, '')
// const name = aria.replace(num, '')
// aria = cleanedNumber + name
} else {
handleCompactBeforeDisplay({ value, locale, compact, decimals, opts })

Expand Down Expand Up @@ -590,49 +585,52 @@ export const formatPhone = (number, locale = null) => {

switch (locale) {
default: {
// cleanup
number = String(number).replace(/[^+0-9]/g, '')

let code = ''
if (
number.length > 8 &&
number.substring(0, 2) !== '00' &&
!number.startsWith('+')
) {
number = '+' + number
number = String(number)
// Edge case for when a Norwegian number is given without a space after the country code
.replace(/^(00|\+|)47([^\s])/, '+47 $2')
.replace(/^00/, '+')

if (number.substring(0, 1) === '+') {
const codeAndNumber = number.match(
// Split the number into the country code and the rest of the number
/^\+([\d-]{1,8})\s{0,2}([\d\s-]{1,20})$/
)
if (codeAndNumber) {
code = `+${codeAndNumber[1]} `
number = codeAndNumber[2]
}
}

if (number[0] === '+') {
code = number.substring(0, 3) + ' '
number = number.substring(3)
} else if (number.substring(0, 2) === '00') {
code = number.substring(0, 4) + ' '
number = number.substring(4)
}
code = code.replace(/^00/, '+')
number = number.replace(/[^+\d]/g, '')
const length = number.length

// get 800 22 222
if (length === 8 && number[0] === '8') {
// Get 800 22 222
if (length === 8 && number.substring(0, 1) === '8') {
display =
code +
number
.split(/([0-9]{3})([0-9]{2})/)
.split(/([\d]{3})([\d]{2})/)
.filter((s) => s)
.join(' ')
} else {
// get 02000
// Get 02000
if (length < 6) {
display = code + number
} else {
// get 6 or 8 formatting
if (code.includes('-')) {
// Convert +12-3456 to +12 (3456)
code = code.replace(/(\+[\d]{1,2})-([\d]{1,6})/, '$1 ($2)')
}

// Get 6 or 8 formatting
display =
code +
number
.split(
length === 6
? /^(\+[0-9]{2})|([0-9]{3})/
: /^(\+[0-9]{2})|([0-9]{2})/
? /^(\+[\d]{2})|([\d]{3})/
: /^(\+[\d]{2})|([\d]{2})/
)
.filter((s) => s)
.join(' ')
Expand All @@ -642,7 +640,7 @@ export const formatPhone = (number, locale = null) => {
aria =
code +
number
.split(/([0-9]{2})/)
.split(/([\d]{2})/)
.filter((s) => s)
.join(' ')
}
Expand Down Expand Up @@ -671,7 +669,7 @@ export const formatBAN = (number, locale = null) => {

switch (locale) {
default: {
// get 2000 12 34567
// Get 2000 12 34567
display = number
.split(/([0-9]{4})([0-9]{2})([0-9]{1,})/)
.filter((s) => s)
Expand Down Expand Up @@ -707,7 +705,7 @@ export const formatORG = (number, locale = null) => {

switch (locale) {
default: {
// get 123 456 789
// Get 123 456 789
display = number
.split(/([0-9]{3})/)
.filter((s) => s)
Expand Down Expand Up @@ -743,7 +741,7 @@ export const formatNIN = (number, locale = null) => {

switch (locale) {
default: {
// get 180892 12345
// Get 180892 12345
display = number
.split(/([0-9]{6})/)
.filter((s) => s)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/

import { mockClipboard } from '../../../core/jest/jestSetup'
import countries from '../../../extensions/forms/constants/countries'
import { InternalLocale } from '../../../shared/Context'
import { LOCALE } from '../../../shared/defaults'
import * as helpers from '../../../shared/helpers'
Expand Down Expand Up @@ -941,10 +942,28 @@ describe('formatPhone', () => {
expect(number).toBe('12 34 56 78')
})

it('should format a phone number with country code', () => {
const result = formatPhone('+4712345678')
expect(result.number).toBe('+47 12 34 56 78')
expect(result.aria).toBe('+47 12 34 56 78')
it('should format a phone number with single country code', () => {
const result = formatPhone('+1 23456789')
expect(result.number).toBe('+1 23 45 67 89')
expect(result.aria).toBe('+1 23 45 67 89')
})

it('should format a phone number with three country code digits', () => {
const result = formatPhone('+358 23456789')
expect(result.number).toBe('+358 23 45 67 89')
expect(result.aria).toBe('+358 23 45 67 89')
})

it('should format a phone number with slash in country code', () => {
const result = formatPhone('+44-1534 12345678')
expect(result.number).toBe('+44 (1534) 12 34 56 78')
expect(result.aria).toBe('+44 (1534) 12 34 56 78')
})

it('should format a long number with', () => {
const result = formatPhone('+123456 123456789123456789')
expect(result.number).toBe('+123456 12 34 56 78 91 23 45 67 89')
expect(result.aria).toBe('+123456 12 34 56 78 91 23 45 67 89')
})

it('should format a phone number without country code', () => {
Expand All @@ -953,12 +972,6 @@ describe('formatPhone', () => {
expect(result.aria).toBe('12 34 56 78')
})

it('should format a phone number with leading 00 country code', () => {
const result = formatPhone('004712345678')
expect(result.number).toBe('+47 12 34 56 78')
expect(result.aria).toBe('+47 12 34 56 78')
})

it('should format a short phone number', () => {
const result = formatPhone('12345')
expect(result.number).toBe('12345')
Expand All @@ -972,9 +985,9 @@ describe('formatPhone', () => {
})

it('should handle invalid characters in phone number', () => {
const result = formatPhone('+47-123-456-78')
expect(result.number).toBe('+47 12 34 56 78')
expect(result.aria).toBe('+47 12 34 56 78')
const result = formatPhone('+123 123-456-78')
expect(result.number).toBe('+123 12 34 56 78')
expect(result.aria).toBe('+123 12 34 56 78')
})

it('should handle empty input', () => {
Expand All @@ -994,4 +1007,44 @@ describe('formatPhone', () => {
expect(result.number).toBe('')
expect(result.aria).toBe('')
})

it.each(countries.map(({ cdc, i18n }) => [`${i18n.en}`, cdc]))(
'should handle %s country code',
(_, cdc) => {
const result = formatPhone(`+${cdc} 12345678`)

if (cdc.includes('-')) {
cdc = cdc.replace(/([\d]{1,2})-([\d]{1,6})/, '$1 ($2)')
}

expect(result.number).toBe(`+${cdc} 12 34 56 78`)
expect(result.aria).toBe(`+${cdc} 12 34 56 78`)
}
)

describe('Norway', () => {
it('should format a the country code without space', () => {
const result = formatPhone('+4712345678')
expect(result.number).toBe('+47 12 34 56 78')
expect(result.aria).toBe('+47 12 34 56 78')
})

it('should format the country code without + or 00', () => {
const result = formatPhone('4712345678')
expect(result.number).toBe('+47 12 34 56 78')
expect(result.aria).toBe('+47 12 34 56 78')
})

it('should format the country code with 00', () => {
const result = formatPhone('004712345678')
expect(result.number).toBe('+47 12 34 56 78')
expect(result.aria).toBe('+47 12 34 56 78')
})

it('should format the country code with +', () => {
const result = formatPhone('+47 12345678')
expect(result.number).toBe('+47 12 34 56 78')
expect(result.aria).toBe('+47 12 34 56 78')
})
})
})
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import React, { useCallback } from 'react'
import StringValue, { Props as StringValueProps } from '../String'
import {
format,
cleanNumber,
} from '../../../../components/number-format/NumberUtils'
import { format } from '../../../../components/number-format/NumberUtils'
import useTranslation from '../../hooks/useTranslation'

export type Props = StringValueProps
Expand All @@ -15,7 +12,8 @@ function PhoneNumber(props: Props) {
props.label ?? (props.inline ? undefined : translations.label)

const toInput = useCallback((value) => {
return format(cleanNumber(value), {
// We can't use the "cleanNumber" function here, because we need to keep the country code separate from the number
return format(value, {
phone: true,
}).toString()
}, [])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1650,7 +1650,7 @@ const countries: Array<CountryType> = [
en: 'Puerto Rico',
nb: 'Puerto Rico',
},
cdc: '1-787, 1-939',
cdc: '1-787',
iso: 'PR',
continent: 'North America',
},
Expand Down

0 comments on commit 96613ed

Please sign in to comment.