Skip to content
Snippets Groups Projects
Commit 79cc7603 authored by Tore.Brede's avatar Tore.Brede
Browse files

GREG-85: Parsing mobile phone if it is coming from server. Adding a test

parent 65b687df
No related branches found
No related tags found
1 merge request!118GREG-85: Parsing mobile phone if it is coming from server. Adding a test
Pipeline #97766 passed
......@@ -84,6 +84,7 @@
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-react": "^7.25.1",
"eslint-plugin-react-hooks": "^4.2.0",
"jest-fetch-mock": "^3.0.3",
"jest-junit": "^12.2.0"
}
}
......@@ -4,6 +4,7 @@
* most of the data there the guest cannot change.
*/
export type EnteredGuestData = {
mobilePhoneCountry: string
mobilePhone: string
nationalIdNumber?: string
passportNumber?: string
......
/**
* This is data about the guest that the sponsor has entered when the invitation was created
*/
import AuthenticationMethod from './authenticationMethod'
export type ContactInformationBySponsor = {
/**
* This is the basis for the data shown in the guest registration form.
*
* It mostly contains data about the guest that was entered during the invite step.
*/
export type GuestInviteInformation = {
first_name: string
last_name: string
ou_name_nb: string
......@@ -18,10 +20,14 @@ export type ContactInformationBySponsor = {
// they are set, with the exception of e-mail, when the guest
// first follows the invite link
email?: string
mobile_phone_country_code?: string
mobile_phone?: string
fnr?: string
passport?: string
passportNationality?: string
countryForCallingCode?: string
authentication_method: AuthenticationMethod
}
import React from 'react'
import fetchMock, { enableFetchMocks } from 'jest-fetch-mock'
import { render, waitFor, screen } from 'test-utils'
import AdapterDateFns from '@mui/lab/AdapterDateFns'
import { LocalizationProvider } from '@mui/lab'
import GuestRegister from './index'
enableFetchMocks()
const testData = {
person: {
first_name: 'Test',
last_name: 'Tester',
mobile_phone: '+4797543910',
email: 'test@example.org',
fnr: '04062141242',
passport: 'NO-123456',
},
role: {
ou_name_en: 'English organizational unit name',
ou_name_nb: 'Norsk navn organisasjonsenhet',
name_en: 'Guest role',
name_nb: 'Gjesterolle',
start: '2021-08-10',
end: '2021-08-16',
comment: '',
},
meta: {
session_type: 'invite',
},
}
beforeEach(() => {
fetchMock.mockIf('/api/ui/v1/invited', () =>
Promise.resolve<any>(JSON.stringify(testData))
)
})
// This is just an example of how jest-fetch-mock can be used, it is not a proper test
test('Mobile phone number parsed and split correctly', async () => {
render(
<LocalizationProvider dateAdapter={AdapterDateFns}>
<GuestRegister />
</LocalizationProvider>
)
await waitFor(() => screen.queryByText(testData.person.first_name))
await waitFor(() => screen.queryByText(testData.person.last_name))
await waitFor(() => screen.queryByText(testData.person.email))
await waitFor(() => screen.queryByText(testData.person.fnr))
await waitFor(() => screen.queryByText('NO'))
await waitFor(() => screen.queryByText('123456'))
// There is no proper i18n loaded so the country name will be undefined, the country code should still show though
await waitFor(() => screen.queryByText('undefined (47)'))
await waitFor(() => screen.queryByText('97543910'))
})
......@@ -5,13 +5,18 @@ import { Box, Button } from '@mui/material'
import Page from 'components/page'
import { useHistory } from 'react-router-dom'
import {
CountryCode,
getCountries,
getCountryCallingCode,
} from 'libphonenumber-js'
import OverviewGuestButton from '../../components/overviewGuestButton'
import GuestRegisterStep from './registerPage'
import { GuestRegisterCallableMethods } from './guestRegisterCallableMethods'
import { EnteredGuestData } from './enteredGuestData'
import { ContactInformationBySponsor } from './guestDataForm'
import { GuestInviteInformation } from './guestDataForm'
import AuthenticationMethod from './authenticationMethod'
import { submitJsonOpts } from '../../../utils'
import { splitPhoneNumber, submitJsonOpts } from '../../../utils'
enum SubmitState {
NotSubmitted,
......@@ -25,7 +30,7 @@ enum SubmitState {
export default function GuestRegister() {
const { t } = useTranslation(['common'])
const history = useHistory()
// TODO On submit successful the user should be directed to some page telling h
// TODO On submit successful the user should be directed to some page telling
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [submitState, setSubmitState] = useState<SubmitState>(
SubmitState.NotSubmitted
......@@ -36,24 +41,24 @@ export default function GuestRegister() {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [activeStep, setActiveStep] = useState(0)
const [guestFormData, setGuestFormData] =
useState<ContactInformationBySponsor>({
first_name: '',
last_name: '',
ou_name_en: '',
ou_name_nb: '',
role_name_en: '',
role_name_nb: '',
role_start: '',
role_end: '',
comment: '',
email: '',
mobile_phone: '',
fnr: '',
passport: '',
passportNationality: '',
authentication_method: AuthenticationMethod.Invite,
})
const [guestFormData, setGuestFormData] = useState<GuestInviteInformation>({
first_name: '',
last_name: '',
ou_name_en: '',
ou_name_nb: '',
role_name_en: '',
role_name_nb: '',
role_start: '',
role_end: '',
comment: '',
email: '',
mobile_phone: '',
fnr: '',
passport: '',
passportNationality: '',
countryForCallingCode: '',
authentication_method: AuthenticationMethod.Invite,
})
const [errorState, setErrorState] = useState<string>('')
......@@ -63,13 +68,29 @@ export default function GuestRegister() {
if (response.ok) {
response.json().then((jsonResponse) => {
console.log(`Guest data from server: ${JSON.stringify(jsonResponse)}`)
// TODO Remove after development
console.log(`Data from server: ${JSON.stringify(jsonResponse)}`)
const authenticationMethod =
jsonResponse.meta.session_type === 'invite'
? AuthenticationMethod.Invite
: AuthenticationMethod.Feide
const [countryCode, nationalNumber] = jsonResponse.person.mobile_phone
? splitPhoneNumber(jsonResponse.person.mobile_phone)
: ['', '']
let extractedCountryCode = ''
if (countryCode) {
const matchingCountries = getCountries().find(
(value) => getCountryCallingCode(value) === countryCode
)
if (matchingCountries && matchingCountries.length > 0) {
extractedCountryCode = matchingCountries.toString()
}
}
setGuestFormData({
first_name: jsonResponse.person.first_name,
last_name: jsonResponse.person.last_name,
......@@ -82,11 +103,13 @@ export default function GuestRegister() {
comment: jsonResponse.role.comments,
email: jsonResponse.person.email,
mobile_phone: jsonResponse.person.mobile_phone,
mobile_phone_country_code: countryCode,
mobile_phone: nationalNumber,
fnr: jsonResponse.fnr,
passport: jsonResponse.passport,
// TODO Separate out nationality based on what is in the server response
passportNationality: '',
countryForCallingCode: extractedCountryCode,
authentication_method: authenticationMethod,
})
......@@ -119,7 +142,9 @@ export default function GuestRegister() {
): void => {
const payload: any = {}
payload.person = {}
payload.person.mobile_phone = updateFormData.mobilePhone
payload.person.mobile_phone = `+${getCountryCallingCode(
updateFormData.mobilePhoneCountry as CountryCode
)}${updateFormData.mobilePhone}`
if (updateFormData.passportNumber && updateFormData.passportNationality) {
// The user has entered some passport information, check that both nationality and number are present
......
......@@ -5,12 +5,10 @@ import { LocalizationProvider } from '@mui/lab'
import GuestRegisterStep from './registerPage'
import { EnteredGuestData } from './enteredGuestData'
import AuthenticationMethod from './authenticationMethod'
import { GuestInviteInformation } from './guestDataForm'
test('Guest register page showing passport field on manual registration', async () => {
const nextHandler = (enteredGuestData: EnteredGuestData) => {
console.log(`Entered data: ${enteredGuestData}`)
}
const guestData = {
function getEmptyGuestData(): GuestInviteInformation {
return {
first_name: '',
last_name: '',
ou_name_en: '',
......@@ -24,12 +22,22 @@ test('Guest register page showing passport field on manual registration', async
mobile_phone: '',
fnr: '',
passport: '',
countryForCallingCode: '',
authentication_method: AuthenticationMethod.Invite,
}
}
test('Guest register page showing passport field on manual registration', async () => {
const nextHandler = (enteredGuestData: EnteredGuestData) => {
console.log(`Entered data: ${enteredGuestData}`)
}
render(
<LocalizationProvider dateAdapter={AdapterDateFns}>
<GuestRegisterStep nextHandler={nextHandler} guestData={guestData} />
<GuestRegisterStep
nextHandler={nextHandler}
guestData={getEmptyGuestData()}
/>
</LocalizationProvider>
)
......
......@@ -8,7 +8,13 @@ import {
Typography,
} from '@mui/material'
import { SubmitHandler, useForm } from 'react-hook-form'
import React, { forwardRef, Ref, useImperativeHandle, useState } from 'react'
import React, {
forwardRef,
Ref,
useEffect,
useImperativeHandle,
useState,
} from 'react'
import { useTranslation } from 'react-i18next'
import {
CountryCallingCode,
......@@ -17,7 +23,7 @@ import {
getCountryCallingCode,
} from 'libphonenumber-js'
import { getAlpha2Codes, getName } from 'i18n-iso-countries'
import { ContactInformationBySponsor } from './guestDataForm'
import { GuestInviteInformation } from './guestDataForm'
import { EnteredGuestData } from './enteredGuestData'
import { GuestRegisterCallableMethods } from './guestRegisterCallableMethods'
import { isValidFnr, isValidMobilePhoneNumber } from '../../../utils'
......@@ -26,7 +32,7 @@ import AuthenticationMethod from './authenticationMethod'
interface GuestRegisterProperties {
nextHandler(guestData: EnteredGuestData): void
guestData: ContactInformationBySponsor
guestData: GuestInviteInformation
}
/**
......@@ -42,19 +48,7 @@ const GuestRegisterStep = forwardRef(
const [countryCode, setCountryCode] = useState<
CountryCallingCode | undefined
>(undefined)
const handleCountryCodeChange = (event: SelectChangeEvent) => {
if (event.target.value) {
const countryCodeType = getCountries().find(
(value) => value.toString() === event.target.value
)
if (countryCodeType) {
setCountryCode(getCountryCallingCode(countryCodeType))
}
} else {
setCountryCode(undefined)
}
}
const [mobilePhone, setMobilePhone] = useState<string>('')
const submit: SubmitHandler<EnteredGuestData> = (data) => {
nextHandler(data)
......@@ -64,19 +58,66 @@ const GuestRegisterStep = forwardRef(
register,
handleSubmit,
setValue,
trigger,
setError,
clearErrors,
formState: { errors },
} = useForm<EnteredGuestData>()
const onSubmit = handleSubmit<EnteredGuestData>(submit)
const handleCountryCodeChange = (event: SelectChangeEvent) => {
if (event.target.value) {
const countryCodeType = event.target.value as CountryCode
setCountryCode(countryCodeType)
setValue('mobilePhoneCountry', countryCodeType)
} else {
setCountryCode(undefined)
}
}
const handleMobilePhoneChange = (value: any) => {
if (countryCode) {
// The country code and the rest of the mobile number are in two fields, so cannot
// register the field directly in form, but need to have extra logic defined
// to combine the values before writing them to the form handling
const phoneNumberWithCountryCode = `+${getCountryCallingCode(
countryCode as CountryCode
)}${value.target.value}`
const isValidPhoneNumber = isValidMobilePhoneNumber(
phoneNumberWithCountryCode
)
if (isValidPhoneNumber === true) {
setValue('mobilePhone', value.target.value)
clearErrors('mobilePhone')
} else {
setError('mobilePhone', {
type: 'manual',
message: isValidPhoneNumber || undefined,
})
}
}
setMobilePhone(value.target.value)
}
useEffect(() => {
setCountryCode(guestData.countryForCallingCode)
setMobilePhone(guestData.mobile_phone ? guestData.mobile_phone : '')
setValue(
'mobilePhoneCountry',
guestData.countryForCallingCode ? guestData.countryForCallingCode : ''
)
}, [guestData])
function doSubmit() {
return onSubmit()
}
register('mobilePhone', {
required: t<string>('validation.mobilePhoneRequired'),
validate: isValidMobilePhoneNumber,
})
register('mobilePhoneCountry')
useImperativeHandle(ref, () => ({ doSubmit }))
......@@ -132,10 +173,10 @@ const GuestRegisterStep = forwardRef(
labelId="phone-number-select"
id="phone-number-select"
displayEmpty
defaultValue=""
onChange={handleCountryCodeChange}
value={countryCode ? countryCode.toString() : ''}
renderValue={(selected: any) => {
if (selected.length === 0 || selected === '') {
if (!selected) {
return t('input.countryCallingCode')
}
return `${getName(
......@@ -143,6 +184,10 @@ const GuestRegisterStep = forwardRef(
i18n.language
)} (${getCountryCallingCode(selected as CountryCode)})`
}}
data-testid="phone-country-code-select"
inputProps={{
'data-testid': 'phone-country-code-select-inner',
}}
>
<MenuItem disabled value="">
{t('input.countryCallingCode')}
......@@ -159,19 +204,9 @@ const GuestRegisterStep = forwardRef(
sx={{ flexGrow: 2 }}
label={t('input.mobilePhone')}
error={!!errors.mobilePhone}
value={mobilePhone}
helperText={errors.mobilePhone && errors.mobilePhone.message}
onChange={(value) => {
if (countryCode) {
// The country code and the rest of the mobile number are in two fields, so cannot
// register the field directly in form, but need to have extra logic defined
// to combine the values before writing them to the form handling
setValue(
'mobilePhone',
`+${countryCode.toString()}${value.target.value}`
)
trigger('mobilePhone')
}
}}
onChange={handleMobilePhoneChange}
/>
</Box>
{guestData.authentication_method ===
......
// adds the 'fetchMock' global variable and rewires 'fetch' global to call 'fetchMock' instead of the real implementation
// eslint-disable-next-line import/no-extraneous-dependencies
require('jest-fetch-mock').enableMocks()
// changes default behavior of fetchMock to use the real 'fetch' implementation and not mock responses
// eslint-disable-next-line no-undef
fetchMock.dontMock()
import validator from '@navikt/fnrvalidator'
import i18n from 'i18next'
import { isValidPhoneNumber } from 'libphonenumber-js'
import { isValidPhoneNumber, parsePhoneNumber } from 'libphonenumber-js'
const validEmailRegex =
/^(?=.{1,254}$)(?=.{1,64}@)[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+(\.[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+)*@[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$/
......@@ -83,3 +83,17 @@ export function isValidEmail(data: string | undefined): boolean | string {
}
return i18n.t<string>('common:validation.invalidEmail')
}
/**
* Splits a phone number into a country code and the national number.
*
* @param phoneNumber The phone number to split
*/
export function splitPhoneNumber(phoneNumber: string): [string, string] {
const parsedNumber = parsePhoneNumber(phoneNumber)
return [
parsedNumber.countryCallingCode.toString(),
parsedNumber.nationalNumber.toString(),
]
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment