mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-30 10:19:51 -06:00
fixes unit tests
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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: {},
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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" } },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user