@carlonicora/nextjs-jsonapi 3.20.0 → 3.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
- import { render, screen } from "@testing-library/react";
2
- import { describe, expect, it, vi } from "vitest";
1
+ import { fireEvent, render, screen } from "@testing-library/react";
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3
3
  import { ContentListTable } from "../ContentListTable";
4
4
 
5
5
  // `ContentListTable` imports `useTableGenerator` from the `../../hooks` barrel,
@@ -7,20 +7,40 @@ import { ContentListTable } from "../ContentListTable";
7
7
  // barrel (spreading the actual module so the other exports survive) is the same
8
8
  // approach the sibling `ContentListTable.test.tsx` in this folder uses; one
9
9
  // plain column is enough here.
10
+ // `mockColumns.expandToggle` switches the mocked name cell to a button that
11
+ // toggles row expansion (the `renderExpandedRow` suite), plus a second column so
12
+ // the detail row's colSpan is checked against more than one column.
13
+ const mockColumns = vi.hoisted(() => ({ expandToggle: false }));
14
+
10
15
  vi.mock("../../../hooks", async () => {
11
16
  const actual = await vi.importActual("../../../hooks");
12
17
  return {
13
18
  ...actual,
14
19
  useTableGenerator: vi.fn((_type: any, params: any) => ({
15
20
  data: params.data.map((item: any) => ({ ...item, jsonApiData: item })),
16
- columns: [
17
- {
18
- id: "name",
19
- header: "Name",
20
- accessorKey: "name",
21
- cell: ({ row }: any) => row.original.name,
22
- },
23
- ],
21
+ columns: mockColumns.expandToggle
22
+ ? [
23
+ {
24
+ id: "name",
25
+ header: "Name",
26
+ accessorKey: "name",
27
+ cell: ({ row }: any) => <button onClick={row.getToggleExpandedHandler()}>{row.original.name}</button>,
28
+ },
29
+ {
30
+ id: "id",
31
+ header: "Id",
32
+ accessorKey: "id",
33
+ cell: ({ row }: any) => row.original.id,
34
+ },
35
+ ]
36
+ : [
37
+ {
38
+ id: "name",
39
+ header: "Name",
40
+ accessorKey: "name",
41
+ cell: ({ row }: any) => row.original.name,
42
+ },
43
+ ],
24
44
  })),
25
45
  };
26
46
  });
@@ -60,3 +80,106 @@ describe("ContentListTable pagination footer", () => {
60
80
  expect(screen.getByText("Row one")).toBeInTheDocument();
61
81
  });
62
82
  });
83
+
84
+ describe("ContentListTable renderExpandedRow", () => {
85
+ beforeEach(() => {
86
+ mockColumns.expandToggle = true;
87
+ });
88
+
89
+ afterEach(() => {
90
+ mockColumns.expandToggle = false;
91
+ });
92
+
93
+ // The callback receives `row.original.jsonApiData`; the mocked generator sets
94
+ // that to the raw data item, so `d` is `{ id, name }`.
95
+ const renderExpandedRow = (d: any) => `Detail for ${d.name}`;
96
+
97
+ it("renders no detail row until a row is expanded", () => {
98
+ render(
99
+ <ContentListTable
100
+ data={baseData}
101
+ fields={["name", "id"]}
102
+ tableGeneratorType={mockModule}
103
+ renderExpandedRow={renderExpandedRow}
104
+ />,
105
+ );
106
+ expect(screen.queryByTestId("content-list-table-expanded-row")).toBeNull();
107
+ });
108
+
109
+ it("renders the callback output in a full-width row after toggling", () => {
110
+ render(
111
+ <ContentListTable
112
+ data={baseData}
113
+ fields={["name", "id"]}
114
+ tableGeneratorType={mockModule}
115
+ renderExpandedRow={renderExpandedRow}
116
+ />,
117
+ );
118
+ fireEvent.click(screen.getByText("Row one"));
119
+ const detailRow = screen.getByTestId("content-list-table-expanded-row");
120
+ expect(detailRow).toHaveTextContent("Detail for Row one");
121
+ const cells = detailRow.querySelectorAll("td");
122
+ expect(cells).toHaveLength(1);
123
+ expect(cells[0].getAttribute("colspan")).toBe("2");
124
+
125
+ fireEvent.click(screen.getByText("Row one"));
126
+ expect(screen.queryByTestId("content-list-table-expanded-row")).toBeNull();
127
+ });
128
+
129
+ it("keeps the expansion on the record, not on the row position, when the rows change", () => {
130
+ const twoRows = {
131
+ ...baseData,
132
+ data: [
133
+ { id: "1", name: "Row one" },
134
+ { id: "2", name: "Row two" },
135
+ ],
136
+ };
137
+ const { rerender } = render(
138
+ <ContentListTable
139
+ data={twoRows}
140
+ fields={["name", "id"]}
141
+ tableGeneratorType={mockModule}
142
+ renderExpandedRow={renderExpandedRow}
143
+ />,
144
+ );
145
+ fireEvent.click(screen.getByText("Row one"));
146
+ expect(screen.getByTestId("content-list-table-expanded-row")).toHaveTextContent("Detail for Row one");
147
+
148
+ // A search narrows the list: "Row two" now sits where "Row one" was.
149
+ rerender(
150
+ <ContentListTable
151
+ data={{ ...baseData, data: [{ id: "2", name: "Row two" }] }}
152
+ fields={["name", "id"]}
153
+ tableGeneratorType={mockModule}
154
+ renderExpandedRow={renderExpandedRow}
155
+ />,
156
+ );
157
+ expect(screen.queryByTestId("content-list-table-expanded-row")).toBeNull();
158
+ });
159
+
160
+ it("rows cannot expand without the prop", () => {
161
+ render(<ContentListTable data={baseData} fields={["name", "id"]} tableGeneratorType={mockModule} />);
162
+ fireEvent.click(screen.getByText("Row one"));
163
+ expect(screen.queryByTestId("content-list-table-expanded-row")).toBeNull();
164
+ expect(screen.queryByText("Detail for Row one")).toBeNull();
165
+ });
166
+
167
+ it("grouped rows also expand", () => {
168
+ render(
169
+ <ContentListTable
170
+ data={baseData}
171
+ fields={["name", "id"]}
172
+ tableGeneratorType={mockModule}
173
+ groupBy="name"
174
+ renderExpandedRow={renderExpandedRow}
175
+ />,
176
+ );
177
+ fireEvent.click(screen.getByRole("button", { name: "Row one" }));
178
+ const detailRow = screen.getByTestId("content-list-table-expanded-row");
179
+ expect(detailRow).toHaveTextContent("Detail for Row one");
180
+ // The detail row sits directly under the grouped data row, after the group header.
181
+ const dataRow = screen.getByRole("button", { name: "Row one" }).closest("tr")!;
182
+ expect(dataRow.nextElementSibling).toBe(detailRow);
183
+ expect(dataRow.previousElementSibling).toHaveTextContent("Row one");
184
+ });
185
+ });
@@ -25,6 +25,8 @@ const badgeVariants = cva(
25
25
  softGray: "bg-gray-500/10 text-gray-700 dark:bg-gray-500/15 dark:text-gray-400",
26
26
  softOrange: "bg-orange-500/10 text-orange-700 dark:bg-orange-500/15 dark:text-orange-400",
27
27
  softAmber: "bg-amber-500/10 text-amber-700 dark:bg-amber-500/15 dark:text-amber-400",
28
+ softEmerald: "bg-emerald-500/10 text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-400",
29
+ softPurple: "bg-purple-500/10 text-purple-700 dark:bg-purple-500/15 dark:text-purple-400",
28
30
  blue: "bg-sky-500 text-primary-foreground [a]:hover:bg-sky-500/80",
29
31
  green: "bg-emerald-500 text-primary-foreground [a]:hover:bg-emerald-500/80",
30
32
  red: "bg-red-500 text-primary-foreground [a]:hover:bg-red-500/80",