chore: fix tests

This commit is contained in:
Matti Nannt
2025-11-12 11:16:31 +01:00
parent 3ea81dc7c1
commit 4b2d48397d
3 changed files with 160 additions and 797 deletions

View File

@@ -1,15 +1,6 @@
import { describe, expect, test } from "vitest";
import { TShuffleOption, TSurveyLogic, TSurveyQuestionTypeEnum } from "@formbricks/types/surveys/types";
import {
buildCTAQuestion,
buildConsentQuestion,
buildMultipleChoiceQuestion,
buildNPSQuestion,
buildOpenTextQuestion,
buildRatingQuestion,
buildSurvey,
createChoiceJumpLogic,
createJumpLogic,
getDefaultEndingCard,
getDefaultSurveyPreset,
getDefaultWelcomeCard,
@@ -19,595 +10,81 @@ import {
const mockT = (props: any): string => (typeof props === "string" ? props : props.key);
describe("Survey Builder", () => {
describe("buildMultipleChoiceQuestion", () => {
test("creates a single choice question with required fields", () => {
const question = buildMultipleChoiceQuestion({
headline: "Test Question",
type: TSurveyQuestionTypeEnum.MultipleChoiceSingle,
choices: ["Option 1", "Option 2", "Option 3"],
t: mockT,
});
expect(question).toMatchObject({
type: TSurveyQuestionTypeEnum.MultipleChoiceSingle,
headline: { default: "Test Question" },
choices: expect.arrayContaining([
expect.objectContaining({ label: { default: "Option 1" } }),
expect.objectContaining({ label: { default: "Option 2" } }),
expect.objectContaining({ label: { default: "Option 3" } }),
]),
buttonLabel: { default: "common.next" },
backButtonLabel: { default: "common.back" },
shuffleOption: "none",
required: false,
});
expect(question.choices.length).toBe(3);
expect(question.id).toBeDefined();
describe("Helper Functions", () => {
test("getDefaultSurveyPreset returns expected default survey preset", () => {
const preset = getDefaultSurveyPreset(mockT);
expect(preset.name).toBe("New Survey");
// test welcomeCard and endings
expect(preset.welcomeCard).toHaveProperty("headline");
expect(preset.endings).toHaveLength(1);
expect(preset.endings[0]).toHaveProperty("headline");
expect(preset.hiddenFields).toEqual(hiddenFieldsDefault);
expect(preset.blocks).toEqual([]);
});
test("creates a multiple choice question with provided ID", () => {
const customId = "custom-id-123";
const question = buildMultipleChoiceQuestion({
id: customId,
headline: "Test Question",
type: TSurveyQuestionTypeEnum.MultipleChoiceMulti,
choices: ["Option 1", "Option 2"],
t: mockT,
test("getDefaultWelcomeCard returns expected welcome card", () => {
const welcomeCard = getDefaultWelcomeCard(mockT);
expect(welcomeCard).toMatchObject({
enabled: false,
headline: { default: "templates.default_welcome_card_headline" },
timeToFinish: false,
showResponseCount: false,
});
expect(question.id).toBe(customId);
expect(question.type).toBe(TSurveyQuestionTypeEnum.MultipleChoiceMulti);
// Check that the welcome card is properly structured
expect(welcomeCard).toHaveProperty("enabled");
expect(welcomeCard).toHaveProperty("headline");
expect(welcomeCard).toHaveProperty("showResponseCount");
expect(welcomeCard).toHaveProperty("timeToFinish");
});
test("handles 'other' option correctly", () => {
const choices = ["Option 1", "Option 2", "Other"];
const question = buildMultipleChoiceQuestion({
headline: "Test Question",
type: TSurveyQuestionTypeEnum.MultipleChoiceSingle,
choices,
containsOther: true,
t: mockT,
test("getDefaultEndingCard returns expected ending card", () => {
const languages: string[] = [];
const endingCard = getDefaultEndingCard(languages, mockT);
expect(endingCard).toMatchObject({
type: "endScreen",
headline: { default: "templates.default_ending_card_headline" },
subheader: { default: "templates.default_ending_card_subheader" },
});
expect(question.choices.length).toBe(3);
expect(question.choices[2].id).toBe("other");
expect(endingCard.id).toBeDefined();
expect(endingCard).toHaveProperty("buttonLabel");
expect(endingCard).toHaveProperty("buttonLink");
});
test("uses provided choice IDs when available", () => {
const choiceIds = ["id1", "id2", "id3"];
const question = buildMultipleChoiceQuestion({
headline: "Test Question",
type: TSurveyQuestionTypeEnum.MultipleChoiceSingle,
choices: ["Option 1", "Option 2", "Option 3"],
choiceIds,
t: mockT,
test("hiddenFieldsDefault has expected structure", () => {
expect(hiddenFieldsDefault).toMatchObject({
enabled: true,
fieldIds: [],
});
expect(question.choices[0].id).toBe(choiceIds[0]);
expect(question.choices[1].id).toBe(choiceIds[1]);
expect(question.choices[2].id).toBe(choiceIds[2]);
});
test("applies all optional parameters correctly", () => {
const logic: TSurveyLogic[] = [
{
id: "logic-1",
conditions: {
id: "cond-1",
connector: "and",
conditions: [],
},
actions: [],
},
];
test("buildSurvey returns built survey with overridden preset properties", () => {
const config = {
name: "Custom Survey",
role: "productManager" as const,
industries: ["saas" as const],
channels: ["link" as const],
description: "A custom survey description",
blocks: [],
endings: [getDefaultEndingCard([], mockT)],
hiddenFields: hiddenFieldsDefault,
};
const shuffleOption: TShuffleOption = "all";
const survey = buildSurvey(config, mockT);
const question = buildMultipleChoiceQuestion({
headline: "Test Question",
type: TSurveyQuestionTypeEnum.MultipleChoiceSingle,
subheader: "This is a subheader",
choices: ["Option 1", "Option 2"],
buttonLabel: "Custom Next",
backButtonLabel: "Custom Back",
shuffleOption,
required: false,
logic,
t: mockT,
});
// role, industries, channels, description
expect(survey.role).toBe(config.role);
expect(survey.industries).toEqual(config.industries);
expect(survey.channels).toEqual(config.channels);
expect(survey.description).toBe(config.description);
expect(question.subheader).toEqual({ default: "This is a subheader" });
expect(question.buttonLabel).toEqual({ default: "Custom Next" });
expect(question.backButtonLabel).toEqual({ default: "Custom Back" });
expect(question.shuffleOption).toBe("all");
expect(question.required).toBe(false);
expect(question.logic).toBe(logic);
});
});
// preset overrides
expect(survey.preset.name).toBe(config.name);
expect(survey.preset.endings).toEqual(config.endings);
expect(survey.preset.hiddenFields).toEqual(config.hiddenFields);
expect(survey.preset.blocks).toEqual(config.blocks);
describe("buildOpenTextQuestion", () => {
test("creates an open text question with required fields", () => {
const question = buildOpenTextQuestion({
headline: "Open Question",
inputType: "text",
t: mockT,
});
expect(question).toMatchObject({
type: TSurveyQuestionTypeEnum.OpenText,
headline: { default: "Open Question" },
inputType: "text",
buttonLabel: { default: "common.next" },
backButtonLabel: { default: "common.back" },
required: false,
charLimit: {
enabled: false,
},
});
expect(question.id).toBeDefined();
});
test("applies all optional parameters correctly", () => {
const logic: TSurveyLogic[] = [
{
id: "logic-1",
conditions: {
id: "cond-1",
connector: "and",
conditions: [],
},
actions: [],
},
];
const question = buildOpenTextQuestion({
id: "custom-id",
headline: "Open Question",
subheader: "Answer this question",
placeholder: "Type here",
buttonLabel: "Submit",
backButtonLabel: "Previous",
required: false,
longAnswer: true,
inputType: "email",
logic,
t: mockT,
});
expect(question.id).toBe("custom-id");
expect(question.subheader).toEqual({ default: "Answer this question" });
expect(question.placeholder).toEqual({ default: "Type here" });
expect(question.buttonLabel).toEqual({ default: "Submit" });
expect(question.backButtonLabel).toEqual({ default: "Previous" });
expect(question.required).toBe(false);
expect(question.longAnswer).toBe(true);
expect(question.inputType).toBe("email");
expect(question.logic).toBe(logic);
});
});
describe("buildRatingQuestion", () => {
test("creates a rating question with required fields", () => {
const question = buildRatingQuestion({
headline: "Rating Question",
scale: "number",
range: 5,
t: mockT,
});
expect(question).toMatchObject({
type: TSurveyQuestionTypeEnum.Rating,
headline: { default: "Rating Question" },
scale: "number",
range: 5,
buttonLabel: { default: "common.next" },
backButtonLabel: { default: "common.back" },
required: false,
isColorCodingEnabled: false,
});
expect(question.id).toBeDefined();
});
test("applies all optional parameters correctly", () => {
const logic: TSurveyLogic[] = [
{
id: "logic-1",
conditions: {
id: "cond-1",
connector: "and",
conditions: [],
},
actions: [],
},
];
const question = buildRatingQuestion({
id: "custom-id",
headline: "Rating Question",
subheader: "Rate us",
scale: "star",
range: 10,
lowerLabel: "Poor",
upperLabel: "Excellent",
buttonLabel: "Submit",
backButtonLabel: "Previous",
required: false,
isColorCodingEnabled: true,
logic,
t: mockT,
});
expect(question.id).toBe("custom-id");
expect(question.subheader).toEqual({ default: "Rate us" });
expect(question.scale).toBe("star");
expect(question.range).toBe(10);
expect(question.lowerLabel).toEqual({ default: "Poor" });
expect(question.upperLabel).toEqual({ default: "Excellent" });
expect(question.buttonLabel).toEqual({ default: "Submit" });
expect(question.backButtonLabel).toEqual({ default: "Previous" });
expect(question.required).toBe(false);
expect(question.isColorCodingEnabled).toBe(true);
expect(question.logic).toBe(logic);
});
});
describe("buildNPSQuestion", () => {
test("creates an NPS question with required fields", () => {
const question = buildNPSQuestion({
headline: "NPS Question",
t: mockT,
});
expect(question).toMatchObject({
type: TSurveyQuestionTypeEnum.NPS,
headline: { default: "NPS Question" },
buttonLabel: { default: "common.next" },
backButtonLabel: { default: "common.back" },
required: false,
isColorCodingEnabled: false,
});
expect(question.id).toBeDefined();
});
test("applies all optional parameters correctly", () => {
const logic: TSurveyLogic[] = [
{
id: "logic-1",
conditions: {
id: "cond-1",
connector: "and",
conditions: [],
},
actions: [],
},
];
const question = buildNPSQuestion({
id: "custom-id",
headline: "NPS Question",
subheader: "How likely are you to recommend us?",
lowerLabel: "Not likely",
upperLabel: "Very likely",
buttonLabel: "Submit",
backButtonLabel: "Previous",
required: false,
isColorCodingEnabled: true,
logic,
t: mockT,
});
expect(question.id).toBe("custom-id");
expect(question.subheader).toEqual({ default: "How likely are you to recommend us?" });
expect(question.lowerLabel).toEqual({ default: "Not likely" });
expect(question.upperLabel).toEqual({ default: "Very likely" });
expect(question.buttonLabel).toEqual({ default: "Submit" });
expect(question.backButtonLabel).toEqual({ default: "Previous" });
expect(question.required).toBe(false);
expect(question.isColorCodingEnabled).toBe(true);
expect(question.logic).toBe(logic);
});
});
describe("buildConsentQuestion", () => {
test("creates a consent question with required fields", () => {
const question = buildConsentQuestion({
headline: "Consent Question",
subheader: "",
label: "I agree to terms",
t: mockT,
});
expect(question).toMatchObject({
type: TSurveyQuestionTypeEnum.Consent,
headline: { default: "Consent Question" },
subheader: { default: "" },
label: { default: "I agree to terms" },
buttonLabel: { default: "common.next" },
backButtonLabel: { default: "common.back" },
required: false,
});
expect(question.id).toBeDefined();
});
test("applies all optional parameters correctly", () => {
const logic: TSurveyLogic[] = [
{
id: "logic-1",
conditions: {
id: "cond-1",
connector: "and",
conditions: [],
},
actions: [],
},
];
const question = buildConsentQuestion({
id: "custom-id",
headline: "Consent Question",
subheader: "Please read the terms",
label: "I agree to terms",
buttonLabel: "Submit",
backButtonLabel: "Previous",
required: false,
logic,
t: mockT,
});
expect(question.id).toBe("custom-id");
expect(question.subheader).toEqual({ default: "Please read the terms" });
expect(question.label).toEqual({ default: "I agree to terms" });
expect(question.buttonLabel).toEqual({ default: "Submit" });
expect(question.backButtonLabel).toEqual({ default: "Previous" });
expect(question.required).toBe(false);
expect(question.logic).toBe(logic);
});
});
describe("buildCTAQuestion", () => {
test("creates a CTA question with required fields", () => {
const question = buildCTAQuestion({
headline: "CTA Question",
subheader: "",
buttonExternal: false,
t: mockT,
});
expect(question).toMatchObject({
type: TSurveyQuestionTypeEnum.CTA,
headline: { default: "CTA Question" },
subheader: { default: "" },
buttonLabel: { default: "common.next" },
backButtonLabel: { default: "common.back" },
required: false,
buttonExternal: false,
});
expect(question.id).toBeDefined();
});
test("applies all optional parameters correctly", () => {
const logic: TSurveyLogic[] = [
{
id: "logic-1",
conditions: {
id: "cond-1",
connector: "and",
conditions: [],
},
actions: [],
},
];
const question = buildCTAQuestion({
id: "custom-id",
headline: "CTA Question",
subheader: "<p>Click the button</p>",
buttonLabel: "Click me",
buttonExternal: true,
buttonUrl: "https://example.com",
backButtonLabel: "Previous",
required: false,
dismissButtonLabel: "No thanks",
logic,
t: mockT,
});
expect(question.id).toBe("custom-id");
expect(question.subheader).toEqual({ default: "<p>Click the button</p>" });
expect(question.buttonLabel).toEqual({ default: "Click me" });
expect(question.buttonExternal).toBe(true);
expect(question.buttonUrl).toBe("https://example.com");
expect(question.backButtonLabel).toEqual({ default: "Previous" });
expect(question.required).toBe(false);
expect(question.dismissButtonLabel).toEqual({ default: "No thanks" });
expect(question.logic).toBe(logic);
});
test("handles external button with URL", () => {
const question = buildCTAQuestion({
headline: "CTA Question",
subheader: "",
buttonExternal: true,
buttonUrl: "https://formbricks.com",
t: mockT,
});
expect(question.buttonExternal).toBe(true);
expect(question.buttonUrl).toBe("https://formbricks.com");
});
});
// Test combinations of parameters for edge cases
describe("Edge cases", () => {
test("multiple choice question with empty choices array", () => {
const question = buildMultipleChoiceQuestion({
headline: "Test Question",
type: TSurveyQuestionTypeEnum.MultipleChoiceSingle,
choices: [],
t: mockT,
});
expect(question.choices).toEqual([]);
});
test("open text question with all parameters", () => {
const question = buildOpenTextQuestion({
id: "custom-id",
headline: "Open Question",
subheader: "Answer this question",
placeholder: "Type here",
buttonLabel: "Submit",
backButtonLabel: "Previous",
required: false,
longAnswer: true,
inputType: "email",
logic: [],
t: mockT,
});
expect(question).toMatchObject({
id: "custom-id",
type: TSurveyQuestionTypeEnum.OpenText,
headline: { default: "Open Question" },
subheader: { default: "Answer this question" },
placeholder: { default: "Type here" },
buttonLabel: { default: "Submit" },
backButtonLabel: { default: "Previous" },
required: false,
longAnswer: true,
inputType: "email",
logic: [],
});
// default values from getDefaultSurveyPreset
expect(survey.preset.welcomeCard).toHaveProperty("headline");
});
});
});
describe("Helper Functions", () => {
test("createJumpLogic returns valid jump logic", () => {
const sourceId = "q1";
const targetId = "q2";
const operator: "isClicked" = "isClicked";
const logic = createJumpLogic(sourceId, targetId, operator);
// Check structure
expect(logic).toHaveProperty("id");
expect(logic).toHaveProperty("conditions");
expect(logic.conditions).toHaveProperty("conditions");
expect(Array.isArray(logic.conditions.conditions)).toBe(true);
// Check one of the inner conditions
const condition = logic.conditions.conditions[0];
// Need to use type checking to ensure condition is a TSingleCondition not a TConditionGroup
if (!("connector" in condition)) {
expect(condition.leftOperand.value).toBe(sourceId);
expect(condition.operator).toBe(operator);
}
// Check actions
expect(Array.isArray(logic.actions)).toBe(true);
const action = logic.actions[0];
if (action.objective === "jumpToQuestion") {
expect(action.target).toBe(targetId);
}
});
test("createChoiceJumpLogic returns valid jump logic based on choice selection", () => {
const sourceId = "q1";
const choiceId = "choice1";
const targetId = "q2";
const logic = createChoiceJumpLogic(sourceId, choiceId, targetId);
expect(logic).toHaveProperty("id");
expect(logic.conditions).toHaveProperty("conditions");
const condition = logic.conditions.conditions[0];
if (!("connector" in condition)) {
expect(condition.leftOperand.value).toBe(sourceId);
expect(condition.operator).toBe("equals");
expect(condition.rightOperand?.value).toBe(choiceId);
}
const action = logic.actions[0];
if (action.objective === "jumpToQuestion") {
expect(action.target).toBe(targetId);
}
});
test("getDefaultWelcomeCard returns expected welcome card", () => {
const card = getDefaultWelcomeCard(mockT);
expect(card.enabled).toBe(false);
expect(card.headline).toEqual({ default: "templates.default_welcome_card_headline" });
expect(card.subheader).toEqual({ default: "templates.default_welcome_card_html" });
expect(card.buttonLabel).toEqual({ default: "templates.default_welcome_card_button_label" });
// boolean flags
expect(card.timeToFinish).toBe(false);
expect(card.showResponseCount).toBe(false);
});
test("getDefaultEndingCard returns expected end screen card", () => {
// Pass empty languages array to simulate no languages
const card = getDefaultEndingCard([], mockT);
expect(card).toHaveProperty("id");
expect(card.type).toBe("endScreen");
expect(card.headline).toEqual({ default: "templates.default_ending_card_headline" });
expect(card.subheader).toEqual({ default: "templates.default_ending_card_subheader" });
expect(card.buttonLabel).toEqual({ default: "templates.default_ending_card_button_label" });
expect(card.buttonLink).toBe("https://formbricks.com");
});
test("getDefaultSurveyPreset returns expected default survey preset", () => {
const preset = getDefaultSurveyPreset(mockT);
expect(preset.name).toBe("New Survey");
expect(preset.questions).toEqual([]);
// test welcomeCard and endings
expect(preset.welcomeCard).toHaveProperty("headline");
expect(Array.isArray(preset.endings)).toBe(true);
expect(preset.hiddenFields).toEqual(hiddenFieldsDefault);
});
test("buildSurvey returns built survey with overridden preset properties", () => {
const config = {
name: "Custom Survey",
industries: ["eCommerce"] as string[],
channels: ["link"],
description: "Test survey",
questions: [
{
id: "q1",
type: TSurveyQuestionTypeEnum.OpenText, // changed from "OpenText"
headline: { default: "Question 1" },
inputType: "text",
buttonLabel: { default: "Next" },
backButtonLabel: { default: "Back" },
required: true,
},
],
endings: [
{
id: "end1",
type: "endScreen",
headline: { default: "End Screen" },
subheader: { default: "Thanks" },
buttonLabel: { default: "Finish" },
buttonLink: "https://formbricks.com",
},
],
hiddenFields: { enabled: false, fieldIds: ["f1"] },
};
const survey = buildSurvey(config as any, mockT);
expect(survey.name).toBe(config.name);
expect(survey.industries).toEqual(config.industries);
expect(survey.channels).toEqual(config.channels);
expect(survey.description).toBe(config.description);
// preset overrides
expect(survey.preset.name).toBe(config.name);
expect(survey.preset.questions).toEqual(config.questions);
expect(survey.preset.endings).toEqual(config.endings);
expect(survey.preset.hiddenFields).toEqual(config.hiddenFields);
});
test("hiddenFieldsDefault has expected default configuration", () => {
expect(hiddenFieldsDefault).toEqual({ enabled: true, fieldIds: [] });
});
});

View File

@@ -1,164 +1,119 @@
import { beforeEach, describe, expect, test, vi } from "vitest";
import { TProject } from "@formbricks/types/project";
import { TSurveyQuestion, TSurveyQuestionTypeEnum } from "@formbricks/types/surveys/types";
import { TTemplate } from "@formbricks/types/templates";
import { getLocalizedValue } from "@/lib/i18n/utils";
import { type TProject } from "@formbricks/types/project";
import type { TSurveyElement } from "@formbricks/types/surveys/elements";
import type { TTemplate } from "@formbricks/types/templates";
import * as i18nUtils from "@/lib/i18n/utils";
import { structuredClone } from "@/lib/pollyfills/structuredClone";
import { replacePresetPlaceholders, replaceQuestionPresetPlaceholders } from "./templates";
import { replaceElementPresetPlaceholders, replacePresetPlaceholders } from "./templates";
// Mock the imported functions
vi.mock("@/lib/i18n/utils", () => ({
getLocalizedValue: vi.fn(),
}));
vi.mock("@/lib/i18n/utils");
vi.mock("@/lib/pollyfills/structuredClone");
vi.mock("@/lib/pollyfills/structuredClone", () => ({
structuredClone: vi.fn((obj) => JSON.parse(JSON.stringify(obj))),
}));
beforeEach(() => {
vi.clearAllMocks();
vi.mocked(structuredClone).mockImplementation((obj) => JSON.parse(JSON.stringify(obj)));
// Mock getLocalizedValue to return the value from the object
vi.mocked(i18nUtils.getLocalizedValue).mockImplementation((obj: any, lang: string) => obj?.[lang] || "");
});
describe("Template Utilities", () => {
beforeEach(() => {
vi.clearAllMocks();
});
describe("replaceElementPresetPlaceholders", () => {
test("returns original element when project is not provided", () => {
const element = {
type: "openText",
headline: { default: "Question about $[projectName]?" },
} as unknown as TSurveyElement;
describe("replaceQuestionPresetPlaceholders", () => {
test("returns original question when project is not provided", () => {
const question: TSurveyQuestion = {
id: "test-id",
type: TSurveyQuestionTypeEnum.OpenText,
headline: {
default: "Test Question $[projectName]",
},
} as unknown as TSurveyQuestion;
const result = replaceElementPresetPlaceholders(element, undefined as any);
const result = replaceQuestionPresetPlaceholders(question, undefined as unknown as TProject);
expect(result).toEqual(element);
});
expect(result).toEqual(question);
expect(structuredClone).not.toHaveBeenCalled();
test("replaces projectName placeholder in headline", () => {
const element = {
type: "openText",
headline: { default: "How do you like $[projectName]?" },
} as unknown as TSurveyElement;
const project = {
name: "TestProject",
} as unknown as TProject;
const result = replaceElementPresetPlaceholders(element, project);
// The function directly replaces without calling getLocalizedValue in the test scenario
expect(result.headline?.default).toBe("How do you like TestProject?");
});
test("replaces projectName placeholder in subheader", () => {
const question: TSurveyQuestion = {
id: "test-id",
type: TSurveyQuestionTypeEnum.OpenText,
headline: {
default: "Test Question",
},
subheader: {
default: "Subheader for $[projectName]",
},
} as unknown as TSurveyQuestion;
const element = {
type: "openText",
headline: { default: "Question" },
subheader: { default: "Subheader for $[projectName]" },
} as unknown as TSurveyElement;
const project: TProject = {
id: "project-id",
name: "Test Project",
organizationId: "org-id",
const project = {
name: "TestProject",
} as unknown as TProject;
// Mock for headline and subheader with correct return values
vi.mocked(getLocalizedValue).mockReturnValueOnce("Test Question");
vi.mocked(getLocalizedValue).mockReturnValueOnce("Subheader for $[projectName]");
const result = replaceElementPresetPlaceholders(element, project);
const result = replaceQuestionPresetPlaceholders(question, project);
expect(vi.mocked(getLocalizedValue)).toHaveBeenCalledTimes(2);
expect(result.subheader?.default).toBe("Subheader for Test Project");
expect(result.headline?.default).toBe("Question");
expect(result.subheader?.default).toBe("Subheader for TestProject");
});
test("handles missing headline and subheader", () => {
const question: TSurveyQuestion = {
id: "test-id",
type: TSurveyQuestionTypeEnum.OpenText,
} as unknown as TSurveyQuestion;
const element = {
type: "openText",
} as unknown as TSurveyElement;
const project: TProject = {
id: "project-id",
name: "Test Project",
organizationId: "org-id",
const project = {
name: "TestProject",
} as unknown as TProject;
const result = replaceQuestionPresetPlaceholders(question, project);
const result = replaceElementPresetPlaceholders(element, project);
expect(structuredClone).toHaveBeenCalledWith(question);
expect(result).toEqual(question);
expect(getLocalizedValue).not.toHaveBeenCalled();
expect(structuredClone).toHaveBeenCalledWith(element);
expect(result).toEqual(element);
});
});
describe("replacePresetPlaceholders", () => {
test("replaces projectName placeholder in template name and questions", () => {
const template: TTemplate = {
id: "template-1",
name: "Test Template",
description: "Template Description",
test("replaces projectName placeholder in template name and blocks", () => {
const mockTemplate = {
name: "Template 1",
preset: {
name: "$[projectName] Feedback",
questions: [
welcomeCard: { enabled: false, timeToFinish: false, showResponseCount: false },
blocks: [
{
id: "q1",
type: TSurveyQuestionTypeEnum.OpenText,
headline: {
default: "How do you like $[projectName]?",
},
},
{
id: "q2",
type: TSurveyQuestionTypeEnum.OpenText,
headline: {
default: "Another question",
},
subheader: {
default: "About $[projectName]",
},
id: "block1",
name: "Block 1",
elements: [
{
id: "elem1",
type: "openText",
headline: { default: "How would you rate $[projectName]?" },
required: true,
inputType: "text",
},
],
},
],
endings: [],
hiddenFields: { enabled: true, fieldIds: [] },
},
category: "product",
} as unknown as TTemplate;
const project = {
name: "Awesome App",
};
name: "TestProject",
} as TProject;
// Mock getLocalizedValue to return the original strings with placeholders
vi.mocked(getLocalizedValue)
.mockReturnValueOnce("How do you like $[projectName]?")
.mockReturnValueOnce("Another question")
.mockReturnValueOnce("About $[projectName]");
const result = replacePresetPlaceholders(mockTemplate, project);
const result = replacePresetPlaceholders(template, project);
expect(result.preset.name).toBe("Awesome App Feedback");
expect(structuredClone).toHaveBeenCalledWith(template.preset);
// Verify that replaceQuestionPresetPlaceholders was applied to both questions
expect(vi.mocked(getLocalizedValue)).toHaveBeenCalledTimes(3);
expect(result.preset.questions[0].headline?.default).toBe("How do you like Awesome App?");
expect(result.preset.questions[1].subheader?.default).toBe("About Awesome App");
});
test("maintains other template properties", () => {
const template: TTemplate = {
id: "template-1",
name: "Test Template",
description: "Template Description",
preset: {
name: "$[projectName] Feedback",
questions: [],
},
category: "product",
} as unknown as TTemplate;
const project = {
name: "Awesome App",
};
const result = replacePresetPlaceholders(template, project) as unknown as {
name: string;
description: string;
};
expect(result.name).toBe(template.name);
expect(result.description).toBe(template.description);
expect(structuredClone).toHaveBeenCalledWith(mockTemplate.preset);
expect(result.preset.name).toBe("TestProject Feedback");
expect(result.preset.blocks[0].elements[0].headline?.default).toBe("How would you rate TestProject?");
});
});
});

View File

@@ -1,110 +1,43 @@
import "@testing-library/jest-dom/vitest";
import { describe, expect, test, vi } from "vitest";
import { TProject } from "@formbricks/types/project";
import { TSurveyQuestion, TSurveyQuestionTypeEnum } from "@formbricks/types/surveys/types";
import type { TSurveyElement } from "@formbricks/types/surveys/elements";
import { TTemplate } from "@formbricks/types/templates";
import { getLocalizedValue } from "@/lib/i18n/utils";
import { structuredClone } from "@/lib/pollyfills/structuredClone";
import { replacePresetPlaceholders, replaceQuestionPresetPlaceholders } from "@/lib/utils/templates";
import { replacePresetPlaceholders } from "@/lib/utils/templates";
import { getChannelMapping, getIndustryMapping, getRoleMapping } from "./utils";
vi.mock("@/lib/i18n/utils", () => ({
getLocalizedValue: vi.fn(),
}));
vi.mock("@/lib/pollyfills/structuredClone", () => ({
structuredClone: vi.fn((val) => JSON.parse(JSON.stringify(val))),
}));
describe("Template utils", () => {
test("replaceQuestionPresetPlaceholders replaces project name in headline and subheader", () => {
const mockQuestion = {
id: "q1",
type: TSurveyQuestionTypeEnum.OpenText,
headline: {
default: "How would you rate $[projectName]?",
},
subheader: {
default: "Tell us about $[projectName]",
},
required: false,
} as unknown as TSurveyQuestion;
const mockProject = {
id: "project-1",
name: "TestProject",
createdAt: new Date(),
updatedAt: new Date(),
} as unknown as TProject;
// Reset and setup mocks with simple return values
vi.mocked(getLocalizedValue).mockReset();
vi.mocked(getLocalizedValue)
.mockReturnValueOnce("How would you rate $[projectName]?")
.mockReturnValueOnce("Tell us about $[projectName]");
const result = replaceQuestionPresetPlaceholders(mockQuestion, mockProject);
expect(structuredClone).toHaveBeenCalledWith(mockQuestion);
expect(getLocalizedValue).toHaveBeenCalledTimes(2);
expect(result.headline?.default).toBe("How would you rate TestProject?");
expect(result.subheader?.default).toBe("Tell us about TestProject");
});
test("replaceQuestionPresetPlaceholders returns original question if project is null", () => {
const mockQuestion = {
id: "q1",
type: TSurveyQuestionTypeEnum.OpenText,
headline: {
default: "How would you rate $[projectName]?",
},
required: false,
} as unknown as TSurveyQuestion;
const result = replaceQuestionPresetPlaceholders(mockQuestion, null as unknown as TProject);
expect(result).toBe(mockQuestion);
});
test("replaceQuestionPresetPlaceholders handles missing subheader", () => {
const mockQuestion = {
id: "q1",
type: TSurveyQuestionTypeEnum.OpenText,
headline: {
default: "How would you rate $[projectName]?",
},
required: false,
} as unknown as TSurveyQuestion;
const mockProject = {
id: "project-1",
name: "TestProject",
createdAt: new Date(),
updatedAt: new Date(),
} as unknown as TProject;
vi.mocked(getLocalizedValue).mockReturnValueOnce("How would you rate $[projectName]?");
const result = replaceQuestionPresetPlaceholders(mockQuestion, mockProject);
expect(result.headline?.default).toBe("How would you rate TestProject?");
expect(result.subheader).toBeUndefined();
});
test("replacePresetPlaceholders replaces project name in template", () => {
test("replacePresetPlaceholders replaces project name in template with blocks", () => {
const mockTemplate: TTemplate = {
name: "Test Template",
description: "Template description",
preset: {
name: "$[projectName] Feedback",
questions: [
welcomeCard: { enabled: false, timeToFinish: false, showResponseCount: false },
blocks: [
{
id: "q1",
type: TSurveyQuestionTypeEnum.OpenText,
headline: {
default: "How would you rate $[projectName]?",
},
required: false,
} as unknown as TSurveyQuestion,
id: "block1",
name: "Block 1",
elements: [
{
id: "elem1",
type: "openText",
headline: {
default: "How would you rate $[projectName]?",
},
required: false,
inputType: "text",
} as unknown as TSurveyElement,
],
},
],
endings: [],
hiddenFields: { enabled: true, fieldIds: [] },
} as any,
};
@@ -112,13 +45,11 @@ describe("Template utils", () => {
name: "TestProject",
};
vi.mocked(getLocalizedValue).mockReturnValueOnce("How would you rate $[projectName]?");
const result = replacePresetPlaceholders(mockTemplate, mockProject);
expect(structuredClone).toHaveBeenCalledWith(mockTemplate.preset);
expect(result.preset.name).toBe("TestProject Feedback");
expect(result.preset.questions[0].headline?.default).toBe("How would you rate TestProject?");
expect(result.preset.blocks[0].elements[0].headline?.default).toBe("How would you rate TestProject?");
});
test("getChannelMapping returns correct channel mappings", () => {