fixes unit tests

This commit is contained in:
pandeymangg
2025-11-05 11:57:35 +05:30
parent e7d8803a13
commit f349f7199d
9 changed files with 147 additions and 100 deletions

View File

@@ -360,6 +360,7 @@ describe("Response Utils", () => {
});
});
// TODO: Fix this test after the survey editor poc is merged
describe("extractSurveyDetails", () => {
const mockSurvey: Partial<TSurvey> = {
id: "survey1",

View File

@@ -1,6 +1,7 @@
import { describe, expect, test, vi } from "vitest";
import { TJsEnvironmentStateSurvey } from "@formbricks/types/js";
import { TResponseData, TResponseVariables } from "@formbricks/types/responses";
import { TSurveyBlockLogicAction } from "@formbricks/types/surveys/blocks";
import { TConditionGroup, TSingleCondition } from "@formbricks/types/surveys/logic";
import { TSurveyLogic, TSurveyLogicAction, TSurveyQuestionTypeEnum } from "@formbricks/types/surveys/types";
import {
@@ -206,13 +207,13 @@ describe("surveyLogic", () => {
});
test("getUpdatedActionBody returns new action bodies correctly", () => {
const base: TSurveyLogicAction = { id: "A", objective: "requireAnswer", target: "q" };
const base: TSurveyBlockLogicAction = { id: "A", objective: "requireAnswer", target: "q" };
const calc = getUpdatedActionBody(base, "calculate");
expect(calc.objective).toBe("calculate");
const req = getUpdatedActionBody(calc, "requireAnswer");
expect(req.objective).toBe("requireAnswer");
const jump = getUpdatedActionBody(req, "jumpToQuestion");
expect(jump.objective).toBe("jumpToQuestion");
const jump = getUpdatedActionBody(req, "jumpToBlock");
expect(jump.objective).toBe("jumpToBlock");
});
test("evaluateLogic handles AND/OR groups and single conditions", () => {
@@ -244,7 +245,7 @@ describe("surveyLogic", () => {
test("performActions calculates, requires, and jumps correctly", () => {
const data: TResponseData = { q: "5" };
const initialVars: TResponseVariables = {};
const actions: TSurveyLogicAction[] = [
const actions: TSurveyBlockLogicAction[] = [
{
id: "a1",
objective: "calculate",
@@ -253,7 +254,7 @@ describe("surveyLogic", () => {
value: { type: "static", value: 3 },
},
{ id: "a2", objective: "requireAnswer", target: "q2" },
{ id: "a3", objective: "jumpToQuestion", target: "q3" },
{ id: "a3", objective: "jumpToBlock", target: "q3" },
];
const result = performActions(mockSurvey, actions, data, initialVars);
expect(result.calculations.v).toBe(3);

View File

@@ -145,7 +145,7 @@ describe("recall utility functions", () => {
const headline = { en: "How do you like #recall:product/fallback:ournbspproduct#?" };
const survey = {
id: "test-survey",
questions: [{ id: "product", headline: { en: "Product Question" } }],
blocks: [{ id: "b1", elements: [{ id: "product", headline: { en: "Product Question" } }] }],
hiddenFields: { fieldIds: [] },
variables: [],
} as any;
@@ -158,7 +158,7 @@ describe("recall utility functions", () => {
const headline = { en: "Rate #recall:product/fallback:ournbspproduct#" };
const survey = {
id: "test-survey",
questions: [{ id: "product", headline: { en: "Product Question" } }],
blocks: [{ id: "b1", elements: [{ id: "product", headline: { en: "Product Question" } }] }],
hiddenFields: { fieldIds: [] },
variables: [],
} as any;
@@ -171,7 +171,7 @@ describe("recall utility functions", () => {
const headline = { en: "Your email is #recall:email/fallback:notnbspprovided#" };
const survey: TSurvey = {
id: "test-survey",
questions: [],
blocks: [],
hiddenFields: { fieldIds: ["email"] },
variables: [],
} as unknown as TSurvey;
@@ -184,7 +184,7 @@ describe("recall utility functions", () => {
const headline = { en: "Your plan is #recall:plan/fallback:unknown#" };
const survey: TSurvey = {
id: "test-survey",
questions: [],
blocks: [],
hiddenFields: { fieldIds: [] },
variables: [{ id: "plan", name: "Subscription Plan" }],
} as unknown as TSurvey;
@@ -207,7 +207,7 @@ describe("recall utility functions", () => {
};
const survey = {
id: "test-survey",
questions: [{ id: "inner", headline: { en: "Inner with @outer" } }],
blocks: [{ id: "b1", elements: [{ id: "inner", headline: { en: "Inner with @outer" } }] }],
hiddenFields: { fieldIds: [] },
variables: [],
} as any;
@@ -241,41 +241,56 @@ describe("recall utility functions", () => {
test("identifies question with empty fallback value", () => {
const questionHeadline = { en: "Question with #recall:id1/fallback:# empty fallback" };
const survey = {
questions: [
blocks: [
{
id: "q1",
headline: questionHeadline,
id: "b1",
elements: [
{
id: "q1",
headline: questionHeadline,
},
],
},
],
} as any;
const result = checkForEmptyFallBackValue(survey, "en");
expect(result).toBe(survey.questions[0]);
expect(result).toBe(survey.blocks[0].elements[0]);
});
test("identifies question with empty fallback in subheader", () => {
const questionSubheader = { en: "Subheader with #recall:id1/fallback:# empty fallback" };
const survey = {
questions: [
blocks: [
{
id: "q1",
headline: { en: "Normal question" },
subheader: questionSubheader,
id: "b1",
elements: [
{
id: "q1",
headline: { en: "Normal question" },
subheader: questionSubheader,
},
],
},
],
} as any;
const result = checkForEmptyFallBackValue(survey, "en");
expect(result).toBe(survey.questions[0]);
expect(result).toBe(survey.blocks[0].elements[0]);
});
test("returns null when no empty fallback values are found", () => {
const questionHeadline = { en: "Question with #recall:id1/fallback:default# valid fallback" };
const survey = {
questions: [
blocks: [
{
id: "q1",
headline: questionHeadline,
id: "b1",
elements: [
{
id: "q1",
headline: questionHeadline,
},
],
},
],
} as any;
@@ -288,16 +303,21 @@ describe("recall utility functions", () => {
describe("replaceHeadlineRecall", () => {
test("processes all questions in a survey", () => {
const survey: TSurvey = {
questions: [
blocks: [
{
id: "q1",
headline: { en: "Question with #recall:id1/fallback:default#" },
id: "b1",
elements: [
{
id: "q1",
headline: { en: "Question with #recall:id1/fallback:default#" },
},
{
id: "q2",
headline: { en: "Another with #recall:id2/fallback:other#" },
},
],
},
{
id: "q2",
headline: { en: "Another with #recall:id2/fallback:other#" },
},
] as unknown as TSurveyQuestion[],
],
hiddenFields: { fieldIds: [] },
variables: [],
} as unknown as TSurvey;
@@ -308,8 +328,8 @@ describe("recall utility functions", () => {
// Verify recallToHeadline was called for each question
expect(result).not.toBe(survey); // Should be a clone
expect(result.questions[0].headline).not.toEqual(survey.questions[0].headline);
expect(result.questions[1].headline).not.toEqual(survey.questions[1].headline);
expect(result.blocks[0].elements[0].headline).not.toEqual(survey.blocks[0].elements[0].headline);
expect(result.blocks[0].elements[1].headline).not.toEqual(survey.blocks[0].elements[1].headline);
});
});
@@ -317,10 +337,15 @@ describe("recall utility functions", () => {
test("extracts recall items from text", () => {
const text = "Text with #recall:id1/fallback:val1# and #recall:id2/fallback:val2#";
const survey: TSurvey = {
questions: [
{ id: "id1", headline: { en: "Question One" } },
{ id: "id2", headline: { en: "Question Two" } },
] as unknown as TSurveyQuestion[],
blocks: [
{
id: "b1",
elements: [
{ id: "id1", headline: { en: "Question One" } },
{ id: "id2", headline: { en: "Question Two" } },
],
},
],
hiddenFields: { fieldIds: [] },
variables: [],
} as unknown as TSurvey;
@@ -339,7 +364,7 @@ describe("recall utility functions", () => {
test("handles hidden fields in recall items", () => {
const text = "Text with #recall:hidden1/fallback:val1#";
const survey: TSurvey = {
questions: [],
blocks: [],
hiddenFields: { fieldIds: ["hidden1"] },
variables: [],
} as unknown as TSurvey;
@@ -354,7 +379,7 @@ describe("recall utility functions", () => {
test("handles variables in recall items", () => {
const text = "Text with #recall:var1/fallback:val1#";
const survey: TSurvey = {
questions: [],
blocks: [],
hiddenFields: { fieldIds: [] },
variables: [{ id: "var1", name: "Variable One" }],
} as unknown as TSurvey;

View File

@@ -146,7 +146,8 @@ export const checkForEmptyFallBackValue = (survey: TSurvey, language: string): T
// Processes each question in a survey to ensure headlines are formatted correctly for recall and return the modified survey.
export const replaceHeadlineRecall = <T extends TSurvey>(survey: T, language: string): T => {
const modifiedSurvey = structuredClone(survey);
modifiedSurvey.questions.forEach((question) => {
const questions = modifiedSurvey.blocks.flatMap((b) => b.elements);
questions.forEach((question) => {
question.headline = recallToHeadline(question.headline, modifiedSurvey, false, language);
});
return modifiedSurvey;

View File

@@ -6,13 +6,12 @@ import { ImagePlusIcon, TrashIcon } from "lucide-react";
import { useCallback, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { type TI18nString } from "@formbricks/types/i18n";
import { TSurveyElement } from "@formbricks/types/surveys/elements";
import { TSurveyElement, TSurveyElementTypeEnum } from "@formbricks/types/surveys/elements";
import {
TSurvey,
TSurveyEndScreenCard,
TSurveyQuestion,
TSurveyQuestionChoice,
TSurveyQuestionTypeEnum,
TSurveyRedirectUrlCard,
} from "@formbricks/types/surveys/types";
import { TUserLocale } from "@formbricks/types/user";
@@ -93,7 +92,7 @@ export const QuestionFormInput = ({
const defaultLanguageCode =
localSurvey.languages.filter((lang) => lang.default)[0]?.language.code ?? "default";
const usedLanguageCode = selectedLanguageCode === defaultLanguageCode ? "default" : selectedLanguageCode;
const questions = localSurvey.blocks?.flatMap((block) => block.elements) ?? localSurvey.questions;
const questions = localSurvey.blocks.flatMap((block) => block.elements);
const question: TSurveyElement = questions[questionIdx];
const isChoice = id.includes("choice");
@@ -147,9 +146,9 @@ export const QuestionFormInput = ({
(question &&
(id.includes(".")
? // Handle nested properties
(question[id.split(".")[0] as keyof TSurveyQuestion] as any)?.[id.split(".")[1]]
(question[id.split(".")[0] as keyof TSurveyElement] as any)?.[id.split(".")[1]]
: // Original behavior
(question[id as keyof TSurveyQuestion] as TI18nString))) ||
(question[id as keyof TSurveyElement] as TI18nString))) ||
createI18nString("", surveyLanguageCodes)
);
}, [
@@ -297,7 +296,7 @@ export const QuestionFormInput = ({
const renderRemoveDescriptionButton = () => {
if (
question &&
(question.type === TSurveyQuestionTypeEnum.CTA || question.type === TSurveyQuestionTypeEnum.Consent)
(question.type === TSurveyElementTypeEnum.CTA || question.type === TSurveyElementTypeEnum.Consent)
) {
return false;
}

View File

@@ -225,7 +225,7 @@ describe("utils", () => {
pin: null,
} as unknown as TSurvey;
const result = getEndingCardText(survey, "headline", surveyLanguageCodes, 0);
const result = getEndingCardText(survey, [], "headline", surveyLanguageCodes, 0);
expect(result).toEqual(createI18nString("End Screen", surveyLanguageCodes));
});
@@ -257,7 +257,7 @@ describe("utils", () => {
pin: null,
} as unknown as TSurvey;
const result = getEndingCardText(survey, "headline", surveyLanguageCodes, 0);
const result = getEndingCardText(survey, [], "headline", surveyLanguageCodes, 0);
expect(result).toEqual(createI18nString("", surveyLanguageCodes));
});
});
@@ -294,14 +294,19 @@ describe("utils", () => {
createdAt: new Date(),
updatedAt: new Date(),
status: "draft",
questions: [
blocks: [
{
id: "q1",
type: TSurveyQuestionTypeEnum.OpenText,
headline: createI18nString("Question?", surveyLanguageCodes),
required: true,
imageUrl: "https://example.com/image.jpg",
} as unknown as TSurveyQuestion,
id: "b1",
elements: [
{
id: "q1",
type: TSurveyQuestionTypeEnum.OpenText,
headline: createI18nString("Question?", surveyLanguageCodes),
required: true,
imageUrl: "https://example.com/image.jpg",
},
],
},
],
welcomeCard: { enabled: true } as unknown as TSurvey["welcomeCard"],
styling: {},
@@ -326,14 +331,19 @@ describe("utils", () => {
createdAt: new Date(),
updatedAt: new Date(),
status: "draft",
questions: [
blocks: [
{
id: "q1",
type: TSurveyQuestionTypeEnum.OpenText,
headline: createI18nString("Question?", surveyLanguageCodes),
required: true,
videoUrl: "https://example.com/video.mp4",
} as unknown as TSurveyQuestion,
id: "b1",
elements: [
{
id: "q1",
type: TSurveyQuestionTypeEnum.OpenText,
headline: createI18nString("Question?", surveyLanguageCodes),
required: true,
videoUrl: "https://example.com/video.mp4",
},
],
},
],
welcomeCard: { enabled: true } as unknown as TSurvey["welcomeCard"],
styling: {},
@@ -358,13 +368,18 @@ describe("utils", () => {
createdAt: new Date(),
updatedAt: new Date(),
status: "draft",
questions: [
blocks: [
{
id: "q1",
type: TSurveyQuestionTypeEnum.OpenText,
headline: createI18nString("Question?", surveyLanguageCodes),
required: true,
} as unknown as TSurveyQuestion,
id: "b1",
elements: [
{
id: "q1",
type: TSurveyQuestionTypeEnum.OpenText,
headline: createI18nString("Question?", surveyLanguageCodes),
required: true,
},
],
},
],
welcomeCard: { enabled: true } as unknown as TSurvey["welcomeCard"],
styling: {},

View File

@@ -1,12 +1,11 @@
import { TFunction } from "i18next";
import { type TI18nString } from "@formbricks/types/i18n";
import { TSurveyElement } from "@formbricks/types/surveys/elements";
import {
TSurvey,
TSurveyMatrixQuestion,
TSurveyMultipleChoiceQuestion,
TSurveyQuestion,
} from "@formbricks/types/surveys/types";
TSurveyElement,
TSurveyMatrixElement,
TSurveyMultipleChoiceElement,
} from "@formbricks/types/surveys/elements";
import { TSurvey } from "@formbricks/types/surveys/types";
import { createI18nString } from "@/lib/i18n/utils";
import { isLabelValidForAllLanguages } from "@/lib/i18n/utils";
@@ -22,21 +21,21 @@ export const getIndex = (id: string, isChoice: boolean) => {
};
export const getChoiceLabel = (
question: TSurveyQuestion,
question: TSurveyElement,
choiceIdx: number,
surveyLanguageCodes: string[]
): TI18nString => {
const choiceQuestion = question as TSurveyMultipleChoiceQuestion;
const choiceQuestion = question as TSurveyMultipleChoiceElement;
return choiceQuestion.choices[choiceIdx]?.label || createI18nString("", surveyLanguageCodes);
};
export const getMatrixLabel = (
question: TSurveyQuestion,
question: TSurveyElement,
idx: number,
surveyLanguageCodes: string[],
type: "row" | "column"
): TI18nString => {
const matrixQuestion = question as TSurveyMatrixQuestion;
const matrixQuestion = question as TSurveyMatrixElement;
const matrixFields = type === "row" ? matrixQuestion.rows : matrixQuestion.columns;
return matrixFields[idx]?.label || createI18nString("", surveyLanguageCodes);
};
@@ -59,7 +58,8 @@ export const getEndingCardText = (
): TI18nString => {
const endingCardIndex = questionIdx - questions.length;
const card = survey.endings[endingCardIndex];
if (card.type === "endScreen") {
if (card?.type === "endScreen") {
return (card[id as keyof typeof card] as TI18nString) || createI18nString("", surveyLanguageCodes);
} else {
return createI18nString("", surveyLanguageCodes);

View File

@@ -100,28 +100,33 @@ describe("shared-conditions-factory", () => {
id: "survey1",
name: "Test Survey",
type: "app",
questions: [
blocks: [
{
id: "question1",
type: TSurveyQuestionTypeEnum.OpenText,
headline: { default: "Question 1" },
required: false,
inputType: "text",
charLimit: { enabled: false },
},
{
id: "matrix-question",
type: TSurveyQuestionTypeEnum.Matrix,
headline: { default: "Matrix Question" },
required: false,
shuffleOption: "none",
rows: [
{ id: "row1", label: { default: "Row 1" } },
{ id: "row2", label: { default: "Row 2" } },
],
columns: [
{ id: "col1", label: { default: "Column 1" } },
{ id: "col2", label: { default: "Column 2" } },
id: "block1",
elements: [
{
id: "question1",
type: TSurveyQuestionTypeEnum.OpenText,
headline: { default: "Question 1" },
required: false,
inputType: "text",
charLimit: { enabled: false },
},
{
id: "matrix-question",
type: TSurveyQuestionTypeEnum.Matrix,
headline: { default: "Matrix Question" },
required: false,
shuffleOption: "none",
rows: [
{ id: "row1", label: { default: "Row 1" } },
{ id: "row2", label: { default: "Row 2" } },
],
columns: [
{ id: "col1", label: { default: "Column 1" } },
{ id: "col2", label: { default: "Column 2" } },
],
},
],
},
],

View File

@@ -427,7 +427,7 @@ export function Survey({
) {
const { jumpTarget, requiredQuestionIds, calculations } = performActions(
localSurvey,
logic.actions as TSurveyBlockLogicAction[], //TODO: Temporary type assertion until the survey editor poc is completed, fix properly later
logic.actions as TSurveyBlockLogicAction[], // TODO: Temporary type assertion until the survey editor poc is completed, fix properly later
localResponseData,
calculationResults
);