mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-29 09:50:10 -06:00
fix: response data table settings modal breaking (#6501)
This commit is contained in:
committed by
GitHub
parent
892b55662e
commit
326872a86b
@@ -1,5 +1,4 @@
|
||||
import { extractChoiceIdsFromResponse } from "@/lib/response/utils";
|
||||
import { processResponseData } from "@/lib/responses";
|
||||
import { getContactIdentifier } from "@/lib/utils/contact";
|
||||
import { getFormattedDateTimeString } from "@/lib/utils/datetime";
|
||||
import { getSelectionColumn } from "@/modules/ui/components/data-table";
|
||||
@@ -31,10 +30,6 @@ vi.mock("@/lib/i18n/utils", () => ({
|
||||
getLocalizedValue: vi.fn((localizedString, locale) => localizedString[locale] || localizedString.default),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/responses", () => ({
|
||||
processResponseData: vi.fn((data) => (Array.isArray(data) ? data.join(", ") : String(data))),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/utils/contact", () => ({
|
||||
getContactIdentifier: vi.fn((person) => person?.attributes?.email || person?.id || "Anonymous"),
|
||||
}));
|
||||
@@ -139,13 +134,13 @@ const mockSurvey = {
|
||||
questions: [
|
||||
{
|
||||
id: "q1open",
|
||||
type: TSurveyQuestionTypeEnum.OpenText,
|
||||
type: "openText",
|
||||
headline: { default: "Open Text Question" },
|
||||
required: true,
|
||||
} as unknown as TSurveyQuestion,
|
||||
{
|
||||
id: "q2matrix",
|
||||
type: TSurveyQuestionTypeEnum.Matrix,
|
||||
type: "matrix",
|
||||
headline: { default: "Matrix Question" },
|
||||
rows: [
|
||||
{ id: "row-1", label: { default: "Row1" } },
|
||||
@@ -159,19 +154,19 @@ const mockSurvey = {
|
||||
} as unknown as TSurveyQuestion,
|
||||
{
|
||||
id: "q3address",
|
||||
type: TSurveyQuestionTypeEnum.Address,
|
||||
type: "address",
|
||||
headline: { default: "Address Question" },
|
||||
required: false,
|
||||
} as unknown as TSurveyQuestion,
|
||||
{
|
||||
id: "q4contact",
|
||||
type: TSurveyQuestionTypeEnum.ContactInfo,
|
||||
type: "contactInfo",
|
||||
headline: { default: "Contact Info Question" },
|
||||
required: false,
|
||||
} as unknown as TSurveyQuestion,
|
||||
{
|
||||
id: "q5single",
|
||||
type: TSurveyQuestionTypeEnum.MultipleChoiceSingle,
|
||||
type: "multipleChoiceSingle",
|
||||
headline: { default: "Single Choice Question" },
|
||||
required: false,
|
||||
choices: [
|
||||
@@ -182,7 +177,7 @@ const mockSurvey = {
|
||||
} as unknown as TSurveyQuestion,
|
||||
{
|
||||
id: "q6multi",
|
||||
type: TSurveyQuestionTypeEnum.MultipleChoiceMulti,
|
||||
type: "multipleChoiceMulti",
|
||||
headline: { default: "Multi Choice Question" },
|
||||
required: false,
|
||||
choices: [
|
||||
@@ -275,12 +270,12 @@ describe("generateResponseTableColumns", () => {
|
||||
|
||||
test("should generate columns for variables", () => {
|
||||
const columns = generateResponseTableColumns(mockSurvey, false, true, t as any);
|
||||
const var1Col = columns.find((col) => (col as any).accessorKey === "var1");
|
||||
const var1Col = columns.find((col) => (col as any).accessorKey === "VARIABLE_var1");
|
||||
expect(var1Col).toBeDefined();
|
||||
const var1Cell = (var1Col?.cell as any)?.({ row: { original: mockResponseData } } as any);
|
||||
expect(var1Cell.props.children).toBe("Segment A");
|
||||
|
||||
const var2Col = columns.find((col) => (col as any).accessorKey === "var2");
|
||||
const var2Col = columns.find((col) => (col as any).accessorKey === "VARIABLE_var2");
|
||||
expect(var2Col).toBeDefined();
|
||||
const var2Cell = (var2Col?.cell as any)?.({ row: { original: mockResponseData } } as any);
|
||||
expect(var2Cell.props.children).toBe(100);
|
||||
@@ -288,7 +283,7 @@ describe("generateResponseTableColumns", () => {
|
||||
|
||||
test("should generate columns for hidden fields if fieldIds exist", () => {
|
||||
const columns = generateResponseTableColumns(mockSurvey, false, true, t as any);
|
||||
const hf1Col = columns.find((col) => (col as any).accessorKey === "hf1");
|
||||
const hf1Col = columns.find((col) => (col as any).accessorKey === "HIDDEN_FIELD_hf1");
|
||||
expect(hf1Col).toBeDefined();
|
||||
const hf1Cell = (hf1Col?.cell as any)?.({ row: { original: mockResponseData } } as any);
|
||||
expect(hf1Cell.props.children).toBe("Hidden Field 1 Value");
|
||||
@@ -443,7 +438,7 @@ describe("ResponseTableColumns - Column Implementations", () => {
|
||||
const columns = generateResponseTableColumns(mockSurvey, false, true, t as any);
|
||||
|
||||
// Find the variable column for var1
|
||||
const var1Column: any = columns.find((col) => (col as any).accessorKey === "var1");
|
||||
const var1Column: any = columns.find((col) => (col as any).accessorKey === "VARIABLE_var1");
|
||||
expect(var1Column).toBeDefined();
|
||||
|
||||
// Test the header
|
||||
@@ -460,7 +455,7 @@ describe("ResponseTableColumns - Column Implementations", () => {
|
||||
expect(cellResult?.props.children).toBe("Test Value");
|
||||
|
||||
// Test with a number variable
|
||||
const var2Column: any = columns.find((col) => (col as any).accessorKey === "var2");
|
||||
const var2Column: any = columns.find((col) => (col as any).accessorKey === "VARIABLE_var2");
|
||||
expect(var2Column).toBeDefined();
|
||||
|
||||
const mockRowNumber = {
|
||||
@@ -475,7 +470,7 @@ describe("ResponseTableColumns - Column Implementations", () => {
|
||||
const columns = generateResponseTableColumns(mockSurvey, false, true, t as any);
|
||||
|
||||
// Find the hidden field column
|
||||
const hfColumn: any = columns.find((col) => (col as any).accessorKey === "hf1");
|
||||
const hfColumn: any = columns.find((col) => (col as any).accessorKey === "HIDDEN_FIELD_hf1");
|
||||
expect(hfColumn).toBeDefined();
|
||||
|
||||
// Test the header
|
||||
@@ -502,7 +497,7 @@ describe("ResponseTableColumns - Column Implementations", () => {
|
||||
const columns = generateResponseTableColumns(surveyWithNoHiddenFields, false, true, t as any);
|
||||
|
||||
// Check that no hidden field columns were created
|
||||
const hfColumn = columns.find((col) => (col as any).accessorKey === "hf1");
|
||||
const hfColumn = columns.find((col) => (col as any).accessorKey === "HIDDEN_FIELD_hf1");
|
||||
expect(hfColumn).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -520,11 +515,11 @@ describe("ResponseTableColumns - Multiple Choice Questions", () => {
|
||||
const columns = generateResponseTableColumns(mockSurvey, false, true, t as any);
|
||||
|
||||
// Should have main response column
|
||||
const mainColumn = columns.find((col) => (col as any).accessorKey === "q5single");
|
||||
const mainColumn = columns.find((col) => (col as any).accessorKey === "QUESTION_q5single");
|
||||
expect(mainColumn).toBeDefined();
|
||||
|
||||
// Should have option IDs column
|
||||
const optionIdsColumn = columns.find((col) => (col as any).accessorKey === "q5singleoptionIds");
|
||||
const optionIdsColumn = columns.find((col) => (col as any).accessorKey === "QUESTION_q5singleoptionIds");
|
||||
expect(optionIdsColumn).toBeDefined();
|
||||
});
|
||||
|
||||
@@ -532,17 +527,17 @@ describe("ResponseTableColumns - Multiple Choice Questions", () => {
|
||||
const columns = generateResponseTableColumns(mockSurvey, false, true, t as any);
|
||||
|
||||
// Should have main response column
|
||||
const mainColumn = columns.find((col) => (col as any).accessorKey === "q6multi");
|
||||
const mainColumn = columns.find((col) => (col as any).accessorKey === "QUESTION_q6multi");
|
||||
expect(mainColumn).toBeDefined();
|
||||
|
||||
// Should have option IDs column
|
||||
const optionIdsColumn = columns.find((col) => (col as any).accessorKey === "q6multioptionIds");
|
||||
const optionIdsColumn = columns.find((col) => (col as any).accessorKey === "QUESTION_q6multioptionIds");
|
||||
expect(optionIdsColumn).toBeDefined();
|
||||
});
|
||||
|
||||
test("multipleChoiceSingle main column renders RenderResponse component", () => {
|
||||
const columns = generateResponseTableColumns(mockSurvey, false, true, t as any);
|
||||
const mainColumn: any = columns.find((col) => (col as any).accessorKey === "q5single");
|
||||
const mainColumn: any = columns.find((col) => (col as any).accessorKey === "QUESTION_q5single");
|
||||
|
||||
const mockRow = {
|
||||
original: {
|
||||
@@ -558,7 +553,7 @@ describe("ResponseTableColumns - Multiple Choice Questions", () => {
|
||||
|
||||
test("multipleChoiceMulti main column renders RenderResponse component", () => {
|
||||
const columns = generateResponseTableColumns(mockSurvey, false, true, t as any);
|
||||
const mainColumn: any = columns.find((col) => (col as any).accessorKey === "q6multi");
|
||||
const mainColumn: any = columns.find((col) => (col as any).accessorKey === "QUESTION_q6multi");
|
||||
|
||||
const mockRow = {
|
||||
original: {
|
||||
@@ -584,7 +579,9 @@ describe("ResponseTableColumns - Choice ID Columns", () => {
|
||||
|
||||
test("option IDs column calls extractChoiceIdsFromResponse for string response", () => {
|
||||
const columns = generateResponseTableColumns(mockSurvey, false, true, t as any);
|
||||
const optionIdsColumn: any = columns.find((col) => (col as any).accessorKey === "q5singleoptionIds");
|
||||
const optionIdsColumn: any = columns.find(
|
||||
(col) => (col as any).accessorKey === "QUESTION_q5singleoptionIds"
|
||||
);
|
||||
|
||||
const mockRow = {
|
||||
original: {
|
||||
@@ -604,7 +601,9 @@ describe("ResponseTableColumns - Choice ID Columns", () => {
|
||||
|
||||
test("option IDs column calls extractChoiceIdsFromResponse for array response", () => {
|
||||
const columns = generateResponseTableColumns(mockSurvey, false, true, t as any);
|
||||
const optionIdsColumn: any = columns.find((col) => (col as any).accessorKey === "q6multioptionIds");
|
||||
const optionIdsColumn: any = columns.find(
|
||||
(col) => (col as any).accessorKey === "QUESTION_q6multioptionIds"
|
||||
);
|
||||
|
||||
const mockRow = {
|
||||
original: {
|
||||
@@ -624,7 +623,9 @@ describe("ResponseTableColumns - Choice ID Columns", () => {
|
||||
|
||||
test("option IDs column renders IdBadge components for choice IDs", () => {
|
||||
const columns = generateResponseTableColumns(mockSurvey, false, true, t as any);
|
||||
const optionIdsColumn: any = columns.find((col) => (col as any).accessorKey === "q6multioptionIds");
|
||||
const optionIdsColumn: any = columns.find(
|
||||
(col) => (col as any).accessorKey === "QUESTION_q6multioptionIds"
|
||||
);
|
||||
|
||||
const mockRow = {
|
||||
original: {
|
||||
@@ -646,7 +647,9 @@ describe("ResponseTableColumns - Choice ID Columns", () => {
|
||||
|
||||
test("option IDs column returns null for non-string/array response values", () => {
|
||||
const columns = generateResponseTableColumns(mockSurvey, false, true, t as any);
|
||||
const optionIdsColumn: any = columns.find((col) => (col as any).accessorKey === "q5singleoptionIds");
|
||||
const optionIdsColumn: any = columns.find(
|
||||
(col) => (col as any).accessorKey === "QUESTION_q5singleoptionIds"
|
||||
);
|
||||
|
||||
const mockRow = {
|
||||
original: {
|
||||
@@ -663,7 +666,9 @@ describe("ResponseTableColumns - Choice ID Columns", () => {
|
||||
|
||||
test("option IDs column returns null when no choice IDs found", () => {
|
||||
const columns = generateResponseTableColumns(mockSurvey, false, true, t as any);
|
||||
const optionIdsColumn: any = columns.find((col) => (col as any).accessorKey === "q5singleoptionIds");
|
||||
const optionIdsColumn: any = columns.find(
|
||||
(col) => (col as any).accessorKey === "QUESTION_q5singleoptionIds"
|
||||
);
|
||||
|
||||
const mockRow = {
|
||||
original: {
|
||||
@@ -682,7 +687,9 @@ describe("ResponseTableColumns - Choice ID Columns", () => {
|
||||
|
||||
test("option IDs column handles missing language gracefully", () => {
|
||||
const columns = generateResponseTableColumns(mockSurvey, false, true, t as any);
|
||||
const optionIdsColumn: any = columns.find((col) => (col as any).accessorKey === "q5singleoptionIds");
|
||||
const optionIdsColumn: any = columns.find(
|
||||
(col) => (col as any).accessorKey === "QUESTION_q5singleoptionIds"
|
||||
);
|
||||
|
||||
const mockRow = {
|
||||
original: {
|
||||
@@ -712,8 +719,10 @@ describe("ResponseTableColumns - Helper Functions", () => {
|
||||
|
||||
test("question headers are properly created for multiple choice questions", () => {
|
||||
const columns = generateResponseTableColumns(mockSurvey, false, true, t as any);
|
||||
const mainColumn: any = columns.find((col) => (col as any).accessorKey === "q5single");
|
||||
const optionIdsColumn: any = columns.find((col) => (col as any).accessorKey === "q5singleoptionIds");
|
||||
const mainColumn: any = columns.find((col) => (col as any).accessorKey === "QUESTION_q5single");
|
||||
const optionIdsColumn: any = columns.find(
|
||||
(col) => (col as any).accessorKey === "QUESTION_q5singleoptionIds"
|
||||
);
|
||||
|
||||
// Test main column header
|
||||
const mainHeader = mainColumn?.header?.();
|
||||
@@ -728,8 +737,8 @@ describe("ResponseTableColumns - Helper Functions", () => {
|
||||
|
||||
test("question headers include proper icons for multiple choice questions", () => {
|
||||
const columns = generateResponseTableColumns(mockSurvey, false, true, t as any);
|
||||
const singleChoiceColumn: any = columns.find((col) => (col as any).accessorKey === "q5single");
|
||||
const multiChoiceColumn: any = columns.find((col) => (col as any).accessorKey === "q6multi");
|
||||
const singleChoiceColumn: any = columns.find((col) => (col as any).accessorKey === "QUESTION_q5single");
|
||||
const multiChoiceColumn: any = columns.find((col) => (col as any).accessorKey === "QUESTION_q6multi");
|
||||
|
||||
// Headers should be functions that return JSX
|
||||
expect(typeof singleChoiceColumn?.header).toBe("function");
|
||||
@@ -754,10 +763,10 @@ describe("ResponseTableColumns - Integration Tests", () => {
|
||||
const columns = generateResponseTableColumns(mockSurvey, false, true, t as any);
|
||||
|
||||
// Find all multiple choice related columns
|
||||
const singleMainCol = columns.find((col) => (col as any).accessorKey === "q5single");
|
||||
const singleIdsCol = columns.find((col) => (col as any).accessorKey === "q5singleoptionIds");
|
||||
const multiMainCol = columns.find((col) => (col as any).accessorKey === "q6multi");
|
||||
const multiIdsCol = columns.find((col) => (col as any).accessorKey === "q6multioptionIds");
|
||||
const singleMainCol = columns.find((col) => (col as any).accessorKey === "QUESTION_q5single");
|
||||
const singleIdsCol = columns.find((col) => (col as any).accessorKey === "QUESTION_q5singleoptionIds");
|
||||
const multiMainCol = columns.find((col) => (col as any).accessorKey === "QUESTION_q6multi");
|
||||
const multiIdsCol = columns.find((col) => (col as any).accessorKey === "QUESTION_q6multioptionIds");
|
||||
|
||||
expect(singleMainCol).toBeDefined();
|
||||
expect(singleIdsCol).toBeDefined();
|
||||
|
||||
@@ -34,6 +34,8 @@ const getQuestionColumnsData = (
|
||||
t: TFnType
|
||||
): ColumnDef<TResponseTableData>[] => {
|
||||
const QUESTIONS_ICON_MAP = getQuestionIconMap(t);
|
||||
const addressFields = ["addressLine1", "addressLine2", "city", "state", "zip", "country"];
|
||||
const contactInfoFields = ["firstName", "lastName", "email", "phone", "company"];
|
||||
|
||||
// Helper function to create consistent column headers
|
||||
const createQuestionHeader = (questionType: string, headline: string, suffix?: string) => {
|
||||
@@ -74,7 +76,7 @@ const getQuestionColumnsData = (
|
||||
case "matrix":
|
||||
return question.rows.map((matrixRow) => {
|
||||
return {
|
||||
accessorKey: matrixRow.label.default,
|
||||
accessorKey: "QUESTION_" + question.id + "_" + matrixRow.label.default,
|
||||
header: () => {
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
@@ -99,10 +101,9 @@ const getQuestionColumnsData = (
|
||||
});
|
||||
|
||||
case "address":
|
||||
const addressFields = ["addressLine1", "addressLine2", "city", "state", "zip", "country"];
|
||||
return addressFields.map((addressField) => {
|
||||
return {
|
||||
accessorKey: addressField,
|
||||
accessorKey: "QUESTION_" + question.id + "_" + addressField,
|
||||
header: () => {
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
@@ -123,10 +124,9 @@ const getQuestionColumnsData = (
|
||||
});
|
||||
|
||||
case "contactInfo":
|
||||
const contactInfoFields = ["firstName", "lastName", "email", "phone", "company"];
|
||||
return contactInfoFields.map((contactInfoField) => {
|
||||
return {
|
||||
accessorKey: contactInfoField,
|
||||
accessorKey: "QUESTION_" + question.id + "_" + contactInfoField,
|
||||
header: () => {
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
@@ -153,7 +153,7 @@ const getQuestionColumnsData = (
|
||||
const questionHeadline = getQuestionHeadline(question, survey);
|
||||
return [
|
||||
{
|
||||
accessorKey: question.id,
|
||||
accessorKey: "QUESTION_" + question.id,
|
||||
header: createQuestionHeader(question.type, questionHeadline),
|
||||
cell: ({ row }) => {
|
||||
const responseValue = row.original.responseData[question.id];
|
||||
@@ -171,7 +171,7 @@ const getQuestionColumnsData = (
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: question.id + "optionIds",
|
||||
accessorKey: "QUESTION_" + question.id + "optionIds",
|
||||
header: createQuestionHeader(question.type, questionHeadline, t("common.option_id")),
|
||||
cell: ({ row }) => {
|
||||
const responseValue = row.original.responseData[question.id];
|
||||
@@ -193,7 +193,7 @@ const getQuestionColumnsData = (
|
||||
default:
|
||||
return [
|
||||
{
|
||||
accessorKey: question.id,
|
||||
accessorKey: "QUESTION_" + question.id,
|
||||
header: () => (
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-2 overflow-hidden">
|
||||
@@ -233,7 +233,7 @@ const getMetadataColumnsData = (t: TFnType): ColumnDef<TResponseTableData>[] =>
|
||||
const IconComponent = COLUMNS_ICON_MAP[label];
|
||||
|
||||
metadataColumns.push({
|
||||
accessorKey: label,
|
||||
accessorKey: "METADATA_" + label,
|
||||
header: () => (
|
||||
<div className="flex items-center space-x-2 overflow-hidden">
|
||||
<span className="h-4 w-4">{IconComponent && <IconComponent className="h-4 w-4" />}</span>
|
||||
@@ -309,7 +309,7 @@ export const generateResponseTableColumns = (
|
||||
const statusColumn: ColumnDef<TResponseTableData> = {
|
||||
accessorKey: "status",
|
||||
size: 200,
|
||||
header: t("common.status"),
|
||||
header: () => <div className="gap-x-1.5">{t("common.status")}</div>,
|
||||
cell: ({ row }) => {
|
||||
const status = row.original.status;
|
||||
return <ResponseBadges items={[{ value: status }]} showId={false} />;
|
||||
@@ -318,7 +318,7 @@ export const generateResponseTableColumns = (
|
||||
|
||||
const tagsColumn: ColumnDef<TResponseTableData> = {
|
||||
accessorKey: "tags",
|
||||
header: t("common.tags"),
|
||||
header: () => <div className="gap-x-1.5">{t("common.tags")}</div>,
|
||||
cell: ({ row }) => {
|
||||
const tags = row.original.tags;
|
||||
if (Array.isArray(tags)) {
|
||||
@@ -337,7 +337,7 @@ export const generateResponseTableColumns = (
|
||||
|
||||
const variableColumns: ColumnDef<TResponseTableData>[] = survey.variables.map((variable) => {
|
||||
return {
|
||||
accessorKey: variable.id,
|
||||
accessorKey: "VARIABLE_" + variable.id,
|
||||
header: () => (
|
||||
<div className="flex items-center space-x-2 overflow-hidden">
|
||||
<span className="h-4 w-4">{VARIABLES_ICON_MAP[variable.type]}</span>
|
||||
@@ -356,7 +356,7 @@ export const generateResponseTableColumns = (
|
||||
const hiddenFieldColumns: ColumnDef<TResponseTableData>[] = survey.hiddenFields.fieldIds
|
||||
? survey.hiddenFields.fieldIds.map((hiddenFieldId) => {
|
||||
return {
|
||||
accessorKey: hiddenFieldId,
|
||||
accessorKey: "HIDDEN_FIELD_" + hiddenFieldId,
|
||||
header: () => (
|
||||
<div className="flex items-center space-x-2 overflow-hidden">
|
||||
<span className="h-4 w-4">
|
||||
|
||||
@@ -3,113 +3,135 @@ import userEvent from "@testing-library/user-event";
|
||||
import { afterEach, describe, expect, test, vi } from "vitest";
|
||||
import { DataTableSettingsModalItem } from "./data-table-settings-modal-item";
|
||||
|
||||
// Mock the dnd-kit hooks
|
||||
vi.mock("@dnd-kit/sortable", async () => {
|
||||
const actual = await vi.importActual("@dnd-kit/sortable");
|
||||
return {
|
||||
...actual,
|
||||
useSortable: () => ({
|
||||
attributes: {},
|
||||
listeners: {},
|
||||
setNodeRef: vi.fn(),
|
||||
transform: { x: 0, y: 0, scaleX: 1, scaleY: 1 },
|
||||
transition: "transform 100ms ease",
|
||||
isDragging: false,
|
||||
}),
|
||||
};
|
||||
});
|
||||
// Mock the Switch component
|
||||
vi.mock("@/modules/ui/components/switch", () => ({
|
||||
Switch: ({ id, checked, onCheckedChange, disabled }: any) => (
|
||||
<input
|
||||
data-testid={`switch-${id}`}
|
||||
type="checkbox"
|
||||
checked={checked}
|
||||
onChange={(e) => onCheckedChange(e.target.checked)}
|
||||
disabled={disabled}
|
||||
/>
|
||||
),
|
||||
}));
|
||||
|
||||
// Mock lucide-react
|
||||
vi.mock("lucide-react", () => ({
|
||||
GripVertical: () => <div data-testid="grip-vertical" />,
|
||||
}));
|
||||
|
||||
// Mock dnd-kit hooks
|
||||
vi.mock("@dnd-kit/sortable", () => ({
|
||||
useSortable: vi.fn(() => ({
|
||||
attributes: { "data-testid": "sortable-attributes" },
|
||||
listeners: { "data-testid": "sortable-listeners" },
|
||||
setNodeRef: vi.fn(),
|
||||
transform: null,
|
||||
transition: null,
|
||||
isDragging: false,
|
||||
})),
|
||||
}));
|
||||
|
||||
vi.mock("@dnd-kit/utilities", () => ({
|
||||
CSS: {
|
||||
Translate: {
|
||||
toString: vi.fn(() => "translate3d(0px, 0px, 0px)"),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
// Mock @tanstack/react-table
|
||||
vi.mock("@tanstack/react-table", () => ({
|
||||
flexRender: vi.fn((_, context) => `Header: ${context.column.id}`),
|
||||
}));
|
||||
|
||||
describe("DataTableSettingsModalItem", () => {
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
test("renders standard column name correctly", () => {
|
||||
const mockColumn = {
|
||||
id: "firstName",
|
||||
getIsVisible: vi.fn().mockReturnValue(true),
|
||||
toggleVisibility: vi.fn(),
|
||||
};
|
||||
|
||||
render(<DataTableSettingsModalItem column={mockColumn as any} />);
|
||||
|
||||
expect(screen.getByText("environments.contacts.first_name")).toBeInTheDocument();
|
||||
const switchElement = screen.getByRole("switch");
|
||||
expect(switchElement).toBeInTheDocument();
|
||||
expect(switchElement).toHaveAttribute("aria-checked", "true");
|
||||
const createMockColumn = (id: string, isVisible: boolean = true) => ({
|
||||
id,
|
||||
getIsVisible: vi.fn(() => isVisible),
|
||||
toggleVisibility: vi.fn(),
|
||||
columnDef: {
|
||||
header: `${id} Header`,
|
||||
},
|
||||
});
|
||||
|
||||
test("renders createdAt column with correct label", () => {
|
||||
const mockColumn = {
|
||||
id: "createdAt",
|
||||
getIsVisible: vi.fn().mockReturnValue(true),
|
||||
toggleVisibility: vi.fn(),
|
||||
const createMockTable = (columns: any[]) => {
|
||||
const headers = columns.map((column) => ({
|
||||
column,
|
||||
getContext: vi.fn(() => ({ column })),
|
||||
}));
|
||||
|
||||
return {
|
||||
getHeaderGroups: vi.fn(() => [{ headers }]),
|
||||
};
|
||||
};
|
||||
|
||||
render(<DataTableSettingsModalItem column={mockColumn as any} />);
|
||||
test("renders column item with grip icon and switch", () => {
|
||||
const mockColumn = createMockColumn("firstName");
|
||||
const mockTable = createMockTable([mockColumn]);
|
||||
|
||||
expect(screen.getByText("common.date")).toBeInTheDocument();
|
||||
render(<DataTableSettingsModalItem column={mockColumn as any} table={mockTable as any} />);
|
||||
|
||||
expect(screen.getByTestId("grip-vertical")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("switch-firstName")).toBeInTheDocument();
|
||||
expect(screen.getByText("Header: firstName")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("renders verifiedEmail column with correct label", () => {
|
||||
const mockColumn = {
|
||||
id: "verifiedEmail",
|
||||
getIsVisible: vi.fn().mockReturnValue(true),
|
||||
toggleVisibility: vi.fn(),
|
||||
};
|
||||
test("switch reflects column visibility state", () => {
|
||||
const mockColumn = createMockColumn("firstName", true);
|
||||
const mockTable = createMockTable([mockColumn]);
|
||||
|
||||
render(<DataTableSettingsModalItem column={mockColumn as any} />);
|
||||
render(<DataTableSettingsModalItem column={mockColumn as any} table={mockTable as any} />);
|
||||
|
||||
expect(screen.getByText("common.verified_email")).toBeInTheDocument();
|
||||
const switchElement = screen.getByTestId("switch-firstName") as HTMLInputElement;
|
||||
expect(switchElement.checked).toBe(true);
|
||||
});
|
||||
|
||||
test("renders userId column with correct label", () => {
|
||||
const mockColumn = {
|
||||
id: "userId",
|
||||
getIsVisible: vi.fn().mockReturnValue(true),
|
||||
toggleVisibility: vi.fn(),
|
||||
};
|
||||
test("switch shows unchecked when column is hidden", () => {
|
||||
const mockColumn = createMockColumn("firstName", false);
|
||||
const mockTable = createMockTable([mockColumn]);
|
||||
|
||||
render(<DataTableSettingsModalItem column={mockColumn as any} />);
|
||||
render(<DataTableSettingsModalItem column={mockColumn as any} table={mockTable as any} />);
|
||||
|
||||
expect(screen.getByText("common.user_id")).toBeInTheDocument();
|
||||
const switchElement = screen.getByTestId("switch-firstName") as HTMLInputElement;
|
||||
expect(switchElement.checked).toBe(false);
|
||||
});
|
||||
|
||||
test("renders question from survey with localized headline", () => {
|
||||
const mockColumn = {
|
||||
id: "question1",
|
||||
getIsVisible: vi.fn().mockReturnValue(true),
|
||||
toggleVisibility: vi.fn(),
|
||||
};
|
||||
test("calls toggleVisibility when switch is clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
const mockColumn = createMockColumn("firstName", true);
|
||||
const mockTable = createMockTable([mockColumn]);
|
||||
|
||||
const mockSurvey = {
|
||||
questions: [
|
||||
{
|
||||
id: "question1",
|
||||
type: "open",
|
||||
headline: { default: "Test Question" },
|
||||
},
|
||||
],
|
||||
};
|
||||
render(<DataTableSettingsModalItem column={mockColumn as any} table={mockTable as any} />);
|
||||
|
||||
render(<DataTableSettingsModalItem column={mockColumn as any} survey={mockSurvey as any} />);
|
||||
const switchElement = screen.getByTestId("switch-firstName");
|
||||
await user.click(switchElement);
|
||||
|
||||
expect(screen.getByText("Test Question")).toBeInTheDocument();
|
||||
expect(mockColumn.toggleVisibility).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
test("toggles visibility when switch is clicked", async () => {
|
||||
const toggleVisibilityMock = vi.fn();
|
||||
const mockColumn = {
|
||||
id: "lastName",
|
||||
getIsVisible: vi.fn().mockReturnValue(true),
|
||||
toggleVisibility: toggleVisibilityMock,
|
||||
};
|
||||
test("renders with correct column id as element id", () => {
|
||||
const mockColumn = createMockColumn("lastName");
|
||||
const mockTable = createMockTable([mockColumn]);
|
||||
|
||||
render(<DataTableSettingsModalItem column={mockColumn as any} />);
|
||||
render(<DataTableSettingsModalItem column={mockColumn as any} table={mockTable as any} />);
|
||||
|
||||
const switchElement = screen.getByRole("switch");
|
||||
await userEvent.click(switchElement);
|
||||
const elementWithId = screen.getByText("Header: lastName").closest("[id='lastName']");
|
||||
expect(elementWithId).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(toggleVisibilityMock).toHaveBeenCalledWith(false);
|
||||
test("renders reorder button with correct aria-label", () => {
|
||||
const mockColumn = createMockColumn("firstName");
|
||||
const mockTable = createMockTable([mockColumn]);
|
||||
|
||||
render(<DataTableSettingsModalItem column={mockColumn as any} table={mockTable as any} />);
|
||||
|
||||
const reorderButton = screen.getByRole("button", { name: "Reorder column" });
|
||||
expect(reorderButton).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,97 +1,22 @@
|
||||
"use client";
|
||||
|
||||
import { COLUMNS_ICON_MAP } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/lib/utils";
|
||||
import { getLocalizedValue } from "@/lib/i18n/utils";
|
||||
import { getQuestionIconMap } from "@/modules/survey/lib/questions";
|
||||
import { Switch } from "@/modules/ui/components/switch";
|
||||
import { useSortable } from "@dnd-kit/sortable";
|
||||
import { CSS } from "@dnd-kit/utilities";
|
||||
import { Column } from "@tanstack/react-table";
|
||||
import { useTranslate } from "@tolgee/react";
|
||||
import { capitalize } from "lodash";
|
||||
import { Column, Table, flexRender } from "@tanstack/react-table";
|
||||
import { GripVertical } from "lucide-react";
|
||||
import { useMemo } from "react";
|
||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||
|
||||
interface DataTableSettingsModalItemProps<T> {
|
||||
column: Column<T, unknown>;
|
||||
table: Table<T>;
|
||||
survey?: TSurvey;
|
||||
}
|
||||
|
||||
const getColumnIcon = (columnId: string) => {
|
||||
const Icon = COLUMNS_ICON_MAP[columnId];
|
||||
if (Icon) {
|
||||
return (
|
||||
<span className="h-4 w-4" aria-hidden="true">
|
||||
<Icon className="h-4 w-4" aria-hidden="true" focusable="false" />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const DataTableSettingsModalItem = <T,>({ column, survey }: DataTableSettingsModalItemProps<T>) => {
|
||||
const { t } = useTranslate();
|
||||
const QUESTIONS_ICON_MAP = getQuestionIconMap(t);
|
||||
export const DataTableSettingsModalItem = <T,>({ column, table }: DataTableSettingsModalItemProps<T>) => {
|
||||
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
||||
id: column.id,
|
||||
});
|
||||
const isOptionIdColumn = column.id.endsWith("optionIds");
|
||||
|
||||
const getOptionIdColumnLabel = () => {
|
||||
const questionId = column.id.split("optionIds")[0];
|
||||
const question = survey?.questions.find((q) => q.id === questionId);
|
||||
if (question) {
|
||||
return `${getLocalizedValue(question.headline, "default")} - ${t("common.option_id")}`;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const getLabelFromColumnId = () => {
|
||||
switch (column.id) {
|
||||
case "createdAt":
|
||||
return t("common.date");
|
||||
case "addressLine1":
|
||||
return t("environments.surveys.edit.address_line_1");
|
||||
case "addressLine2":
|
||||
return t("environments.surveys.edit.address_line_2");
|
||||
case "city":
|
||||
return t("environments.surveys.edit.city");
|
||||
case "state":
|
||||
return t("environments.surveys.edit.state");
|
||||
case "zip":
|
||||
return t("environments.surveys.edit.zip");
|
||||
case "verifiedEmail":
|
||||
return t("common.verified_email");
|
||||
case "userId":
|
||||
return t("common.user_id");
|
||||
case "contactsTableUser":
|
||||
return "ID";
|
||||
case "firstName":
|
||||
return t("environments.contacts.first_name");
|
||||
case "lastName":
|
||||
return t("environments.contacts.last_name");
|
||||
case "action":
|
||||
return t("common.action");
|
||||
case "country":
|
||||
return t("environments.surveys.responses.country");
|
||||
case "os":
|
||||
return t("environments.surveys.responses.os");
|
||||
case "device":
|
||||
return t("environments.surveys.responses.device");
|
||||
case "browser":
|
||||
return t("environments.surveys.responses.browser");
|
||||
case "url":
|
||||
return t("common.url");
|
||||
case "source":
|
||||
return t("environments.surveys.responses.source");
|
||||
|
||||
default:
|
||||
return capitalize(column.id);
|
||||
}
|
||||
};
|
||||
|
||||
const question = survey?.questions.find((question) => question.id === column.id);
|
||||
|
||||
const style = {
|
||||
transition: transition ?? "transform 100ms ease",
|
||||
@@ -99,9 +24,11 @@ export const DataTableSettingsModalItem = <T,>({ column, survey }: DataTableSett
|
||||
zIndex: isDragging ? 10 : 1,
|
||||
};
|
||||
|
||||
const iconMemo = useMemo(() => {
|
||||
return getColumnIcon(column.id);
|
||||
}, [column.id]);
|
||||
// Find the header for this column from the table's header groups
|
||||
const header = table
|
||||
.getHeaderGroups()
|
||||
.flatMap((headerGroup) => headerGroup.headers)
|
||||
.find((h) => h.column.id === column.id);
|
||||
|
||||
return (
|
||||
<div ref={setNodeRef} style={style} id={column.id}>
|
||||
@@ -113,23 +40,7 @@ export const DataTableSettingsModalItem = <T,>({ column, survey }: DataTableSett
|
||||
<button type="button" aria-label="Reorder column" onClick={(e) => e.preventDefault()}>
|
||||
<GripVertical className="h-4 w-4" />
|
||||
</button>
|
||||
<div className="flex items-center space-x-2 overflow-hidden">
|
||||
{question ? (
|
||||
<>
|
||||
<span className="h-4 w-4" aria-hidden="true">
|
||||
{QUESTIONS_ICON_MAP[question.type]}
|
||||
</span>
|
||||
<span className="truncate">{getLocalizedValue(question.headline, "default")}</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{iconMemo}
|
||||
<span className="truncate">
|
||||
{isOptionIdColumn ? getOptionIdColumnLabel() : getLabelFromColumnId()}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{flexRender(column.columnDef.header, header?.getContext() ?? { column })}
|
||||
</div>
|
||||
<Switch
|
||||
id={column.id}
|
||||
|
||||
@@ -72,7 +72,9 @@ export const DataTableSettingsModal = <T,>({
|
||||
if (columnId === "select" || columnId === "createdAt") return;
|
||||
const column = tableColumns.find((column) => column.id === columnId);
|
||||
if (!column) return null;
|
||||
return <DataTableSettingsModalItem column={column} key={column.id} survey={survey} />;
|
||||
return (
|
||||
<DataTableSettingsModalItem column={column} table={table} key={column.id} survey={survey} />
|
||||
);
|
||||
})}
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
|
||||
Reference in New Issue
Block a user