@carlonicora/nextjs-jsonapi 1.81.0 → 1.82.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.
@@ -0,0 +1,153 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import { render, screen, act } from "@testing-library/react";
3
+ import { ContentListGrid } from "../ContentListGrid";
4
+ import { DataListRetriever } from "../../../hooks";
5
+
6
+ vi.mock("../../tables/ContentTableSearch", () => ({
7
+ ContentTableSearch: () => <div data-testid="content-table-search">Search</div>,
8
+ }));
9
+
10
+ type Item = { id: string; title: string };
11
+
12
+ function createMockDataRetriever(overrides: Partial<DataListRetriever<Item>> = {}): DataListRetriever<Item> {
13
+ return {
14
+ ready: true,
15
+ setReady: vi.fn(),
16
+ isLoaded: true,
17
+ data: [],
18
+ search: vi.fn(),
19
+ refresh: vi.fn(),
20
+ addAdditionalParameter: vi.fn(),
21
+ removeAdditionalParameter: vi.fn(),
22
+ setRefreshedElement: vi.fn(),
23
+ removeElement: vi.fn(),
24
+ isSearch: false,
25
+ ...overrides,
26
+ } as DataListRetriever<Item>;
27
+ }
28
+
29
+ const mockModule = {
30
+ name: "items",
31
+ model: class MockItem {},
32
+ icon: ({ className }: { className?: string }) => <svg data-testid="module-icon" className={className} />,
33
+ } as any;
34
+
35
+ function ItemComponent({ item }: { item: Item }) {
36
+ return <div data-testid={`item-${item.id}`}>{item.title}</div>;
37
+ }
38
+
39
+ describe("ContentListGrid", () => {
40
+ let observerCallback: ((entries: IntersectionObserverEntry[]) => void) | null;
41
+ let observerInstance: { observe: ReturnType<typeof vi.fn>; disconnect: ReturnType<typeof vi.fn> } | null;
42
+
43
+ beforeEach(() => {
44
+ vi.clearAllMocks();
45
+ observerCallback = null;
46
+ observerInstance = null;
47
+ (globalThis as any).IntersectionObserver = class {
48
+ observe = vi.fn();
49
+ disconnect = vi.fn();
50
+ constructor(cb: any) {
51
+ observerCallback = cb;
52
+ observerInstance = { observe: this.observe, disconnect: this.disconnect };
53
+ }
54
+ };
55
+ });
56
+
57
+ it("renders the title and module icon", () => {
58
+ const data = createMockDataRetriever({
59
+ data: [{ id: "1", title: "Hello" }],
60
+ });
61
+ render(
62
+ <ContentListGrid<Item>
63
+ data={data}
64
+ tableGeneratorType={mockModule}
65
+ ItemComponent={ItemComponent}
66
+ title="My Items"
67
+ />,
68
+ );
69
+
70
+ expect(screen.getByText("My Items")).toBeInTheDocument();
71
+ expect(screen.getByTestId("module-icon")).toBeInTheDocument();
72
+ });
73
+
74
+ it("renders one ItemComponent per item with stable keys", () => {
75
+ const data = createMockDataRetriever({
76
+ data: [
77
+ { id: "a", title: "First" },
78
+ { id: "b", title: "Second" },
79
+ ],
80
+ });
81
+ render(<ContentListGrid<Item> data={data} tableGeneratorType={mockModule} ItemComponent={ItemComponent} />);
82
+
83
+ expect(screen.getByTestId("item-a")).toHaveTextContent("First");
84
+ expect(screen.getByTestId("item-b")).toHaveTextContent("Second");
85
+ });
86
+
87
+ it("renders the empty-state copy when there are no items", () => {
88
+ const data = createMockDataRetriever({ data: [] });
89
+ render(<ContentListGrid<Item> data={data} tableGeneratorType={mockModule} ItemComponent={ItemComponent} />);
90
+
91
+ expect(screen.getByText("No results.")).toBeInTheDocument();
92
+ });
93
+
94
+ it("hides search by default and shows it when allowSearch is true", () => {
95
+ const data = createMockDataRetriever({ data: [{ id: "1", title: "x" }] });
96
+ const { rerender } = render(
97
+ <ContentListGrid<Item> data={data} tableGeneratorType={mockModule} ItemComponent={ItemComponent} title="t" />,
98
+ );
99
+ expect(screen.queryByTestId("content-table-search")).not.toBeInTheDocument();
100
+
101
+ rerender(
102
+ <ContentListGrid<Item>
103
+ data={data}
104
+ tableGeneratorType={mockModule}
105
+ ItemComponent={ItemComponent}
106
+ title="t"
107
+ allowSearch
108
+ />,
109
+ );
110
+ expect(screen.getByTestId("content-table-search")).toBeInTheDocument();
111
+ });
112
+
113
+ it("does not render the sentinel when data.next is undefined", () => {
114
+ const data = createMockDataRetriever({ data: [{ id: "1", title: "x" }], next: undefined });
115
+ const { container } = render(
116
+ <ContentListGrid<Item> data={data} tableGeneratorType={mockModule} ItemComponent={ItemComponent} />,
117
+ );
118
+ // Sentinel is the only 1px-tall div inline-styled; assert via inline style probe.
119
+ const sentinel = container.querySelector('div[style*="height: 1px"]');
120
+ expect(sentinel).toBeNull();
121
+ });
122
+
123
+ it("calls data.next when the sentinel intersects", () => {
124
+ const next = vi.fn();
125
+ const data = createMockDataRetriever({
126
+ data: [{ id: "1", title: "x" }],
127
+ next,
128
+ });
129
+ render(<ContentListGrid<Item> data={data} tableGeneratorType={mockModule} ItemComponent={ItemComponent} />);
130
+
131
+ expect(observerInstance?.observe).toHaveBeenCalled();
132
+
133
+ act(() => {
134
+ observerCallback?.([{ isIntersecting: true } as IntersectionObserverEntry]);
135
+ });
136
+
137
+ expect(next).toHaveBeenCalledTimes(1);
138
+ });
139
+
140
+ it("uses gridClassName when supplied", () => {
141
+ const data = createMockDataRetriever({ data: [{ id: "1", title: "x" }] });
142
+ const { container } = render(
143
+ <ContentListGrid<Item>
144
+ data={data}
145
+ tableGeneratorType={mockModule}
146
+ ItemComponent={ItemComponent}
147
+ gridClassName="my-custom-grid"
148
+ />,
149
+ );
150
+ const grid = container.querySelector(".my-custom-grid");
151
+ expect(grid).not.toBeNull();
152
+ });
153
+ });
@@ -0,0 +1 @@
1
+ export * from "./ContentListGrid";
@@ -11,6 +11,7 @@ export * from "./forms";
11
11
  export * from "./navigations";
12
12
  export * from "./pages";
13
13
  export * from "./tables";
14
+ export * from "./grids";
14
15
  export * from "./fiscal";
15
16
  export { parseFiscalData } from "../utils/fiscal-utils";
16
17