Skip to content

Commit

Permalink
Apply more specific date error messages (#2022)
Browse files Browse the repository at this point in the history
* Apply more specific date error messages

https://eaflood.atlassian.net/browse/IWTF-3913

This PR makes use of the new date validations to give more specific date error messages.

* Generalise date validation and apply to date of birth page

* Added validation for licence to start page

* Refactor date of birth and start date validators to be reusable functions, apply them to the date of birth, start date and renewals id page (start date will be applied to renewal start date in a new ticket)

* Add function to get error flags for a date, to pass through to the template so the relevant fields can be highlighted

---------

Co-authored-by: Phil Benson <[email protected]>
  • Loading branch information
irisfaraway and jaucourt authored Dec 9, 2024
1 parent fffcc3f commit 19abcb6
Show file tree
Hide file tree
Showing 19 changed files with 894 additions and 196 deletions.
33 changes: 26 additions & 7 deletions packages/gafl-webapp-service/src/locales/cy.json
Original file line number Diff line number Diff line change
Expand Up @@ -272,9 +272,16 @@
"disability_concession_title_you": "Ydych chi’n derbyn unrhyw un o’r canlynol?",
"dob_day": "Diwrnod",
"dob_entry_hint": "Er enghraifft, 23 11 1979",
"dob_error_format_max": "Mae'n rhaid i’r dyddiad geni fod yn y gorffennol",
"dob_error_format_min": "Nodwch eich dyddiad geni a chynnwys y diwrnod, y mis a’r flwyddyn",
"dob_error_format": "Nodwch ddyddiad geni deiliad y drwydded a chynnwys diwrnod, mis a blwyddyn",
"dob_error_date_real": "Mae’n rhaid i’r dyddiad geni fod yn ddyddiad dilys",
"dob_error_missing_day_and_month": "Mae’n rhaid i’r dyddiad geni gynnwys diwrnod a mis",
"dob_error_missing_day_and_year": "Mae’n rhaid i’r dyddiad geni gynnwys diwrnod a blwyddyn",
"dob_error_missing_month_and_year": "Mae’n rhaid i’r dyddiad geni gynnwys mis a blwyddyn",
"dob_error_missing_day": "Mae’n rhaid i’r dyddiad geni gynnwys diwrnod",
"dob_error_missing_month": "Mae’n rhaid i’r dyddiad geni gynnwys mis",
"dob_error_missing_year": "Mae’n rhaid i’r dyddiad geni gynnwys blwyddyn",
"dob_error_non_numeric": "Rhowch rifau yn unig",
"dob_error_year_min": "Mae’r dyddiad geni yn rhy bell yn ôl",
"dob_error_year_max": "Mae'n rhaid i’r dyddiad geni fod yn y gorffennol",
"dob_error": "Rhowch y dyddiad geni",
"dob_month": "Mis",
"dob_privacy_link_prefix": "Os nad ydych yn darparu dyddiad geni cywir, gallai hynny achosi oedi wrth adnewyddu trwydded, neu olygu nad yw’r drwydded yn ddilys. Darllenwch am ",
Expand Down Expand Up @@ -318,15 +325,19 @@
"header_service_name_title": " - Cael trwydded bysgota â gwialen",
"identification": "Rhif adnabod",
"identify_body_protect_info": "Er mwyn dod o hyd i fanylion eich trwydded, bydd angen i ni wybod pwy ydych chi. Mae hyn yn ein helpu i ddiogelu eich gwybodaeth bersonol.",
"identify_error_date_real": "Mae’n rhaid i’r dyddiad geni fod yn ddyddiad dilys",
"identify_error_empty_postcode": "Nid ydych wedi nodi cod post",
"identify_error_empty": "Rhowch chwe nodyn olaf eich trwydded",
"identify_error_enter_bday_max": "Mae’n rhaid i’ch dyddiad geni fod yn y gorffennol",
"identify_error_enter_bday_min": "Mae eich dyddiad geni yn rhy bell yn ôl",
"identify_error_enter_bday": "Nodwch eich dyddiad geni a chynnwys y diwrnod, y mis a’r flwyddyn",
"identify_error_invalid_1": "Nid oes gennym gofnod o rif trwydded sy'n gorffen gyda ",
"identify_error_invalid_2": " sy’n cyd-fynd â'r manylion hyn.",
"identify_error_missing_day": "Mae’n rhaid i’r dyddiad geni gynnwys diwrnod",
"identify_error_missing_month": "Mae’n rhaid i’r dyddiad geni gynnwys mis",
"identify_error_missing_year": "Mae’n rhaid i’r dyddiad geni gynnwys blwyddyn",
"identify_error_non_numeric": "Rhowch rifau yn unig",
"identify_error_pattern_postcode": "Your postcode doesn’t look right. Check and enter again",
"identify_error_pattern": "Nid yw chwe nodyn olaf eich trwydded yn edrych yn gywir. Gwiriwch a rhowch gynnig arall arni",
"identify_error_year_min": "Mae’r dyddiad geni yn rhy bell yn ôl",
"identify_error_year_max": "Mae'n rhaid i’r dyddiad geni fod yn y gorffennol",
"identify_label_last_six_hint": "Er enghraifft F4A315",
"identify_label_last_six": "Chwe nodyn olaf eich trwydded.",
"identify_label_licence_ending": "Rhif trwydded yn gorffen gyda’r canlynol",
Expand Down Expand Up @@ -407,8 +418,16 @@
"licence_num": "Rhif trwydded",
"licence_start_days": " diwrnod nesaf",
"licence_start_enter_todays_date": "Rhowch ddyddiad heddiw os ydych chi am i’r drwydded 1 diwrnod neu 8 diwrnod ddechrau yn hwyrach heddiw.",
"licence_start_error_date_real": "Mae’n rhaid i ddyddiad dechrau’r drwydded fod yn ddyddiad dilys",
"licence_start_error_missing_day_and_month": "Mae’n rhaid i ddyddiad dechrau’r drwydded gynnwys diwrnod a mis",
"licence_start_error_missing_day_and_year": "Mae’n rhaid i ddyddiad dechrau’r drwydded gynnwys diwrnod a blwyddyn",
"licence_start_error_missing_month_and_year": "Mae’n rhaid i ddyddiad dechrau’r drwydded gynnwys mis a blwyddyn",
"licence_start_error": "Rhowch ddyddiad dechrau’r drwydded",
"licence_start_error_missing_day": "Mae’n rhaid i ddyddiad dechrau’r drwydded gynnwys diwrnod",
"licence_start_error_missing_month": "Mae’n rhaid i ddyddiad dechrau’r drwydded gynnwys mis",
"licence_start_error_missing_year": "Mae’n rhaid i ddyddiad dechrau’r drwydded gynnwys blwyddyn",
"licence_start_error_non_numeric": "Rhowch rifau yn unig",
"licence_start_error_choose_when": "Dewiswch pryd y dylai'r drwydded ddechrau",
"licence_start_error_format": "Nodwch y dyddiad y mae angen i'r drwydded ddechrau a chynnwys diwrnod, mis a blwyddyn",
"licence_start_error_within": "Nodwch ddyddiad o fewn y ",
"licence_start_hint": "Rhowch ddyddiad hyd at a chan gynnwys ",
"licence_start_later": "Yn hwyrach",
Expand Down
37 changes: 29 additions & 8 deletions packages/gafl-webapp-service/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -272,10 +272,19 @@
"disability_concession_title_you": "Do you receive any of the following?",
"dob_day": "day",
"dob_entry_hint": "For example, 23 11 1979",
"dob_error_format_max": "The date of birth must be in the past",
"dob_error_format_min": "Enter the date of birth and include a day, month and year",
"dob_error_format": "Enter the licence holder’s date of birth and include a day, month and year",
"dob_error": "Enter the date of birth",

"dob_error_date_real": "Date of birth must be a real date",
"dob_error_missing_day_and_month": "Date of birth must include a day and month",
"dob_error_missing_day_and_year": "Date of birth must include a day and year",
"dob_error_missing_month_and_year": "Date of birth must include a month and year",
"dob_error_missing_day": "Date of birth must include a day",
"dob_error_missing_month": "Date of birth must include a month",
"dob_error_missing_year": "Date of birth must include a year",
"dob_error_non_numeric": "Enter only numbers",
"dob_error_year_min": "Date of birth is too long ago",
"dob_error_year_max": "The date of birth must be in the past",
"dob_error": "Enter a date of birth",

"dob_month": "month",
"dob_privacy_link_prefix": "If you do not provide a correct date of birth, this may cause delays when a licence is renewed or mean that a licence is not valid. Read about ",
"dob_privacy_link": "how we use personal information (opens in new tab)",
Expand Down Expand Up @@ -318,15 +327,19 @@
"header_service_name_title": " - Get a rod fishing licence",
"identification": "Identification",
"identify_body_protect_info": "To find your licence details we first need to identify you. This helps us protect your personal information.",
"identify_error_date_real": "Date of birth must be a real date",
"identify_error_empty_postcode": "You did not enter a postcode",
"identify_error_empty": "Enter the last six characters of your licence number",
"identify_error_enter_bday_max": "Your date of birth must be in the past",
"identify_error_enter_bday_min": "Your date of birth is too long ago",
"identify_error_enter_bday": "Enter your date of birth and include a day, month and year",
"identify_error_invalid_1": "We do not have any record of a licence number ending ",
"identify_error_invalid_2": " matching these details.",
"identify_error_missing_day": "Date of birth must include a day",
"identify_error_missing_month": "Date of birth must include a month",
"identify_error_missing_year": "Date of birth must include a year",
"identify_error_non_numeric": "Enter only numbers",
"identify_error_pattern_postcode": "Your postcode doesn’t look right. Check and enter again",
"identify_error_pattern": "The last six characters of your licence number don’t look right. Check and enter again",
"identify_error_year_min": "Date of birth is too long ago",
"identify_error_year_max": "The date of birth must be in the past",
"identify_label_last_six_hint": "For example F4A315",
"identify_label_last_six": "The last six characters of your licence number",
"identify_label_licence_ending": "The licence number ending",
Expand Down Expand Up @@ -407,8 +420,16 @@
"licence_num": "Licence number",
"licence_start_days": " days",
"licence_start_enter_todays_date": "Enter today’s date if you want the 1-day or 8-day licence to start later today.",
"licence_start_error_date_real": "Licence start date must be a real date",
"licence_start_error_missing_day_and_month": "Licence start date must include a day and month",
"licence_start_error_missing_day_and_year": "Licence start date must include a day and year",
"licence_start_error_missing_month_and_year": "Licence start date must include a month and year",
"licence_start_error_missing_day": "Licence start date must include a day",
"licence_start_error_missing_month": "Licence start date must include a month",
"licence_start_error_missing_year": "Licence start date must include a year",
"licence_start_error_non_numeric": "Enter only numbers",
"licence_start_error": "Enter a licence start date",
"licence_start_error_choose_when": "Choose when the licence should start",
"licence_start_error_format": "Enter the date the licence needs to start, include a day, month and year",
"licence_start_error_within": "Enter a date within the next ",
"licence_start_hint": "Enter a date up to and including ",
"licence_start_later": "Later",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,75 +1,130 @@
import { getData, validator } from '../route'
import { getData } from '../route'
import pageRoute from '../../../../routes/page-route.js'
import { nextPage } from '../../../../routes/next-page.js'
import { LICENCE_FOR } from '../../../../uri.js'
import { DATE_OF_BIRTH, LICENCE_FOR } from '../../../../uri.js'
import { dateOfBirthValidator, getDateErrorFlags } from '../../../../schema/validators/validators.js'

jest.mock('../../../../routes/next-page.js', () => ({
nextPage: jest.fn()
}))
jest.mock('../../../../routes/next-page.js')
jest.mock('../../../../routes/page-route.js')
jest.mock('../../../../schema/validators/validators.js')
jest.mock('../../../../uri.js', () => ({
...jest.requireActual('../../../../uri.js'),
DATE_OF_BIRTH: {
page: Symbol('date-of-birth-page'),
uri: Symbol('/date-of-birth')
},
LICENCE_TO_START: {
page: Symbol('licence-to-start-page'),
uri: Symbol('/licence-to-start')
}
}))

describe('name > route', () => {
const mockRequest = (statusGet = () => {}, transactionGet = () => {}) => ({
const mockRequest = ({
pageGet = async () => {},
statusGet = async () => ({ [LICENCE_FOR.page]: true }),
transactionGet = async () => ({ isLicenceForYou: null })
} = {}) => ({
cache: () => ({
helpers: {
transaction: {
getCurrentPermission: transactionGet
},
status: {
getCurrentPermission: statusGet
},
page: {
getCurrentPermission: pageGet
}
}
})
})

describe('getData', () => {
it('should return isLicenceForYou as true, if isLicenceForYou is true on the transaction cache', async () => {
const transaction = () => ({
const transactionGet = async () => ({
isLicenceForYou: true
})
const status = () => ({
const statusGet = async () => ({
[LICENCE_FOR.page]: true
})
const result = await getData(mockRequest(status, transaction))

const result = await getData(mockRequest({ statusGet, transactionGet }))
expect(result.isLicenceForYou).toBeTruthy()
})

it('should return isLicenceForYou as false, if isLicenceForYou is false on the transaction cache', async () => {
const transaction = () => ({
const transactionGet = async () => ({
isLicenceForYou: false
})
const status = () => ({
const statusGet = async () => ({
[LICENCE_FOR.page]: true
})
const result = await getData(mockRequest(status, transaction))
const result = await getData(mockRequest({ statusGet, transactionGet }))
expect(result.isLicenceForYou).toBeFalsy()
})

it.each([
['full-date', 'object.missing'],
['day', 'any.required']
])('should add error details ({%s: %s}) to the page data', async (errorKey, errorValue) => {
const pageGet = async () => ({
error: { [errorKey]: errorValue }
})

const result = await getData(mockRequest({ pageGet }))
expect(result.error).toEqual({ errorKey, errorValue })
})

it('omits error if there is no error', async () => {
const result = await getData(mockRequest())
expect(result.error).toBeUndefined()
})

it('adds return value of getErrorFlags to the page data', async () => {
const errorFlags = { unique: Symbol('error-flags') }
getDateErrorFlags.mockReturnValueOnce(errorFlags)
const result = await getData(mockRequest())
expect(result).toEqual(expect.objectContaining(errorFlags))
})

it('passes error to getErrorFlags', async () => {
const error = Symbol('error')
await getData(mockRequest({ pageGet: async () => ({ error }) }))
expect(getDateErrorFlags).toHaveBeenCalledWith(error)
})

it('passes correct page name when getting page cache', async () => {
const pageGet = jest.fn(() => ({}))
await getData(mockRequest({ pageGet }))
expect(pageGet).toHaveBeenCalledWith(DATE_OF_BIRTH.page)
})
})

describe('redirectToStartOfJourney', () => {
it('should throw a redirect if not been to LICENCE_FOR page', async () => {
const transaction = () => ({
const transactionGet = async () => ({
isLicenceForYou: true
})
const status = () => ({
const statusGet = async () => ({
[LICENCE_FOR.page]: false
})
const func = () => getData(mockRequest(status, transaction))
const func = () => getData(mockRequest({ statusGet, transactionGet }))
await expect(func).rejects.toThrowRedirectTo(LICENCE_FOR.uri)
})

it('should not throw a redirect if not been to LICENCE_FOR page', async () => {
const transaction = () => ({
const transactionGet = async () => ({
isLicenceForYou: true
})
const status = () => ({
const statusGet = async () => ({
[LICENCE_FOR.page]: true
})

let error

try {
await getData(mockRequest(status, transaction))
await getData(mockRequest({ statusGet, transactionGet }))
} catch (e) {
error = e
}
Expand All @@ -79,8 +134,8 @@ describe('name > route', () => {
})

describe('default', () => {
it('should call the pageRoute with date-of-birth, /buy/date-of-birth, validator and nextPage', async () => {
expect(pageRoute).toBeCalledWith('date-of-birth', '/buy/date-of-birth', validator, nextPage, getData)
it('should call the pageRoute with date-of-birth, /buy/date-of-birth, dateOfBirthValidator and nextPage', async () => {
expect(pageRoute).toBeCalledWith(DATE_OF_BIRTH.page, DATE_OF_BIRTH.uri, dateOfBirthValidator, nextPage, getData)
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -9,33 +9,59 @@

{%
set errorMap = {
'date-of-birth': {
'date.format': { ref: '#date-of-birth-day', text: mssgs.dob_error_format },
'date.max': { ref: '#date-of-birth-day', text: mssgs.dob_error_format_max },
'date.min': { ref: '#date-of-birth-day', text: mssgs.dob_error_format_min }
}
'full-date': {
'object.missing': { ref: '#date-of-birth-day', text: mssgs.dob_error }
},
'day-and-month': {
'object.missing': { ref: '#date-of-birth-day', text: mssgs.dob_error_missing_day_and_month }
},
'day-and-year': {
'object.missing': { ref: '#date-of-birth-day', text: mssgs.dob_error_missing_day_and_year }
},
'month-and-year': {
'object.missing': { ref: '#date-of-birth-month', text: mssgs.dob_error_missing_month_and_year }
},
'day': {
'any.required': { ref: '#date-of-birth-day', text: mssgs.dob_error_missing_day }
},
'month': {
'any.required': { ref: '#date-of-birth-month', text: mssgs.dob_error_missing_month }
},
'year': {
'any.required': { ref: '#date-of-birth-year', text: mssgs.dob_error_missing_year }
},
'non-numeric': {
'number.base': { ref: '#date-of-birth-day', text: mssgs.dob_error_non_numeric }
},
'invalid-date': {
'any.custom': { ref: '#date-of-birth-day', text: mssgs.dob_error_date_real }
},
'date-range': {
'date.min': { ref: '#date-of-birth-day', text: mssgs.dob_error_year_min },
'date.max': { ref: '#date-of-birth-day', text: mssgs.dob_error_year_max }
}
}
%}

{% set dateInputItems = [
{
label: mssgs.dob_day,
name: 'day',
classes: "govuk-input--width-2",
classes: "govuk-input--width-2 govuk-input--error" if data.isDayError else "govuk-input--width-2",
value: payload['date-of-birth-day'],
attributes: { maxlength : 2 }
},
{
label: mssgs.dob_month,
name: 'month',
classes: "govuk-input--width-2",
classes: "govuk-input--width-2 govuk-input--error" if data.isMonthError else "govuk-input--width-2",
value: payload['date-of-birth-month'],
attributes: { maxlength : 2 }
},
{
label: mssgs.dob_year,
name: 'year',
classes: "govuk-input--width-4",
classes: "govuk-input--width-4 govuk-input--error" if data.isYearError else "govuk-input--width-4",
value: payload['date-of-birth-year'],
attributes: { maxlength : 4 }
}
Expand All @@ -56,6 +82,6 @@
id: "date-of-birth",
namePrefix: "date-of-birth",
items: dateInputItems,
errorMessage: { text: mssgs.dob_error } if error
errorMessage: { text: errorMap[data.error.errorKey][data.error.errorValue].text } if data.error
}) }}
{% endblock %}
Loading

0 comments on commit 19abcb6

Please sign in to comment.