From c233e1f19bdbdf4e786205c9c978feb13a5e7c21 Mon Sep 17 00:00:00 2001 From: aaschlote Date: Mon, 23 Dec 2024 10:20:13 +0100 Subject: [PATCH 01/16] refactor: fixing typos --- app/components/form/ValidatedFlowForm.tsx | 4 ++-- .../form/__test__/ValidatedFlowForm.test.tsx | 2 +- app/services/validation/buildStepValidator.ts | 16 ++++++++-------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/app/components/form/ValidatedFlowForm.tsx b/app/components/form/ValidatedFlowForm.tsx index fb4fe43d4..1c4302c27 100644 --- a/app/components/form/ValidatedFlowForm.tsx +++ b/app/components/form/ValidatedFlowForm.tsx @@ -7,7 +7,7 @@ import { StrapiFormComponents } from "~/services/cms/components/StrapiFormCompon import type { StrapiFormComponent } from "~/services/cms/models/StrapiFormComponent"; import { splatFromParams } from "~/services/params"; import { CSRFKey } from "~/services/security/csrf/csrfKey"; -import { validatorForFieldnames } from "~/services/validation/buildStepValidator"; +import { validatorForFieldNames } from "~/services/validation/buildStepValidator"; type ValidatedFlowFormProps = { stepData: Context; @@ -25,7 +25,7 @@ function ValidatedFlowForm({ const stepId = splatFromParams(useParams()); const { pathname } = useLocation(); const fieldNames = formElements.map((entry) => entry.name); - const validator = validatorForFieldnames(fieldNames, pathname); + const validator = validatorForFieldNames(fieldNames, pathname); return ( ({ const fieldNameValidatorSpy = vi.spyOn( buildStepValidator, - "validatorForFieldnames", + "validatorForFieldNames", ); describe("ValidatedFlowForm", () => { diff --git a/app/services/validation/buildStepValidator.ts b/app/services/validation/buildStepValidator.ts index 7ed1fff13..c2b4d2326 100644 --- a/app/services/validation/buildStepValidator.ts +++ b/app/services/validation/buildStepValidator.ts @@ -10,17 +10,17 @@ type Schemas = Record; export function buildStepValidator(schemas: Schemas, fieldNames: string[]) { const fieldValidators: Record = {}; - for (const fieldname of fieldNames) { - if (fieldIsArray(fieldname)) { - const [arrayName, arrayFieldname] = splitArrayName(fieldname); + for (const fieldName of fieldNames) { + if (fieldIsArray(fieldName)) { + const [arrayName, arrayFieldName] = splitArrayName(fieldName); const arraySchema = schemas[arrayName] as z.ZodArray; const objectSchemas = arraySchema.element.shape as Schemas; - if (!isKeyOfObject(arrayFieldname, objectSchemas)) { - throw Error(`No schema found for ${arrayFieldname as string}`); + if (!isKeyOfObject(arrayFieldName, objectSchemas)) { + throw Error(`No schema found for ${arrayFieldName as string}`); } - fieldValidators[fieldname] = objectSchemas[arrayFieldname]; + fieldValidators[fieldName] = objectSchemas[arrayFieldName]; } else { - const stepOrFieldName = fieldname.split(".")[0]; + const stepOrFieldName = fieldName.split(".")[0]; if (!isKeyOfObject(stepOrFieldName, schemas)) { throw Error(`No schema found for ${stepOrFieldName as string}`); } @@ -30,7 +30,7 @@ export function buildStepValidator(schemas: Schemas, fieldNames: string[]) { return withZod(z.object(fieldValidators)); } -export function validatorForFieldnames(fieldNames: string[], pathname: string) { +export function validatorForFieldNames(fieldNames: string[], pathname: string) { const context = getContext(parsePathname(pathname).flowId); return buildStepValidator(context, fieldNames); } From dd9962736ea41a9f1955952b79c5515e8cc8debc Mon Sep 17 00:00:00 2001 From: aaschlote Date: Mon, 23 Dec 2024 14:35:23 +0100 Subject: [PATCH 02/16] feat: add option to validate multiple fields --- .../services/__test__/validation.test.ts | 70 ++++++++++++++++ .../formular/services/validation.ts | 83 +++++++++++++++++++ .../formular/validationMultipleFields.ts | 7 ++ app/domains/validationsMultipleFields.ts | 32 +++++++ .../getValidationMultipleFieldsByPathName.ts | 13 +++ 5 files changed, 205 insertions(+) create mode 100644 app/domains/fluggastrechte/formular/services/__test__/validation.test.ts create mode 100644 app/domains/fluggastrechte/formular/services/validation.ts create mode 100644 app/domains/fluggastrechte/formular/validationMultipleFields.ts create mode 100644 app/domains/validationsMultipleFields.ts create mode 100644 app/services/validation/stepValidator/getValidationMultipleFieldsByPathName.ts diff --git a/app/domains/fluggastrechte/formular/services/__test__/validation.test.ts b/app/domains/fluggastrechte/formular/services/__test__/validation.test.ts new file mode 100644 index 000000000..9217d77ae --- /dev/null +++ b/app/domains/fluggastrechte/formular/services/__test__/validation.test.ts @@ -0,0 +1,70 @@ +import { z } from "zod"; +import { validateDepartureAfterArrival } from "../validation"; + +describe("validation", () => { + describe("validateDepartureAfterArrival", () => { + const baseSchema = z.object({ + direktAbflugsDatum: z.string(), + direktAbflugsZeit: z.string(), + direktAnkunftsDatum: z.string(), + direktAnkunftsZeit: z.string(), + }); + + const validator = validateDepartureAfterArrival(baseSchema); + + it("should return success false given undefined values", () => { + const result = validator.safeParse({ + direktAbflugsDatum: undefined, + direktAbflugsZeit: undefined, + direktAnkunftsDatum: undefined, + direktAnkunftsZeit: undefined, + }); + + expect(result.success).toBe(false); + }); + + it("should return success false given a departure time after the arrival", () => { + const result = validator.safeParse({ + direktAbflugsDatum: "01.01.2024", + direktAbflugsZeit: "14:00", + direktAnkunftsDatum: "01.01.2024", + direktAnkunftsZeit: "11:00", + }); + + expect(result.success).toBe(false); + }); + + it("should return success false given a departure date after the arrival", () => { + const result = validator.safeParse({ + direktAbflugsDatum: "02.01.2024", + direktAbflugsZeit: "14:00", + direktAnkunftsDatum: "01.01.2024", + direktAnkunftsZeit: "15:00", + }); + + expect(result.success).toBe(false); + }); + + it("should return success true given a departure date before the arrival", () => { + const result = validator.safeParse({ + direktAbflugsDatum: "01.01.2024", + direktAbflugsZeit: "14:00", + direktAnkunftsDatum: "02.01.2024", + direktAnkunftsZeit: "15:00", + }); + + expect(result.success).toBe(true); + }); + + it("should return success true given a departure time before the arrival", () => { + const result = validator.safeParse({ + direktAbflugsDatum: "01.01.2024", + direktAbflugsZeit: "14:00", + direktAnkunftsDatum: "01.01.2024", + direktAnkunftsZeit: "15:00", + }); + + expect(result.success).toBe(true); + }); + }); +}); diff --git a/app/domains/fluggastrechte/formular/services/validation.ts b/app/domains/fluggastrechte/formular/services/validation.ts new file mode 100644 index 000000000..2afc49e63 --- /dev/null +++ b/app/domains/fluggastrechte/formular/services/validation.ts @@ -0,0 +1,83 @@ +import { z } from "zod"; +import type { ValidationMultipleFieldsBaseSchema } from "~/domains/validationsMultipleFields"; + +// Helper function to convert German date/time format to timestamp +function convertToTimestamp(date: string, time: string): number { + const [day, month, year] = date.split(".").map(Number); + const [hours, minutes] = time.split(":").map(Number); + return new Date(year, month - 1, day, hours, minutes).getTime(); +} +function convertToDate(date: string): number { + const [day, month, year] = date.split(".").map(Number); + return new Date(year, month - 1, day).getTime(); +} + +export function getArrivalTimeDelayValidator( + baseSchema: ValidationMultipleFieldsBaseSchema, +) { + return baseSchema.refine( + (data) => { + const arrivalTimestamp = convertToTimestamp( + data.tatsaechlicherAnkunftsDatum, + data.tatsaechlicherAnkunftsZeit, + ); + const departureTimestamp = convertToTimestamp( + data.direktAbflugsDatum, + data.direktAbflugsZeit, + ); + + const minimumTimeDifferenceInMs = 3 * 60 * 60 * 1000; // 3 hours in milliseconds + const actualTimeDifferenceInMs = arrivalTimestamp - departureTimestamp; + return actualTimeDifferenceInMs >= minimumTimeDifferenceInMs; + }, + { + message: "invalidTimeDelay", + path: ["tatsaechlicherAnkunftsZeit"], + }, + ); +} + +export function getArrivalDateValidator( + baseSchema: ValidationMultipleFieldsBaseSchema, +) { + return baseSchema.refine( + (data) => { + const arrivalDate = convertToDate(data.tatsaechlicherAnkunftsDatum); + const departureDate = convertToDate(data.direktAbflugsDatum); + + const actualTimeDifferenceInMs = arrivalDate - departureDate; + return actualTimeDifferenceInMs >= 0; + }, + { + message: "invalidDate", + path: ["tatsaechlicherAnkunftsDatum"], + }, + ); +} + +export function validateDepartureAfterArrival( + baseSchema: ValidationMultipleFieldsBaseSchema, +) { + return baseSchema.superRefine((data, ctx) => { + const departureDateTime = convertToTimestamp( + data.direktAbflugsDatum, + data.direktAbflugsZeit, + ); + + const arrivalDateTime = convertToTimestamp( + data.direktAnkunftsDatum, + data.direktAnkunftsZeit, + ); + + if (departureDateTime > arrivalDateTime) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "too_late", + path: ["direktAnkunftsDatum"], + fatal: true, + }); + + return z.NEVER; + } + }); +} diff --git a/app/domains/fluggastrechte/formular/validationMultipleFields.ts b/app/domains/fluggastrechte/formular/validationMultipleFields.ts new file mode 100644 index 000000000..a3cccb8c1 --- /dev/null +++ b/app/domains/fluggastrechte/formular/validationMultipleFields.ts @@ -0,0 +1,7 @@ +import type { ValidationMultipleFieldsPathName } from "~/domains/validationsMultipleFields"; +import { validateDepartureAfterArrival } from "./services/validation"; + +export const fluggastrechtValidationMultipleFields: ValidationMultipleFieldsPathName = + { + "/flugdaten/geplanter-flug": validateDepartureAfterArrival, + }; diff --git a/app/domains/validationsMultipleFields.ts b/app/domains/validationsMultipleFields.ts new file mode 100644 index 000000000..52e36dbf7 --- /dev/null +++ b/app/domains/validationsMultipleFields.ts @@ -0,0 +1,32 @@ +import type { z } from "zod"; +import type { FlowId } from "./flowIds"; +import { fluggastrechtValidationMultipleFields } from "./fluggastrechte/formular/validationMultipleFields"; + +export type ValidationMultipleFieldsBaseSchema = z.ZodObject< + Record +>; + +export type ValidationFunctionMultipleFields = ( + baseSchema: ValidationMultipleFieldsBaseSchema, +) => z.ZodTypeAny; + +export type ValidationMultipleFieldsPathName = Record< + string, + ValidationFunctionMultipleFields +>; + +const validationMultipleFields = { + "/beratungshilfe/antrag": undefined, + "/beratungshilfe/vorabcheck": undefined, + "/geld-einklagen/vorabcheck": undefined, + "/geld-einklagen/formular": undefined, + "/fluggastrechte/vorabcheck": undefined, + "/fluggastrechte/formular": fluggastrechtValidationMultipleFields, + "/prozesskostenhilfe/formular": undefined, +} as const satisfies Record< + FlowId, + ValidationMultipleFieldsPathName | undefined +>; + +export const getValidationMultipleFields = (flowId: FlowId) => + validationMultipleFields[flowId]; diff --git a/app/services/validation/stepValidator/getValidationMultipleFieldsByPathName.ts b/app/services/validation/stepValidator/getValidationMultipleFieldsByPathName.ts new file mode 100644 index 000000000..4fb5cff70 --- /dev/null +++ b/app/services/validation/stepValidator/getValidationMultipleFieldsByPathName.ts @@ -0,0 +1,13 @@ +import { parsePathname } from "~/domains/flowIds"; +import { getValidationMultipleFields } from "~/domains/validationsMultipleFields"; + +export const getValidationMultipleFieldsByPathname = (pathname: string) => { + const { flowId, stepId } = parsePathname(pathname); + const validationMultipleFields = getValidationMultipleFields(flowId); + + if (!validationMultipleFields) { + return undefined; + } + + return validationMultipleFields[stepId]; +}; From 532b5309b52f43a11416ebc5a99908d5baf21912 Mon Sep 17 00:00:00 2001 From: aaschlote Date: Mon, 23 Dec 2024 14:36:27 +0100 Subject: [PATCH 03/16] refactor: move validatorForFieldNames to new path --- app/components/form/ValidatedFlowForm.tsx | 2 +- .../form/__test__/ValidatedFlowForm.test.tsx | 4 ++-- .../stepValidator/validatorForFieldNames.ts | 13 +++++++++++++ 3 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 app/services/validation/stepValidator/validatorForFieldNames.ts diff --git a/app/components/form/ValidatedFlowForm.tsx b/app/components/form/ValidatedFlowForm.tsx index 1c4302c27..607062f30 100644 --- a/app/components/form/ValidatedFlowForm.tsx +++ b/app/components/form/ValidatedFlowForm.tsx @@ -7,7 +7,7 @@ import { StrapiFormComponents } from "~/services/cms/components/StrapiFormCompon import type { StrapiFormComponent } from "~/services/cms/models/StrapiFormComponent"; import { splatFromParams } from "~/services/params"; import { CSRFKey } from "~/services/security/csrf/csrfKey"; -import { validatorForFieldNames } from "~/services/validation/buildStepValidator"; +import { validatorForFieldNames } from "~/services/validation/stepValidator/validatorForFieldNames"; type ValidatedFlowFormProps = { stepData: Context; diff --git a/app/components/form/__test__/ValidatedFlowForm.test.tsx b/app/components/form/__test__/ValidatedFlowForm.test.tsx index 37d0882d3..adc16983a 100644 --- a/app/components/form/__test__/ValidatedFlowForm.test.tsx +++ b/app/components/form/__test__/ValidatedFlowForm.test.tsx @@ -13,10 +13,10 @@ import { getStrapiTextareaComponent } from "tests/factories/cmsModels/strapiText import { getStrapiTileGroupComponent } from "tests/factories/cmsModels/strapiTileGroupComponent"; import ValidatedFlowForm from "~/components/form/ValidatedFlowForm"; import type { StrapiFormComponent } from "~/services/cms/models/StrapiFormComponent"; -import * as buildStepValidator from "~/services/validation/buildStepValidator"; import { checkedRequired } from "~/services/validation/checkedCheckbox"; import { createDateSchema } from "~/services/validation/date"; import { integerSchema } from "~/services/validation/integer"; +import * as validatorForFieldNames from "~/services/validation/stepValidator/validatorForFieldNames"; import { stringRequiredSchema } from "~/services/validation/stringRequired"; import { timeSchema } from "~/services/validation/time"; import { @@ -36,7 +36,7 @@ vi.mock("~/services/params", () => ({ })); const fieldNameValidatorSpy = vi.spyOn( - buildStepValidator, + validatorForFieldNames, "validatorForFieldNames", ); diff --git a/app/services/validation/stepValidator/validatorForFieldNames.ts b/app/services/validation/stepValidator/validatorForFieldNames.ts new file mode 100644 index 000000000..c114f0bf2 --- /dev/null +++ b/app/services/validation/stepValidator/validatorForFieldNames.ts @@ -0,0 +1,13 @@ +import { getContext } from "~/domains/contexts"; +import { parsePathname } from "~/domains/flowIds"; +import { buildStepValidator } from "./buildStepValidator"; +import { getValidationMultipleFieldsByPathname } from "./getValidationMultipleFieldsByPathName"; + +export function validatorForFieldNames(fieldNames: string[], pathname: string) { + const flowId = parsePathname(pathname).flowId; + const context = getContext(flowId); + const validationMultipleFields = + getValidationMultipleFieldsByPathname(pathname); + + return buildStepValidator(context, fieldNames, validationMultipleFields); +} From a4900eaf4201e96c0170bc1d73721bb6597516d6 Mon Sep 17 00:00:00 2001 From: aaschlote Date: Mon, 23 Dec 2024 14:36:59 +0100 Subject: [PATCH 04/16] refactor: move buildStepValidator to new path and add option to validate multiple fields --- app/routes/shared/formular.server.ts | 2 +- app/routes/shared/vorabcheck.server.ts | 2 +- .../__test__/buildStepValidator.test.ts | 2 +- .../{ => stepValidator}/buildStepValidator.ts | 23 +++++++++++-------- .../validation/validateFormData.server.ts | 8 +++---- 5 files changed, 20 insertions(+), 17 deletions(-) rename app/services/validation/{ => stepValidator}/__test__/buildStepValidator.test.ts (96%) rename app/services/validation/{ => stepValidator}/buildStepValidator.ts (66%) diff --git a/app/routes/shared/formular.server.ts b/app/routes/shared/formular.server.ts index 2c0f70827..3f898123d 100644 --- a/app/routes/shared/formular.server.ts +++ b/app/routes/shared/formular.server.ts @@ -239,7 +239,7 @@ export const action = async ({ request }: ActionFunctionArgs) => { } } - const validationResult = await validateFormData(flowId, relevantFormData); + const validationResult = await validateFormData(pathname, relevantFormData); if (validationResult.error) return validationError( diff --git a/app/routes/shared/vorabcheck.server.ts b/app/routes/shared/vorabcheck.server.ts index 56484eb33..981d64f35 100644 --- a/app/routes/shared/vorabcheck.server.ts +++ b/app/routes/shared/vorabcheck.server.ts @@ -132,7 +132,7 @@ export const action = async ({ request }: ActionFunctionArgs) => { const formData = await request.formData(); const relevantFormData = filterFormData(formData); - const validationResult = await validateFormData(flowId, relevantFormData); + const validationResult = await validateFormData(pathname, relevantFormData); if (validationResult.error) return validationError( validationResult.error, diff --git a/app/services/validation/__test__/buildStepValidator.test.ts b/app/services/validation/stepValidator/__test__/buildStepValidator.test.ts similarity index 96% rename from app/services/validation/__test__/buildStepValidator.test.ts rename to app/services/validation/stepValidator/__test__/buildStepValidator.test.ts index 3e5afc1b7..d31bafed7 100644 --- a/app/services/validation/__test__/buildStepValidator.test.ts +++ b/app/services/validation/stepValidator/__test__/buildStepValidator.test.ts @@ -1,5 +1,5 @@ import { z } from "zod"; -import { buildStepValidator } from "~/services/validation/buildStepValidator"; +import { buildStepValidator } from "~/services/validation/stepValidator/buildStepValidator"; describe("buildStepValidator", () => { describe("nested fields", () => { diff --git a/app/services/validation/buildStepValidator.ts b/app/services/validation/stepValidator/buildStepValidator.ts similarity index 66% rename from app/services/validation/buildStepValidator.ts rename to app/services/validation/stepValidator/buildStepValidator.ts index c2b4d2326..c48b52130 100644 --- a/app/services/validation/buildStepValidator.ts +++ b/app/services/validation/stepValidator/buildStepValidator.ts @@ -1,13 +1,16 @@ import { withZod } from "@remix-validated-form/with-zod"; import { z } from "zod"; -import { getContext } from "~/domains/contexts"; -import { parsePathname } from "~/domains/flowIds"; +import type { ValidationFunctionMultipleFields } from "~/domains/validationsMultipleFields"; import { isKeyOfObject } from "~/util/objects"; -import { fieldIsArray, splitArrayName } from "../array"; +import { fieldIsArray, splitArrayName } from "../../array"; type Schemas = Record; -export function buildStepValidator(schemas: Schemas, fieldNames: string[]) { +export function buildStepValidator( + schemas: Schemas, + fieldNames: string[], + validationMultipleFields?: ValidationFunctionMultipleFields, +) { const fieldValidators: Record = {}; for (const fieldName of fieldNames) { @@ -27,10 +30,12 @@ export function buildStepValidator(schemas: Schemas, fieldNames: string[]) { fieldValidators[stepOrFieldName] = schemas[stepOrFieldName]; } } - return withZod(z.object(fieldValidators)); -} -export function validatorForFieldNames(fieldNames: string[], pathname: string) { - const context = getContext(parsePathname(pathname).flowId); - return buildStepValidator(context, fieldNames); + const baseSchema = z.object(fieldValidators); + + const validationSchema = validationMultipleFields + ? validationMultipleFields(baseSchema) + : baseSchema; + + return withZod(validationSchema); } diff --git a/app/services/validation/validateFormData.server.ts b/app/services/validation/validateFormData.server.ts index 4e0160ffc..0d9a36cfe 100644 --- a/app/services/validation/validateFormData.server.ts +++ b/app/services/validation/validateFormData.server.ts @@ -1,12 +1,10 @@ -import { getContext } from "~/domains/contexts"; -import type { FlowId } from "~/domains/flowIds"; -import { buildStepValidator } from "./buildStepValidator"; +import { validatorForFieldNames } from "./stepValidator/validatorForFieldNames"; export async function validateFormData( - flowId: FlowId, + pathname: string, formData: Record, ) { const formDataKeys = Object.keys(formData); - const validator = buildStepValidator(getContext(flowId), formDataKeys); + const validator = validatorForFieldNames(formDataKeys, pathname); return validator.validate(formData); } From 548c16a816bf3d4b3711f9b97f3b06bed4ae9e7f Mon Sep 17 00:00:00 2001 From: aaschlote Date: Mon, 23 Dec 2024 15:44:21 +0100 Subject: [PATCH 05/16] validation(refactor): remove functions and add new issue --- .../formular/services/validation.ts | 55 +++---------------- 1 file changed, 8 insertions(+), 47 deletions(-) diff --git a/app/domains/fluggastrechte/formular/services/validation.ts b/app/domains/fluggastrechte/formular/services/validation.ts index 2afc49e63..5fcc81ee8 100644 --- a/app/domains/fluggastrechte/formular/services/validation.ts +++ b/app/domains/fluggastrechte/formular/services/validation.ts @@ -7,53 +7,6 @@ function convertToTimestamp(date: string, time: string): number { const [hours, minutes] = time.split(":").map(Number); return new Date(year, month - 1, day, hours, minutes).getTime(); } -function convertToDate(date: string): number { - const [day, month, year] = date.split(".").map(Number); - return new Date(year, month - 1, day).getTime(); -} - -export function getArrivalTimeDelayValidator( - baseSchema: ValidationMultipleFieldsBaseSchema, -) { - return baseSchema.refine( - (data) => { - const arrivalTimestamp = convertToTimestamp( - data.tatsaechlicherAnkunftsDatum, - data.tatsaechlicherAnkunftsZeit, - ); - const departureTimestamp = convertToTimestamp( - data.direktAbflugsDatum, - data.direktAbflugsZeit, - ); - - const minimumTimeDifferenceInMs = 3 * 60 * 60 * 1000; // 3 hours in milliseconds - const actualTimeDifferenceInMs = arrivalTimestamp - departureTimestamp; - return actualTimeDifferenceInMs >= minimumTimeDifferenceInMs; - }, - { - message: "invalidTimeDelay", - path: ["tatsaechlicherAnkunftsZeit"], - }, - ); -} - -export function getArrivalDateValidator( - baseSchema: ValidationMultipleFieldsBaseSchema, -) { - return baseSchema.refine( - (data) => { - const arrivalDate = convertToDate(data.tatsaechlicherAnkunftsDatum); - const departureDate = convertToDate(data.direktAbflugsDatum); - - const actualTimeDifferenceInMs = arrivalDate - departureDate; - return actualTimeDifferenceInMs >= 0; - }, - { - message: "invalidDate", - path: ["tatsaechlicherAnkunftsDatum"], - }, - ); -} export function validateDepartureAfterArrival( baseSchema: ValidationMultipleFieldsBaseSchema, @@ -70,6 +23,14 @@ export function validateDepartureAfterArrival( ); if (departureDateTime > arrivalDateTime) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "too_late", + path: ["direktAnkunftsZeit"], + fatal: true, + }); + + // add new issue to invalidate this field as well ctx.addIssue({ code: z.ZodIssueCode.custom, message: "too_late", From ea7691c11c028c7ca4ff340060cd210140ff50a0 Mon Sep 17 00:00:00 2001 From: aaschlote Date: Mon, 23 Dec 2024 18:24:41 +0100 Subject: [PATCH 06/16] test(getValidationMultipleFieldsByPathname): add unit test --- ...ValidationMultipleFieldsByPathname.test.ts | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 app/services/validation/stepValidator/__test__/getValidationMultipleFieldsByPathname.test.ts diff --git a/app/services/validation/stepValidator/__test__/getValidationMultipleFieldsByPathname.test.ts b/app/services/validation/stepValidator/__test__/getValidationMultipleFieldsByPathname.test.ts new file mode 100644 index 000000000..bc86651e4 --- /dev/null +++ b/app/services/validation/stepValidator/__test__/getValidationMultipleFieldsByPathname.test.ts @@ -0,0 +1,62 @@ +import type { + ValidationMultipleFieldsBaseSchema, + ValidationMultipleFieldsPathName, +} from "~/domains/validationsMultipleFields"; +import { getValidationMultipleFields } from "~/domains/validationsMultipleFields"; +import { getValidationMultipleFieldsByPathname } from "../getValidationMultipleFieldsByPathName"; + +vi.mock("~/domains/validationsMultipleFields"); + +describe("getValidationMultipleFieldsByPathname", () => { + it("should return undefined given a mocked getValidationMultipleFields as undefined", () => { + vi.mocked(getValidationMultipleFields).mockReturnValue(undefined); + + const actual = getValidationMultipleFieldsByPathname( + "/fluggastrechte/formular/flugdaten/geplanter-flug", + ); + + expect(actual).toBeUndefined(); + }); + + it("should return a value given a mocked getValidationMultipleFields", () => { + const mockValidationMultipleFields: ValidationMultipleFieldsPathName = { + "/flugdaten/geplanter-flug": ( + baseSchema: ValidationMultipleFieldsBaseSchema, + ) => { + return baseSchema.describe("TEST"); + }, + }; + + vi.mocked(getValidationMultipleFields).mockReturnValue( + mockValidationMultipleFields, + ); + + const actual = getValidationMultipleFieldsByPathname( + "/fluggastrechte/formular/flugdaten/geplanter-flug", + ); + + expect(actual).toEqual( + mockValidationMultipleFields["/flugdaten/geplanter-flug"], + ); + }); + + it("should return undefined given a not exist mocked pathname getValidationMultipleFields", () => { + const mockValidationMultipleFields: ValidationMultipleFieldsPathName = { + "/flugdaten/geplanter-flug": ( + baseSchema: ValidationMultipleFieldsBaseSchema, + ) => { + return baseSchema.describe("TEST"); + }, + }; + + vi.mocked(getValidationMultipleFields).mockReturnValue( + mockValidationMultipleFields, + ); + + const actual = getValidationMultipleFieldsByPathname( + "/fluggastrechte/formular/flugdaten/tatsaechlicher-flug", + ); + + expect(actual).toBeUndefined(); + }); +}); From 57b10dd17154c20f904ce3c08c0e6f0c2a20a2fb Mon Sep 17 00:00:00 2001 From: aaschlote Date: Mon, 23 Dec 2024 18:25:00 +0100 Subject: [PATCH 07/16] test(buildStepValidator): add unit test for multiple fields validation --- .../__test__/buildStepValidator.test.ts | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/app/services/validation/stepValidator/__test__/buildStepValidator.test.ts b/app/services/validation/stepValidator/__test__/buildStepValidator.test.ts index d31bafed7..aa1783f4e 100644 --- a/app/services/validation/stepValidator/__test__/buildStepValidator.test.ts +++ b/app/services/validation/stepValidator/__test__/buildStepValidator.test.ts @@ -1,4 +1,5 @@ import { z } from "zod"; +import type { ValidationFunctionMultipleFields } from "~/domains/validationsMultipleFields"; import { buildStepValidator } from "~/services/validation/stepValidator/buildStepValidator"; describe("buildStepValidator", () => { @@ -85,4 +86,70 @@ describe("buildStepValidator", () => { expect((await validator.validate({})).error).toBeDefined(); }); }); + + describe("multiple fields validation", () => { + const schemas = { + field1: z.number(), + field2: z.number(), + }; + + const multipleFieldsValidation: ValidationFunctionMultipleFields = ( + schemas, + ) => + schemas.refine( + ({ field1, field2 }) => { + return field1 < field2; + }, + { + path: ["field1"], + message: "invalid", + }, + ); + + const fieldNames = ["field1", "field2"]; + + it("should return an error object given the field1 bigger than field2", async () => { + const validator = buildStepValidator( + schemas, + fieldNames, + multipleFieldsValidation, + ); + + const actualValidation = await validator.validate({ + field1: 1, + field2: 0, + }); + + expect(actualValidation).toEqual( + expect.objectContaining({ + error: { + fieldErrors: { field1: "invalid" }, + }, + }), + ); + }); + + it("should return data object given the field1 smaller than field2", async () => { + const validator = buildStepValidator( + schemas, + fieldNames, + multipleFieldsValidation, + ); + + const actualValidation = await validator.validate({ + field1: 1, + field2: 2, + }); + + expect(actualValidation).toEqual( + expect.objectContaining({ + error: undefined, + data: { + field1: 1, + field2: 2, + }, + }), + ); + }); + }); }); From 45e56aec80af0f40d0e7e0f80a74672080d2ba81 Mon Sep 17 00:00:00 2001 From: aaschlote Date: Mon, 23 Dec 2024 18:25:16 +0100 Subject: [PATCH 08/16] refactor: change error message code --- app/domains/fluggastrechte/formular/services/validation.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/domains/fluggastrechte/formular/services/validation.ts b/app/domains/fluggastrechte/formular/services/validation.ts index 5fcc81ee8..1c3b1d50e 100644 --- a/app/domains/fluggastrechte/formular/services/validation.ts +++ b/app/domains/fluggastrechte/formular/services/validation.ts @@ -25,7 +25,7 @@ export function validateDepartureAfterArrival( if (departureDateTime > arrivalDateTime) { ctx.addIssue({ code: z.ZodIssueCode.custom, - message: "too_late", + message: "departureAfterArrival", path: ["direktAnkunftsZeit"], fatal: true, }); @@ -33,7 +33,7 @@ export function validateDepartureAfterArrival( // add new issue to invalidate this field as well ctx.addIssue({ code: z.ZodIssueCode.custom, - message: "too_late", + message: "departureAfterArrival", path: ["direktAnkunftsDatum"], fatal: true, }); From 84034e19514a8c5d144fdeb1f6a7a45bf27b0c93 Mon Sep 17 00:00:00 2001 From: aaschlote Date: Mon, 30 Dec 2024 13:40:01 +0100 Subject: [PATCH 09/16] feat: add validations for other pages --- .../services/__test__/validation.test.ts | 266 +++++++++++++++++- .../formular/services/validation.ts | 180 ++++++++++++ .../formular/validationMultipleFields.ts | 11 +- 3 files changed, 455 insertions(+), 2 deletions(-) diff --git a/app/domains/fluggastrechte/formular/services/__test__/validation.test.ts b/app/domains/fluggastrechte/formular/services/__test__/validation.test.ts index 9217d77ae..366deaa56 100644 --- a/app/domains/fluggastrechte/formular/services/__test__/validation.test.ts +++ b/app/domains/fluggastrechte/formular/services/__test__/validation.test.ts @@ -1,5 +1,10 @@ import { z } from "zod"; -import { validateDepartureAfterArrival } from "../validation"; +import { + validateAnotherFlightPage, + validateDepartureAfterArrival, + validateReplacementConnectionPage, + validateSameFlightPage, +} from "../validation"; describe("validation", () => { describe("validateDepartureAfterArrival", () => { @@ -67,4 +72,263 @@ describe("validation", () => { expect(result.success).toBe(true); }); }); + + describe("validateSameFlightArrivedAfterThreeHours", () => { + const baseSchema = z.object({ + direktAbflugsDatum: z.string(), + direktAbflugsZeit: z.string(), + tatsaechlicherAnkunftsDatum: z.string(), + tatsaechlicherAnkunftsZeit: z.string(), + }); + + const validatorSameFlightPage = validateSameFlightPage(baseSchema); + + it("should return success false given undefined values", () => { + const result = validatorSameFlightPage.safeParse({ + direktAbflugsDatum: undefined, + direktAbflugsZeit: undefined, + tatsaechlicherAnkunftsDatum: undefined, + tatsaechlicherAnkunftsZeit: undefined, + }); + + expect(result.success).toBe(false); + }); + + it("should return success false given a departure time after the arrival", () => { + const result = validatorSameFlightPage.safeParse({ + direktAbflugsDatum: "01.01.2024", + direktAbflugsZeit: "14:00", + tatsaechlicherAnkunftsDatum: "01.01.2024", + tatsaechlicherAnkunftsZeit: "11:00", + }); + + expect(result.success).toBe(false); + }); + + it("should return success false given a departure date after the arrival", () => { + const result = validatorSameFlightPage.safeParse({ + direktAbflugsDatum: "02.01.2024", + direktAbflugsZeit: "14:00", + tatsaechlicherAnkunftsDatum: "01.01.2024", + tatsaechlicherAnkunftsZeit: "15:00", + }); + + expect(result.success).toBe(false); + }); + + it("should return success false given a departure date before three hours after the arrival", () => { + const result = validatorSameFlightPage.safeParse({ + direktAbflugsDatum: "02.01.2024", + direktAbflugsZeit: "14:00", + tatsaechlicherAnkunftsDatum: "02.01.2024", + tatsaechlicherAnkunftsZeit: "15:00", + }); + + expect(result.success).toBe(false); + }); + + it("should return success true given a departure date after three hours after the arrival", () => { + const result = validatorSameFlightPage.safeParse({ + direktAbflugsDatum: "02.01.2024", + direktAbflugsZeit: "14:00", + tatsaechlicherAnkunftsDatum: "02.01.2024", + tatsaechlicherAnkunftsZeit: "19:01", + }); + + expect(result.success).toBe(true); + }); + }); + + describe("validateAnotherFlightPage", () => { + const baseSchema = z.object({ + direktAbflugsDatum: z.string(), + direktAbflugsZeit: z.string(), + ersatzFlugAnkunftsDatum: z.string(), + ersatzFlugAnkunftsZeit: z.string(), + bereich: z.string(), + }); + + const validatorAnotherPage = validateAnotherFlightPage(baseSchema); + + it("should return success false given undefined values", () => { + const result = validatorAnotherPage.safeParse({ + direktAbflugsDatum: undefined, + direktAbflugsZeit: undefined, + ersatzFlugAnkunftsDatum: undefined, + ersatzFlugAnkunftsZeit: undefined, + bereich: "verspaetet", + }); + + expect(result.success).toBe(false); + }); + + it("should return success false given a departure time after the arrival", () => { + const result = validatorAnotherPage.safeParse({ + direktAbflugsDatum: "01.01.2024", + direktAbflugsZeit: "14:00", + ersatzFlugAnkunftsDatum: "01.01.2024", + ersatzFlugAnkunftsZeit: "11:00", + bereich: "verspaetet", + }); + + expect(result.success).toBe(false); + }); + + it("should return success false given a departure date after the arrival", () => { + const result = validatorAnotherPage.safeParse({ + direktAbflugsDatum: "02.01.2024", + direktAbflugsZeit: "14:00", + ersatzFlugAnkunftsDatum: "01.01.2024", + ersatzFlugAnkunftsZeit: "15:00", + bereich: "verspaetet", + }); + + expect(result.success).toBe(false); + }); + + it("should return success false given a departure date before three hours after the arrival and bereich verspaetet", () => { + const result = validatorAnotherPage.safeParse({ + direktAbflugsDatum: "02.01.2024", + direktAbflugsZeit: "14:00", + ersatzFlugAnkunftsDatum: "02.01.2024", + ersatzFlugAnkunftsZeit: "15:00", + bereich: "verspaetet", + }); + + expect(result.success).toBe(false); + }); + + it("should return success true given a departure date after three hours after the arrival and bereich verspaetet", () => { + const result = validatorAnotherPage.safeParse({ + direktAbflugsDatum: "02.01.2024", + direktAbflugsZeit: "14:00", + ersatzFlugAnkunftsDatum: "02.01.2024", + ersatzFlugAnkunftsZeit: "19:01", + bereich: "verspaetet", + }); + + expect(result.success).toBe(true); + }); + + it("should return success true given a departure date before three hours after the arrival and bereich annullierung", () => { + const result = validatorAnotherPage.safeParse({ + direktAbflugsDatum: "02.01.2024", + direktAbflugsZeit: "14:00", + ersatzFlugAnkunftsDatum: "02.01.2024", + ersatzFlugAnkunftsZeit: "15:00", + bereich: "annullierung", + }); + + expect(result.success).toBe(true); + }); + + it("should return success true given a departure date before three hours after the arrival and bereich nichtbefoerderung", () => { + const result = validatorAnotherPage.safeParse({ + direktAbflugsDatum: "02.01.2024", + direktAbflugsZeit: "14:00", + ersatzFlugAnkunftsDatum: "02.01.2024", + ersatzFlugAnkunftsZeit: "15:00", + bereich: "nichtbefoerderung", + }); + + expect(result.success).toBe(true); + }); + }); + + describe("validateReplacementConnectionPage", () => { + const baseSchema = z.object({ + direktAbflugsDatum: z.string(), + direktAbflugsZeit: z.string(), + andereErsatzverbindungAnkunftsDatum: z.string(), + andereErsatzverbindungAnkunftsZeit: z.string(), + bereich: z.string(), + }); + + const validatorReplacementConnectionPage = + validateReplacementConnectionPage(baseSchema); + + it("should return success false given undefined values", () => { + const result = validatorReplacementConnectionPage.safeParse({ + direktAbflugsDatum: undefined, + direktAbflugsZeit: undefined, + andereErsatzverbindungAnkunftsDatum: undefined, + andereErsatzverbindungAnkunftsZeit: undefined, + bereich: "verspaetet", + }); + + expect(result.success).toBe(false); + }); + + it("should return success false given a departure time after the arrival", () => { + const result = validatorReplacementConnectionPage.safeParse({ + direktAbflugsDatum: "01.01.2024", + direktAbflugsZeit: "14:00", + andereErsatzverbindungAnkunftsDatum: "01.01.2024", + andereErsatzverbindungAnkunftsZeit: "11:00", + bereich: "verspaetet", + }); + + expect(result.success).toBe(false); + }); + + it("should return success false given a departure date after the arrival", () => { + const result = validatorReplacementConnectionPage.safeParse({ + direktAbflugsDatum: "02.01.2024", + direktAbflugsZeit: "14:00", + andereErsatzverbindungAnkunftsDatum: "01.01.2024", + andereErsatzverbindungAnkunftsZeit: "15:00", + bereich: "verspaetet", + }); + + expect(result.success).toBe(false); + }); + + it("should return success false given a departure date before three hours after the arrival and bereich verspaetet", () => { + const result = validatorReplacementConnectionPage.safeParse({ + direktAbflugsDatum: "02.01.2024", + direktAbflugsZeit: "14:00", + andereErsatzverbindungAnkunftsDatum: "02.01.2024", + andereErsatzverbindungAnkunftsZeit: "15:00", + bereich: "verspaetet", + }); + + expect(result.success).toBe(false); + }); + + it("should return success true given a departure date after three hours after the arrival and bereich verspaetet", () => { + const result = validatorReplacementConnectionPage.safeParse({ + direktAbflugsDatum: "02.01.2024", + direktAbflugsZeit: "14:00", + andereErsatzverbindungAnkunftsDatum: "02.01.2024", + andereErsatzverbindungAnkunftsZeit: "19:01", + bereich: "verspaetet", + }); + + expect(result.success).toBe(true); + }); + + it("should return success true given a departure date before three hours after the arrival and bereich annullierung", () => { + const result = validatorReplacementConnectionPage.safeParse({ + direktAbflugsDatum: "02.01.2024", + direktAbflugsZeit: "14:00", + andereErsatzverbindungAnkunftsDatum: "02.01.2024", + andereErsatzverbindungAnkunftsZeit: "15:00", + bereich: "annullierung", + }); + + expect(result.success).toBe(true); + }); + + it("should return success true given a departure date before three hours after the arrival and bereich nichtbefoerderung", () => { + const result = validatorReplacementConnectionPage.safeParse({ + direktAbflugsDatum: "02.01.2024", + direktAbflugsZeit: "14:00", + andereErsatzverbindungAnkunftsDatum: "02.01.2024", + andereErsatzverbindungAnkunftsZeit: "15:00", + bereich: "nichtbefoerderung", + }); + + expect(result.success).toBe(true); + }); + }); }); diff --git a/app/domains/fluggastrechte/formular/services/validation.ts b/app/domains/fluggastrechte/formular/services/validation.ts index 1c3b1d50e..2cd86f70f 100644 --- a/app/domains/fluggastrechte/formular/services/validation.ts +++ b/app/domains/fluggastrechte/formular/services/validation.ts @@ -1,6 +1,8 @@ import { z } from "zod"; import type { ValidationMultipleFieldsBaseSchema } from "~/domains/validationsMultipleFields"; +const THREE_HOURS_MILLISECONDS = 3 * 60 * 60 * 1000; + // Helper function to convert German date/time format to timestamp function convertToTimestamp(date: string, time: string): number { const [day, month, year] = date.split(".").map(Number); @@ -8,6 +10,184 @@ function convertToTimestamp(date: string, time: string): number { return new Date(year, month - 1, day, hours, minutes).getTime(); } +function isStartTimestampLessThanThreeHours( + startTimestamp: number, + endTimestamp: number, +) { + const actualTimeDifferenceInMs = endTimestamp - startTimestamp; + return actualTimeDifferenceInMs < THREE_HOURS_MILLISECONDS; +} + +export function validateReplacementConnectionPage( + baseSchema: ValidationMultipleFieldsBaseSchema, +) { + return baseSchema.superRefine((data, ctx) => { + const departureDateTime = convertToTimestamp( + data.direktAbflugsDatum, + data.direktAbflugsZeit, + ); + + const arrivalDateTime = convertToTimestamp( + data.andereErsatzverbindungAnkunftsDatum, + data.andereErsatzverbindungAnkunftsZeit, + ); + + if (departureDateTime > arrivalDateTime) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "departureAfterArrival", + path: ["andereErsatzverbindungAnkunftsDatum"], + fatal: true, + }); + + // add new issue to invalidate this field as well + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "departureAfterArrival", + path: ["andereErsatzverbindungAnkunftsZeit"], + fatal: true, + }); + + return z.NEVER; + } + + if ( + isStartTimestampLessThanThreeHours(departureDateTime, arrivalDateTime) && + data.bereich === "verspaetet" + ) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "arrivalThreeHoursLessThanDeparture", + path: ["andereErsatzverbindungAnkunftsDatum"], + fatal: true, + }); + + // add new issue to invalidate this field as well + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "arrivalThreeHoursLessThanDeparture", + path: ["andereErsatzverbindungAnkunftsZeit"], + fatal: true, + }); + + return z.NEVER; + } + }); +} + +export function validateAnotherFlightPage( + baseSchema: ValidationMultipleFieldsBaseSchema, +) { + return baseSchema.superRefine((data, ctx) => { + const departureDateTime = convertToTimestamp( + data.direktAbflugsDatum, + data.direktAbflugsZeit, + ); + + const arrivalDateTime = convertToTimestamp( + data.ersatzFlugAnkunftsDatum, + data.ersatzFlugAnkunftsZeit, + ); + + if (departureDateTime > arrivalDateTime) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "departureAfterArrival", + path: ["ersatzFlugAnkunftsDatum"], + fatal: true, + }); + + // add new issue to invalidate this field as well + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "departureAfterArrival", + path: ["ersatzFlugAnkunftsZeit"], + fatal: true, + }); + + return z.NEVER; + } + + if ( + isStartTimestampLessThanThreeHours(departureDateTime, arrivalDateTime) && + data.bereich === "verspaetet" + ) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "arrivalThreeHoursLessThanDeparture", + path: ["ersatzFlugAnkunftsDatum"], + fatal: true, + }); + + // add new issue to invalidate this field as well + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "arrivalThreeHoursLessThanDeparture", + path: ["ersatzFlugAnkunftsZeit"], + fatal: true, + }); + + return z.NEVER; + } + }); +} + +export function validateSameFlightPage( + baseSchema: ValidationMultipleFieldsBaseSchema, +) { + return baseSchema.superRefine((data, ctx) => { + const departureDateTime = convertToTimestamp( + data.direktAbflugsDatum, + data.direktAbflugsZeit, + ); + + const arrivalDateTime = convertToTimestamp( + data.tatsaechlicherAnkunftsDatum, + data.tatsaechlicherAnkunftsZeit, + ); + + if (departureDateTime > arrivalDateTime) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "departureAfterArrival", + path: ["tatsaechlicherAnkunftsDatum"], + fatal: true, + }); + + // add new issue to invalidate this field as well + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "departureAfterArrival", + path: ["tatsaechlicherAnkunftsZeit"], + fatal: true, + }); + + return z.NEVER; + } + + if ( + isStartTimestampLessThanThreeHours(departureDateTime, arrivalDateTime) + ) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "arrivalThreeHoursLessThanDeparture", + path: ["tatsaechlicherAnkunftsDatum"], + fatal: true, + }); + + // add new issue to invalidate this field as well + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "arrivalThreeHoursLessThanDeparture", + path: ["tatsaechlicherAnkunftsZeit"], + fatal: true, + }); + + return z.NEVER; + } + }); +} + export function validateDepartureAfterArrival( baseSchema: ValidationMultipleFieldsBaseSchema, ) { diff --git a/app/domains/fluggastrechte/formular/validationMultipleFields.ts b/app/domains/fluggastrechte/formular/validationMultipleFields.ts index a3cccb8c1..0a83be03f 100644 --- a/app/domains/fluggastrechte/formular/validationMultipleFields.ts +++ b/app/domains/fluggastrechte/formular/validationMultipleFields.ts @@ -1,7 +1,16 @@ import type { ValidationMultipleFieldsPathName } from "~/domains/validationsMultipleFields"; -import { validateDepartureAfterArrival } from "./services/validation"; +import { + validateAnotherFlightPage, + validateDepartureAfterArrival, + validateReplacementConnectionPage, + validateSameFlightPage, +} from "./services/validation"; export const fluggastrechtValidationMultipleFields: ValidationMultipleFieldsPathName = { "/flugdaten/geplanter-flug": validateDepartureAfterArrival, + "/flugdaten/tatsaechlicher-flug-ankunft": validateSameFlightPage, + "/flugdaten/anderer-flug-ankunft": validateAnotherFlightPage, + "/flugdaten/ersatzverbindung-beschreibung": + validateReplacementConnectionPage, }; From 344abffce61282efabfbeb34287dac7d7405f565 Mon Sep 17 00:00:00 2001 From: aaschlote Date: Mon, 30 Dec 2024 14:04:59 +0100 Subject: [PATCH 10/16] refactor: rename files, functions, types and variables --- ...eFields.ts => multipleFieldsValidation.ts} | 4 +- .../formular/services/validation.ts | 10 +-- app/domains/multipleFieldsFlowValidation.ts | 29 +++++++++ app/domains/validationsMultipleFields.ts | 32 ---------- .../__test__/buildStepValidator.test.ts | 4 +- ...etMultipleFieldsByStepIdValidation.test.ts | 62 +++++++++++++++++++ ...ValidationMultipleFieldsByPathname.test.ts | 62 ------------------- .../stepValidator/buildStepValidator.ts | 8 +-- .../getMultipleFieldsByStepIdValidation.ts | 15 +++++ .../getValidationMultipleFieldsByPathName.ts | 13 ---- .../stepValidator/validatorForFieldNames.ts | 8 +-- 11 files changed, 123 insertions(+), 124 deletions(-) rename app/domains/fluggastrechte/formular/{validationMultipleFields.ts => multipleFieldsValidation.ts} (72%) create mode 100644 app/domains/multipleFieldsFlowValidation.ts delete mode 100644 app/domains/validationsMultipleFields.ts create mode 100644 app/services/validation/stepValidator/__test__/getMultipleFieldsByStepIdValidation.test.ts delete mode 100644 app/services/validation/stepValidator/__test__/getValidationMultipleFieldsByPathname.test.ts create mode 100644 app/services/validation/stepValidator/getMultipleFieldsByStepIdValidation.ts delete mode 100644 app/services/validation/stepValidator/getValidationMultipleFieldsByPathName.ts diff --git a/app/domains/fluggastrechte/formular/validationMultipleFields.ts b/app/domains/fluggastrechte/formular/multipleFieldsValidation.ts similarity index 72% rename from app/domains/fluggastrechte/formular/validationMultipleFields.ts rename to app/domains/fluggastrechte/formular/multipleFieldsValidation.ts index 0a83be03f..8a20352ec 100644 --- a/app/domains/fluggastrechte/formular/validationMultipleFields.ts +++ b/app/domains/fluggastrechte/formular/multipleFieldsValidation.ts @@ -1,4 +1,4 @@ -import type { ValidationMultipleFieldsPathName } from "~/domains/validationsMultipleFields"; +import type { MultipleFieldsStepIdValidation } from "~/domains/multipleFieldsFlowValidation"; import { validateAnotherFlightPage, validateDepartureAfterArrival, @@ -6,7 +6,7 @@ import { validateSameFlightPage, } from "./services/validation"; -export const fluggastrechtValidationMultipleFields: ValidationMultipleFieldsPathName = +export const fluggastrechtMultipleFieldsValidation: MultipleFieldsStepIdValidation = { "/flugdaten/geplanter-flug": validateDepartureAfterArrival, "/flugdaten/tatsaechlicher-flug-ankunft": validateSameFlightPage, diff --git a/app/domains/fluggastrechte/formular/services/validation.ts b/app/domains/fluggastrechte/formular/services/validation.ts index 2cd86f70f..c86eff80c 100644 --- a/app/domains/fluggastrechte/formular/services/validation.ts +++ b/app/domains/fluggastrechte/formular/services/validation.ts @@ -1,5 +1,5 @@ import { z } from "zod"; -import type { ValidationMultipleFieldsBaseSchema } from "~/domains/validationsMultipleFields"; +import type { MultipleFieldsValidationBaseSchema } from "~/domains/multipleFieldsFlowValidation"; const THREE_HOURS_MILLISECONDS = 3 * 60 * 60 * 1000; @@ -19,7 +19,7 @@ function isStartTimestampLessThanThreeHours( } export function validateReplacementConnectionPage( - baseSchema: ValidationMultipleFieldsBaseSchema, + baseSchema: MultipleFieldsValidationBaseSchema, ) { return baseSchema.superRefine((data, ctx) => { const departureDateTime = convertToTimestamp( @@ -76,7 +76,7 @@ export function validateReplacementConnectionPage( } export function validateAnotherFlightPage( - baseSchema: ValidationMultipleFieldsBaseSchema, + baseSchema: MultipleFieldsValidationBaseSchema, ) { return baseSchema.superRefine((data, ctx) => { const departureDateTime = convertToTimestamp( @@ -133,7 +133,7 @@ export function validateAnotherFlightPage( } export function validateSameFlightPage( - baseSchema: ValidationMultipleFieldsBaseSchema, + baseSchema: MultipleFieldsValidationBaseSchema, ) { return baseSchema.superRefine((data, ctx) => { const departureDateTime = convertToTimestamp( @@ -189,7 +189,7 @@ export function validateSameFlightPage( } export function validateDepartureAfterArrival( - baseSchema: ValidationMultipleFieldsBaseSchema, + baseSchema: MultipleFieldsValidationBaseSchema, ) { return baseSchema.superRefine((data, ctx) => { const departureDateTime = convertToTimestamp( diff --git a/app/domains/multipleFieldsFlowValidation.ts b/app/domains/multipleFieldsFlowValidation.ts new file mode 100644 index 000000000..2b126d515 --- /dev/null +++ b/app/domains/multipleFieldsFlowValidation.ts @@ -0,0 +1,29 @@ +import type { z } from "zod"; +import type { FlowId } from "./flowIds"; +import { fluggastrechtMultipleFieldsValidation } from "./fluggastrechte/formular/multipleFieldsValidation"; + +export type MultipleFieldsValidationBaseSchema = z.ZodObject< + Record +>; + +export type FunctionMultipleFieldsValidation = ( + baseSchema: MultipleFieldsValidationBaseSchema, +) => z.ZodTypeAny; + +export type MultipleFieldsStepIdValidation = Record< + string, + FunctionMultipleFieldsValidation +>; + +const multipleFieldsFlowValidation = { + "/beratungshilfe/antrag": undefined, + "/beratungshilfe/vorabcheck": undefined, + "/geld-einklagen/vorabcheck": undefined, + "/geld-einklagen/formular": undefined, + "/fluggastrechte/vorabcheck": undefined, + "/fluggastrechte/formular": fluggastrechtMultipleFieldsValidation, + "/prozesskostenhilfe/formular": undefined, +} as const satisfies Record; + +export const getMultipleFieldsValidation = (flowId: FlowId) => + multipleFieldsFlowValidation[flowId]; diff --git a/app/domains/validationsMultipleFields.ts b/app/domains/validationsMultipleFields.ts deleted file mode 100644 index 52e36dbf7..000000000 --- a/app/domains/validationsMultipleFields.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { z } from "zod"; -import type { FlowId } from "./flowIds"; -import { fluggastrechtValidationMultipleFields } from "./fluggastrechte/formular/validationMultipleFields"; - -export type ValidationMultipleFieldsBaseSchema = z.ZodObject< - Record ->; - -export type ValidationFunctionMultipleFields = ( - baseSchema: ValidationMultipleFieldsBaseSchema, -) => z.ZodTypeAny; - -export type ValidationMultipleFieldsPathName = Record< - string, - ValidationFunctionMultipleFields ->; - -const validationMultipleFields = { - "/beratungshilfe/antrag": undefined, - "/beratungshilfe/vorabcheck": undefined, - "/geld-einklagen/vorabcheck": undefined, - "/geld-einklagen/formular": undefined, - "/fluggastrechte/vorabcheck": undefined, - "/fluggastrechte/formular": fluggastrechtValidationMultipleFields, - "/prozesskostenhilfe/formular": undefined, -} as const satisfies Record< - FlowId, - ValidationMultipleFieldsPathName | undefined ->; - -export const getValidationMultipleFields = (flowId: FlowId) => - validationMultipleFields[flowId]; diff --git a/app/services/validation/stepValidator/__test__/buildStepValidator.test.ts b/app/services/validation/stepValidator/__test__/buildStepValidator.test.ts index aa1783f4e..b0f411560 100644 --- a/app/services/validation/stepValidator/__test__/buildStepValidator.test.ts +++ b/app/services/validation/stepValidator/__test__/buildStepValidator.test.ts @@ -1,5 +1,5 @@ import { z } from "zod"; -import type { ValidationFunctionMultipleFields } from "~/domains/validationsMultipleFields"; +import type { FunctionMultipleFieldsValidation } from "~/domains/multipleFieldsFlowValidation"; import { buildStepValidator } from "~/services/validation/stepValidator/buildStepValidator"; describe("buildStepValidator", () => { @@ -93,7 +93,7 @@ describe("buildStepValidator", () => { field2: z.number(), }; - const multipleFieldsValidation: ValidationFunctionMultipleFields = ( + const multipleFieldsValidation: FunctionMultipleFieldsValidation = ( schemas, ) => schemas.refine( diff --git a/app/services/validation/stepValidator/__test__/getMultipleFieldsByStepIdValidation.test.ts b/app/services/validation/stepValidator/__test__/getMultipleFieldsByStepIdValidation.test.ts new file mode 100644 index 000000000..7658e1aa2 --- /dev/null +++ b/app/services/validation/stepValidator/__test__/getMultipleFieldsByStepIdValidation.test.ts @@ -0,0 +1,62 @@ +import type { + MultipleFieldsValidationBaseSchema, + MultipleFieldsStepIdValidation, +} from "~/domains/multipleFieldsFlowValidation"; +import { getMultipleFieldsValidation } from "~/domains/multipleFieldsFlowValidation"; +import { getMultipleFieldsByStepIdValidation } from "../getMultipleFieldsByStepIdValidation"; + +vi.mock("~/domains/multipleFieldsFlowValidation"); + +describe("getMultipleFieldsByStepIdValidation", () => { + it("should return undefined given a mocked getMultipleFieldsValidation as undefined", () => { + vi.mocked(getMultipleFieldsValidation).mockReturnValue(undefined); + + const actual = getMultipleFieldsByStepIdValidation( + "/fluggastrechte/formular/flugdaten/geplanter-flug", + ); + + expect(actual).toBeUndefined(); + }); + + it("should return a value given a mocked getMultipleFieldsValidation", () => { + const mockMultipleFieldsValidation: MultipleFieldsStepIdValidation = { + "/flugdaten/geplanter-flug": ( + baseSchema: MultipleFieldsValidationBaseSchema, + ) => { + return baseSchema.describe("TEST"); + }, + }; + + vi.mocked(getMultipleFieldsValidation).mockReturnValue( + mockMultipleFieldsValidation, + ); + + const actual = getMultipleFieldsByStepIdValidation( + "/fluggastrechte/formular/flugdaten/geplanter-flug", + ); + + expect(actual).toEqual( + mockMultipleFieldsValidation["/flugdaten/geplanter-flug"], + ); + }); + + it("should return undefined given a not exist mocked pathname getMultipleFieldsValidation", () => { + const mockMultipleFieldsValidation: MultipleFieldsStepIdValidation = { + "/flugdaten/geplanter-flug": ( + baseSchema: MultipleFieldsValidationBaseSchema, + ) => { + return baseSchema.describe("TEST"); + }, + }; + + vi.mocked(getMultipleFieldsValidation).mockReturnValue( + mockMultipleFieldsValidation, + ); + + const actual = getMultipleFieldsByStepIdValidation( + "/fluggastrechte/formular/flugdaten/tatsaechlicher-flug", + ); + + expect(actual).toBeUndefined(); + }); +}); diff --git a/app/services/validation/stepValidator/__test__/getValidationMultipleFieldsByPathname.test.ts b/app/services/validation/stepValidator/__test__/getValidationMultipleFieldsByPathname.test.ts deleted file mode 100644 index bc86651e4..000000000 --- a/app/services/validation/stepValidator/__test__/getValidationMultipleFieldsByPathname.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -import type { - ValidationMultipleFieldsBaseSchema, - ValidationMultipleFieldsPathName, -} from "~/domains/validationsMultipleFields"; -import { getValidationMultipleFields } from "~/domains/validationsMultipleFields"; -import { getValidationMultipleFieldsByPathname } from "../getValidationMultipleFieldsByPathName"; - -vi.mock("~/domains/validationsMultipleFields"); - -describe("getValidationMultipleFieldsByPathname", () => { - it("should return undefined given a mocked getValidationMultipleFields as undefined", () => { - vi.mocked(getValidationMultipleFields).mockReturnValue(undefined); - - const actual = getValidationMultipleFieldsByPathname( - "/fluggastrechte/formular/flugdaten/geplanter-flug", - ); - - expect(actual).toBeUndefined(); - }); - - it("should return a value given a mocked getValidationMultipleFields", () => { - const mockValidationMultipleFields: ValidationMultipleFieldsPathName = { - "/flugdaten/geplanter-flug": ( - baseSchema: ValidationMultipleFieldsBaseSchema, - ) => { - return baseSchema.describe("TEST"); - }, - }; - - vi.mocked(getValidationMultipleFields).mockReturnValue( - mockValidationMultipleFields, - ); - - const actual = getValidationMultipleFieldsByPathname( - "/fluggastrechte/formular/flugdaten/geplanter-flug", - ); - - expect(actual).toEqual( - mockValidationMultipleFields["/flugdaten/geplanter-flug"], - ); - }); - - it("should return undefined given a not exist mocked pathname getValidationMultipleFields", () => { - const mockValidationMultipleFields: ValidationMultipleFieldsPathName = { - "/flugdaten/geplanter-flug": ( - baseSchema: ValidationMultipleFieldsBaseSchema, - ) => { - return baseSchema.describe("TEST"); - }, - }; - - vi.mocked(getValidationMultipleFields).mockReturnValue( - mockValidationMultipleFields, - ); - - const actual = getValidationMultipleFieldsByPathname( - "/fluggastrechte/formular/flugdaten/tatsaechlicher-flug", - ); - - expect(actual).toBeUndefined(); - }); -}); diff --git a/app/services/validation/stepValidator/buildStepValidator.ts b/app/services/validation/stepValidator/buildStepValidator.ts index c48b52130..72ab143e7 100644 --- a/app/services/validation/stepValidator/buildStepValidator.ts +++ b/app/services/validation/stepValidator/buildStepValidator.ts @@ -1,6 +1,6 @@ import { withZod } from "@remix-validated-form/with-zod"; import { z } from "zod"; -import type { ValidationFunctionMultipleFields } from "~/domains/validationsMultipleFields"; +import type { FunctionMultipleFieldsValidation } from "~/domains/multipleFieldsFlowValidation"; import { isKeyOfObject } from "~/util/objects"; import { fieldIsArray, splitArrayName } from "../../array"; @@ -9,7 +9,7 @@ type Schemas = Record; export function buildStepValidator( schemas: Schemas, fieldNames: string[], - validationMultipleFields?: ValidationFunctionMultipleFields, + multipleFieldsValidation?: FunctionMultipleFieldsValidation, ) { const fieldValidators: Record = {}; @@ -33,8 +33,8 @@ export function buildStepValidator( const baseSchema = z.object(fieldValidators); - const validationSchema = validationMultipleFields - ? validationMultipleFields(baseSchema) + const validationSchema = multipleFieldsValidation + ? multipleFieldsValidation(baseSchema) : baseSchema; return withZod(validationSchema); diff --git a/app/services/validation/stepValidator/getMultipleFieldsByStepIdValidation.ts b/app/services/validation/stepValidator/getMultipleFieldsByStepIdValidation.ts new file mode 100644 index 000000000..2ed4ea552 --- /dev/null +++ b/app/services/validation/stepValidator/getMultipleFieldsByStepIdValidation.ts @@ -0,0 +1,15 @@ +import { parsePathname } from "~/domains/flowIds"; +import { getMultipleFieldsValidation } from "~/domains/multipleFieldsFlowValidation"; + +export const getMultipleFieldsByStepIdValidation = ( + stepIdParameter: string, +) => { + const { flowId, stepId } = parsePathname(stepIdParameter); + const multipleFieldsValidation = getMultipleFieldsValidation(flowId); + + if (!multipleFieldsValidation) { + return undefined; + } + + return multipleFieldsValidation[stepId]; +}; diff --git a/app/services/validation/stepValidator/getValidationMultipleFieldsByPathName.ts b/app/services/validation/stepValidator/getValidationMultipleFieldsByPathName.ts deleted file mode 100644 index 4fb5cff70..000000000 --- a/app/services/validation/stepValidator/getValidationMultipleFieldsByPathName.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { parsePathname } from "~/domains/flowIds"; -import { getValidationMultipleFields } from "~/domains/validationsMultipleFields"; - -export const getValidationMultipleFieldsByPathname = (pathname: string) => { - const { flowId, stepId } = parsePathname(pathname); - const validationMultipleFields = getValidationMultipleFields(flowId); - - if (!validationMultipleFields) { - return undefined; - } - - return validationMultipleFields[stepId]; -}; diff --git a/app/services/validation/stepValidator/validatorForFieldNames.ts b/app/services/validation/stepValidator/validatorForFieldNames.ts index c114f0bf2..6f530d13c 100644 --- a/app/services/validation/stepValidator/validatorForFieldNames.ts +++ b/app/services/validation/stepValidator/validatorForFieldNames.ts @@ -1,13 +1,13 @@ import { getContext } from "~/domains/contexts"; import { parsePathname } from "~/domains/flowIds"; import { buildStepValidator } from "./buildStepValidator"; -import { getValidationMultipleFieldsByPathname } from "./getValidationMultipleFieldsByPathName"; +import { getMultipleFieldsByStepIdValidation } from "./getMultipleFieldsByStepIdValidation"; export function validatorForFieldNames(fieldNames: string[], pathname: string) { const flowId = parsePathname(pathname).flowId; const context = getContext(flowId); - const validationMultipleFields = - getValidationMultipleFieldsByPathname(pathname); + const multipleFieldsValidation = + getMultipleFieldsByStepIdValidation(pathname); - return buildStepValidator(context, fieldNames, validationMultipleFields); + return buildStepValidator(context, fieldNames, multipleFieldsValidation); } From 186125d197020e41504b4b89808aa99c3c6a6858 Mon Sep 17 00:00:00 2001 From: aaschlote Date: Mon, 30 Dec 2024 14:17:24 +0100 Subject: [PATCH 11/16] test: fix e2e test --- .../fluggastrechte/formular/startFluggastrechteFormular.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/e2e/domains/fluggastrechte/formular/startFluggastrechteFormular.ts b/tests/e2e/domains/fluggastrechte/formular/startFluggastrechteFormular.ts index 819f617f6..682d88fb9 100644 --- a/tests/e2e/domains/fluggastrechte/formular/startFluggastrechteFormular.ts +++ b/tests/e2e/domains/fluggastrechte/formular/startFluggastrechteFormular.ts @@ -71,8 +71,11 @@ export async function startFluggastrechteFormular( // /fluggastrechte/formular/flugdaten/anderer-flug-ankunft await formular.fillInput("ersatzFlugnummer", "BCA4321"); - await formular.fillInput("ersatzFlugAnkunftsDatum", "10.01.2023"); - await formular.fillInput("ersatzFlugAnkunftsZeit", "10:10"); + await formular.fillInput( + "ersatzFlugAnkunftsDatum", + toGermanDateFormat(today()), + ); + await formular.fillInput("ersatzFlugAnkunftsZeit", "15:10"); await formular.clickNext(); // /fluggastrechte/formular/flugdaten/zusaetzliche-angaben From 7d6a6ffb985077a68733372fb7240f481d7222f8 Mon Sep 17 00:00:00 2001 From: aaschlote Date: Mon, 6 Jan 2025 10:31:32 +0100 Subject: [PATCH 12/16] refactor(getMultipleFieldsByStepIdValidation): improve return function condition --- .../stepValidator/getMultipleFieldsByStepIdValidation.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/services/validation/stepValidator/getMultipleFieldsByStepIdValidation.ts b/app/services/validation/stepValidator/getMultipleFieldsByStepIdValidation.ts index 2ed4ea552..2fda851b2 100644 --- a/app/services/validation/stepValidator/getMultipleFieldsByStepIdValidation.ts +++ b/app/services/validation/stepValidator/getMultipleFieldsByStepIdValidation.ts @@ -7,9 +7,7 @@ export const getMultipleFieldsByStepIdValidation = ( const { flowId, stepId } = parsePathname(stepIdParameter); const multipleFieldsValidation = getMultipleFieldsValidation(flowId); - if (!multipleFieldsValidation) { - return undefined; - } - - return multipleFieldsValidation[stepId]; + return multipleFieldsValidation + ? multipleFieldsValidation[stepId] + : undefined; }; From 2b7dd4650e7b2e9fd45f8daf6e79defb7ef2cc0c Mon Sep 17 00:00:00 2001 From: aaschlote Date: Mon, 6 Jan 2025 13:11:47 +0100 Subject: [PATCH 13/16] refactor(getMultipleFieldsByStepIdValidation): get multiple validation function by flow and step id --- .../__test__/getMultipleFieldsByStepIdValidation.test.ts | 9 ++++++--- .../stepValidator/getMultipleFieldsByStepIdValidation.ts | 6 +++--- .../validation/stepValidator/validatorForFieldNames.ts | 8 +++++--- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/app/services/validation/stepValidator/__test__/getMultipleFieldsByStepIdValidation.test.ts b/app/services/validation/stepValidator/__test__/getMultipleFieldsByStepIdValidation.test.ts index 7658e1aa2..60b0e39ac 100644 --- a/app/services/validation/stepValidator/__test__/getMultipleFieldsByStepIdValidation.test.ts +++ b/app/services/validation/stepValidator/__test__/getMultipleFieldsByStepIdValidation.test.ts @@ -12,7 +12,8 @@ describe("getMultipleFieldsByStepIdValidation", () => { vi.mocked(getMultipleFieldsValidation).mockReturnValue(undefined); const actual = getMultipleFieldsByStepIdValidation( - "/fluggastrechte/formular/flugdaten/geplanter-flug", + "/fluggastrechte/formular", + "/flugdaten/geplanter-flug", ); expect(actual).toBeUndefined(); @@ -32,7 +33,8 @@ describe("getMultipleFieldsByStepIdValidation", () => { ); const actual = getMultipleFieldsByStepIdValidation( - "/fluggastrechte/formular/flugdaten/geplanter-flug", + "/fluggastrechte/formular", + "/flugdaten/geplanter-flug", ); expect(actual).toEqual( @@ -54,7 +56,8 @@ describe("getMultipleFieldsByStepIdValidation", () => { ); const actual = getMultipleFieldsByStepIdValidation( - "/fluggastrechte/formular/flugdaten/tatsaechlicher-flug", + "/fluggastrechte/formular", + "/flugdaten/tatsaechlicher-flug", ); expect(actual).toBeUndefined(); diff --git a/app/services/validation/stepValidator/getMultipleFieldsByStepIdValidation.ts b/app/services/validation/stepValidator/getMultipleFieldsByStepIdValidation.ts index 2fda851b2..8716a417a 100644 --- a/app/services/validation/stepValidator/getMultipleFieldsByStepIdValidation.ts +++ b/app/services/validation/stepValidator/getMultipleFieldsByStepIdValidation.ts @@ -1,10 +1,10 @@ -import { parsePathname } from "~/domains/flowIds"; +import type { FlowId } from "~/domains/flowIds"; import { getMultipleFieldsValidation } from "~/domains/multipleFieldsFlowValidation"; export const getMultipleFieldsByStepIdValidation = ( - stepIdParameter: string, + flowId: FlowId, + stepId: string, ) => { - const { flowId, stepId } = parsePathname(stepIdParameter); const multipleFieldsValidation = getMultipleFieldsValidation(flowId); return multipleFieldsValidation diff --git a/app/services/validation/stepValidator/validatorForFieldNames.ts b/app/services/validation/stepValidator/validatorForFieldNames.ts index 6f530d13c..1d2b33c25 100644 --- a/app/services/validation/stepValidator/validatorForFieldNames.ts +++ b/app/services/validation/stepValidator/validatorForFieldNames.ts @@ -4,10 +4,12 @@ import { buildStepValidator } from "./buildStepValidator"; import { getMultipleFieldsByStepIdValidation } from "./getMultipleFieldsByStepIdValidation"; export function validatorForFieldNames(fieldNames: string[], pathname: string) { - const flowId = parsePathname(pathname).flowId; + const { flowId, stepId } = parsePathname(pathname); const context = getContext(flowId); - const multipleFieldsValidation = - getMultipleFieldsByStepIdValidation(pathname); + const multipleFieldsValidation = getMultipleFieldsByStepIdValidation( + flowId, + stepId, + ); return buildStepValidator(context, fieldNames, multipleFieldsValidation); } From b6003dfcd7c61b004ba241d3edecd34ee9627b27 Mon Sep 17 00:00:00 2001 From: aaschlote Date: Mon, 6 Jan 2025 17:14:03 +0100 Subject: [PATCH 14/16] refactor: rename multiple to multi --- .../formular/multiFieldsValidation.ts | 14 ++++++ .../formular/multipleFieldsValidation.ts | 16 ------- .../formular/services/validation.ts | 10 ++-- app/domains/multiFieldsFlowValidation.ts | 29 ++++++++++++ app/domains/multipleFieldsFlowValidation.ts | 29 ------------ .../__test__/buildStepValidator.test.ts | 12 ++--- ...etMultipleFieldsByStepIdValidation.test.ts | 46 +++++++++---------- .../stepValidator/buildStepValidator.ts | 8 ++-- .../getMultiFieldsByStepIdValidation.ts | 11 +++++ .../getMultipleFieldsByStepIdValidation.ts | 13 ------ .../stepValidator/validatorForFieldNames.ts | 6 +-- 11 files changed, 94 insertions(+), 100 deletions(-) create mode 100644 app/domains/fluggastrechte/formular/multiFieldsValidation.ts delete mode 100644 app/domains/fluggastrechte/formular/multipleFieldsValidation.ts create mode 100644 app/domains/multiFieldsFlowValidation.ts delete mode 100644 app/domains/multipleFieldsFlowValidation.ts create mode 100644 app/services/validation/stepValidator/getMultiFieldsByStepIdValidation.ts delete mode 100644 app/services/validation/stepValidator/getMultipleFieldsByStepIdValidation.ts diff --git a/app/domains/fluggastrechte/formular/multiFieldsValidation.ts b/app/domains/fluggastrechte/formular/multiFieldsValidation.ts new file mode 100644 index 000000000..82d73648a --- /dev/null +++ b/app/domains/fluggastrechte/formular/multiFieldsValidation.ts @@ -0,0 +1,14 @@ +import type { MultiFieldsStepIdValidation } from "~/domains/multiFieldsFlowValidation"; +import { + validateAnotherFlightPage, + validateDepartureAfterArrival, + validateReplacementConnectionPage, + validateSameFlightPage, +} from "./services/validation"; + +export const fluggastrechtMultiFieldsValidation: MultiFieldsStepIdValidation = { + "/flugdaten/geplanter-flug": validateDepartureAfterArrival, + "/flugdaten/tatsaechlicher-flug-ankunft": validateSameFlightPage, + "/flugdaten/anderer-flug-ankunft": validateAnotherFlightPage, + "/flugdaten/ersatzverbindung-beschreibung": validateReplacementConnectionPage, +}; diff --git a/app/domains/fluggastrechte/formular/multipleFieldsValidation.ts b/app/domains/fluggastrechte/formular/multipleFieldsValidation.ts deleted file mode 100644 index 8a20352ec..000000000 --- a/app/domains/fluggastrechte/formular/multipleFieldsValidation.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { MultipleFieldsStepIdValidation } from "~/domains/multipleFieldsFlowValidation"; -import { - validateAnotherFlightPage, - validateDepartureAfterArrival, - validateReplacementConnectionPage, - validateSameFlightPage, -} from "./services/validation"; - -export const fluggastrechtMultipleFieldsValidation: MultipleFieldsStepIdValidation = - { - "/flugdaten/geplanter-flug": validateDepartureAfterArrival, - "/flugdaten/tatsaechlicher-flug-ankunft": validateSameFlightPage, - "/flugdaten/anderer-flug-ankunft": validateAnotherFlightPage, - "/flugdaten/ersatzverbindung-beschreibung": - validateReplacementConnectionPage, - }; diff --git a/app/domains/fluggastrechte/formular/services/validation.ts b/app/domains/fluggastrechte/formular/services/validation.ts index c86eff80c..34672bb29 100644 --- a/app/domains/fluggastrechte/formular/services/validation.ts +++ b/app/domains/fluggastrechte/formular/services/validation.ts @@ -1,5 +1,5 @@ import { z } from "zod"; -import type { MultipleFieldsValidationBaseSchema } from "~/domains/multipleFieldsFlowValidation"; +import type { MultiFieldsValidationBaseSchema } from "~/domains/multiFieldsFlowValidation"; const THREE_HOURS_MILLISECONDS = 3 * 60 * 60 * 1000; @@ -19,7 +19,7 @@ function isStartTimestampLessThanThreeHours( } export function validateReplacementConnectionPage( - baseSchema: MultipleFieldsValidationBaseSchema, + baseSchema: MultiFieldsValidationBaseSchema, ) { return baseSchema.superRefine((data, ctx) => { const departureDateTime = convertToTimestamp( @@ -76,7 +76,7 @@ export function validateReplacementConnectionPage( } export function validateAnotherFlightPage( - baseSchema: MultipleFieldsValidationBaseSchema, + baseSchema: MultiFieldsValidationBaseSchema, ) { return baseSchema.superRefine((data, ctx) => { const departureDateTime = convertToTimestamp( @@ -133,7 +133,7 @@ export function validateAnotherFlightPage( } export function validateSameFlightPage( - baseSchema: MultipleFieldsValidationBaseSchema, + baseSchema: MultiFieldsValidationBaseSchema, ) { return baseSchema.superRefine((data, ctx) => { const departureDateTime = convertToTimestamp( @@ -189,7 +189,7 @@ export function validateSameFlightPage( } export function validateDepartureAfterArrival( - baseSchema: MultipleFieldsValidationBaseSchema, + baseSchema: MultiFieldsValidationBaseSchema, ) { return baseSchema.superRefine((data, ctx) => { const departureDateTime = convertToTimestamp( diff --git a/app/domains/multiFieldsFlowValidation.ts b/app/domains/multiFieldsFlowValidation.ts new file mode 100644 index 000000000..382c724a4 --- /dev/null +++ b/app/domains/multiFieldsFlowValidation.ts @@ -0,0 +1,29 @@ +import type { z } from "zod"; +import type { FlowId } from "./flowIds"; +import { fluggastrechtMultiFieldsValidation } from "./fluggastrechte/formular/multiFieldsValidation"; + +export type MultiFieldsValidationBaseSchema = z.ZodObject< + Record +>; + +export type FunctionMultiFieldsValidation = ( + baseSchema: MultiFieldsValidationBaseSchema, +) => z.ZodTypeAny; + +export type MultiFieldsStepIdValidation = Record< + string, + FunctionMultiFieldsValidation +>; + +const multiFieldsFlowValidation = { + "/beratungshilfe/antrag": undefined, + "/beratungshilfe/vorabcheck": undefined, + "/geld-einklagen/vorabcheck": undefined, + "/geld-einklagen/formular": undefined, + "/fluggastrechte/vorabcheck": undefined, + "/fluggastrechte/formular": fluggastrechtMultiFieldsValidation, + "/prozesskostenhilfe/formular": undefined, +} as const satisfies Record; + +export const getMultiFieldsValidation = (flowId: FlowId) => + multiFieldsFlowValidation[flowId]; diff --git a/app/domains/multipleFieldsFlowValidation.ts b/app/domains/multipleFieldsFlowValidation.ts deleted file mode 100644 index 2b126d515..000000000 --- a/app/domains/multipleFieldsFlowValidation.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { z } from "zod"; -import type { FlowId } from "./flowIds"; -import { fluggastrechtMultipleFieldsValidation } from "./fluggastrechte/formular/multipleFieldsValidation"; - -export type MultipleFieldsValidationBaseSchema = z.ZodObject< - Record ->; - -export type FunctionMultipleFieldsValidation = ( - baseSchema: MultipleFieldsValidationBaseSchema, -) => z.ZodTypeAny; - -export type MultipleFieldsStepIdValidation = Record< - string, - FunctionMultipleFieldsValidation ->; - -const multipleFieldsFlowValidation = { - "/beratungshilfe/antrag": undefined, - "/beratungshilfe/vorabcheck": undefined, - "/geld-einklagen/vorabcheck": undefined, - "/geld-einklagen/formular": undefined, - "/fluggastrechte/vorabcheck": undefined, - "/fluggastrechte/formular": fluggastrechtMultipleFieldsValidation, - "/prozesskostenhilfe/formular": undefined, -} as const satisfies Record; - -export const getMultipleFieldsValidation = (flowId: FlowId) => - multipleFieldsFlowValidation[flowId]; diff --git a/app/services/validation/stepValidator/__test__/buildStepValidator.test.ts b/app/services/validation/stepValidator/__test__/buildStepValidator.test.ts index b0f411560..eab3a9021 100644 --- a/app/services/validation/stepValidator/__test__/buildStepValidator.test.ts +++ b/app/services/validation/stepValidator/__test__/buildStepValidator.test.ts @@ -1,5 +1,5 @@ import { z } from "zod"; -import type { FunctionMultipleFieldsValidation } from "~/domains/multipleFieldsFlowValidation"; +import type { FunctionMultiFieldsValidation } from "~/domains/multiFieldsFlowValidation"; import { buildStepValidator } from "~/services/validation/stepValidator/buildStepValidator"; describe("buildStepValidator", () => { @@ -87,15 +87,13 @@ describe("buildStepValidator", () => { }); }); - describe("multiple fields validation", () => { + describe("multi fields validation", () => { const schemas = { field1: z.number(), field2: z.number(), }; - const multipleFieldsValidation: FunctionMultipleFieldsValidation = ( - schemas, - ) => + const multiFieldsValidation: FunctionMultiFieldsValidation = (schemas) => schemas.refine( ({ field1, field2 }) => { return field1 < field2; @@ -112,7 +110,7 @@ describe("buildStepValidator", () => { const validator = buildStepValidator( schemas, fieldNames, - multipleFieldsValidation, + multiFieldsValidation, ); const actualValidation = await validator.validate({ @@ -133,7 +131,7 @@ describe("buildStepValidator", () => { const validator = buildStepValidator( schemas, fieldNames, - multipleFieldsValidation, + multiFieldsValidation, ); const actualValidation = await validator.validate({ diff --git a/app/services/validation/stepValidator/__test__/getMultipleFieldsByStepIdValidation.test.ts b/app/services/validation/stepValidator/__test__/getMultipleFieldsByStepIdValidation.test.ts index 60b0e39ac..7e80207fc 100644 --- a/app/services/validation/stepValidator/__test__/getMultipleFieldsByStepIdValidation.test.ts +++ b/app/services/validation/stepValidator/__test__/getMultipleFieldsByStepIdValidation.test.ts @@ -1,17 +1,17 @@ import type { - MultipleFieldsValidationBaseSchema, - MultipleFieldsStepIdValidation, -} from "~/domains/multipleFieldsFlowValidation"; -import { getMultipleFieldsValidation } from "~/domains/multipleFieldsFlowValidation"; -import { getMultipleFieldsByStepIdValidation } from "../getMultipleFieldsByStepIdValidation"; + MultiFieldsValidationBaseSchema, + MultiFieldsStepIdValidation, +} from "~/domains/multiFieldsFlowValidation"; +import { getMultiFieldsValidation } from "~/domains/multiFieldsFlowValidation"; +import { getMultiFieldsByStepIdValidation } from "../getMultiFieldsByStepIdValidation"; -vi.mock("~/domains/multipleFieldsFlowValidation"); +vi.mock("~/domains/multiFieldsFlowValidation"); -describe("getMultipleFieldsByStepIdValidation", () => { - it("should return undefined given a mocked getMultipleFieldsValidation as undefined", () => { - vi.mocked(getMultipleFieldsValidation).mockReturnValue(undefined); +describe("getMultiFieldsByStepIdValidation", () => { + it("should return undefined given a mocked getMultiFieldsValidation as undefined", () => { + vi.mocked(getMultiFieldsValidation).mockReturnValue(undefined); - const actual = getMultipleFieldsByStepIdValidation( + const actual = getMultiFieldsByStepIdValidation( "/fluggastrechte/formular", "/flugdaten/geplanter-flug", ); @@ -19,43 +19,43 @@ describe("getMultipleFieldsByStepIdValidation", () => { expect(actual).toBeUndefined(); }); - it("should return a value given a mocked getMultipleFieldsValidation", () => { - const mockMultipleFieldsValidation: MultipleFieldsStepIdValidation = { + it("should return a value given a mocked getMultiFieldsValidation", () => { + const mockMultiFieldsValidation: MultiFieldsStepIdValidation = { "/flugdaten/geplanter-flug": ( - baseSchema: MultipleFieldsValidationBaseSchema, + baseSchema: MultiFieldsValidationBaseSchema, ) => { return baseSchema.describe("TEST"); }, }; - vi.mocked(getMultipleFieldsValidation).mockReturnValue( - mockMultipleFieldsValidation, + vi.mocked(getMultiFieldsValidation).mockReturnValue( + mockMultiFieldsValidation, ); - const actual = getMultipleFieldsByStepIdValidation( + const actual = getMultiFieldsByStepIdValidation( "/fluggastrechte/formular", "/flugdaten/geplanter-flug", ); expect(actual).toEqual( - mockMultipleFieldsValidation["/flugdaten/geplanter-flug"], + mockMultiFieldsValidation["/flugdaten/geplanter-flug"], ); }); - it("should return undefined given a not exist mocked pathname getMultipleFieldsValidation", () => { - const mockMultipleFieldsValidation: MultipleFieldsStepIdValidation = { + it("should return undefined given a not exist mocked pathname getMultiFieldsValidation", () => { + const mockMultiFieldsValidation: MultiFieldsStepIdValidation = { "/flugdaten/geplanter-flug": ( - baseSchema: MultipleFieldsValidationBaseSchema, + baseSchema: MultiFieldsValidationBaseSchema, ) => { return baseSchema.describe("TEST"); }, }; - vi.mocked(getMultipleFieldsValidation).mockReturnValue( - mockMultipleFieldsValidation, + vi.mocked(getMultiFieldsValidation).mockReturnValue( + mockMultiFieldsValidation, ); - const actual = getMultipleFieldsByStepIdValidation( + const actual = getMultiFieldsByStepIdValidation( "/fluggastrechte/formular", "/flugdaten/tatsaechlicher-flug", ); diff --git a/app/services/validation/stepValidator/buildStepValidator.ts b/app/services/validation/stepValidator/buildStepValidator.ts index 72ab143e7..02da6f48c 100644 --- a/app/services/validation/stepValidator/buildStepValidator.ts +++ b/app/services/validation/stepValidator/buildStepValidator.ts @@ -1,6 +1,6 @@ import { withZod } from "@remix-validated-form/with-zod"; import { z } from "zod"; -import type { FunctionMultipleFieldsValidation } from "~/domains/multipleFieldsFlowValidation"; +import type { FunctionMultiFieldsValidation } from "~/domains/multiFieldsFlowValidation"; import { isKeyOfObject } from "~/util/objects"; import { fieldIsArray, splitArrayName } from "../../array"; @@ -9,7 +9,7 @@ type Schemas = Record; export function buildStepValidator( schemas: Schemas, fieldNames: string[], - multipleFieldsValidation?: FunctionMultipleFieldsValidation, + multiFieldsValidation?: FunctionMultiFieldsValidation, ) { const fieldValidators: Record = {}; @@ -33,8 +33,8 @@ export function buildStepValidator( const baseSchema = z.object(fieldValidators); - const validationSchema = multipleFieldsValidation - ? multipleFieldsValidation(baseSchema) + const validationSchema = multiFieldsValidation + ? multiFieldsValidation(baseSchema) : baseSchema; return withZod(validationSchema); diff --git a/app/services/validation/stepValidator/getMultiFieldsByStepIdValidation.ts b/app/services/validation/stepValidator/getMultiFieldsByStepIdValidation.ts new file mode 100644 index 000000000..3356280e0 --- /dev/null +++ b/app/services/validation/stepValidator/getMultiFieldsByStepIdValidation.ts @@ -0,0 +1,11 @@ +import type { FlowId } from "~/domains/flowIds"; +import { getMultiFieldsValidation } from "~/domains/multiFieldsFlowValidation"; + +export const getMultiFieldsByStepIdValidation = ( + flowId: FlowId, + stepId: string, +) => { + const multiFieldsValidation = getMultiFieldsValidation(flowId); + + return multiFieldsValidation ? multiFieldsValidation[stepId] : undefined; +}; diff --git a/app/services/validation/stepValidator/getMultipleFieldsByStepIdValidation.ts b/app/services/validation/stepValidator/getMultipleFieldsByStepIdValidation.ts deleted file mode 100644 index 8716a417a..000000000 --- a/app/services/validation/stepValidator/getMultipleFieldsByStepIdValidation.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { FlowId } from "~/domains/flowIds"; -import { getMultipleFieldsValidation } from "~/domains/multipleFieldsFlowValidation"; - -export const getMultipleFieldsByStepIdValidation = ( - flowId: FlowId, - stepId: string, -) => { - const multipleFieldsValidation = getMultipleFieldsValidation(flowId); - - return multipleFieldsValidation - ? multipleFieldsValidation[stepId] - : undefined; -}; diff --git a/app/services/validation/stepValidator/validatorForFieldNames.ts b/app/services/validation/stepValidator/validatorForFieldNames.ts index 1d2b33c25..7bfc3a824 100644 --- a/app/services/validation/stepValidator/validatorForFieldNames.ts +++ b/app/services/validation/stepValidator/validatorForFieldNames.ts @@ -1,15 +1,15 @@ import { getContext } from "~/domains/contexts"; import { parsePathname } from "~/domains/flowIds"; import { buildStepValidator } from "./buildStepValidator"; -import { getMultipleFieldsByStepIdValidation } from "./getMultipleFieldsByStepIdValidation"; +import { getMultiFieldsByStepIdValidation } from "./getMultiFieldsByStepIdValidation"; export function validatorForFieldNames(fieldNames: string[], pathname: string) { const { flowId, stepId } = parsePathname(pathname); const context = getContext(flowId); - const multipleFieldsValidation = getMultipleFieldsByStepIdValidation( + const multiFieldsValidation = getMultiFieldsByStepIdValidation( flowId, stepId, ); - return buildStepValidator(context, fieldNames, multipleFieldsValidation); + return buildStepValidator(context, fieldNames, multiFieldsValidation); } From de43993e3c404164517aac3ffb410468aca87ad4 Mon Sep 17 00:00:00 2001 From: aaschlote Date: Mon, 6 Jan 2025 18:17:14 +0100 Subject: [PATCH 15/16] test(validation.test): improve tests Co-authored-by: judithmh <40442827+judithmh@users.noreply.github.com> --- .../formular/services/__test__/validation.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/domains/fluggastrechte/formular/services/__test__/validation.test.ts b/app/domains/fluggastrechte/formular/services/__test__/validation.test.ts index 366deaa56..f2e5bff4a 100644 --- a/app/domains/fluggastrechte/formular/services/__test__/validation.test.ts +++ b/app/domains/fluggastrechte/formular/services/__test__/validation.test.ts @@ -55,7 +55,7 @@ describe("validation", () => { direktAbflugsDatum: "01.01.2024", direktAbflugsZeit: "14:00", direktAnkunftsDatum: "02.01.2024", - direktAnkunftsZeit: "15:00", + direktAnkunftsZeit: "10:00", }); expect(result.success).toBe(true); @@ -73,7 +73,7 @@ describe("validation", () => { }); }); - describe("validateSameFlightArrivedAfterThreeHours", () => { + describe("validateSameFlightPage", () => { const baseSchema = z.object({ direktAbflugsDatum: z.string(), direktAbflugsZeit: z.string(), From 8aa00f7cbd1054d9b73cfbcffb1c167a1d81f296 Mon Sep 17 00:00:00 2001 From: aaschlote Date: Mon, 6 Jan 2025 18:30:58 +0100 Subject: [PATCH 16/16] refactor(buildStepValidator): improve variable name --- app/services/validation/stepValidator/buildStepValidator.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/services/validation/stepValidator/buildStepValidator.ts b/app/services/validation/stepValidator/buildStepValidator.ts index 02da6f48c..ecda9ee40 100644 --- a/app/services/validation/stepValidator/buildStepValidator.ts +++ b/app/services/validation/stepValidator/buildStepValidator.ts @@ -31,11 +31,11 @@ export function buildStepValidator( } } - const baseSchema = z.object(fieldValidators); + const validationFieldsSchema = z.object(fieldValidators); const validationSchema = multiFieldsValidation - ? multiFieldsValidation(baseSchema) - : baseSchema; + ? multiFieldsValidation(validationFieldsSchema) + : validationFieldsSchema; return withZod(validationSchema); }