Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OOC-4135: Date validation #188

Merged
merged 5 commits into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 9 additions & 10 deletions runner/src/server/forms/ReportAnOutbreak.json
Original file line number Diff line number Diff line change
Expand Up @@ -440,8 +440,7 @@
"name": "S4Q6",
"options": {
"required": true,
"maxDaysInFuture": "",
"maxDaysInPast": ""
"maxDaysInFuture": "0"
},
"type": "DatePartsField",
"title": "S4Q6. Symptom onset date of first case",
Expand All @@ -453,7 +452,7 @@
"name": "S4Q7",
"options": {
"required": false,
"maxDaysInFuture": ""
"maxDaysInFuture": "0"
},
"type": "DatePartsField",
"title": "S4Q7. Symptom onset date of second case",
Expand All @@ -465,7 +464,7 @@
"name": "S4Q8",
"options": {
"required": true,
"maxDaysInFuture": ""
"maxDaysInFuture": "0"
},
"type": "DatePartsField",
"title": "S4Q8. Symptom onset date of most recent case",
Expand Down Expand Up @@ -714,7 +713,7 @@
"name": "S5Q12",
"options": {
"required": true,
"maxDaysInFuture": ""
"maxDaysInFuture": "0"
},
"type": "DatePartsField",
"title": "S5Q12. Symptom onset date of first case (confirmed flu, suspected flu OR chest infections)",
Expand All @@ -726,7 +725,7 @@
"name": "S5Q13",
"options": {
"required": false,
"maxDaysInFuture": ""
"maxDaysInFuture": "0"
},
"type": "DatePartsField",
"title": "S5Q13. Symptom onset date of second case (confirmed flu, suspected flu OR chest infections)",
Expand All @@ -738,7 +737,7 @@
"name": "S5Q14",
"options": {
"required": true,
"maxDaysInFuture": ""
"maxDaysInFuture": "0"
},
"type": "DatePartsField",
"title": "S5Q14. Symptom onset date of most recent case (confirmed flu, suspected flu OR chest infections)",
Expand Down Expand Up @@ -882,7 +881,7 @@
"name": "S6Q4",
"options": {
"required": true,
"maxDaysInFuture": ""
"maxDaysInFuture": "0"
},
"type": "DatePartsField",
"title": "S6Q4. Symptom onset date of first case",
Expand All @@ -894,7 +893,7 @@
"name": "S6Q5",
"options": {
"required": false,
"maxDaysInFuture": ""
"maxDaysInFuture": "0"
},
"type": "DatePartsField",
"title": "S6Q5. Symptom onset date of second case",
Expand All @@ -906,7 +905,7 @@
"name": "S6Q6",
"options": {
"required": true,
"maxDaysInFuture": ""
"maxDaysInFuture": "0"
},
"type": "DatePartsField",
"title": "S6Q6. Symptom onset date of most recent case",
Expand Down
81 changes: 25 additions & 56 deletions runner/src/server/plugins/engine/components/DatePartsField.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { add, sub, parseISO, format } from "date-fns";
import { parseISO, format } from "date-fns";
import { InputFieldsComponentsDef } from "@xgovformbuilder/model";

import { FormComponent } from "./FormComponent";
Expand All @@ -21,11 +21,10 @@ export class DatePartsField extends FormComponent {
constructor(def: InputFieldsComponentsDef, model: FormModel) {
super(def, model);

const { name, options, title } = this;
const { name, options } = this;
const isRequired =
"required" in options && options.required === false ? false : true;
const optionalText = "optionalText" in options && options.optionalText;

this.children = new ComponentCollection(
[
{
Expand Down Expand Up @@ -84,8 +83,9 @@ export class DatePartsField extends FormComponent {
helpers.getCustomDateValidator(maxDaysInPast, maxDaysInFuture)
);

const returnValue = { [this.name]: schema };
return returnValue;
this.schema = schema;

return { [this.name]: schema };
}

getFormDataFromState(state: FormSubmissionState) {
Expand All @@ -102,14 +102,21 @@ export class DatePartsField extends FormComponent {

getStateValueFromValidForm(payload: FormPayload) {
const name = this.name;

return payload[`${name}__year`]
? new Date(
payload[`${name}__year`],
payload[`${name}__month`] - 1,
payload[`${name}__day`]
)
: null;
const day = payload[`${name}__day`];
const month = payload[`${name}__month`];
const year = payload[`${name}__year`];

if (day || month || year) {
const indexedMonth = month - 1; // Adjust month for zero-based index
const parsedDate = new Date(year, indexedMonth, day);

if (month - 1 === parsedDate.getMonth()) {
return parsedDate;
} else {
return new Date(0, 0, 0); // Invalid date fallback
}
}
return null;
}

getDisplayStringFromState(state: FormSubmissionState) {
Expand All @@ -120,10 +127,6 @@ export class DatePartsField extends FormComponent {

// @ts-ignore - eslint does not report this as an error, only tsc
getViewModel(formData: FormData, errors: FormSubmissionErrors) {
const isRequired =
"required" in this.options && this.options.required === false
? false
: true;
const viewModel = super.getViewModel(formData, errors);

// Use the component collection to generate the subitems
Expand All @@ -137,54 +140,20 @@ export class DatePartsField extends FormComponent {
optionalText,
""
) as any;
//componentViewModel.label = `DATE: ${new Date()}` as any;

if (componentViewModel.errorMessage) {
componentViewModel.classes += " govuk-input--error";
}
});

const firstError = errors?.errorList?.[0];
//const errorMessage = isRequired && firstError && { text: firstError?.text };

let text = "";

let missingParts: any = [];

if (formData[`${this.name}__day`] === "") {
missingParts.push("day");
}
if (formData[`${this.name}__month`] === "") {
missingParts.push("month");
}
if (formData[`${this.name}__year`] === "") {
missingParts.push("year");
}

if (missingParts.length === 3) {
text = `${this.title} is required.`;
} else if (missingParts.length > 0) {
text = `${this.title} must have a ${missingParts.join(", ")}.`;
}

if (errors?.errorList?.length === 1) {
text = errors?.errorList?.[0].text;
}

//if(formData[`${this.name}__day`])

if (errors?.errorList?.length === 1) {
text = errors?.errorList?.[0].text;
}

if (!text.includes(this.title)) {
text = "";
}
const relevantErrors =
errors?.errorList?.filter((error) => error.path.includes(this.name)) ??
[];
const firstError = relevantErrors[0];
const errorMessage = firstError && { text: firstError?.text };

const errorMessage = isRequired && firstError && { text };
return {
...viewModel,
title: this.title,
errorMessage,
fieldset: {
legend: viewModel.label,
Expand Down
8 changes: 8 additions & 0 deletions runner/src/server/plugins/engine/components/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ export function getCustomDateValidator(
});
}
}

if (maxDaysInFuture) {
const maxDate = add(startOfToday(), { days: maxDaysInFuture });
if (value > maxDate) {
Expand All @@ -111,6 +112,13 @@ export function getCustomDateValidator(
});
}
}

if (value.getFullYear() == 1899) {
return helpers.error("date.base", {
label: helpers.state.key,
});
}

return value;
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ export class PageControllerBase {
href: `#${name}`,
name: name.split("__")[0],
title: title,
text: `${title} is required.`,
text: `${title} must be a valid date.`,
});
}
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const messageTemplate = {
min: "{{#label}} must be at least {{#limit}} characters",
regex: "enter a valid {{#label}}",
email: "{{#label}} must be a valid email address",
date: "{{#label}} must be a date",
date: "{{#label}} must be a valid date",
dateMin: "{{#label}} must be on or after {{#limit}}",
dateMax: "{{#label}} must be on or before {{#limit}}",
number: "{{#label}} must be a number",
Expand All @@ -27,13 +27,11 @@ export const messages: ValidationOptions["messages"] = {
"string.regex.base": messageTemplate.format,
"string.maxWords": messageTemplate.maxWords,


"date.base": messageTemplate.date,
"date.empty": messageTemplate.required,
"date.required": messageTemplate.required,
"date.min": messageTemplate.dateMin,
"date.max": messageTemplate.dateMax,

"number.base": messageTemplate.number,
"number.empty": messageTemplate.required,
"number.required": messageTemplate.required,
Expand All @@ -42,7 +40,6 @@ export const messages: ValidationOptions["messages"] = {

"any.required": messageTemplate.required,
"any.empty": messageTemplate.required,

};

export const validationOptions: ValidationOptions = {
Expand Down
8 changes: 8 additions & 0 deletions runner/test/cases/server/plugins/engine/datefield.test.ts
masuk-kazi98 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ suite("Date field", () => {
schema.validate("4000-40-40", { messages }).error.message
).to.contain("must be a valid date");

expect(
schema.validate("2024-02-30", { messages }).error.message
).to.contain("must be a valid date");

expect(
schema.validate("2023-02-29", { messages }).error.message
).to.contain("must be a valid date");

expect(schema.validate("2021-12-25", { messages }).error).to.be.undefined();
});

Expand Down
Loading