Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
26 changes: 21 additions & 5 deletions front/src/views/BoxCreate/BoxCreateView.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -336,11 +336,27 @@ describe("BoxCreateView", () => {
const createBoxButton = screen.getByRole("button", { name: /^save$/i });
await user.click(createBoxButton);

// Check for validation errors
expect(await screen.findByText(/please select a product/i)).toBeInTheDocument();
expect(screen.getByText(/please select a size/i)).toBeInTheDocument();
expect(screen.getByText(/please select a location/i)).toBeInTheDocument();
expect(screen.getByText(/please enter a number of items/i)).toBeInTheDocument();
// Check for validation errors only (ignore same placeholder text in fields)
expect(
await screen.findByText(/please select a product/i, {
selector: ".chakra-form__error-message",
}),
).toBeInTheDocument();
expect(
screen.getByText(/please select a size/i, {
selector: ".chakra-form__error-message",
}),
).toBeInTheDocument();
expect(
screen.getByText(/please select a location/i, {
selector: ".chakra-form__error-message",
}),
).toBeInTheDocument();
expect(
screen.getByText(/please enter a number of items/i, {
selector: ".chakra-form__error-message",
}),
).toBeInTheDocument();
});

it("handles API errors during box creation", async () => {
Expand Down
32 changes: 12 additions & 20 deletions front/src/views/BoxCreate/components/BoxCreate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,32 +41,24 @@ interface ILocationData {
seq?: number | null | undefined;
}

const singleSelectOptionSchema = z.object({
const singleSelectOptionSchema = {
label: z.string(),
value: z.string(),
__isNew__: z.boolean().optional(),
});
};

export const CreateBoxFormDataSchema = z.object({
// Single Select Fields are a tough nut to validate. This feels like a hacky solution, but the best I could find.
// It is based on this example https://codesandbox.io/s/chakra-react-select-single-react-hook-form-with-zod-validation-typescript-m1dqme?file=/app.tsx
productId: singleSelectOptionSchema
// If the Select is empty it returns null. If we put required() here. The error is "expected object, received null". I did not find a way to edit this message. Hence, this solution.
.nullable()
// We make the field nullable and can then check in the next step if it is empty or not with the refine function.
.refine(Boolean, { error: "Please select a product" })
// since the expected return type should not have a null we add this transform at the en.
.transform((selectedOption) => selectedOption || z.NEVER),
sizeId: singleSelectOptionSchema
.nullable()
.refine(Boolean, { error: "Please select a size" })
.transform((selectedOption) => selectedOption || z.NEVER),
productId: z.object(singleSelectOptionSchema, {
error: (iss) => (iss.input === undefined ? "Please select a product" : "Invalid input."),
}),
sizeId: z.object(singleSelectOptionSchema, {
error: (iss) => (iss.input === undefined ? "Please select a size" : "Invalid input."),
}),
numberOfItems: z.number({ error: "Please enter a number of items" }).int().nonnegative(),
locationId: singleSelectOptionSchema
.nullable()
.refine(Boolean, { error: "Please select a location" })
.transform((selectedOption) => selectedOption || z.NEVER),
tags: singleSelectOptionSchema.array().optional(),
locationId: z.object(singleSelectOptionSchema, {
error: (iss) => (iss.input === undefined ? "Please select a location" : "Invalid input."),
}),
tags: z.object(singleSelectOptionSchema).array().optional(),
comment: z.string().optional(),
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,10 +205,18 @@ it("4.3.2 - Input Validations", async () => {
await user.click(submitButton);
// Test case 4.3.2.1 - Partner Organisation SELECT field cannot be empty
expect((screen.getByLabelText(/organisation/i) as HTMLInputElement).value).toEqual("");
expect(await screen.findByText(/please select an organisation/i)).toBeInTheDocument();
expect(
await screen.findByText(/please select an organisation/i, {
selector: ".chakra-form__error-message",
}),
).toBeInTheDocument();
// Test case 4.3.2.2 - Partner Organisation Base SELECT field cannot be empty
expect((screen.getByLabelText(/base/i) as HTMLInputElement).value).toEqual("");
expect((await screen.findAllByText(/please select a base/i))[0]).toBeInTheDocument();
expect(
await screen.findByText(/please select a base/i, {
selector: ".chakra-form__error-message",
}),
).toBeInTheDocument();
});

// Test case 4.3.3
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,24 +43,22 @@ export interface IOrganisationBaseData {
}

// Define schema of the form
const singleSelectOptionSchema = z.object({
const singleSelectOptionSchema = {
label: z.string(),
value: z.string(),
});
};

const shipmentTargetSchema = z.union([z.literal("partners"), z.literal("currentOrg")]);

// Define validation checks on form by defining the form schema
export const ShipmentFormSchema = z.object({
shipmentTarget: shipmentTargetSchema,
receivingOrganisation: singleSelectOptionSchema
.nullable()
.refine(Boolean, { error: "Please select an organisation" })
.transform((selectedOption) => selectedOption || { label: "", value: "" }),
receivingBase: singleSelectOptionSchema
.nullable()
.refine(Boolean, { error: "Please select a base" })
.transform((selectedOption) => selectedOption || { label: "", value: "" }),
receivingOrganisation: z.object(singleSelectOptionSchema, {
error: (iss) => (iss.input === undefined ? "Please select an organisation" : "Invalid input."),
}),
receivingBase: z.object(singleSelectOptionSchema, {
error: (iss) => (iss.input === undefined ? "Please select a base" : "Invalid input."),
}),
Comment on lines 54 to +61
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ShipmentFormSchema now makes receivingOrganisation required for all cases, but the intra-org tab doesn’t render an organisation field and relies on programmatic setValueIntraOrg to satisfy validation. Consider modeling this as a discriminated union by shipmentTarget (partners: require organisation+base; currentOrg: require base only) or provide defaultValues for receivingOrganisation (set once in an effect) to avoid render-time setValue workarounds.

Copilot uses AI. Check for mistakes.
});

export type ICreateShipmentFormData = z.infer<typeof ShipmentFormSchema>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,23 @@ export interface IBasesForOrganisationData {
bases: IBaseData[];
}

const singleSelectOptionSchema = z.object({
const singleSelectOptionSchema = {
label: z.string(),
value: z.string(),
});
};

export const TransferAgreementFormDataSchema = z
.object({
currentOrganisationSelectedBases: singleSelectOptionSchema
currentOrganisationSelectedBases: z
.object(singleSelectOptionSchema)
.array()
.min(1, "Please select at least one base"),

partnerOrganisation: singleSelectOptionSchema
.refine(Boolean, { error: "Please select a partner organisation" })
.transform((selectedOption) => selectedOption || { label: "", value: "" }),
partnerOrganisationSelectedBases: singleSelectOptionSchema.array().optional(),
partnerOrganisation: z.object(singleSelectOptionSchema, {
error: (iss) =>
iss.input === undefined ? "Please select a partner organisation" : "Invalid input.",
}),
partnerOrganisationSelectedBases: z.object(singleSelectOptionSchema).array().optional(),
validFrom: z
.date({
error: (issue) =>
Expand Down
Loading