fix: Make identifier conform to RHF field requirement (#10860)
Co-authored-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>pull/10869/head^2
parent
7a7a5b5285
commit
51caa6834a
|
@ -202,7 +202,7 @@ async function runTestStepsCommonForTeamAndUserEventType(
|
||||||
await addQuestionAndSave({
|
await addQuestionAndSave({
|
||||||
page,
|
page,
|
||||||
question: {
|
question: {
|
||||||
name: "how_are_you",
|
name: "how-are-you",
|
||||||
type: "Address",
|
type: "Address",
|
||||||
label: "How are you?",
|
label: "How are you?",
|
||||||
placeholder: "I'm fine, thanks",
|
placeholder: "I'm fine, thanks",
|
||||||
|
@ -214,7 +214,7 @@ async function runTestStepsCommonForTeamAndUserEventType(
|
||||||
const allFieldsLocator = await expectSystemFieldsToBeThereOnBookingPage({ page });
|
const allFieldsLocator = await expectSystemFieldsToBeThereOnBookingPage({ page });
|
||||||
const userFieldLocator = allFieldsLocator.nth(5);
|
const userFieldLocator = allFieldsLocator.nth(5);
|
||||||
|
|
||||||
await expect(userFieldLocator.locator('[name="how_are_you"]')).toBeVisible();
|
await expect(userFieldLocator.locator('[name="how-are-you"]')).toBeVisible();
|
||||||
// There are 2 labels right now. Will be one in future. The second one is hidden
|
// There are 2 labels right now. Will be one in future. The second one is hidden
|
||||||
expect(await getLabelText(userFieldLocator)).toBe("How are you?");
|
expect(await getLabelText(userFieldLocator)).toBe("How are you?");
|
||||||
await expect(userFieldLocator.locator("input")).toBeVisible();
|
await expect(userFieldLocator.locator("input")).toBeVisible();
|
||||||
|
@ -223,18 +223,18 @@ async function runTestStepsCommonForTeamAndUserEventType(
|
||||||
|
|
||||||
await test.step("Hide Question and see that it's not shown on Booking Page", async () => {
|
await test.step("Hide Question and see that it's not shown on Booking Page", async () => {
|
||||||
await toggleQuestionAndSave({
|
await toggleQuestionAndSave({
|
||||||
name: "how_are_you",
|
name: "how-are-you",
|
||||||
page,
|
page,
|
||||||
});
|
});
|
||||||
await doOnFreshPreview(page, context, async (page) => {
|
await doOnFreshPreview(page, context, async (page) => {
|
||||||
const formBuilderFieldLocator = page.locator('[data-fob-field-name="how_are_you"]');
|
const formBuilderFieldLocator = page.locator('[data-fob-field-name="how-are-you"]');
|
||||||
await expect(formBuilderFieldLocator).toBeHidden();
|
await expect(formBuilderFieldLocator).toBeHidden();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
await test.step("Show Question Again", async () => {
|
await test.step("Show Question Again", async () => {
|
||||||
await toggleQuestionAndSave({
|
await toggleQuestionAndSave({
|
||||||
name: "how_are_you",
|
name: "how-are-you",
|
||||||
page,
|
page,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -242,7 +242,7 @@ async function runTestStepsCommonForTeamAndUserEventType(
|
||||||
await test.step('Try to book without providing "How are you?" response', async () => {
|
await test.step('Try to book without providing "How are you?" response', async () => {
|
||||||
await doOnFreshPreview(page, context, async (page) => {
|
await doOnFreshPreview(page, context, async (page) => {
|
||||||
await bookTimeSlot({ page, name: "Booker", email: "booker@example.com" });
|
await bookTimeSlot({ page, name: "Booker", email: "booker@example.com" });
|
||||||
await expectErrorToBeThereFor({ page, name: "how_are_you" });
|
await expectErrorToBeThereFor({ page, name: "how-are-you" });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -260,18 +260,18 @@ async function runTestStepsCommonForTeamAndUserEventType(
|
||||||
page,
|
page,
|
||||||
context,
|
context,
|
||||||
async (page) => {
|
async (page) => {
|
||||||
const formBuilderFieldLocator = page.locator('[data-fob-field-name="how_are_you"]');
|
const formBuilderFieldLocator = page.locator('[data-fob-field-name="how-are-you"]');
|
||||||
await expect(formBuilderFieldLocator).toBeVisible();
|
await expect(formBuilderFieldLocator).toBeVisible();
|
||||||
expect(
|
expect(
|
||||||
await formBuilderFieldLocator.locator('[name="how_are_you"]').getAttribute("placeholder")
|
await formBuilderFieldLocator.locator('[name="how-are-you"]').getAttribute("placeholder")
|
||||||
).toBe("I'm fine, thanks");
|
).toBe("I'm fine, thanks");
|
||||||
expect(await getLabelText(formBuilderFieldLocator)).toBe("How are you?");
|
expect(await getLabelText(formBuilderFieldLocator)).toBe("How are you?");
|
||||||
await formBuilderFieldLocator.locator('[name="how_are_you"]').fill("I am great!");
|
await formBuilderFieldLocator.locator('[name="how-are-you"]').fill("I am great!");
|
||||||
await bookTimeSlot({ page, name: "Booker", email: "booker@example.com" });
|
await bookTimeSlot({ page, name: "Booker", email: "booker@example.com" });
|
||||||
await expect(page.locator("[data-testid=success-page]")).toBeVisible();
|
await expect(page.locator("[data-testid=success-page]")).toBeVisible();
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
await page.locator('[data-testid="field-response"][data-fob-field="how_are_you"]').innerText()
|
await page.locator('[data-testid="field-response"][data-fob-field="how-are-you"]').innerText()
|
||||||
).toBe("I am great!");
|
).toBe("I am great!");
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
|
@ -287,7 +287,7 @@ async function runTestStepsCommonForTeamAndUserEventType(
|
||||||
label: "email_address",
|
label: "email_address",
|
||||||
value: "booker@example.com",
|
value: "booker@example.com",
|
||||||
},
|
},
|
||||||
how_are_you: {
|
"how-are-you": {
|
||||||
label: "How are you?",
|
label: "How are you?",
|
||||||
value: "I am great!",
|
value: "I am great!",
|
||||||
},
|
},
|
||||||
|
@ -303,7 +303,7 @@ async function runTestStepsCommonForTeamAndUserEventType(
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(payload.userFieldsResponses).toMatchObject({
|
expect(payload.userFieldsResponses).toMatchObject({
|
||||||
how_are_you: {
|
"how-are-you": {
|
||||||
label: "How are you?",
|
label: "How are you?",
|
||||||
value: "I am great!",
|
value: "I am great!",
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,6 +3,7 @@ import type { z } from "zod";
|
||||||
|
|
||||||
import { SMS_REMINDER_NUMBER_FIELD } from "@calcom/features/bookings/lib/SystemField";
|
import { SMS_REMINDER_NUMBER_FIELD } from "@calcom/features/bookings/lib/SystemField";
|
||||||
import { fieldsThatSupportLabelAsSafeHtml } from "@calcom/features/form-builder/fieldsThatSupportLabelAsSafeHtml";
|
import { fieldsThatSupportLabelAsSafeHtml } from "@calcom/features/form-builder/fieldsThatSupportLabelAsSafeHtml";
|
||||||
|
import { getFieldIdentifier } from "@calcom/features/form-builder/utils/getFieldIdentifier";
|
||||||
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
|
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
|
||||||
import slugify from "@calcom/lib/slugify";
|
import slugify from "@calcom/lib/slugify";
|
||||||
import { EventTypeCustomInputType } from "@calcom/prisma/enums";
|
import { EventTypeCustomInputType } from "@calcom/prisma/enums";
|
||||||
|
@ -285,7 +286,9 @@ export const ensureBookingInputsHaveSystemFields = ({
|
||||||
|
|
||||||
const missingSystemBeforeFields = [];
|
const missingSystemBeforeFields = [];
|
||||||
for (const field of systemBeforeFields) {
|
for (const field of systemBeforeFields) {
|
||||||
const existingBookingFieldIndex = bookingFields.findIndex((f) => f.name === field.name);
|
const existingBookingFieldIndex = bookingFields.findIndex(
|
||||||
|
(f) => getFieldIdentifier(f.name) === getFieldIdentifier(field.name)
|
||||||
|
);
|
||||||
// Only do a push, we must not update existing system fields as user could have modified any property in it,
|
// Only do a push, we must not update existing system fields as user could have modified any property in it,
|
||||||
if (existingBookingFieldIndex === -1) {
|
if (existingBookingFieldIndex === -1) {
|
||||||
missingSystemBeforeFields.push(field);
|
missingSystemBeforeFields.push(field);
|
||||||
|
@ -303,8 +306,13 @@ export const ensureBookingInputsHaveSystemFields = ({
|
||||||
// Backward Compatibility for SMS Reminder Number
|
// Backward Compatibility for SMS Reminder Number
|
||||||
// Note: We still need workflows in `getBookingFields` due to Backward Compatibility. If we do a one time entry for all event-types, we can remove workflows from `getBookingFields`
|
// Note: We still need workflows in `getBookingFields` due to Backward Compatibility. If we do a one time entry for all event-types, we can remove workflows from `getBookingFields`
|
||||||
// Also, note that even if Workflows don't explicity add smsReminderNumber field to bookingFields, it would be added as a side effect of this backward compatibility logic
|
// Also, note that even if Workflows don't explicity add smsReminderNumber field to bookingFields, it would be added as a side effect of this backward compatibility logic
|
||||||
if (smsNumberSources.length && !bookingFields.find((f) => f.name !== SMS_REMINDER_NUMBER_FIELD)) {
|
if (
|
||||||
const indexForLocation = bookingFields.findIndex((f) => f.name === "location");
|
smsNumberSources.length &&
|
||||||
|
!bookingFields.find((f) => getFieldIdentifier(f.name) !== getFieldIdentifier(SMS_REMINDER_NUMBER_FIELD))
|
||||||
|
) {
|
||||||
|
const indexForLocation = bookingFields.findIndex(
|
||||||
|
(f) => getFieldIdentifier(f.name) === getFieldIdentifier("location")
|
||||||
|
);
|
||||||
// Add the SMS Reminder Number field after `location` field always
|
// Add the SMS Reminder Number field after `location` field always
|
||||||
bookingFields.splice(indexForLocation + 1, 0, {
|
bookingFields.splice(indexForLocation + 1, 0, {
|
||||||
...getSmsReminderNumberField(),
|
...getSmsReminderNumberField(),
|
||||||
|
@ -340,7 +348,9 @@ export const ensureBookingInputsHaveSystemFields = ({
|
||||||
|
|
||||||
const missingSystemAfterFields = [];
|
const missingSystemAfterFields = [];
|
||||||
for (const field of systemAfterFields) {
|
for (const field of systemAfterFields) {
|
||||||
const existingBookingFieldIndex = bookingFields.findIndex((f) => f.name === field.name);
|
const existingBookingFieldIndex = bookingFields.findIndex(
|
||||||
|
(f) => getFieldIdentifier(f.name) === getFieldIdentifier(field.name)
|
||||||
|
);
|
||||||
// Only do a push, we must not update existing system fields as user could have modified any property in it,
|
// Only do a push, we must not update existing system fields as user could have modified any property in it,
|
||||||
if (existingBookingFieldIndex === -1) {
|
if (existingBookingFieldIndex === -1) {
|
||||||
missingSystemAfterFields.push(field);
|
missingSystemAfterFields.push(field);
|
||||||
|
|
|
@ -30,6 +30,7 @@ import { fieldTypesConfigMap } from "./fieldTypes";
|
||||||
import { fieldsThatSupportLabelAsSafeHtml } from "./fieldsThatSupportLabelAsSafeHtml";
|
import { fieldsThatSupportLabelAsSafeHtml } from "./fieldsThatSupportLabelAsSafeHtml";
|
||||||
import type { fieldsSchema } from "./schema";
|
import type { fieldsSchema } from "./schema";
|
||||||
import { getVariantsConfig } from "./utils";
|
import { getVariantsConfig } from "./utils";
|
||||||
|
import { getFieldIdentifier } from "./utils/getFieldIdentifier";
|
||||||
|
|
||||||
type RhfForm = {
|
type RhfForm = {
|
||||||
fields: z.infer<typeof fieldsSchema>;
|
fields: z.infer<typeof fieldsSchema>;
|
||||||
|
@ -448,6 +449,9 @@ function FieldEditDialog({
|
||||||
required
|
required
|
||||||
{...fieldForm.register("name")}
|
{...fieldForm.register("name")}
|
||||||
containerClassName="mt-6"
|
containerClassName="mt-6"
|
||||||
|
onChange={(e) => {
|
||||||
|
fieldForm.setValue("name", getFieldIdentifier(e.target.value || ""));
|
||||||
|
}}
|
||||||
disabled={
|
disabled={
|
||||||
fieldForm.getValues("editable") === "system" ||
|
fieldForm.getValues("editable") === "system" ||
|
||||||
fieldForm.getValues("editable") === "system-but-optional"
|
fieldForm.getValues("editable") === "system-but-optional"
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { getValidRhfFieldName } from "@calcom/lib/getValidRhfFieldName";
|
||||||
|
|
||||||
import { fieldTypesConfigMap } from "./fieldTypes";
|
import { fieldTypesConfigMap } from "./fieldTypes";
|
||||||
import { getVariantsConfig, preprocessNameFieldDataWithVariant } from "./utils";
|
import { getVariantsConfig, preprocessNameFieldDataWithVariant } from "./utils";
|
||||||
|
|
||||||
|
@ -31,7 +33,7 @@ export const EditableSchema = z.enum([
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const baseFieldSchema = z.object({
|
const baseFieldSchema = z.object({
|
||||||
name: z.string(),
|
name: z.string().transform(getValidRhfFieldName),
|
||||||
type: fieldTypeEnum,
|
type: fieldTypeEnum,
|
||||||
// TODO: We should make at least one of `defaultPlaceholder` and `placeholder` required. Do the same for label.
|
// TODO: We should make at least one of `defaultPlaceholder` and `placeholder` required. Do the same for label.
|
||||||
label: z.string().optional(),
|
label: z.string().optional(),
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { getValidRhfFieldName } from "@calcom/lib/getValidRhfFieldName";
|
||||||
|
|
||||||
|
export const getFieldIdentifier = (name: string) => {
|
||||||
|
return getValidRhfFieldName(name);
|
||||||
|
};
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
import { getValidRhfFieldName } from "./getValidRhfFieldName";
|
||||||
|
|
||||||
|
describe("getValidRhfFieldName", () => {
|
||||||
|
it("should not convert to lowercase", () => {
|
||||||
|
expect(getValidRhfFieldName("Hello")).toEqual("Hello");
|
||||||
|
expect(getValidRhfFieldName("HELLO")).toEqual("HELLO");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should convert spaces, _, and any other special character to -", () => {
|
||||||
|
expect(getValidRhfFieldName("hello there")).toEqual("hello-there");
|
||||||
|
expect(getValidRhfFieldName("hello_there")).toEqual("hello-there");
|
||||||
|
expect(getValidRhfFieldName("hello$there")).toEqual("hello-there");
|
||||||
|
expect(getValidRhfFieldName("$hello$there")).toEqual("-hello-there");
|
||||||
|
expect(getValidRhfFieldName("$hello.there")).toEqual("-hello-there");
|
||||||
|
});
|
||||||
|
|
||||||
|
// So that user can freely add spaces and any other character iteratively and it get's converted to - and he can add more characters.
|
||||||
|
// We don't really care about a hyphen in the end
|
||||||
|
it("should not remove dashes from start and end.", () => {
|
||||||
|
expect(getValidRhfFieldName("hello-there-")).toEqual("hello-there-");
|
||||||
|
expect(getValidRhfFieldName("hello-there_")).toEqual("hello-there-");
|
||||||
|
expect(getValidRhfFieldName("_hello-there_")).toEqual("-hello-there-");
|
||||||
|
expect(getValidRhfFieldName("$hello-there_")).toEqual("-hello-there-");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should remove unicode and emoji characters", () => {
|
||||||
|
expect(getValidRhfFieldName("Hello 📚🕯️®️ There")).toEqual("Hello---------There");
|
||||||
|
expect(getValidRhfFieldName("📚🕯️®️")).toEqual("-------");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should keep numbers as is", () => {
|
||||||
|
expect(getValidRhfFieldName("hellothere123")).toEqual("hellothere123");
|
||||||
|
expect(getValidRhfFieldName("321hello there123")).toEqual("321hello-there123");
|
||||||
|
expect(getValidRhfFieldName("hello$there")).toEqual("hello-there");
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,5 @@
|
||||||
|
export const getValidRhfFieldName = (fieldName: string) => {
|
||||||
|
// Remember that any transformation that you do here would run on System Field names as well. So, be careful and avoiding doing anything here that would modify the SystemField names.
|
||||||
|
// e.g. SystemField name currently have uppercases in them. So, no need to lowercase unless absolutely needed.
|
||||||
|
return fieldName.replace(/[^a-zA-Z0-9]/g, "-");
|
||||||
|
};
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
import { slugify } from "./slugify";
|
||||||
|
|
||||||
|
describe("slugify", () => {
|
||||||
|
it("should convert to lowercase", () => {
|
||||||
|
expect(slugify("Hello")).toEqual("hello");
|
||||||
|
expect(slugify("HELLO")).toEqual("hello");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should convert spaces, _, and any other special character to -", () => {
|
||||||
|
expect(slugify("hello there")).toEqual("hello-there");
|
||||||
|
expect(slugify("hello_there")).toEqual("hello-there");
|
||||||
|
expect(slugify("hello$there")).toEqual("hello-there");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should keep numbers as is", () => {
|
||||||
|
expect(slugify("hellothere123")).toEqual("hellothere123");
|
||||||
|
expect(slugify("321hello there123")).toEqual("321hello-there123");
|
||||||
|
expect(slugify("hello$there")).toEqual("hello-there");
|
||||||
|
});
|
||||||
|
|
||||||
|
// So that user can freely add spaces and any other character iteratively and it get's converted to - later on.
|
||||||
|
it("should remove dashes from start and end.", () => {
|
||||||
|
expect(slugify("hello-there-")).toEqual("hello-there");
|
||||||
|
expect(slugify("hello-there_")).toEqual("hello-there");
|
||||||
|
expect(slugify("_hello-there_")).toEqual("hello-there");
|
||||||
|
expect(slugify("$hello-there_")).toEqual("hello-there");
|
||||||
|
});
|
||||||
|
|
||||||
|
// This is failing, if we want to fix it, one approach is as used in getValidRhfFieldName
|
||||||
|
it.skip("should remove unicode and emoji characters", () => {
|
||||||
|
expect(slugify("Hello 📚🕯️®️ There")).toEqual("hello---------there");
|
||||||
|
expect(slugify("📚🕯️®️")).toEqual("");
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue