Files
calendar/packages/features/form-builder/FormBuilder.test.tsx
T
+1 55e6682832 feat: Custom Fields OR Add-Ons with a fee associated (#19997)
* feat: Addons to bookings

* Add tests

* Add tests

* Fix price field not shown immediately after enabling payment for an event

* Add support for all currencies in addons and fix type errors

* Address review comments

* Add tests to cover all input types

* Address cubic-dev-ai comments

* Remove openapi.json from git tracking

* Revert changes

* Revert unrelated linter/formatter changes

* Address cubic-dev-ai comments

* Addres cubic-dev-ai comment

* Update to use test id instead of hardcoded label

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Address coderabbits review comments

* Fix failing unit test

* some fixes...

* Fix tests

* Fix import

* Move handlePayment test file to appropriate location

* Fix test

---------

Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: amrit <iamamrit27@gmail.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Kartik Saini <41051387+kart1ka@users.noreply.github.com>
Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
Co-authored-by: Devanshu Sharma <devanshusharma658@gmail.com>
Co-authored-by: Benny Joo <sldisek783@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Volnei Munhoz <volnei.munhoz@gmail.com>
2025-09-16 21:33:23 +09:00

275 lines
8.5 KiB
TypeScript

import { TooltipProvider } from "@radix-ui/react-tooltip";
import { render } from "@testing-library/react";
import type { ReactNode } from "react";
import * as React from "react";
import { FormProvider, useForm } from "react-hook-form";
import { vi } from "vitest";
import { FormBuilder } from "./FormBuilder";
import {
mockProps,
verifier,
setMockIntersectionObserver,
setMockMatchMedia,
pageObject,
expectScenario,
getLocationBookingField,
} from "./testUtils";
vi.mock("@formkit/auto-animate/react", () => ({
useAutoAnimate: () => [null],
}));
vi.mock("next/navigation", async (importOriginal) => {
const actual = await importOriginal<typeof import("next/navigation")>();
return {
...actual,
useRouter: vi.fn(() => ({
push: vi.fn(() => {
return;
}),
})),
};
});
const renderComponent = ({
formBuilderProps: formBuilderProps,
formDefaultValues: formDefaultValues,
}: {
formBuilderProps: Parameters<typeof FormBuilder>[0];
formDefaultValues;
}) => {
const Wrapper = ({ children }: { children: ReactNode }) => {
const form = useForm({
defaultValues: formDefaultValues,
});
return (
<TooltipProvider>
<FormProvider {...form}>{children}</FormProvider>
</TooltipProvider>
);
};
return render(<FormBuilder {...formBuilderProps} />, { wrapper: Wrapper });
};
describe("FormBuilder", () => {
beforeAll(() => {
setMockMatchMedia();
setMockIntersectionObserver();
});
describe("Basic Tests", () => {
const fieldTypes = [
{ fieldType: "email", label: "Email Field" },
{ fieldType: "phone", label: "Phone Field" },
{ fieldType: "address", label: "Address Field" },
{ fieldType: "text", label: "Short Text Field" },
{ fieldType: "number", label: "Number Field" },
{ fieldType: "textarea", label: "LongText Field" },
{ fieldType: "select", label: "Select Field" },
{ fieldType: "multiselect", label: "MultiSelect Field" },
{ fieldType: "multiemail", label: "Multiple Emails Field" },
{ fieldType: "checkbox", label: "CheckBox Group Field" },
{ fieldType: "radio", label: "Radio Group Field" },
{ fieldType: "boolean", label: "Checkbox Field" },
];
beforeEach(() => {
renderComponent({ formBuilderProps: mockProps, formDefaultValues: {} });
});
for (const { fieldType, label } of fieldTypes) {
it(`Should add new field of type ${fieldType} `, async () => {
const defaultIdentifier = `${fieldType}-id`;
const newIdentifier = `${defaultIdentifier}-edited`;
await verifier.verifyFieldAddition({
fieldType,
identifier: defaultIdentifier,
label,
});
await verifier.verifyIdentifierChange({
newIdentifier: newIdentifier,
existingIdentifier: defaultIdentifier,
});
await verifier.verifyThatFieldCanBeMarkedOptional({
identifier: newIdentifier,
});
await verifier.verifyFieldToggle({ identifier: newIdentifier });
await verifier.verifyFieldDeletion({ identifier: newIdentifier });
});
}
});
describe("radioInput type field with options available in dataStore", () => {
test('Should add new field of type "radioInput" and select option from dataStore', async () => {
const field = {
identifier: "location",
type: "radioInput",
};
renderComponent({
formBuilderProps: {
...mockProps,
dataStore: {
options: {
locations: {
value: [
{
label: "Attendee Phone",
value: "phone",
inputPlaceholder: "Phone Number",
},
],
source: { label: "Location" },
},
},
},
// @typescript-eslint/no-explicit-any
// eslint-disable-next-line @typescript-eslint/no-explicit-any
shouldConsiderRequired: (field: any) => {
return field.name === "location" ? true : false;
},
},
formDefaultValues: {
fields: [getLocationBookingField()],
},
});
// editable:'system' field can't be deleted
expectScenario.toNotHaveDeleteButton({ identifier: field.identifier });
// editable:'system' field can't be toggled
expectScenario.toNotHaveToggleButton({ identifier: field.identifier });
expectScenario.toHaveSourceBadge({ identifier: field.identifier, sourceLabel: "1 Location" });
expectScenario.toHaveRequiredBadge({ identifier: field.identifier });
const newFieldDialog = pageObject.openAddFieldDialog();
// radioInput type field isn't selectable by the user.
expect(
pageObject.dialog.fieldTypeDropdown.queryOptionForFieldType({
dialog: newFieldDialog,
fieldType: "radioInput",
})
).toBeNull();
pageObject.dialog.close({ dialog: newFieldDialog });
const dialog = pageObject.openEditFieldDialog({ identifier: field.identifier });
expectScenario.toHaveFieldTypeDropdownDisabled({ dialog });
expectScenario.toHaveIdentifierChangeDisabled({ dialog });
expectScenario.toHaveRequirablityToggleDisabled({ dialog });
expectScenario.toHaveLabelChangeAllowed({ dialog });
expect(pageObject.dialog.isFieldShowingAsRequired({ dialog })).toBe(true);
});
});
describe("Addon Fields Tests", () => {
beforeEach(() => {
renderComponent({
formBuilderProps: {
...mockProps,
showPriceField: true,
},
formDefaultValues: {},
});
});
it("Should add number field with price", async () => {
const identifier = "quantity-addon";
const price = 10;
await verifier.verifyFieldAddition({
fieldType: "number",
identifier,
label: "Quantity",
price,
});
pageObject.openEditFieldDialog({ identifier });
await expectScenario.toHavePriceField({ identifier, price });
});
it("Should add boolean field with price", async () => {
const identifier = "extra-service-addon";
const price = 15;
await verifier.verifyFieldAddition({
fieldType: "boolean",
identifier,
label: "Extra Service",
price,
});
pageObject.openEditFieldDialog({ identifier });
await expectScenario.toHavePriceField({ identifier, price });
});
it("Should add select field with price and options", async () => {
const identifier = "meal-selection";
await verifier.verifyFieldAddition({
fieldType: "select",
identifier,
label: "Meal Selection",
options: [
{ label: "Vegetarian", value: "veg", price: 20 },
{ label: "Non-Vegetarian", value: "non-veg", price: 25 },
],
});
verifier.verifyOptionPrices({ identifier, prices: [20, 25] });
});
it("Should add multiselect field with price and options", async () => {
const identifier = "add-ons";
await verifier.verifyFieldAddition({
fieldType: "multiselect",
identifier,
label: "Add-ons",
options: [
{ label: "WiFi", value: "wifi", price: 10 },
{ label: "Breakfast", value: "breakfast", price: 15 },
{ label: "Parking", value: "parking", price: 20 },
],
});
verifier.verifyOptionPrices({ identifier, prices: [10, 15, 20] });
});
it("Should add checkbox group field with price and options", async () => {
const identifier = "amenities";
await verifier.verifyFieldAddition({
fieldType: "checkbox",
identifier,
label: "Amenities",
options: [
{ label: "Pool Access", value: "pool", price: 15 },
{ label: "Gym Access", value: "gym", price: 20 },
{ label: "Spa Access", value: "spa", price: 25 },
],
});
verifier.verifyOptionPrices({ identifier, prices: [15, 20, 25] });
});
it("Should add radio group field with price and options", async () => {
const identifier = "room-type";
await verifier.verifyFieldAddition({
fieldType: "radio",
identifier,
label: "Room Type",
options: [
{ label: "Standard", value: "standard", price: 50 },
{ label: "Deluxe", value: "deluxe", price: 75 },
{ label: "Suite", value: "suite", price: 100 },
],
});
verifier.verifyOptionPrices({ identifier, prices: [50, 75, 100] });
});
});
});