fix: workflow locale (#26509)
* fix: workflow locale * fix: workflow locale * fix: check if tmeplate is changed * fix: regression * fix: also check subject when determining if template is customized Co-Authored-By: morgan@cal.com <morgan@cal.com> * refactor: extract template matching logic into testable function - Create detectMatchedTemplate function in separate file - Add comprehensive unit tests (20 test cases) - Refactor EmailWorkflowService to use the new function Addresses review comment from hariombalhara requesting abstraction and tests for the template matching logic. Co-Authored-By: udit@cal.com <udit222001@gmail.com> --------- Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: morgan@cal.com <morgan@cal.com>
This commit is contained in:
co-authored by
udit@cal.com
Morgan
Devin AI
morgan@cal.com
parent
f4303d8d60
commit
3860717ae3
@@ -145,6 +145,29 @@ export function getTemplateBodyForAction({
|
||||
return templateFunction({ isEditingMode: true, locale, t, action, timeFormat }).emailBody;
|
||||
}
|
||||
|
||||
export function getTemplateSubjectForAction({
|
||||
action,
|
||||
locale,
|
||||
t,
|
||||
template,
|
||||
timeFormat,
|
||||
}: {
|
||||
action: WorkflowActions;
|
||||
locale: string;
|
||||
t: TFunction;
|
||||
template: WorkflowTemplates;
|
||||
timeFormat: TimeFormat;
|
||||
}): string | null {
|
||||
// SMS and WhatsApp don't have subjects
|
||||
if (isSMSAction(action) || isWhatsappAction(action)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// For email actions, get the subject from the template
|
||||
const templateFunction = getEmailTemplateFunction(template);
|
||||
return templateFunction({ isEditingMode: true, locale, t, action, timeFormat }).emailSubject;
|
||||
}
|
||||
|
||||
export function isFormTrigger(trigger: WorkflowTriggerEvents) {
|
||||
return FORM_TRIGGER_WORKFLOW_EVENTS.includes(trigger);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import { WorkflowTemplates } from "@calcom/prisma/enums";
|
||||
|
||||
import compareReminderBodyToTemplate from "./compareReminderBodyToTemplate";
|
||||
|
||||
export type DefaultTemplates = {
|
||||
reminder: { body: string | null; subject: string | null };
|
||||
rating: { body: string | null; subject: string | null };
|
||||
};
|
||||
|
||||
export type DetectMatchedTemplateParams = {
|
||||
emailBody: string;
|
||||
emailSubject: string;
|
||||
template?: WorkflowTemplates;
|
||||
defaultTemplates: DefaultTemplates;
|
||||
};
|
||||
|
||||
/**
|
||||
* Detects if the email body and subject match a default template (REMINDER or RATING).
|
||||
*
|
||||
* Logic:
|
||||
* 1. If emailBody is empty but template is specified as REMINDER or RATING, return that template
|
||||
* 2. If emailBody exists, check if both body AND subject match the REMINDER default template
|
||||
* 3. If not matched, check if both body AND subject match the RATING default template
|
||||
* 4. Return null if no match (indicating custom content that should be preserved)
|
||||
*
|
||||
* This is used to determine whether to regenerate the template with the recipient's locale
|
||||
* for proper translation, or to preserve user customizations.
|
||||
*/
|
||||
export function detectMatchedTemplate({
|
||||
emailBody,
|
||||
emailSubject,
|
||||
template,
|
||||
defaultTemplates,
|
||||
}: DetectMatchedTemplateParams): WorkflowTemplates | null {
|
||||
if (!emailBody && template === WorkflowTemplates.REMINDER) {
|
||||
return WorkflowTemplates.REMINDER;
|
||||
}
|
||||
|
||||
if (!emailBody && template === WorkflowTemplates.RATING) {
|
||||
return WorkflowTemplates.RATING;
|
||||
}
|
||||
|
||||
if (emailBody) {
|
||||
const { reminder, rating } = defaultTemplates;
|
||||
|
||||
const bodyMatchesReminder =
|
||||
reminder.body && compareReminderBodyToTemplate({ reminderBody: emailBody, template: reminder.body });
|
||||
const subjectMatchesReminder = reminder.subject && emailSubject === reminder.subject;
|
||||
|
||||
if (bodyMatchesReminder && subjectMatchesReminder) {
|
||||
return WorkflowTemplates.REMINDER;
|
||||
}
|
||||
|
||||
const bodyMatchesRating =
|
||||
rating.body && compareReminderBodyToTemplate({ reminderBody: emailBody, template: rating.body });
|
||||
const subjectMatchesRating = rating.subject && emailSubject === rating.subject;
|
||||
|
||||
if (bodyMatchesRating && subjectMatchesRating) {
|
||||
return WorkflowTemplates.RATING;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -71,9 +71,9 @@ const emailReminderTemplate = ({
|
||||
"date_and_time"
|
||||
)}: </strong></div>${eventDate} - ${endTime} (${timeZone})<br><br>`;
|
||||
|
||||
const attendeeHtml = `<div><strong class="editor-text-bold">${t("attendees")}: </strong></div>${t(
|
||||
"you_and_conjunction"
|
||||
)} ${otherPerson}<br><br>`;
|
||||
const attendeeHtml = `<div><strong class="editor-text-bold">${t(
|
||||
"attendees"
|
||||
)}: </strong></div>${t("you_and_conjunction")} ${otherPerson}<br><br>`;
|
||||
|
||||
const locationHtml = `<div><strong class="editor-text-bold">${t(
|
||||
"location"
|
||||
|
||||
@@ -12,6 +12,7 @@ import { UserRepository } from "@calcom/features/users/repositories/UserReposito
|
||||
import { getVideoCallUrlFromCalEvent } from "@calcom/lib/CalEventParser";
|
||||
import { SENDER_NAME, WEBSITE_URL } from "@calcom/lib/constants";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { TimeFormat } from "@calcom/lib/timeFormat";
|
||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import { WorkflowActions, WorkflowTemplates } from "@calcom/prisma/enums";
|
||||
@@ -20,7 +21,8 @@ import { bookingMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
import { CalendarEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import type { WorkflowReminderRepository } from "../../repositories/WorkflowReminderRepository";
|
||||
import { isEmailAction } from "../actionHelperFunctions";
|
||||
import { isEmailAction, getTemplateBodyForAction, getTemplateSubjectForAction } from "../actionHelperFunctions";
|
||||
import { detectMatchedTemplate } from "../detectMatchedTemplate";
|
||||
import { getWorkflowRecipientEmail } from "../getWorkflowReminders";
|
||||
import type { VariablesType } from "../reminders/templates/customTemplate";
|
||||
import customTemplate, {
|
||||
@@ -51,9 +53,8 @@ export class EmailWorkflowService {
|
||||
evt: CalendarEvent;
|
||||
workflowReminderId: number;
|
||||
}) {
|
||||
const workflowReminder = await this.workflowReminderRepository.findByIdIncludeStepAndWorkflow(
|
||||
workflowReminderId
|
||||
);
|
||||
const workflowReminder =
|
||||
await this.workflowReminderRepository.findByIdIncludeStepAndWorkflow(workflowReminderId);
|
||||
|
||||
if (!workflowReminder) {
|
||||
throw new Error(`Workflow reminder not found with id ${workflowReminderId}`);
|
||||
@@ -94,7 +95,9 @@ export class EmailWorkflowService {
|
||||
|
||||
const emailWorkflowContentParams = await this.generateParametersToBuildEmailWorkflowContent({
|
||||
evt,
|
||||
workflowStep: workflowReminder.workflowStep as WorkflowStep & { action: ScheduleEmailReminderAction },
|
||||
workflowStep: workflowReminder.workflowStep as WorkflowStep & {
|
||||
action: ScheduleEmailReminderAction;
|
||||
},
|
||||
workflow: workflowReminder.workflowStep.workflow,
|
||||
emailAttendeeSendToOverride,
|
||||
commonScheduleFunctionParams,
|
||||
@@ -286,9 +289,8 @@ export class EmailWorkflowService {
|
||||
case WorkflowActions.EMAIL_ATTENDEE: {
|
||||
// For seated events, get the correct attendee based on seatReferenceUid
|
||||
if (seatReferenceUid) {
|
||||
const seatAttendeeData = await this.bookingSeatRepository.getByReferenceUidWithAttendeeDetails(
|
||||
seatReferenceUid
|
||||
);
|
||||
const seatAttendeeData =
|
||||
await this.bookingSeatRepository.getByReferenceUidWithAttendeeDetails(seatReferenceUid);
|
||||
if (seatAttendeeData?.attendee) {
|
||||
const nameParts = seatAttendeeData.attendee.name.split(" ").map((part: string) => part.trim());
|
||||
const firstName = nameParts[0];
|
||||
@@ -326,14 +328,105 @@ export class EmailWorkflowService {
|
||||
throw new Error("Failed to determine attendee email");
|
||||
}
|
||||
|
||||
const isEmailAttendeeAction = action === WorkflowActions.EMAIL_ATTENDEE;
|
||||
const locale = isEmailAttendeeAction
|
||||
? attendeeToBeUsedInMail.language?.locale || "en"
|
||||
: evt.organizer.language.locale || "en";
|
||||
|
||||
let emailContent = {
|
||||
emailSubject,
|
||||
emailBody: `<body style="white-space: pre-wrap;">${emailBody}</body>`,
|
||||
};
|
||||
const bookerUrl = evt.bookerUrl ?? WEBSITE_URL;
|
||||
|
||||
// Detect if the email content matches a default template for locale-based regeneration
|
||||
const timeFormat = evt.organizer.timeFormat || TimeFormat.TWELVE_HOUR;
|
||||
let defaultTemplates = {
|
||||
reminder: { body: null as string | null, subject: null as string | null },
|
||||
rating: { body: null as string | null, subject: null as string | null },
|
||||
};
|
||||
|
||||
if (emailBody) {
|
||||
const isEmailAttendeeAction = action === WorkflowActions.EMAIL_ATTENDEE;
|
||||
const tEn = await getTranslation("en", "common");
|
||||
defaultTemplates = {
|
||||
reminder: {
|
||||
body: getTemplateBodyForAction({
|
||||
action,
|
||||
template: WorkflowTemplates.REMINDER,
|
||||
locale: "en",
|
||||
t: tEn,
|
||||
timeFormat,
|
||||
}),
|
||||
subject: getTemplateSubjectForAction({
|
||||
action,
|
||||
template: WorkflowTemplates.REMINDER,
|
||||
locale: "en",
|
||||
t: tEn,
|
||||
timeFormat,
|
||||
}),
|
||||
},
|
||||
rating: {
|
||||
body: getTemplateBodyForAction({
|
||||
action,
|
||||
template: WorkflowTemplates.RATING,
|
||||
locale: "en",
|
||||
t: tEn,
|
||||
timeFormat,
|
||||
}),
|
||||
subject: getTemplateSubjectForAction({
|
||||
action,
|
||||
template: WorkflowTemplates.RATING,
|
||||
locale: "en",
|
||||
t: tEn,
|
||||
timeFormat,
|
||||
}),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const matchedTemplate = detectMatchedTemplate({
|
||||
emailBody,
|
||||
emailSubject,
|
||||
template,
|
||||
defaultTemplates,
|
||||
});
|
||||
|
||||
if (matchedTemplate === WorkflowTemplates.REMINDER) {
|
||||
const t = await getTranslation(locale, "common");
|
||||
|
||||
emailContent = emailReminderTemplate({
|
||||
isEditingMode: false,
|
||||
locale,
|
||||
t,
|
||||
action,
|
||||
timeFormat: evt.organizer.timeFormat,
|
||||
startTime,
|
||||
endTime,
|
||||
eventName: evt.title,
|
||||
timeZone,
|
||||
location: evt.location || "",
|
||||
meetingUrl:
|
||||
evt.videoCallData?.url || bookingMetadataSchema.parse(evt.metadata || {})?.videoCallUrl || "",
|
||||
otherPerson: attendeeName,
|
||||
name,
|
||||
});
|
||||
} else if (matchedTemplate === WorkflowTemplates.RATING) {
|
||||
emailContent = emailRatingTemplate({
|
||||
isEditingMode: false,
|
||||
locale,
|
||||
action,
|
||||
t: await getTranslation(locale, "common"),
|
||||
timeFormat: evt.organizer.timeFormat,
|
||||
startTime,
|
||||
endTime,
|
||||
eventName: evt.title,
|
||||
timeZone,
|
||||
organizer: evt.organizer.name,
|
||||
name,
|
||||
ratingUrl: `${bookerUrl}/booking/${evt.uid}?rating`,
|
||||
noShowUrl: `${bookerUrl}/booking/${evt.uid}?noShow=true`,
|
||||
});
|
||||
} else if (emailBody) {
|
||||
const recipientEmail = getWorkflowRecipientEmail({
|
||||
action,
|
||||
attendeeEmail: attendeeToBeUsedInMail.email,
|
||||
@@ -368,8 +461,8 @@ export class EmailWorkflowService {
|
||||
: ""
|
||||
}`
|
||||
: isEmailAttendeeAction && seatReferenceUid
|
||||
? `?seatReferenceUid=${encodeURIComponent(seatReferenceUid)}`
|
||||
: ""
|
||||
? `?seatReferenceUid=${encodeURIComponent(seatReferenceUid)}`
|
||||
: ""
|
||||
}`,
|
||||
|
||||
rescheduleReason: evt.rescheduleReason,
|
||||
@@ -380,10 +473,6 @@ export class EmailWorkflowService {
|
||||
eventEndTimeInAttendeeTimezone: dayjs(endTime).tz(attendeeToBeUsedInMail.timeZone),
|
||||
};
|
||||
|
||||
const locale = isEmailAttendeeAction
|
||||
? attendeeToBeUsedInMail.language?.locale
|
||||
: evt.organizer.language.locale;
|
||||
|
||||
const emailSubjectTemplate = customTemplate(emailSubject, variables, locale, evt.organizer.timeFormat);
|
||||
emailContent.emailSubject = emailSubjectTemplate.text;
|
||||
emailContent.emailBody = customTemplate(
|
||||
@@ -393,39 +482,6 @@ export class EmailWorkflowService {
|
||||
evt.organizer.timeFormat,
|
||||
hideBranding
|
||||
).html;
|
||||
} else if (template === WorkflowTemplates.REMINDER) {
|
||||
emailContent = emailReminderTemplate({
|
||||
isEditingMode: false,
|
||||
locale: evt.organizer.language.locale,
|
||||
t: await getTranslation(evt.organizer.language.locale || "en", "common"),
|
||||
action,
|
||||
timeFormat: evt.organizer.timeFormat,
|
||||
startTime,
|
||||
endTime,
|
||||
eventName: evt.title,
|
||||
timeZone,
|
||||
location: evt.location || "",
|
||||
meetingUrl:
|
||||
evt.videoCallData?.url || bookingMetadataSchema.parse(evt.metadata || {})?.videoCallUrl || "",
|
||||
otherPerson: attendeeName,
|
||||
name,
|
||||
});
|
||||
} else if (template === WorkflowTemplates.RATING) {
|
||||
emailContent = emailRatingTemplate({
|
||||
isEditingMode: true,
|
||||
locale: evt.organizer.language.locale,
|
||||
action,
|
||||
t: await getTranslation(evt.organizer.language.locale || "en", "common"),
|
||||
timeFormat: evt.organizer.timeFormat,
|
||||
startTime,
|
||||
endTime,
|
||||
eventName: evt.title,
|
||||
timeZone,
|
||||
organizer: evt.organizer.name,
|
||||
name,
|
||||
ratingUrl: `${bookerUrl}/booking/${evt.uid}?rating`,
|
||||
noShowUrl: `${bookerUrl}/booking/${evt.uid}?noShow=true`,
|
||||
});
|
||||
}
|
||||
|
||||
// Allows debugging generated email content without waiting for sendgrid to send emails
|
||||
@@ -450,7 +506,10 @@ export class EmailWorkflowService {
|
||||
const emailEvent = {
|
||||
...evt,
|
||||
type: evt.eventType?.slug || "",
|
||||
organizer: { ...evt.organizer, language: { ...evt.organizer.language, translate: organizerT } },
|
||||
organizer: {
|
||||
...evt.organizer,
|
||||
language: { ...evt.organizer.language, translate: organizerT },
|
||||
},
|
||||
attendees: processedAttendees,
|
||||
location: bookingMetadataSchema.parse(evt.metadata || {})?.videoCallUrl || evt.location,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,307 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
import { WorkflowTemplates } from "@calcom/prisma/enums";
|
||||
|
||||
import { detectMatchedTemplate, type DefaultTemplates } from "../detectMatchedTemplate";
|
||||
|
||||
const createDefaultTemplates = (overrides?: Partial<DefaultTemplates>): DefaultTemplates => ({
|
||||
reminder: {
|
||||
body: "Default reminder body content",
|
||||
subject: "Default reminder subject",
|
||||
},
|
||||
rating: {
|
||||
body: "Default rating body content",
|
||||
subject: "Default rating subject",
|
||||
},
|
||||
...overrides,
|
||||
});
|
||||
|
||||
describe("detectMatchedTemplate", () => {
|
||||
describe("when emailBody is empty", () => {
|
||||
test("returns REMINDER when template is REMINDER", () => {
|
||||
const result = detectMatchedTemplate({
|
||||
emailBody: "",
|
||||
emailSubject: "Any subject",
|
||||
template: WorkflowTemplates.REMINDER,
|
||||
defaultTemplates: createDefaultTemplates(),
|
||||
});
|
||||
|
||||
expect(result).toBe(WorkflowTemplates.REMINDER);
|
||||
});
|
||||
|
||||
test("returns RATING when template is RATING", () => {
|
||||
const result = detectMatchedTemplate({
|
||||
emailBody: "",
|
||||
emailSubject: "Any subject",
|
||||
template: WorkflowTemplates.RATING,
|
||||
defaultTemplates: createDefaultTemplates(),
|
||||
});
|
||||
|
||||
expect(result).toBe(WorkflowTemplates.RATING);
|
||||
});
|
||||
|
||||
test("returns null when template is CUSTOM", () => {
|
||||
const result = detectMatchedTemplate({
|
||||
emailBody: "",
|
||||
emailSubject: "Any subject",
|
||||
template: WorkflowTemplates.CUSTOM,
|
||||
defaultTemplates: createDefaultTemplates(),
|
||||
});
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
test("returns null when template is undefined", () => {
|
||||
const result = detectMatchedTemplate({
|
||||
emailBody: "",
|
||||
emailSubject: "Any subject",
|
||||
template: undefined,
|
||||
defaultTemplates: createDefaultTemplates(),
|
||||
});
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when emailBody exists", () => {
|
||||
describe("REMINDER template matching", () => {
|
||||
test("returns REMINDER when both body AND subject match default", () => {
|
||||
const defaultTemplates = createDefaultTemplates();
|
||||
|
||||
const result = detectMatchedTemplate({
|
||||
emailBody: defaultTemplates.reminder.body!,
|
||||
emailSubject: defaultTemplates.reminder.subject!,
|
||||
template: WorkflowTemplates.REMINDER,
|
||||
defaultTemplates,
|
||||
});
|
||||
|
||||
expect(result).toBe(WorkflowTemplates.REMINDER);
|
||||
});
|
||||
|
||||
test("returns null when body matches but subject is customized", () => {
|
||||
const defaultTemplates = createDefaultTemplates();
|
||||
|
||||
const result = detectMatchedTemplate({
|
||||
emailBody: defaultTemplates.reminder.body!,
|
||||
emailSubject: "Custom subject",
|
||||
template: WorkflowTemplates.REMINDER,
|
||||
defaultTemplates,
|
||||
});
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
test("returns null when subject matches but body is customized", () => {
|
||||
const defaultTemplates = createDefaultTemplates();
|
||||
|
||||
const result = detectMatchedTemplate({
|
||||
emailBody: "Custom body content",
|
||||
emailSubject: defaultTemplates.reminder.subject!,
|
||||
template: WorkflowTemplates.REMINDER,
|
||||
defaultTemplates,
|
||||
});
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
test("returns null when both body and subject are customized", () => {
|
||||
const defaultTemplates = createDefaultTemplates();
|
||||
|
||||
const result = detectMatchedTemplate({
|
||||
emailBody: "Custom body content",
|
||||
emailSubject: "Custom subject",
|
||||
template: WorkflowTemplates.REMINDER,
|
||||
defaultTemplates,
|
||||
});
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
test("matches body with HTML stripped (body has HTML tags)", () => {
|
||||
const defaultTemplates = createDefaultTemplates({
|
||||
reminder: {
|
||||
body: "Plain text reminder body",
|
||||
subject: "Default reminder subject",
|
||||
},
|
||||
});
|
||||
|
||||
const result = detectMatchedTemplate({
|
||||
emailBody: "<p>Plain text reminder body</p>",
|
||||
emailSubject: defaultTemplates.reminder.subject!,
|
||||
template: WorkflowTemplates.REMINDER,
|
||||
defaultTemplates,
|
||||
});
|
||||
|
||||
expect(result).toBe(WorkflowTemplates.REMINDER);
|
||||
});
|
||||
|
||||
test("matches body with HTML stripped (template has HTML tags)", () => {
|
||||
const defaultTemplates = createDefaultTemplates({
|
||||
reminder: {
|
||||
body: "<div>Plain text reminder body</div>",
|
||||
subject: "Default reminder subject",
|
||||
},
|
||||
});
|
||||
|
||||
const result = detectMatchedTemplate({
|
||||
emailBody: "Plain text reminder body",
|
||||
emailSubject: defaultTemplates.reminder.subject!,
|
||||
template: WorkflowTemplates.REMINDER,
|
||||
defaultTemplates,
|
||||
});
|
||||
|
||||
expect(result).toBe(WorkflowTemplates.REMINDER);
|
||||
});
|
||||
});
|
||||
|
||||
describe("RATING template matching", () => {
|
||||
test("returns RATING when both body AND subject match default", () => {
|
||||
const defaultTemplates = createDefaultTemplates();
|
||||
|
||||
const result = detectMatchedTemplate({
|
||||
emailBody: defaultTemplates.rating.body!,
|
||||
emailSubject: defaultTemplates.rating.subject!,
|
||||
template: WorkflowTemplates.RATING,
|
||||
defaultTemplates,
|
||||
});
|
||||
|
||||
expect(result).toBe(WorkflowTemplates.RATING);
|
||||
});
|
||||
|
||||
test("returns null when body matches but subject is customized", () => {
|
||||
const defaultTemplates = createDefaultTemplates();
|
||||
|
||||
const result = detectMatchedTemplate({
|
||||
emailBody: defaultTemplates.rating.body!,
|
||||
emailSubject: "Custom rating subject",
|
||||
template: WorkflowTemplates.RATING,
|
||||
defaultTemplates,
|
||||
});
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
test("returns null when subject matches but body is customized", () => {
|
||||
const defaultTemplates = createDefaultTemplates();
|
||||
|
||||
const result = detectMatchedTemplate({
|
||||
emailBody: "Custom rating body",
|
||||
emailSubject: defaultTemplates.rating.subject!,
|
||||
template: WorkflowTemplates.RATING,
|
||||
defaultTemplates,
|
||||
});
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("template detection regardless of template field value", () => {
|
||||
test("detects REMINDER even when template field is CUSTOM", () => {
|
||||
const defaultTemplates = createDefaultTemplates();
|
||||
|
||||
const result = detectMatchedTemplate({
|
||||
emailBody: defaultTemplates.reminder.body!,
|
||||
emailSubject: defaultTemplates.reminder.subject!,
|
||||
template: WorkflowTemplates.CUSTOM,
|
||||
defaultTemplates,
|
||||
});
|
||||
|
||||
expect(result).toBe(WorkflowTemplates.REMINDER);
|
||||
});
|
||||
|
||||
test("detects RATING even when template field is CUSTOM", () => {
|
||||
const defaultTemplates = createDefaultTemplates();
|
||||
|
||||
const result = detectMatchedTemplate({
|
||||
emailBody: defaultTemplates.rating.body!,
|
||||
emailSubject: defaultTemplates.rating.subject!,
|
||||
template: WorkflowTemplates.CUSTOM,
|
||||
defaultTemplates,
|
||||
});
|
||||
|
||||
expect(result).toBe(WorkflowTemplates.RATING);
|
||||
});
|
||||
|
||||
test("detects REMINDER even when template field is RATING", () => {
|
||||
const defaultTemplates = createDefaultTemplates();
|
||||
|
||||
const result = detectMatchedTemplate({
|
||||
emailBody: defaultTemplates.reminder.body!,
|
||||
emailSubject: defaultTemplates.reminder.subject!,
|
||||
template: WorkflowTemplates.RATING,
|
||||
defaultTemplates,
|
||||
});
|
||||
|
||||
expect(result).toBe(WorkflowTemplates.REMINDER);
|
||||
});
|
||||
});
|
||||
|
||||
describe("edge cases", () => {
|
||||
test("returns null when default body is null", () => {
|
||||
const defaultTemplates = createDefaultTemplates({
|
||||
reminder: { body: null, subject: "Default reminder subject" },
|
||||
rating: { body: null, subject: "Default rating subject" },
|
||||
});
|
||||
|
||||
const result = detectMatchedTemplate({
|
||||
emailBody: "Some body content",
|
||||
emailSubject: "Default reminder subject",
|
||||
template: WorkflowTemplates.REMINDER,
|
||||
defaultTemplates,
|
||||
});
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
test("returns null when default subject is null", () => {
|
||||
const defaultTemplates = createDefaultTemplates({
|
||||
reminder: { body: "Default reminder body", subject: null },
|
||||
rating: { body: "Default rating body", subject: null },
|
||||
});
|
||||
|
||||
const result = detectMatchedTemplate({
|
||||
emailBody: "Default reminder body",
|
||||
emailSubject: "Some subject",
|
||||
template: WorkflowTemplates.REMINDER,
|
||||
defaultTemplates,
|
||||
});
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
test("handles & entity in body comparison", () => {
|
||||
const defaultTemplates = createDefaultTemplates({
|
||||
reminder: {
|
||||
body: "Date & Time",
|
||||
subject: "Default reminder subject",
|
||||
},
|
||||
});
|
||||
|
||||
const result = detectMatchedTemplate({
|
||||
emailBody: "Date & Time",
|
||||
emailSubject: defaultTemplates.reminder.subject!,
|
||||
template: WorkflowTemplates.REMINDER,
|
||||
defaultTemplates,
|
||||
});
|
||||
|
||||
expect(result).toBe(WorkflowTemplates.REMINDER);
|
||||
});
|
||||
|
||||
test("prioritizes REMINDER over RATING when both could match", () => {
|
||||
const defaultTemplates = createDefaultTemplates({
|
||||
reminder: { body: "Same body", subject: "Same subject" },
|
||||
rating: { body: "Same body", subject: "Same subject" },
|
||||
});
|
||||
|
||||
const result = detectMatchedTemplate({
|
||||
emailBody: "Same body",
|
||||
emailSubject: "Same subject",
|
||||
template: WorkflowTemplates.RATING,
|
||||
defaultTemplates,
|
||||
});
|
||||
|
||||
expect(result).toBe(WorkflowTemplates.REMINDER);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user