Files
formbricks-formbricks/apps/web/lib/responses.test.ts
2025-09-11 06:57:11 +00:00

499 lines
15 KiB
TypeScript

import { describe, expect, test, vi } from "vitest";
import { TSurveyQuestionType, TSurveyQuestionTypeEnum } from "@formbricks/types/surveys/types";
import { convertResponseValue, getQuestionResponseMapping, processResponseData } from "./responses";
// Mock the recall and i18n utils
vi.mock("@/lib/utils/recall", () => ({
parseRecallInfo: vi.fn((text) => text),
}));
vi.mock("./i18n/utils", () => ({
getLocalizedValue: vi.fn((obj, lang) => obj[lang] || obj.default),
getLanguageCode: vi.fn((surveyLanguages, languageCode) => {
if (!surveyLanguages?.length || !languageCode) return null; // Changed from "default" to null
const language = surveyLanguages.find((surveyLanguage) => surveyLanguage.language.code === languageCode);
return language?.default ? "default" : language?.language.code || "default";
}),
}));
describe("Response Processing", () => {
describe("processResponseData", () => {
test("should handle string input", () => {
expect(processResponseData("test")).toBe("test");
});
test("should handle number input", () => {
expect(processResponseData(42)).toBe("42");
});
test("should handle array input", () => {
expect(processResponseData(["a", "b", "c"])).toBe("a; b; c");
});
test("should filter out empty values from array", () => {
const input = ["a", "", "c"];
expect(processResponseData(input)).toBe("a; c");
});
test("should handle object input", () => {
const input = { key1: "value1", key2: "value2" };
expect(processResponseData(input)).toBe("key1: value1\nkey2: value2");
});
test("should filter out empty values from object", () => {
const input = { key1: "value1", key2: "", key3: "value3" };
expect(processResponseData(input)).toBe("key1: value1\nkey3: value3");
});
test("should return empty string for unsupported types", () => {
expect(processResponseData(undefined as any)).toBe("");
});
test("should filter out null values from array", () => {
const input = ["a", null, "c"] as any;
expect(processResponseData(input)).toBe("a; c");
});
test("should filter out undefined values from array", () => {
const input = ["a", undefined, "c"] as any;
expect(processResponseData(input)).toBe("a; c");
});
});
describe("convertResponseValue", () => {
const mockOpenTextQuestion = {
id: "q1",
type: TSurveyQuestionTypeEnum.OpenText as const,
headline: { default: "Test Question" },
required: true,
inputType: "text" as const,
longAnswer: false,
charLimit: { enabled: false },
};
const mockRankingQuestion = {
id: "q1",
type: TSurveyQuestionTypeEnum.Ranking as const,
headline: { default: "Test Question" },
required: true,
choices: [
{ id: "1", label: { default: "Choice 1" } },
{ id: "2", label: { default: "Choice 2" } },
],
shuffleOption: "none" as const,
};
const mockFileUploadQuestion = {
id: "q1",
type: TSurveyQuestionTypeEnum.FileUpload as const,
headline: { default: "Test Question" },
required: true,
allowMultipleFiles: true,
};
const mockPictureSelectionQuestion = {
id: "q1",
type: TSurveyQuestionTypeEnum.PictureSelection as const,
headline: { default: "Test Question" },
required: true,
allowMulti: false,
choices: [
{ id: "1", imageUrl: "image1.jpg", label: { default: "Choice 1" } },
{ id: "2", imageUrl: "image2.jpg", label: { default: "Choice 2" } },
],
};
test("should handle ranking type with string input", () => {
expect(convertResponseValue("answer", mockRankingQuestion)).toEqual(["answer"]);
});
test("should handle ranking type with array input", () => {
expect(convertResponseValue(["answer1", "answer2"], mockRankingQuestion)).toEqual([
"answer1",
"answer2",
]);
});
test("should handle fileUpload type with string input", () => {
expect(convertResponseValue("file.jpg", mockFileUploadQuestion)).toEqual(["file.jpg"]);
});
test("should handle fileUpload type with array input", () => {
expect(convertResponseValue(["file1.jpg", "file2.jpg"], mockFileUploadQuestion)).toEqual([
"file1.jpg",
"file2.jpg",
]);
});
test("should handle pictureSelection type with string input", () => {
expect(convertResponseValue("1", mockPictureSelectionQuestion)).toEqual(["image1.jpg"]);
});
test("should handle pictureSelection type with array input", () => {
expect(convertResponseValue(["1", "2"], mockPictureSelectionQuestion)).toEqual([
"image1.jpg",
"image2.jpg",
]);
});
test("should handle pictureSelection type with invalid choice", () => {
expect(convertResponseValue("invalid", mockPictureSelectionQuestion)).toEqual([]);
});
test("should handle pictureSelection type with number input", () => {
expect(convertResponseValue(42, mockPictureSelectionQuestion)).toEqual([]);
});
test("should handle pictureSelection type with object input", () => {
expect(convertResponseValue({ key: "value" }, mockPictureSelectionQuestion)).toEqual([]);
});
test("should handle pictureSelection type with null input", () => {
expect(convertResponseValue(null as any, mockPictureSelectionQuestion)).toEqual([]);
});
test("should handle pictureSelection type with undefined input", () => {
expect(convertResponseValue(undefined as any, mockPictureSelectionQuestion)).toEqual([]);
});
test("should handle default case with string input", () => {
expect(convertResponseValue("answer", mockOpenTextQuestion)).toBe("answer");
});
test("should handle default case with number input", () => {
expect(convertResponseValue(42, mockOpenTextQuestion)).toBe("42");
});
test("should handle default case with array input", () => {
expect(convertResponseValue(["a", "b", "c"], mockOpenTextQuestion)).toBe("a; b; c");
});
test("should handle default case with object input", () => {
const input = { key1: "value1", key2: "value2" };
expect(convertResponseValue(input, mockOpenTextQuestion)).toBe("key1: value1\nkey2: value2");
});
});
describe("getQuestionResponseMapping", () => {
const mockSurvey = {
id: "survey1",
type: "link" as const,
status: "inProgress" as const,
createdAt: new Date(),
updatedAt: new Date(),
name: "Test Survey",
environmentId: "env1",
createdBy: null,
questions: [
{
id: "q1",
type: TSurveyQuestionTypeEnum.OpenText as const,
headline: { default: "Question 1" },
required: true,
inputType: "text" as const,
longAnswer: false,
charLimit: { enabled: false },
},
{
id: "q2",
type: TSurveyQuestionTypeEnum.MultipleChoiceMulti as const,
headline: { default: "Question 2" },
required: true,
choices: [
{ id: "1", label: { default: "Option 1" } },
{ id: "2", label: { default: "Option 2" } },
],
shuffleOption: "none" as const,
},
],
hiddenFields: {
enabled: false,
fieldIds: [],
},
displayOption: "displayOnce" as const,
delay: 0,
languages: [
{
language: {
id: "lang1",
code: "default",
createdAt: new Date(),
updatedAt: new Date(),
alias: null,
projectId: "proj1",
},
default: true,
enabled: true,
},
],
variables: [],
endings: [],
displayLimit: null,
autoClose: null,
autoComplete: null,
recontactDays: null,
welcomeCard: {
enabled: false,
timeToFinish: false,
showResponseCount: false,
},
showLanguageSwitch: false,
isBackButtonHidden: false,
isVerifyEmailEnabled: false,
isSingleResponsePerEmailEnabled: false,
displayPercentage: 100,
styling: null,
projectOverwrites: null,
verifyEmail: null,
inlineTriggers: [],
pin: null,
triggers: [],
followUps: [],
segment: null,
recaptcha: null,
surveyClosedMessage: null,
singleUse: {
enabled: false,
isEncrypted: false,
},
};
const mockResponse = {
id: "response1",
surveyId: "survey1",
createdAt: new Date(),
updatedAt: new Date(),
finished: true,
data: {
q1: "Answer 1",
q2: ["Option 1", "Option 2"],
},
language: "default",
meta: {
url: undefined,
country: undefined,
action: undefined,
source: undefined,
userAgent: undefined,
},
tags: [],
person: null,
personAttributes: {},
ttc: {},
variables: {},
contact: null,
contactAttributes: {},
singleUseId: null,
};
test("should map questions to responses correctly", () => {
const mapping = getQuestionResponseMapping(mockSurvey, mockResponse);
expect(mapping).toHaveLength(2);
expect(mapping[0]).toEqual({
question: "Question 1",
response: "Answer 1",
type: TSurveyQuestionTypeEnum.OpenText,
});
expect(mapping[1]).toEqual({
question: "Question 2",
response: "Option 1; Option 2",
type: TSurveyQuestionTypeEnum.MultipleChoiceMulti,
});
});
test("should handle missing response data", () => {
const response = {
id: "response1",
surveyId: "survey1",
createdAt: new Date(),
updatedAt: new Date(),
finished: true,
data: {},
language: "default",
meta: {
url: undefined,
country: undefined,
action: undefined,
source: undefined,
userAgent: undefined,
},
tags: [],
person: null,
personAttributes: {},
ttc: {},
variables: {},
contact: null,
contactAttributes: {},
singleUseId: null,
};
const mapping = getQuestionResponseMapping(mockSurvey, response);
expect(mapping).toHaveLength(2);
expect(mapping[0].response).toBe("");
expect(mapping[1].response).toBe("");
});
test("should handle different language", () => {
const survey = {
...mockSurvey,
questions: [
{
id: "q1",
type: TSurveyQuestionTypeEnum.OpenText as const,
headline: { default: "Question 1", en: "Question 1 EN" },
required: true,
inputType: "text" as const,
longAnswer: false,
charLimit: { enabled: false },
},
],
languages: [
{
language: {
id: "lang1",
code: "default",
createdAt: new Date(),
updatedAt: new Date(),
alias: null,
projectId: "proj1",
},
default: true,
enabled: true,
},
{
language: {
id: "lang2",
code: "en",
createdAt: new Date(),
updatedAt: new Date(),
alias: null,
projectId: "proj1",
},
default: false,
enabled: true,
},
],
};
const response = {
id: "response1",
surveyId: "survey1",
createdAt: new Date(),
updatedAt: new Date(),
finished: true,
data: { q1: "Answer 1" },
language: "en",
meta: {
url: undefined,
country: undefined,
action: undefined,
source: undefined,
userAgent: undefined,
},
tags: [],
person: null,
personAttributes: {},
ttc: {},
variables: {},
contact: null,
contactAttributes: {},
singleUseId: null,
};
const mapping = getQuestionResponseMapping(survey, response);
expect(mapping[0].question).toBe("Question 1 EN");
});
test("should handle null response language", () => {
const response = {
id: "response1",
surveyId: "survey1",
createdAt: new Date(),
updatedAt: new Date(),
finished: true,
data: { q1: "Answer 1" },
language: null,
meta: {
url: undefined,
country: undefined,
action: undefined,
source: undefined,
userAgent: undefined,
},
tags: [],
person: null,
personAttributes: {},
ttc: {},
variables: {},
contact: null,
contactAttributes: {},
singleUseId: null,
};
const mapping = getQuestionResponseMapping(mockSurvey, response);
expect(mapping).toHaveLength(2);
expect(mapping[0].question).toBe("Question 1");
});
test("should handle undefined response language", () => {
const response = {
id: "response1",
surveyId: "survey1",
createdAt: new Date(),
updatedAt: new Date(),
finished: true,
data: { q1: "Answer 1" },
language: null,
meta: {
url: undefined,
country: undefined,
action: undefined,
source: undefined,
userAgent: undefined,
},
tags: [],
person: null,
personAttributes: {},
ttc: {},
variables: {},
contact: null,
contactAttributes: {},
singleUseId: null,
};
const mapping = getQuestionResponseMapping(mockSurvey, response);
expect(mapping).toHaveLength(2);
expect(mapping[0].question).toBe("Question 1");
});
test("should handle empty survey languages", () => {
const survey = {
...mockSurvey,
languages: [], // Empty languages array
};
const response = {
id: "response1",
surveyId: "survey1",
createdAt: new Date(),
updatedAt: new Date(),
finished: true,
data: { q1: "Answer 1" },
language: "en",
meta: {
url: undefined,
country: undefined,
action: undefined,
source: undefined,
userAgent: undefined,
},
tags: [],
person: null,
personAttributes: {},
ttc: {},
variables: {},
contact: null,
contactAttributes: {},
singleUseId: null,
};
const mapping = getQuestionResponseMapping(survey, response);
expect(mapping).toHaveLength(2);
expect(mapping[0].question).toBe("Question 1"); // Should fallback to default
});
});
});