mirror of
https://github.com/formbricks/formbricks.git
synced 2026-05-24 02:47:53 -05:00
594 lines
23 KiB
TypeScript
594 lines
23 KiB
TypeScript
import { beforeEach, describe, expect, test, vi } from "vitest";
|
|
import { TConnectorFormbricksMapping } from "@formbricks/types/connector";
|
|
import { TResponse } from "@formbricks/types/responses";
|
|
import { TSurvey } from "@formbricks/types/surveys/types";
|
|
import { transformResponseToFeedbackRecords } from "./transform";
|
|
|
|
vi.mock("@/lib/i18n/utils", () => ({
|
|
getLocalizedValue: (_val: Record<string, string>, _lang: string) => _val?.default ?? "",
|
|
}));
|
|
|
|
vi.mock("@formbricks/types/surveys/validation", () => ({
|
|
getTextContent: (str: string) => str,
|
|
}));
|
|
|
|
vi.mock("@/lib/survey/utils", () => ({
|
|
getElementsFromBlocks: (blocks: Array<{ elements: unknown[] }>) =>
|
|
blocks.flatMap((block) => block.elements),
|
|
}));
|
|
|
|
const NOW = new Date("2026-02-24T10:00:00.000Z");
|
|
|
|
const mockSurvey = {
|
|
id: "survey-1",
|
|
name: "Product Feedback",
|
|
blocks: [
|
|
{
|
|
elements: [
|
|
{ id: "el-text", type: "openText", headline: { default: "How can we improve?" } },
|
|
{ id: "el-nps", type: "nps", headline: { default: "How likely to recommend?" } },
|
|
{ id: "el-rating", type: "rating", headline: { default: "Rate your experience" } },
|
|
{ id: "el-date", type: "date", headline: { default: "When did you visit?" } },
|
|
{ id: "el-bool", type: "consent", headline: { default: "Do you agree?" } },
|
|
{
|
|
id: "el-multi",
|
|
type: "multipleChoiceMulti",
|
|
headline: { default: "Select features" },
|
|
},
|
|
],
|
|
},
|
|
],
|
|
} as unknown as TSurvey;
|
|
|
|
const mockTenantId = "cmp2f6428000504la7iyh87h1";
|
|
|
|
const mockResponse = {
|
|
id: "resp-1",
|
|
createdAt: NOW,
|
|
data: {
|
|
"el-text": "Great product!",
|
|
"el-nps": 9,
|
|
"el-rating": 4,
|
|
"el-date": "2026-01-15",
|
|
"el-bool": "true",
|
|
"el-multi": ["feat-a", "feat-b"],
|
|
},
|
|
language: "en",
|
|
contact: { userId: "user-42" },
|
|
} as unknown as TResponse;
|
|
|
|
const createMapping = (
|
|
overrides: Partial<TConnectorFormbricksMapping> &
|
|
Pick<TConnectorFormbricksMapping, "elementId" | "hubFieldType">
|
|
): TConnectorFormbricksMapping => ({
|
|
id: `mapping-${overrides.elementId}`,
|
|
createdAt: NOW,
|
|
connectorId: "conn-1",
|
|
workspaceId: "env-1",
|
|
surveyId: "survey-1",
|
|
customFieldLabel: null,
|
|
...overrides,
|
|
});
|
|
|
|
const allMappings: TConnectorFormbricksMapping[] = [
|
|
createMapping({ elementId: "el-text", hubFieldType: "text" }),
|
|
createMapping({ elementId: "el-nps", hubFieldType: "nps" }),
|
|
createMapping({ elementId: "el-rating", hubFieldType: "rating" }),
|
|
createMapping({ elementId: "el-date", hubFieldType: "date" }),
|
|
createMapping({ elementId: "el-bool", hubFieldType: "boolean" }),
|
|
createMapping({ elementId: "el-multi", hubFieldType: "categorical" }),
|
|
];
|
|
|
|
describe("transformResponseToFeedbackRecords", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
test("returns empty array when response has no data", () => {
|
|
const emptyResponse = { ...mockResponse, data: null } as unknown as TResponse;
|
|
const result = transformResponseToFeedbackRecords(emptyResponse, mockSurvey, allMappings, mockTenantId);
|
|
expect(result).toEqual([]);
|
|
});
|
|
|
|
test("returns empty array when no mappings match the survey", () => {
|
|
const otherSurveyMappings = allMappings.map((m) => ({ ...m, surveyId: "other-survey" }));
|
|
const result = transformResponseToFeedbackRecords(
|
|
mockResponse,
|
|
mockSurvey,
|
|
otherSurveyMappings,
|
|
mockTenantId
|
|
);
|
|
expect(result).toEqual([]);
|
|
});
|
|
|
|
test("skips elements with empty string values", () => {
|
|
const response = {
|
|
...mockResponse,
|
|
data: { "el-text": "" },
|
|
} as unknown as TResponse;
|
|
const mappings = [createMapping({ elementId: "el-text", hubFieldType: "text" })];
|
|
const result = transformResponseToFeedbackRecords(response, mockSurvey, mappings, mockTenantId);
|
|
expect(result).toEqual([]);
|
|
});
|
|
|
|
test("skips elements with undefined values", () => {
|
|
const response = {
|
|
...mockResponse,
|
|
data: { "el-nps": 9 },
|
|
} as unknown as TResponse;
|
|
const mappings = [
|
|
createMapping({ elementId: "el-text", hubFieldType: "text" }),
|
|
createMapping({ elementId: "el-nps", hubFieldType: "nps" }),
|
|
];
|
|
const result = transformResponseToFeedbackRecords(response, mockSurvey, mappings, mockTenantId);
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0].field_id).toBe("el-nps");
|
|
});
|
|
|
|
test("transforms text field correctly", () => {
|
|
const mappings = [createMapping({ elementId: "el-text", hubFieldType: "text" })];
|
|
const result = transformResponseToFeedbackRecords(mockResponse, mockSurvey, mappings, mockTenantId);
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0]).toMatchObject({
|
|
source_type: "formbricks_survey",
|
|
field_id: "el-text",
|
|
field_type: "text",
|
|
field_label: "How can we improve?",
|
|
source_id: "survey-1",
|
|
source_name: "Product Feedback",
|
|
value_text: "Great product!",
|
|
language: "en",
|
|
user_id: "user-42",
|
|
});
|
|
});
|
|
|
|
test("transforms nps field correctly", () => {
|
|
const mappings = [createMapping({ elementId: "el-nps", hubFieldType: "nps" })];
|
|
const result = transformResponseToFeedbackRecords(mockResponse, mockSurvey, mappings, mockTenantId);
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0].value_number).toBe(9);
|
|
expect(result[0].field_type).toBe("nps");
|
|
});
|
|
|
|
test("transforms rating field correctly", () => {
|
|
const mappings = [createMapping({ elementId: "el-rating", hubFieldType: "rating" })];
|
|
const result = transformResponseToFeedbackRecords(mockResponse, mockSurvey, mappings, mockTenantId);
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0].value_number).toBe(4);
|
|
});
|
|
|
|
test("transforms date field to ISO string", () => {
|
|
const mappings = [createMapping({ elementId: "el-date", hubFieldType: "date" })];
|
|
const result = transformResponseToFeedbackRecords(mockResponse, mockSurvey, mappings, mockTenantId);
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0].value_date).toBe(new Date("2026-01-15").toISOString());
|
|
});
|
|
|
|
test("transforms boolean field correctly", () => {
|
|
const mappings = [createMapping({ elementId: "el-bool", hubFieldType: "boolean" })];
|
|
const result = transformResponseToFeedbackRecords(mockResponse, mockSurvey, mappings, mockTenantId);
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0].value_boolean).toBe(true);
|
|
});
|
|
|
|
test("transforms categorical (multi-select) field to comma-separated text", () => {
|
|
const mappings = [createMapping({ elementId: "el-multi", hubFieldType: "categorical" })];
|
|
const result = transformResponseToFeedbackRecords(mockResponse, mockSurvey, mappings, mockTenantId);
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0].value_text).toBe("feat-a, feat-b");
|
|
});
|
|
|
|
test("uses customFieldLabel when provided", () => {
|
|
const mappings = [
|
|
createMapping({ elementId: "el-text", hubFieldType: "text", customFieldLabel: "Custom Label" }),
|
|
];
|
|
const result = transformResponseToFeedbackRecords(mockResponse, mockSurvey, mappings, mockTenantId);
|
|
expect(result[0].field_label).toBe("Custom Label");
|
|
});
|
|
|
|
test("sets collected_at from response createdAt", () => {
|
|
const mappings = [createMapping({ elementId: "el-text", hubFieldType: "text" })];
|
|
const result = transformResponseToFeedbackRecords(mockResponse, mockSurvey, mappings, mockTenantId);
|
|
expect(result[0].collected_at).toBe(NOW.toISOString());
|
|
});
|
|
|
|
test("falls back to updatedAt when createdAt is missing", () => {
|
|
const updatedAt = new Date("2026-02-25T10:00:00.000Z");
|
|
const response = { ...mockResponse, createdAt: undefined, updatedAt } as unknown as TResponse;
|
|
const mappings = [createMapping({ elementId: "el-text", hubFieldType: "text" })];
|
|
const result = transformResponseToFeedbackRecords(response, mockSurvey, mappings, mockTenantId);
|
|
expect(result[0].collected_at).toBe(updatedAt.toISOString());
|
|
});
|
|
|
|
test("parses string createdAt values for collected_at", () => {
|
|
const response = {
|
|
...mockResponse,
|
|
createdAt: "2026-02-26T10:00:00.000Z",
|
|
} as unknown as TResponse;
|
|
const mappings = [createMapping({ elementId: "el-text", hubFieldType: "text" })];
|
|
const result = transformResponseToFeedbackRecords(response, mockSurvey, mappings, mockTenantId);
|
|
expect(result[0].collected_at).toBe("2026-02-26T10:00:00.000Z");
|
|
});
|
|
|
|
test("includes tenant_id when provided", () => {
|
|
const mappings = [createMapping({ elementId: "el-text", hubFieldType: "text" })];
|
|
const result = transformResponseToFeedbackRecords(mockResponse, mockSurvey, mappings, "tenant-abc");
|
|
expect(result[0].tenant_id).toBe("tenant-abc");
|
|
});
|
|
|
|
test("omits language when response language is 'default'", () => {
|
|
const response = { ...mockResponse, language: "default" } as unknown as TResponse;
|
|
const mappings = [createMapping({ elementId: "el-text", hubFieldType: "text" })];
|
|
const result = transformResponseToFeedbackRecords(response, mockSurvey, mappings, mockTenantId);
|
|
expect(result[0].language).toBeUndefined();
|
|
});
|
|
|
|
test("omits user_id when contact has no userId", () => {
|
|
const response = { ...mockResponse, contact: null } as unknown as TResponse;
|
|
const mappings = [createMapping({ elementId: "el-text", hubFieldType: "text" })];
|
|
const result = transformResponseToFeedbackRecords(response, mockSurvey, mappings, mockTenantId);
|
|
expect(result[0].user_id).toBeUndefined();
|
|
});
|
|
|
|
test("transforms all mappings in a single call", () => {
|
|
const result = transformResponseToFeedbackRecords(mockResponse, mockSurvey, allMappings, mockTenantId);
|
|
expect(result).toHaveLength(6);
|
|
const fieldIds = result.map((r) => r.field_id);
|
|
expect(fieldIds).toEqual(["el-text", "el-nps", "el-rating", "el-date", "el-bool", "el-multi"]);
|
|
});
|
|
|
|
test("falls back to 'Untitled' for element with no headline", () => {
|
|
const survey = {
|
|
...mockSurvey,
|
|
blocks: [{ elements: [{ id: "el-bare", type: "openText" }] }],
|
|
} as unknown as TSurvey;
|
|
const response = {
|
|
...mockResponse,
|
|
data: { "el-bare": "some text" },
|
|
} as unknown as TResponse;
|
|
const mappings = [createMapping({ elementId: "el-bare", hubFieldType: "text" })];
|
|
const result = transformResponseToFeedbackRecords(response, survey, mappings, mockTenantId);
|
|
expect(result[0].field_label).toBe("Untitled");
|
|
});
|
|
|
|
describe("convertValueToHubFields edge cases", () => {
|
|
test("parses numeric string for nps field", () => {
|
|
const response = {
|
|
...mockResponse,
|
|
data: { "el-nps": "7" },
|
|
} as unknown as TResponse;
|
|
const mappings = [createMapping({ elementId: "el-nps", hubFieldType: "nps" })];
|
|
const result = transformResponseToFeedbackRecords(response, mockSurvey, mappings, mockTenantId);
|
|
expect(result[0].value_number).toBe(7);
|
|
});
|
|
|
|
test("returns empty fields for non-parseable numeric string", () => {
|
|
const response = {
|
|
...mockResponse,
|
|
data: { "el-nps": "not-a-number" },
|
|
} as unknown as TResponse;
|
|
const mappings = [createMapping({ elementId: "el-nps", hubFieldType: "nps" })];
|
|
const result = transformResponseToFeedbackRecords(response, mockSurvey, mappings, mockTenantId);
|
|
expect(result[0].value_number).toBeUndefined();
|
|
});
|
|
|
|
test("handles object value for text field", () => {
|
|
const response = {
|
|
...mockResponse,
|
|
data: { "el-text": { nested: "value" } },
|
|
} as unknown as TResponse;
|
|
const mappings = [createMapping({ elementId: "el-text", hubFieldType: "text" })];
|
|
const result = transformResponseToFeedbackRecords(response, mockSurvey, mappings, mockTenantId);
|
|
expect(result[0].value_text).toBe(JSON.stringify({ nested: "value" }));
|
|
});
|
|
|
|
test("handles invalid date string gracefully", () => {
|
|
const response = {
|
|
...mockResponse,
|
|
data: { "el-date": "not-a-date" },
|
|
} as unknown as TResponse;
|
|
const mappings = [createMapping({ elementId: "el-date", hubFieldType: "date" })];
|
|
const result = transformResponseToFeedbackRecords(response, mockSurvey, mappings, mockTenantId);
|
|
expect(result[0].value_date).toBeUndefined();
|
|
});
|
|
|
|
test("converts boolean string '1' to true", () => {
|
|
const response = {
|
|
...mockResponse,
|
|
data: { "el-bool": "1" },
|
|
} as unknown as TResponse;
|
|
const mappings = [createMapping({ elementId: "el-bool", hubFieldType: "boolean" })];
|
|
const result = transformResponseToFeedbackRecords(response, mockSurvey, mappings, mockTenantId);
|
|
expect(result[0].value_boolean).toBe(true);
|
|
});
|
|
|
|
test("converts boolean string 'false' to false", () => {
|
|
const response = {
|
|
...mockResponse,
|
|
data: { "el-bool": "false" },
|
|
} as unknown as TResponse;
|
|
const mappings = [createMapping({ elementId: "el-bool", hubFieldType: "boolean" })];
|
|
const result = transformResponseToFeedbackRecords(response, mockSurvey, mappings, mockTenantId);
|
|
expect(result[0].value_boolean).toBe(false);
|
|
});
|
|
|
|
test("handles array value for text field", () => {
|
|
const response = {
|
|
...mockResponse,
|
|
data: { "el-text": ["a", "b", "c"] },
|
|
} as unknown as TResponse;
|
|
const mappings = [createMapping({ elementId: "el-text", hubFieldType: "text" })];
|
|
const result = transformResponseToFeedbackRecords(response, mockSurvey, mappings, mockTenantId);
|
|
expect(result[0].value_text).toBe("a, b, c");
|
|
});
|
|
|
|
test("handles single string value for categorical field", () => {
|
|
const response = {
|
|
...mockResponse,
|
|
data: { "el-multi": "single-choice" },
|
|
} as unknown as TResponse;
|
|
const mappings = [createMapping({ elementId: "el-multi", hubFieldType: "categorical" })];
|
|
const result = transformResponseToFeedbackRecords(response, mockSurvey, mappings, mockTenantId);
|
|
expect(result[0].value_text).toBe("single-choice");
|
|
});
|
|
|
|
test("JSON-stringifies object value for categorical field (matrix/ranking responses)", () => {
|
|
const response = {
|
|
...mockResponse,
|
|
data: { "el-multi": { row1: "col1", row2: "col2" } },
|
|
} as unknown as TResponse;
|
|
const mappings = [createMapping({ elementId: "el-multi", hubFieldType: "categorical" })];
|
|
const result = transformResponseToFeedbackRecords(response, mockSurvey, mappings, mockTenantId);
|
|
expect(result[0].value_text).toBe(JSON.stringify({ row1: "col1", row2: "col2" }));
|
|
expect(result[0].value_text).not.toBe("[object Object]");
|
|
});
|
|
|
|
test("creates a record for a ranking response (string array)", () => {
|
|
const response = {
|
|
...mockResponse,
|
|
data: { "el-multi": ["LabelA", "LabelB", "LabelC"] },
|
|
} as unknown as TResponse;
|
|
const mappings = [createMapping({ elementId: "el-multi", hubFieldType: "categorical" })];
|
|
const result = transformResponseToFeedbackRecords(response, mockSurvey, mappings, mockTenantId);
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0].field_id).toBe("el-multi");
|
|
expect(result[0].value_text).toBe("LabelA, LabelB, LabelC");
|
|
});
|
|
|
|
test("creates a record for an empty ranking response (empty array)", () => {
|
|
const response = {
|
|
...mockResponse,
|
|
data: { "el-multi": [] },
|
|
} as unknown as TResponse;
|
|
const mappings = [createMapping({ elementId: "el-multi", hubFieldType: "categorical" })];
|
|
const result = transformResponseToFeedbackRecords(response, mockSurvey, mappings, mockTenantId);
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0].value_text).toBe("");
|
|
});
|
|
|
|
test("JSON-stringifies object value for unknown hubFieldType (default branch)", () => {
|
|
const response = {
|
|
...mockResponse,
|
|
data: { "el-multi": { row1: "col1" } },
|
|
} as unknown as TResponse;
|
|
const mappings = [
|
|
createMapping({
|
|
elementId: "el-multi",
|
|
hubFieldType: "unknown-type" as TConnectorFormbricksMapping["hubFieldType"],
|
|
}),
|
|
];
|
|
const result = transformResponseToFeedbackRecords(response, mockSurvey, mappings, mockTenantId);
|
|
expect(result[0].value_text).toBe(JSON.stringify({ row1: "col1" }));
|
|
expect(result[0].value_text).not.toBe("[object Object]");
|
|
});
|
|
});
|
|
|
|
describe("matrix expansion", () => {
|
|
const matrixSurvey = {
|
|
id: "survey-1",
|
|
name: "Matrix Survey",
|
|
blocks: [
|
|
{
|
|
elements: [
|
|
{
|
|
id: "el-matrix",
|
|
type: "matrix",
|
|
headline: { default: "Rate each feature" },
|
|
rows: [
|
|
{ id: "row-1", label: { default: "Speed" } },
|
|
{ id: "row-2", label: { default: "Quality" } },
|
|
],
|
|
columns: [
|
|
{ id: "col-1", label: { default: "Good" } },
|
|
{ id: "col-2", label: { default: "Bad" } },
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
} as unknown as TSurvey;
|
|
|
|
test("emits one record per answered row with shared field_group_id", () => {
|
|
const response = {
|
|
id: "resp-matrix",
|
|
createdAt: NOW,
|
|
data: { "el-matrix": { Speed: "Good", Quality: "Bad" } },
|
|
language: "default",
|
|
contact: { userId: "user-42" },
|
|
} as unknown as TResponse;
|
|
const mappings = [createMapping({ elementId: "el-matrix", hubFieldType: "categorical" })];
|
|
|
|
const result = transformResponseToFeedbackRecords(response, matrixSurvey, mappings, mockTenantId);
|
|
|
|
expect(result).toHaveLength(2);
|
|
expect(result.every((r) => r.field_group_id === "el-matrix")).toBe(true);
|
|
expect(result.every((r) => r.field_group_label === "Rate each feature")).toBe(true);
|
|
expect(result.every((r) => r.submission_id === "resp-matrix")).toBe(true);
|
|
expect(result.every((r) => r.metadata?.question_type === "matrix")).toBe(true);
|
|
|
|
expect(result[0]).toMatchObject({
|
|
field_id: "el-matrix__row-1",
|
|
field_label: "Speed",
|
|
field_type: "categorical",
|
|
value_text: "Good",
|
|
});
|
|
expect(result[1]).toMatchObject({
|
|
field_id: "el-matrix__row-2",
|
|
field_label: "Quality",
|
|
value_text: "Bad",
|
|
});
|
|
});
|
|
|
|
test("skips matrix rows with empty cell value", () => {
|
|
const response = {
|
|
id: "resp-matrix-partial",
|
|
createdAt: NOW,
|
|
data: { "el-matrix": { Speed: "Good", Quality: "" } },
|
|
} as unknown as TResponse;
|
|
const mappings = [createMapping({ elementId: "el-matrix", hubFieldType: "categorical" })];
|
|
|
|
const result = transformResponseToFeedbackRecords(response, matrixSurvey, mappings, mockTenantId);
|
|
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0].field_id).toBe("el-matrix__row-1");
|
|
});
|
|
|
|
test("skips matrix rows whose label does not match any row choice", () => {
|
|
const response = {
|
|
id: "resp-matrix-stale",
|
|
createdAt: NOW,
|
|
data: { "el-matrix": { "Old Row Label": "Good", Quality: "Bad" } },
|
|
} as unknown as TResponse;
|
|
const mappings = [createMapping({ elementId: "el-matrix", hubFieldType: "categorical" })];
|
|
|
|
const result = transformResponseToFeedbackRecords(response, matrixSurvey, mappings, mockTenantId);
|
|
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0].field_id).toBe("el-matrix__row-2");
|
|
});
|
|
|
|
test("JSON-stringifies non-string matrix cell value (regression for ENG-891)", () => {
|
|
const cellObject = { a: 1 };
|
|
const response = {
|
|
id: "resp-matrix-obj",
|
|
createdAt: NOW,
|
|
data: { "el-matrix": { Speed: cellObject } },
|
|
} as unknown as TResponse;
|
|
const mappings = [createMapping({ elementId: "el-matrix", hubFieldType: "categorical" })];
|
|
|
|
const result = transformResponseToFeedbackRecords(response, matrixSurvey, mappings, mockTenantId);
|
|
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0]).toMatchObject({
|
|
field_id: "el-matrix__row-1",
|
|
field_label: "Speed",
|
|
field_group_id: "el-matrix",
|
|
field_group_label: "Rate each feature",
|
|
metadata: { question_type: "matrix" },
|
|
value_text: JSON.stringify(cellObject),
|
|
});
|
|
expect(result[0].value_text).not.toBe("[object Object]");
|
|
});
|
|
|
|
test("emits no records for empty matrix response", () => {
|
|
const response = {
|
|
id: "resp-empty",
|
|
createdAt: NOW,
|
|
data: { "el-matrix": {} },
|
|
} as unknown as TResponse;
|
|
const mappings = [createMapping({ elementId: "el-matrix", hubFieldType: "categorical" })];
|
|
|
|
const result = transformResponseToFeedbackRecords(response, matrixSurvey, mappings, mockTenantId);
|
|
|
|
expect(result).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe("ranking expansion", () => {
|
|
const rankingSurvey = {
|
|
id: "survey-1",
|
|
name: "Ranking Survey",
|
|
blocks: [
|
|
{
|
|
elements: [
|
|
{
|
|
id: "el-ranking",
|
|
type: "ranking",
|
|
headline: { default: "Rank these features" },
|
|
choices: [
|
|
{ id: "ch-1", label: { default: "Reports" } },
|
|
{ id: "ch-2", label: { default: "Dashboards" } },
|
|
{ id: "ch-3", label: { default: "Alerts" } },
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
} as unknown as TSurvey;
|
|
|
|
test("emits one record per ranked item with rank as value_number", () => {
|
|
const response = {
|
|
id: "resp-ranking",
|
|
createdAt: NOW,
|
|
data: { "el-ranking": ["Dashboards", "Reports", "Alerts"] },
|
|
language: "default",
|
|
contact: { userId: "user-42" },
|
|
} as unknown as TResponse;
|
|
const mappings = [createMapping({ elementId: "el-ranking", hubFieldType: "categorical" })];
|
|
|
|
const result = transformResponseToFeedbackRecords(response, rankingSurvey, mappings, mockTenantId);
|
|
|
|
expect(result).toHaveLength(3);
|
|
expect(result.every((r) => r.field_group_id === "el-ranking")).toBe(true);
|
|
expect(result.every((r) => r.field_group_label === "Rank these features")).toBe(true);
|
|
expect(result.every((r) => r.field_type === "number")).toBe(true);
|
|
expect(result.every((r) => r.metadata?.question_type === "ranking")).toBe(true);
|
|
expect(result.every((r) => r.metadata?.total_items === 3)).toBe(true);
|
|
|
|
expect(result[0]).toMatchObject({
|
|
field_id: "el-ranking__ch-2",
|
|
field_label: "Dashboards",
|
|
value_number: 1,
|
|
});
|
|
expect(result[1]).toMatchObject({
|
|
field_id: "el-ranking__ch-1",
|
|
field_label: "Reports",
|
|
value_number: 2,
|
|
});
|
|
expect(result[2]).toMatchObject({
|
|
field_id: "el-ranking__ch-3",
|
|
field_label: "Alerts",
|
|
value_number: 3,
|
|
});
|
|
});
|
|
|
|
test("emits no records for empty ranking response", () => {
|
|
const response = {
|
|
id: "resp-empty",
|
|
createdAt: NOW,
|
|
data: { "el-ranking": [] },
|
|
} as unknown as TResponse;
|
|
const mappings = [createMapping({ elementId: "el-ranking", hubFieldType: "categorical" })];
|
|
|
|
const result = transformResponseToFeedbackRecords(response, rankingSurvey, mappings, mockTenantId);
|
|
|
|
expect(result).toEqual([]);
|
|
});
|
|
|
|
test("skips ranking items whose label does not match any choice", () => {
|
|
const response = {
|
|
id: "resp-ranking-stale",
|
|
createdAt: NOW,
|
|
data: { "el-ranking": ["Reports", "Removed Option"] },
|
|
} as unknown as TResponse;
|
|
const mappings = [createMapping({ elementId: "el-ranking", hubFieldType: "categorical" })];
|
|
|
|
const result = transformResponseToFeedbackRecords(response, rankingSurvey, mappings, mockTenantId);
|
|
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0].field_id).toBe("el-ranking__ch-1");
|
|
expect(result[0].value_number).toBe(1);
|
|
});
|
|
});
|
|
});
|