Skip to content

Commit

Permalink
feat(Forms): add validator to Field.NationalIdentityNumber
Browse files Browse the repository at this point in the history
  • Loading branch information
langz committed Oct 8, 2024
1 parent 7c34fc9 commit 4c858d1
Show file tree
Hide file tree
Showing 7 changed files with 199 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -188,3 +188,25 @@ export const ValidationExtendValidator = () => {
</ComponentBox>
)
}

export const ValidationExtendValidatorAdult = () => {
return (
<ComponentBox>
{() => {
const myAdultValidator = (value, { validators }) => {
const { adultValidator } = validators

return [adultValidator]
}

return (
<Field.NationalIdentityNumber
required
value="11032455001"
validator={myAdultValidator}
/>
)
}}
</ComponentBox>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ You can provide your own validation function.

### Extend validation with custom validation function

You can [extend the existing validations](/uilib/extensions/forms/create-component/useFieldProps/info/#validators)(`dnrValidator`, `fnrValidator`, and `dnrAndFnrValidator`) with your own validation function.
You can [extend the existing validations](/uilib/extensions/forms/create-component/useFieldProps/info/#validators)(`dnrValidator`, `fnrValidator`, `dnrAndFnrValidator`, and `adultValidator`) with your own validation function.

<Examples.ValidationExtendValidator />

### Extend validation adult validator

You can [extend the existing validations](/uilib/extensions/forms/create-component/useFieldProps/info/#validators)(`dnrValidator` and `fnrValidator`) with the `adultValidator`.

<Examples.ValidationExtendValidatorAdult />
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { NationalIdentityNumberProperties } from '@dnb/eufemia/src/extensions/fo
- `dnrAndFnrValidator`:
- validates the identification number as a d number when first digit is 4 or greater (because a d number has its first number increased by 4).
- validates the identification number as a national identity number (fødselsnummer) when first digit is 3 or less.
- `adultValidator`: validates if the identification number has a date of birth that is 18 years or older.

See the following [example](/uilib/extensions/forms/feature-fields/NationalIdentityNumber/#extend-validation-with-custom-validation-function) on how to extend validation using the exposed validators.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ function NationalIdentityNumber(props: Props) {
const { validate = true, omitMask } = props

const translations = useTranslation().NationalIdentityNumber
const { label, errorRequired, errorFnr, errorDnr } = translations
const { label, errorRequired, errorFnr, errorDnr, errorAdult } =
translations
const errorMessages = useErrorMessage(props.path, props.errorMessages, {
required: errorRequired,
pattern: errorRequired,
errorFnr,
errorDnr,
errorAdult,
})

const mask = useMemo(
Expand Down Expand Up @@ -76,6 +78,15 @@ function NationalIdentityNumber(props: Props) {
[dnrValidator, fnrValidator]
)

const adultValidator = useCallback(
(value: string) => {
if (!is18YearsOrOlder(value)) {
return Error(errorAdult)
}
},
[errorAdult]
)

const StringFieldProps: Props = {
...props,
pattern:
Expand All @@ -93,11 +104,51 @@ function NationalIdentityNumber(props: Props) {
onBlurValidator: validate
? props.onBlurValidator || dnrAndFnrValidator
: undefined,
exportValidators: { dnrValidator, fnrValidator, dnrAndFnrValidator },
exportValidators: {
dnrValidator,
fnrValidator,
dnrAndFnrValidator,
adultValidator,
},
}

return <StringField {...StringFieldProps} />
}

function is18YearsOrOlder(value: string) {
if (value === undefined) return false

function getAge(birthDate: Date): number {
const today = new Date()
const age = today.getFullYear() - birthDate.getFullYear()
const month = today.getMonth() - birthDate.getMonth()
const day = today.getDate() - birthDate.getDate()

if (month < 0 || (month === 0 && day < 0)) {
return age - 1
}

return age
}

const yearPart = value.substring(4, 6)
const individNumber = Number.parseInt(value.substring(6, 9))

const isBornIn20XX = individNumber >= 500 && individNumber <= 999
const year = isBornIn20XX ? `20${yearPart}` : `19${yearPart}`
const month = Number.parseInt(value.substring(2, 4))

const differentiatorValue =
value.length > 0 ? Number.parseInt(value.substring(0, 1)) : undefined
const isDnr = differentiatorValue && differentiatorValue > 3

const day = isDnr
? Number.parseInt(value.substring(0, 2)) - 40
: Number.parseInt(value.substring(0, 2))
const date = new Date(Number.parseInt(year), month - 1, day)

return getAge(date) >= 18
}

NationalIdentityNumber._supportsSpacingProps = true
export default NationalIdentityNumber
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ describe('Field.NationalIdentityNumber', () => {
expect(screen.queryByRole('alert')).toBeInTheDocument()
})

it('should validate "required"', async () => {
it('should validate "required"', () => {
render(<Field.NationalIdentityNumber required validateInitially />)

expect(screen.queryByRole('alert')).toBeInTheDocument()
Expand All @@ -58,7 +58,7 @@ describe('Field.NationalIdentityNumber', () => {
)
})

it('should show "errorRequired" error message when "pattern" not matches', async () => {
it('should show "errorRequired" error message when "pattern" not matches', () => {
render(<Field.NationalIdentityNumber validateInitially value="123" />)

expect(screen.queryByRole('alert')).toBeInTheDocument()
Expand Down Expand Up @@ -160,7 +160,7 @@ describe('Field.NationalIdentityNumber', () => {
expect(input).toHaveAttribute('inputmode', 'numeric')
})

it('should not validate pattern when validate false', async () => {
it('should not validate pattern when validate false', () => {
const invalidPattern = '1234'
render(
<Field.NationalIdentityNumber
Expand All @@ -175,7 +175,7 @@ describe('Field.NationalIdentityNumber', () => {
expect(screen.queryByRole('alert')).toBeNull()
})

it('should not validate custom pattern when validate false', async () => {
it('should not validate custom pattern when validate false', () => {
const invalidPattern = '1234'
render(
<Field.NationalIdentityNumber
Expand All @@ -191,7 +191,7 @@ describe('Field.NationalIdentityNumber', () => {
expect(screen.queryByRole('alert')).toBeNull()
})

it('should not validate dnum when validate false', async () => {
it('should not validate dnum when validate false', () => {
const invalidDnum = '69020112345'
render(
<Field.NationalIdentityNumber
Expand All @@ -206,7 +206,7 @@ describe('Field.NationalIdentityNumber', () => {
expect(screen.queryByRole('alert')).toBeNull()
})

it('should not validate fnr when validate false', async () => {
it('should not validate fnr when validate false', () => {
const invalidFnr = '29020112345'
render(
<Field.NationalIdentityNumber
Expand All @@ -221,7 +221,7 @@ describe('Field.NationalIdentityNumber', () => {
expect(screen.queryByRole('alert')).toBeNull()
})

it('should not validate custom validator when validate false', async () => {
it('should not validate custom validator when validate false', () => {
const customValidator: Validator<string> = (value) => {
if (value?.length < 4) {
return new Error('My error')
Expand All @@ -241,7 +241,7 @@ describe('Field.NationalIdentityNumber', () => {
expect(screen.queryByRole('alert')).toBeNull()
})

it('should not validate extended validator when validate false', async () => {
it('should not validate extended validator when validate false', () => {
const invalidFnrBornInApril = '29040112345'

const bornInApril = (value: string) => value.substring(2, 4) === '04'
Expand Down Expand Up @@ -286,7 +286,7 @@ describe('Field.NationalIdentityNumber', () => {
'53137248022',
]

it.each(validDNum)('Valid D number: %s', async (dNum) => {
it.each(validDNum)('Valid D number: %s', (dNum) => {
render(
<Field.NationalIdentityNumber value={dNum} validateInitially />
)
Expand Down Expand Up @@ -340,7 +340,7 @@ describe('Field.NationalIdentityNumber', () => {

it.each(validFnrNum)(
'Valid national identity number(fnr): %s',
async (fnrNum) => {
(fnrNum) => {
render(
<Field.NationalIdentityNumber validateInitially value={fnrNum} />
)
Expand Down Expand Up @@ -374,6 +374,109 @@ describe('Field.NationalIdentityNumber', () => {
)
})

describe('should validate if identity numbers is adult(18 years and older)', () => {
jest.useFakeTimers().setSystemTime(new Date('2024-10-07').getTime())
const fnr0YearsOld = [
'10072476609',
'29082499936',
'03022450718',
'11032455001',
'30082489912',
]

const fnr17YearsOld = [
'31050752669',
'10040752779',
'28050772596',
'25060798446',
'07100782566',
]

const fnrUnder18YearsOld = [...fnr0YearsOld, ...fnr17YearsOld]

const fnr18Years = [
'24040664900',
'26020682328',
'07070663990',
'11030699302',
'31010699021',
]
const fnr99Years = [
'14102535759',
'20042528022',
'14082523414',
'01022537632',
'01022504416',
]
const fnr18YearsOldTo99 = [
'25047441741',
'06118836551',
'19042648291',
'18053526132',
'29075642618',
]
const fnr99To120YearsOld = [
'22041330302',
'02061234694',
'23020704845',
'28021741177',
'10121933999',
]
const fnr18YearsOldAndOlder = [
...fnr18Years,
...fnr99Years,
...fnr18YearsOldTo99,
...fnr99To120YearsOld,
]

const dnrUnder18YearsOld = [...fnrUnder18YearsOld] // todo: replace this with actual dnrs
const dnr18YearsOldAndOlder = [...fnr18YearsOldAndOlder] // todo: replace this with actual dnrs

const validIds = [...fnr18YearsOldAndOlder, ...dnr18YearsOldAndOlder]

const invalidIds = [...fnrUnder18YearsOld, ...dnrUnder18YearsOld]

const customValidator: Validator<string> = (value, { validators }) => {
const { adultValidator } = validators

return [adultValidator]
}

it.each(validIds)(
'Identity number is 18 years or older : %s',
(validId) => {
render(
<Field.NationalIdentityNumber
validator={customValidator}
validateInitially
value={validId}
/>
)

expect(screen.queryByRole('alert')).toBeNull()
}
)

it.each(invalidIds)(
'Invalid identity number is not 18 years or older: %s',
async (invalidId) => {
render(
<Field.NationalIdentityNumber
validator={customValidator}
validateInitially
value={invalidId}
/>
)
await waitFor(() => {
expect(screen.queryByRole('alert')).toBeInTheDocument()
expect(screen.queryByRole('alert')).toHaveTextContent(
nb.NationalIdentityNumber.errorAdult
)
})
}
)
})

describe('should extend validation using custom validator', () => {
const validFnrNumApril = ['14046512368', '10042223293']
const validDNumApril = ['51041678171']
Expand Down Expand Up @@ -407,7 +510,7 @@ describe('Field.NationalIdentityNumber', () => {
return [dnrAndFnrValidator]
}

it.each(validIds)('Valid identity number: %s', async (fnrNum) => {
it.each(validIds)('Valid identity number: %s', (fnrNum) => {
render(
<Field.NationalIdentityNumber
validator={customValidator}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ export default {
'Invalid national identity number. Enter a valid 11-digit number.',
errorFnr: 'Invalid national identity number.',
errorDnr: 'Invalid D number.',
errorAdult: 'Must be atleast 18 years of age.',
},
OrganizationNumber: {
label: 'Organisation number',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ export default {
'Ugyldig fødselsnummer. Skriv inn et gyldig fødselsnummer med 11 siffer.',
errorFnr: 'Ugyldig fødselsnummer.',
errorDnr: 'Ugyldig d-nummer.',
errorAdult: 'Må være minst 18 år.',
},
OrganizationNumber: {
label: 'Organisasjonsnummer',
Expand Down

0 comments on commit 4c858d1

Please sign in to comment.