@izumisy-tailor/tailor-data-viewer 0.1.19 → 0.1.21

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,350 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import { render, screen, waitFor } from "@testing-library/react";
3
+ import userEvent from "@testing-library/user-event";
4
+ import { SingleRecordTabContent } from "./single-record-tab-content";
5
+ import type {
6
+ TableMetadata,
7
+ TableMetadataMap,
8
+ } from "../generator/metadata-generator";
9
+ import type { SingleRecordDataFetcher } from "./hooks/use-single-record-data";
10
+
11
+ // Mock table metadata for "task" table
12
+ const mockTaskTable: TableMetadata = {
13
+ name: "task",
14
+ pluralForm: "tasks",
15
+ description: "タスク",
16
+ readAllowedRoles: ["admin"],
17
+ fields: [
18
+ { name: "id", type: "uuid", required: true },
19
+ { name: "title", type: "string", required: true, description: "タイトル" },
20
+ { name: "description", type: "string", required: false },
21
+ { name: "status", type: "enum", required: true },
22
+ { name: "userId", type: "uuid", required: true },
23
+ { name: "createdAt", type: "datetime", required: true },
24
+ ],
25
+ relations: [
26
+ {
27
+ fieldName: "comments",
28
+ targetTable: "comment",
29
+ relationType: "oneToMany",
30
+ foreignKeyField: "taskId",
31
+ },
32
+ {
33
+ fieldName: "user",
34
+ targetTable: "user",
35
+ relationType: "manyToOne",
36
+ foreignKeyField: "userId",
37
+ },
38
+ ],
39
+ };
40
+
41
+ // Mock table metadata for "comment" table
42
+ const mockCommentTable: TableMetadata = {
43
+ name: "comment",
44
+ pluralForm: "comments",
45
+ description: "コメント",
46
+ readAllowedRoles: ["admin"],
47
+ fields: [
48
+ { name: "id", type: "uuid", required: true },
49
+ { name: "content", type: "string", required: true },
50
+ { name: "taskId", type: "uuid", required: true },
51
+ { name: "createdAt", type: "datetime", required: true },
52
+ ],
53
+ };
54
+
55
+ // Mock table metadata for "user" table
56
+ const mockUserTable: TableMetadata = {
57
+ name: "user",
58
+ pluralForm: "users",
59
+ description: "ユーザー",
60
+ readAllowedRoles: ["admin"],
61
+ fields: [
62
+ { name: "id", type: "uuid", required: true },
63
+ { name: "name", type: "string", required: true },
64
+ { name: "email", type: "string", required: true },
65
+ ],
66
+ };
67
+
68
+ const mockTableMetadataMap: TableMetadataMap = {
69
+ task: mockTaskTable,
70
+ comment: mockCommentTable,
71
+ user: mockUserTable,
72
+ };
73
+
74
+ // Mock record data
75
+ const mockRecord = {
76
+ id: "task-123-456-789",
77
+ title: "Test Task",
78
+ description: "This is a test task",
79
+ status: "active",
80
+ userId: "user-123-456-789",
81
+ createdAt: "2024-01-01T00:00:00Z",
82
+ };
83
+
84
+ // Mock comment data
85
+ const mockComments = [
86
+ {
87
+ id: "comment-1",
88
+ content: "First comment",
89
+ createdAt: "2024-01-02T00:00:00Z",
90
+ },
91
+ {
92
+ id: "comment-2",
93
+ content: "Second comment",
94
+ createdAt: "2024-01-03T00:00:00Z",
95
+ },
96
+ ];
97
+
98
+ // Mock user data
99
+ const mockUser = {
100
+ id: "user-123-456-789",
101
+ name: "Test User",
102
+ email: "test@example.com",
103
+ };
104
+
105
+ describe("SingleRecordTabContent", () => {
106
+ let mockFetcher: SingleRecordDataFetcher;
107
+
108
+ beforeEach(() => {
109
+ mockFetcher = {
110
+ fetchSingleRecord: vi.fn().mockResolvedValue(mockRecord),
111
+ fetchOneToManyRelation: vi.fn().mockResolvedValue({
112
+ data: mockComments,
113
+ hasNextPage: false,
114
+ endCursor: null,
115
+ }),
116
+ fetchManyToOneRelation: vi.fn().mockResolvedValue(mockUser),
117
+ };
118
+ });
119
+
120
+ describe("基本的な表示", () => {
121
+ it("ローディング状態が表示される", () => {
122
+ // Never resolve to keep loading state
123
+ mockFetcher.fetchSingleRecord = vi
124
+ .fn()
125
+ .mockImplementation(() => new Promise(() => {}));
126
+
127
+ render(
128
+ <SingleRecordTabContent
129
+ tableMetadata={mockTaskTable}
130
+ tableMetadataMap={mockTableMetadataMap}
131
+ appUri="https://test.example.com"
132
+ recordId="task-123-456-789"
133
+ fetcher={mockFetcher}
134
+ />,
135
+ );
136
+
137
+ expect(screen.getByText("読み込み中...")).toBeInTheDocument();
138
+ });
139
+
140
+ it("レコードデータが正しく表示される", async () => {
141
+ render(
142
+ <SingleRecordTabContent
143
+ tableMetadata={mockTaskTable}
144
+ tableMetadataMap={mockTableMetadataMap}
145
+ appUri="https://test.example.com"
146
+ recordId="task-123-456-789"
147
+ fetcher={mockFetcher}
148
+ />,
149
+ );
150
+
151
+ await waitFor(() => {
152
+ expect(screen.getByText("Test Task")).toBeInTheDocument();
153
+ });
154
+
155
+ expect(screen.getByText("This is a test task")).toBeInTheDocument();
156
+ expect(screen.getByText("active")).toBeInTheDocument();
157
+ });
158
+
159
+ it("fetchSingleRecordが正しいパラメータで呼ばれる", async () => {
160
+ render(
161
+ <SingleRecordTabContent
162
+ tableMetadata={mockTaskTable}
163
+ tableMetadataMap={mockTableMetadataMap}
164
+ appUri="https://test.example.com"
165
+ recordId="task-123-456-789"
166
+ fetcher={mockFetcher}
167
+ />,
168
+ );
169
+
170
+ await waitFor(() => {
171
+ expect(mockFetcher.fetchSingleRecord).toHaveBeenCalledWith(
172
+ "task",
173
+ expect.arrayContaining([
174
+ "id",
175
+ "title",
176
+ "description",
177
+ "status",
178
+ "userId",
179
+ "createdAt",
180
+ ]),
181
+ "task-123-456-789",
182
+ );
183
+ });
184
+ });
185
+ });
186
+
187
+ describe("エラーハンドリング", () => {
188
+ it("エラーメッセージが表示される", async () => {
189
+ mockFetcher.fetchSingleRecord = vi
190
+ .fn()
191
+ .mockRejectedValue(new Error("Network error"));
192
+
193
+ render(
194
+ <SingleRecordTabContent
195
+ tableMetadata={mockTaskTable}
196
+ tableMetadataMap={mockTableMetadataMap}
197
+ appUri="https://test.example.com"
198
+ recordId="task-123-456-789"
199
+ fetcher={mockFetcher}
200
+ />,
201
+ );
202
+
203
+ await waitFor(() => {
204
+ expect(
205
+ screen.getByText("データの取得に失敗しました: Network error"),
206
+ ).toBeInTheDocument();
207
+ });
208
+ });
209
+
210
+ it("レコードが見つからない場合、メッセージが表示される", async () => {
211
+ mockFetcher.fetchSingleRecord = vi.fn().mockResolvedValue(null);
212
+
213
+ render(
214
+ <SingleRecordTabContent
215
+ tableMetadata={mockTaskTable}
216
+ tableMetadataMap={mockTableMetadataMap}
217
+ appUri="https://test.example.com"
218
+ recordId="nonexistent-id"
219
+ fetcher={mockFetcher}
220
+ />,
221
+ );
222
+
223
+ await waitFor(() => {
224
+ expect(
225
+ screen.getByText("レコードが見つかりません"),
226
+ ).toBeInTheDocument();
227
+ });
228
+ });
229
+ });
230
+
231
+ describe("リレーションデータの取得", () => {
232
+ it("oneToMany関係のデータがフェッチされる", async () => {
233
+ render(
234
+ <SingleRecordTabContent
235
+ tableMetadata={mockTaskTable}
236
+ tableMetadataMap={mockTableMetadataMap}
237
+ appUri="https://test.example.com"
238
+ recordId="task-123-456-789"
239
+ fetcher={mockFetcher}
240
+ />,
241
+ );
242
+
243
+ await waitFor(() => {
244
+ expect(mockFetcher.fetchOneToManyRelation).toHaveBeenCalledWith(
245
+ "comment",
246
+ "comments",
247
+ "taskId",
248
+ expect.arrayContaining(["id", "content"]),
249
+ "task-123-456-789",
250
+ 10,
251
+ null,
252
+ );
253
+ });
254
+ });
255
+
256
+ it("manyToOne関係のデータがフェッチされる", async () => {
257
+ render(
258
+ <SingleRecordTabContent
259
+ tableMetadata={mockTaskTable}
260
+ tableMetadataMap={mockTableMetadataMap}
261
+ appUri="https://test.example.com"
262
+ recordId="task-123-456-789"
263
+ fetcher={mockFetcher}
264
+ />,
265
+ );
266
+
267
+ await waitFor(() => {
268
+ expect(mockFetcher.fetchManyToOneRelation).toHaveBeenCalledWith(
269
+ "user",
270
+ expect.arrayContaining(["id", "name", "email"]),
271
+ "user-123-456-789",
272
+ );
273
+ });
274
+ });
275
+
276
+ it("関連データが正しく表示される", async () => {
277
+ render(
278
+ <SingleRecordTabContent
279
+ tableMetadata={mockTaskTable}
280
+ tableMetadataMap={mockTableMetadataMap}
281
+ appUri="https://test.example.com"
282
+ recordId="task-123-456-789"
283
+ fetcher={mockFetcher}
284
+ />,
285
+ );
286
+
287
+ await waitFor(() => {
288
+ expect(screen.getByText("First comment")).toBeInTheDocument();
289
+ expect(screen.getByText("Second comment")).toBeInTheDocument();
290
+ });
291
+ });
292
+ });
293
+
294
+ describe("更新ボタン", () => {
295
+ it("更新ボタンをクリックするとデータが再取得される", async () => {
296
+ const user = userEvent.setup();
297
+
298
+ render(
299
+ <SingleRecordTabContent
300
+ tableMetadata={mockTaskTable}
301
+ tableMetadataMap={mockTableMetadataMap}
302
+ appUri="https://test.example.com"
303
+ recordId="task-123-456-789"
304
+ fetcher={mockFetcher}
305
+ />,
306
+ );
307
+
308
+ await waitFor(() => {
309
+ expect(screen.getByText("Test Task")).toBeInTheDocument();
310
+ });
311
+
312
+ // Clear call counts
313
+ vi.clearAllMocks();
314
+
315
+ // Click refresh button
316
+ await user.click(screen.getByRole("button", { name: /更新/ }));
317
+
318
+ await waitFor(() => {
319
+ expect(mockFetcher.fetchSingleRecord).toHaveBeenCalled();
320
+ });
321
+ });
322
+ });
323
+
324
+ describe("無限ループ防止", () => {
325
+ it("fetchRelationDataが無限に呼ばれない", async () => {
326
+ render(
327
+ <SingleRecordTabContent
328
+ tableMetadata={mockTaskTable}
329
+ tableMetadataMap={mockTableMetadataMap}
330
+ appUri="https://test.example.com"
331
+ recordId="task-123-456-789"
332
+ fetcher={mockFetcher}
333
+ />,
334
+ );
335
+
336
+ // Wait for initial render
337
+ await waitFor(() => {
338
+ expect(screen.getByText("Test Task")).toBeInTheDocument();
339
+ });
340
+
341
+ // Wait a bit to ensure no additional calls are made
342
+ await new Promise((resolve) => setTimeout(resolve, 100));
343
+
344
+ // Should only be called once for the initial fetch
345
+ expect(mockFetcher.fetchSingleRecord).toHaveBeenCalledTimes(1);
346
+ expect(mockFetcher.fetchOneToManyRelation).toHaveBeenCalledTimes(1);
347
+ expect(mockFetcher.fetchManyToOneRelation).toHaveBeenCalledTimes(1);
348
+ });
349
+ });
350
+ });