fix: response data table settings modal breaking (#6501)

This commit is contained in:
Dhruwang Jariwala
2025-09-05 16:11:39 +05:30
committed by GitHub
parent 892b55662e
commit 326872a86b
5 changed files with 173 additions and 229 deletions

View File

@@ -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();

View File

@@ -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">

View File

@@ -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();
});
});

View File

@@ -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}

View File

@@ -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>