@carlonicora/nextjs-jsonapi 1.16.0 → 1.17.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.
Files changed (123) hide show
  1. package/dist/ApiData-DPKNfY-9.d.mts +10 -0
  2. package/dist/ApiData-DPKNfY-9.d.ts +10 -0
  3. package/dist/ApiRequestDataTypeInterface-DIEOFn9s.d.mts +40 -0
  4. package/dist/ApiRequestDataTypeInterface-DIEOFn9s.d.ts +40 -0
  5. package/dist/{ApiResponseInterface-BvWIeLkq.d.ts → ApiResponseInterface-BKyod24U.d.ts} +2 -11
  6. package/dist/{ApiResponseInterface-CAbw0sv7.d.mts → ApiResponseInterface-Dqvu09tz.d.mts} +2 -11
  7. package/dist/{BlockNoteEditor-MBFDWP7X.js → BlockNoteEditor-34T5CY27.js} +17 -16
  8. package/dist/BlockNoteEditor-34T5CY27.js.map +1 -0
  9. package/dist/{BlockNoteEditor-HFX7Z5BQ.mjs → BlockNoteEditor-4Z6TZBJE.mjs} +7 -6
  10. package/dist/{BlockNoteEditor-HFX7Z5BQ.mjs.map → BlockNoteEditor-4Z6TZBJE.mjs.map} +1 -1
  11. package/dist/JsonApiContext-Bsm_Q2oe.d.mts +41 -0
  12. package/dist/JsonApiContext-Bsm_Q2oe.d.ts +41 -0
  13. package/dist/JsonApiRequest-54ZBO7WQ.js +24 -0
  14. package/dist/{JsonApiRequest-45CLE65I.js.map → JsonApiRequest-54ZBO7WQ.js.map} +1 -1
  15. package/dist/{JsonApiRequest-6IPS3DZJ.mjs → JsonApiRequest-XWQWTFEQ.mjs} +2 -2
  16. package/dist/chunk-3EPNHTMH.js +26 -0
  17. package/dist/chunk-3EPNHTMH.js.map +1 -0
  18. package/dist/{chunk-BCKYJQ3K.mjs → chunk-3VM3WAOV.mjs} +1 -1
  19. package/dist/{chunk-R5QSSISB.js → chunk-7DTKRMYW.js} +21 -14
  20. package/dist/chunk-7DTKRMYW.js.map +1 -0
  21. package/dist/{chunk-ONB2DAIV.js → chunk-D7H7SRWB.js} +455 -470
  22. package/dist/chunk-D7H7SRWB.js.map +1 -0
  23. package/dist/{chunk-BCQSE3EU.mjs → chunk-KUFWHMMY.mjs} +8 -8
  24. package/dist/{chunk-POKIJ56Q.mjs → chunk-KX7YG6LY.mjs} +22 -15
  25. package/dist/chunk-KX7YG6LY.mjs.map +1 -0
  26. package/dist/{chunk-GPGJNTHP.js → chunk-LI6CPNJI.js} +1 -1
  27. package/dist/{chunk-GPGJNTHP.js.map → chunk-LI6CPNJI.js.map} +1 -1
  28. package/dist/{chunk-5RAUCUAA.mjs → chunk-SXPXC2TY.mjs} +18 -33
  29. package/dist/chunk-SXPXC2TY.mjs.map +1 -0
  30. package/dist/{chunk-2AZLCF6D.js → chunk-UYY34W7R.js} +28 -28
  31. package/dist/{chunk-2AZLCF6D.js.map → chunk-UYY34W7R.js.map} +1 -1
  32. package/dist/chunk-VOXD3ZLY.mjs +26 -0
  33. package/dist/chunk-VOXD3ZLY.mjs.map +1 -0
  34. package/dist/client/index.d.mts +11 -45
  35. package/dist/client/index.d.ts +11 -45
  36. package/dist/client/index.js +9 -7
  37. package/dist/client/index.js.map +1 -1
  38. package/dist/client/index.mjs +11 -9
  39. package/dist/components/index.d.mts +4 -3
  40. package/dist/components/index.d.ts +4 -3
  41. package/dist/components/index.js +7 -6
  42. package/dist/components/index.js.map +1 -1
  43. package/dist/components/index.mjs +6 -5
  44. package/dist/{config-DEaUbBqR.d.ts → config--nwiW74Z.d.ts} +1 -1
  45. package/dist/{config-CWsTwnsK.d.mts → config-BKSQmUWU.d.mts} +1 -1
  46. package/dist/{content.interface-D_4b4RQt.d.ts → content.interface-4VICFRA0.d.ts} +2 -1
  47. package/dist/{content.interface-Dk4UZcJM.d.mts → content.interface-CFc97-Cj.d.mts} +2 -1
  48. package/dist/contexts/index.d.mts +3 -2
  49. package/dist/contexts/index.d.ts +3 -2
  50. package/dist/contexts/index.js +7 -6
  51. package/dist/contexts/index.js.map +1 -1
  52. package/dist/contexts/index.mjs +6 -5
  53. package/dist/core/index.d.mts +11 -8
  54. package/dist/core/index.d.ts +11 -8
  55. package/dist/core/index.js +4 -4
  56. package/dist/core/index.js.map +1 -1
  57. package/dist/core/index.mjs +3 -3
  58. package/dist/index.d.mts +15 -11
  59. package/dist/index.d.ts +15 -11
  60. package/dist/index.js +5 -5
  61. package/dist/index.js.map +1 -1
  62. package/dist/index.mjs +6 -6
  63. package/dist/{notification.interface-BllkURRm.d.ts → notification.interface-BGaPiCUM.d.mts} +2 -40
  64. package/dist/{notification.interface-BllkURRm.d.mts → notification.interface-CqwaOIgM.d.ts} +2 -40
  65. package/dist/{s3.service-BEfGqho0.d.ts → s3.service-BYs88XEE.d.ts} +3 -2
  66. package/dist/{s3.service-DIQRYe93.d.mts → s3.service-C0BjOdvn.d.mts} +3 -2
  67. package/dist/server/index.d.mts +6 -4
  68. package/dist/server/index.d.ts +6 -4
  69. package/dist/server/index.js +13 -13
  70. package/dist/server/index.js.map +1 -1
  71. package/dist/server/index.mjs +3 -3
  72. package/dist/{stripe-subscription.interface-C63L6hVg.d.mts → stripe-subscription.interface-B-TM40Io.d.ts} +1 -1
  73. package/dist/{stripe-subscription.interface-CUvNDvw5.d.ts → stripe-subscription.interface-DDxnpj0F.d.mts} +1 -1
  74. package/dist/testing/index.d.mts +338 -0
  75. package/dist/testing/index.d.ts +338 -0
  76. package/dist/testing/index.js +323 -0
  77. package/dist/testing/index.js.map +1 -0
  78. package/dist/testing/index.mjs +323 -0
  79. package/dist/testing/index.mjs.map +1 -0
  80. package/dist/{useSocket-BpenBR2z.d.mts → useSocket-BNj9PrRw.d.mts} +1 -1
  81. package/dist/{useSocket-D-QYA0Sr.d.ts → useSocket-Dwt8cz1x.d.ts} +1 -1
  82. package/package.json +21 -3
  83. package/src/client/hooks/__tests__/useJsonApiGet.test.tsx +229 -0
  84. package/src/client/hooks/__tests__/useJsonApiMutation.test.tsx +348 -0
  85. package/src/client/hooks/__tests__/useRehydration.test.ts +188 -0
  86. package/src/components/forms/__tests__/FormCheckbox.test.tsx +238 -0
  87. package/src/components/forms/__tests__/FormDate.test.tsx +212 -0
  88. package/src/components/forms/__tests__/FormInput.test.tsx +292 -0
  89. package/src/components/forms/__tests__/FormSelect.test.tsx +173 -0
  90. package/src/components/tables/__tests__/ContentListTable.test.tsx +411 -0
  91. package/src/core/endpoint/__tests__/EndpointCreator.test.ts +168 -0
  92. package/src/core/factories/__tests__/JsonApiDataFactory.test.ts +109 -0
  93. package/src/core/factories/__tests__/RehydrationFactory.test.ts +151 -0
  94. package/src/core/registry/__tests__/DataClassRegistry.test.ts +136 -0
  95. package/src/core/registry/__tests__/ModuleRegistrar.test.ts +159 -0
  96. package/src/features/auth/components/details/LandingComponent.tsx +14 -12
  97. package/src/hooks/__tests__/useDataListRetriever.test.ts +321 -0
  98. package/src/hooks/__tests__/useDebounce.test.ts +170 -0
  99. package/src/index.ts +4 -1
  100. package/src/login/config.ts +27 -0
  101. package/src/login/index.ts +2 -0
  102. package/src/testing/factories/createMockApiData.ts +143 -0
  103. package/src/testing/factories/createMockModule.ts +32 -0
  104. package/src/testing/factories/createMockResponse.ts +93 -0
  105. package/src/testing/factories/createMockService.ts +79 -0
  106. package/src/testing/index.ts +70 -0
  107. package/src/testing/matchers/jsonApiMatchers.ts +174 -0
  108. package/src/testing/providers/MockJsonApiProvider.tsx +58 -0
  109. package/src/testing/utils/renderWithProviders.tsx +76 -0
  110. package/src/utils/__tests__/date-formatter.test.ts +161 -0
  111. package/src/utils/__tests__/exists.test.ts +100 -0
  112. package/src/utils/cn.test.ts +44 -0
  113. package/dist/BlockNoteEditor-MBFDWP7X.js.map +0 -1
  114. package/dist/JsonApiRequest-45CLE65I.js +0 -24
  115. package/dist/chunk-5RAUCUAA.mjs.map +0 -1
  116. package/dist/chunk-ONB2DAIV.js.map +0 -1
  117. package/dist/chunk-POKIJ56Q.mjs.map +0 -1
  118. package/dist/chunk-R5QSSISB.js.map +0 -1
  119. package/src/discord/config.ts +0 -15
  120. package/src/discord/index.ts +0 -1
  121. /package/dist/{JsonApiRequest-6IPS3DZJ.mjs.map → JsonApiRequest-XWQWTFEQ.mjs.map} +0 -0
  122. /package/dist/{chunk-BCKYJQ3K.mjs.map → chunk-3VM3WAOV.mjs.map} +0 -0
  123. /package/dist/{chunk-BCQSE3EU.mjs.map → chunk-KUFWHMMY.mjs.map} +0 -0
@@ -0,0 +1,229 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { renderHook, waitFor, act } from "@testing-library/react";
3
+ import React from "react";
4
+ import { useJsonApiGet } from "../useJsonApiGet";
5
+ import { MockJsonApiProvider } from "../../../testing/providers/MockJsonApiProvider";
6
+ import { createMockModule, createMockResponse, createMockApiData } from "../../../testing";
7
+
8
+ // Mock the JsonApiRequest module
9
+ vi.mock("../../../unified/JsonApiRequest", () => ({
10
+ JsonApiGet: vi.fn(),
11
+ }));
12
+
13
+ describe("useJsonApiGet", () => {
14
+ const mockModule = createMockModule({ name: "articles" });
15
+ const mockData = createMockApiData({ type: "articles", id: "1", attributes: { title: "Test Article" } });
16
+
17
+ const wrapper = ({ children }: { children: React.ReactNode }) => (
18
+ <MockJsonApiProvider>{children}</MockJsonApiProvider>
19
+ );
20
+
21
+ beforeEach(() => {
22
+ vi.clearAllMocks();
23
+ });
24
+
25
+ afterEach(() => {
26
+ vi.resetModules();
27
+ });
28
+
29
+ it("should initialize with loading state", async () => {
30
+ const { JsonApiGet } = await import("../../../unified/JsonApiRequest");
31
+ (JsonApiGet as any).mockImplementation(() => new Promise(() => {})); // Never resolves
32
+
33
+ const { result } = renderHook(
34
+ () =>
35
+ useJsonApiGet({
36
+ classKey: mockModule,
37
+ endpoint: "/articles/1",
38
+ }),
39
+ { wrapper }
40
+ );
41
+
42
+ expect(result.current.loading).toBe(true);
43
+ expect(result.current.data).toBeNull();
44
+ expect(result.current.error).toBeNull();
45
+ });
46
+
47
+ it("should fetch data on mount", async () => {
48
+ const { JsonApiGet } = await import("../../../unified/JsonApiRequest");
49
+ const mockResponse = createMockResponse({ data: mockData, ok: true });
50
+ (JsonApiGet as any).mockResolvedValue(mockResponse);
51
+
52
+ const { result } = renderHook(
53
+ () =>
54
+ useJsonApiGet({
55
+ classKey: mockModule,
56
+ endpoint: "/articles/1",
57
+ }),
58
+ { wrapper }
59
+ );
60
+
61
+ await waitFor(() => {
62
+ expect(result.current.loading).toBe(false);
63
+ });
64
+
65
+ expect(result.current.data).toBe(mockData);
66
+ expect(result.current.error).toBeNull();
67
+ expect(result.current.response).toBe(mockResponse);
68
+ });
69
+
70
+ it("should not fetch when enabled is false", async () => {
71
+ const { JsonApiGet } = await import("../../../unified/JsonApiRequest");
72
+ (JsonApiGet as any).mockResolvedValue(createMockResponse({ data: mockData, ok: true }));
73
+
74
+ const { result } = renderHook(
75
+ () =>
76
+ useJsonApiGet({
77
+ classKey: mockModule,
78
+ endpoint: "/articles/1",
79
+ options: { enabled: false },
80
+ }),
81
+ { wrapper }
82
+ );
83
+
84
+ // Wait a bit to ensure no fetch happens
85
+ await new Promise((r) => setTimeout(r, 100));
86
+
87
+ expect(JsonApiGet).not.toHaveBeenCalled();
88
+ expect(result.current.loading).toBe(false);
89
+ expect(result.current.data).toBeNull();
90
+ });
91
+
92
+ it("should handle errors", async () => {
93
+ const { JsonApiGet } = await import("../../../unified/JsonApiRequest");
94
+ const errorResponse = createMockResponse({
95
+ ok: false,
96
+ response: 404,
97
+ error: "Not Found",
98
+ });
99
+ (JsonApiGet as any).mockResolvedValue(errorResponse);
100
+
101
+ const { result } = renderHook(
102
+ () =>
103
+ useJsonApiGet({
104
+ classKey: mockModule,
105
+ endpoint: "/articles/999",
106
+ }),
107
+ { wrapper }
108
+ );
109
+
110
+ await waitFor(() => {
111
+ expect(result.current.loading).toBe(false);
112
+ });
113
+
114
+ expect(result.current.data).toBeNull();
115
+ expect(result.current.error).toBe("Not Found");
116
+ });
117
+
118
+ it("should handle thrown errors", async () => {
119
+ const { JsonApiGet } = await import("../../../unified/JsonApiRequest");
120
+ (JsonApiGet as any).mockRejectedValue(new Error("Network error"));
121
+
122
+ const { result } = renderHook(
123
+ () =>
124
+ useJsonApiGet({
125
+ classKey: mockModule,
126
+ endpoint: "/articles/1",
127
+ }),
128
+ { wrapper }
129
+ );
130
+
131
+ await waitFor(() => {
132
+ expect(result.current.loading).toBe(false);
133
+ });
134
+
135
+ expect(result.current.error).toBe("Network error");
136
+ });
137
+
138
+ it("should support refetch", async () => {
139
+ const { JsonApiGet } = await import("../../../unified/JsonApiRequest");
140
+ let callCount = 0;
141
+ (JsonApiGet as any).mockImplementation(() => {
142
+ callCount++;
143
+ return Promise.resolve(
144
+ createMockResponse({
145
+ data: createMockApiData({
146
+ type: "articles",
147
+ id: "1",
148
+ attributes: { title: `Article ${callCount}` },
149
+ }),
150
+ ok: true,
151
+ })
152
+ );
153
+ });
154
+
155
+ const { result } = renderHook(
156
+ () =>
157
+ useJsonApiGet({
158
+ classKey: mockModule,
159
+ endpoint: "/articles/1",
160
+ }),
161
+ { wrapper }
162
+ );
163
+
164
+ await waitFor(() => {
165
+ expect(result.current.loading).toBe(false);
166
+ });
167
+
168
+ expect(callCount).toBe(1);
169
+
170
+ // Trigger refetch
171
+ await act(async () => {
172
+ await result.current.refetch();
173
+ });
174
+
175
+ expect(callCount).toBe(2);
176
+ });
177
+
178
+ it("should indicate pagination availability", async () => {
179
+ const { JsonApiGet } = await import("../../../unified/JsonApiRequest");
180
+ const mockResponse = createMockResponse({
181
+ data: mockData,
182
+ ok: true,
183
+ next: "/articles?page=2",
184
+ prev: "/articles?page=0",
185
+ });
186
+ (JsonApiGet as any).mockResolvedValue(mockResponse);
187
+
188
+ const { result } = renderHook(
189
+ () =>
190
+ useJsonApiGet({
191
+ classKey: mockModule,
192
+ endpoint: "/articles",
193
+ }),
194
+ { wrapper }
195
+ );
196
+
197
+ await waitFor(() => {
198
+ expect(result.current.loading).toBe(false);
199
+ });
200
+
201
+ expect(result.current.hasNextPage).toBe(true);
202
+ expect(result.current.hasPreviousPage).toBe(true);
203
+ });
204
+
205
+ it("should handle no pagination", async () => {
206
+ const { JsonApiGet } = await import("../../../unified/JsonApiRequest");
207
+ const mockResponse = createMockResponse({
208
+ data: mockData,
209
+ ok: true,
210
+ });
211
+ (JsonApiGet as any).mockResolvedValue(mockResponse);
212
+
213
+ const { result } = renderHook(
214
+ () =>
215
+ useJsonApiGet({
216
+ classKey: mockModule,
217
+ endpoint: "/articles/1",
218
+ }),
219
+ { wrapper }
220
+ );
221
+
222
+ await waitFor(() => {
223
+ expect(result.current.loading).toBe(false);
224
+ });
225
+
226
+ expect(result.current.hasNextPage).toBe(false);
227
+ expect(result.current.hasPreviousPage).toBe(false);
228
+ });
229
+ });
@@ -0,0 +1,348 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { renderHook, waitFor, act } from "@testing-library/react";
3
+ import React from "react";
4
+ import { useJsonApiMutation } from "../useJsonApiMutation";
5
+ import { MockJsonApiProvider } from "../../../testing/providers/MockJsonApiProvider";
6
+ import { createMockModule, createMockResponse, createMockApiData } from "../../../testing";
7
+
8
+ // Mock the JsonApiRequest module
9
+ vi.mock("../../../unified/JsonApiRequest", () => ({
10
+ JsonApiPost: vi.fn(),
11
+ JsonApiPut: vi.fn(),
12
+ JsonApiPatch: vi.fn(),
13
+ JsonApiDelete: vi.fn(),
14
+ }));
15
+
16
+ describe("useJsonApiMutation", () => {
17
+ const mockModule = createMockModule({ name: "articles" });
18
+ const mockData = createMockApiData({
19
+ type: "articles",
20
+ id: "1",
21
+ attributes: { title: "Created Article" },
22
+ });
23
+
24
+ const wrapper = ({ children }: { children: React.ReactNode }) => (
25
+ <MockJsonApiProvider>{children}</MockJsonApiProvider>
26
+ );
27
+
28
+ beforeEach(() => {
29
+ vi.clearAllMocks();
30
+ });
31
+
32
+ afterEach(() => {
33
+ vi.resetModules();
34
+ });
35
+
36
+ describe("POST mutations", () => {
37
+ it("should initialize with idle state", () => {
38
+ const { result } = renderHook(
39
+ () =>
40
+ useJsonApiMutation({
41
+ method: "POST",
42
+ classKey: mockModule,
43
+ }),
44
+ { wrapper }
45
+ );
46
+
47
+ expect(result.current.loading).toBe(false);
48
+ expect(result.current.data).toBeNull();
49
+ expect(result.current.error).toBeNull();
50
+ expect(result.current.response).toBeNull();
51
+ });
52
+
53
+ it("should execute POST mutation", async () => {
54
+ const { JsonApiPost } = await import("../../../unified/JsonApiRequest");
55
+ const mockResponse = createMockResponse({ data: mockData, ok: true });
56
+ (JsonApiPost as any).mockResolvedValue(mockResponse);
57
+
58
+ const { result } = renderHook(
59
+ () =>
60
+ useJsonApiMutation({
61
+ method: "POST",
62
+ classKey: mockModule,
63
+ }),
64
+ { wrapper }
65
+ );
66
+
67
+ let mutationResult: any;
68
+ await act(async () => {
69
+ mutationResult = await result.current.mutate({
70
+ endpoint: "/articles",
71
+ body: { title: "New Article" },
72
+ });
73
+ });
74
+
75
+ expect(result.current.data).toBe(mockData);
76
+ expect(result.current.error).toBeNull();
77
+ expect(mutationResult).toBe(mockData);
78
+ expect(JsonApiPost).toHaveBeenCalled();
79
+ });
80
+
81
+ it("should handle POST errors", async () => {
82
+ const { JsonApiPost } = await import("../../../unified/JsonApiRequest");
83
+ const errorResponse = createMockResponse({
84
+ ok: false,
85
+ response: 400,
86
+ error: "Validation failed",
87
+ });
88
+ (JsonApiPost as any).mockResolvedValue(errorResponse);
89
+
90
+ const { result } = renderHook(
91
+ () =>
92
+ useJsonApiMutation({
93
+ method: "POST",
94
+ classKey: mockModule,
95
+ }),
96
+ { wrapper }
97
+ );
98
+
99
+ let mutationResult: any;
100
+ await act(async () => {
101
+ mutationResult = await result.current.mutate({
102
+ endpoint: "/articles",
103
+ body: {},
104
+ });
105
+ });
106
+
107
+ expect(result.current.data).toBeNull();
108
+ expect(result.current.error).toBe("Validation failed");
109
+ expect(mutationResult).toBeNull();
110
+ });
111
+
112
+ it("should call onSuccess callback", async () => {
113
+ const { JsonApiPost } = await import("../../../unified/JsonApiRequest");
114
+ const mockResponse = createMockResponse({ data: mockData, ok: true });
115
+ (JsonApiPost as any).mockResolvedValue(mockResponse);
116
+ const onSuccess = vi.fn();
117
+
118
+ const { result } = renderHook(
119
+ () =>
120
+ useJsonApiMutation({
121
+ method: "POST",
122
+ classKey: mockModule,
123
+ onSuccess,
124
+ }),
125
+ { wrapper }
126
+ );
127
+
128
+ await act(async () => {
129
+ await result.current.mutate({
130
+ endpoint: "/articles",
131
+ body: { title: "New" },
132
+ });
133
+ });
134
+
135
+ expect(onSuccess).toHaveBeenCalledWith(mockData);
136
+ });
137
+
138
+ it("should call onError callback", async () => {
139
+ const { JsonApiPost } = await import("../../../unified/JsonApiRequest");
140
+ const errorResponse = createMockResponse({
141
+ ok: false,
142
+ error: "Failed",
143
+ });
144
+ (JsonApiPost as any).mockResolvedValue(errorResponse);
145
+ const onError = vi.fn();
146
+
147
+ const { result } = renderHook(
148
+ () =>
149
+ useJsonApiMutation({
150
+ method: "POST",
151
+ classKey: mockModule,
152
+ onError,
153
+ }),
154
+ { wrapper }
155
+ );
156
+
157
+ await act(async () => {
158
+ await result.current.mutate({
159
+ endpoint: "/articles",
160
+ body: {},
161
+ });
162
+ });
163
+
164
+ expect(onError).toHaveBeenCalledWith("Failed");
165
+ });
166
+ });
167
+
168
+ describe("PUT mutations", () => {
169
+ it("should execute PUT mutation", async () => {
170
+ const { JsonApiPut } = await import("../../../unified/JsonApiRequest");
171
+ const mockResponse = createMockResponse({ data: mockData, ok: true });
172
+ (JsonApiPut as any).mockResolvedValue(mockResponse);
173
+
174
+ const { result } = renderHook(
175
+ () =>
176
+ useJsonApiMutation({
177
+ method: "PUT",
178
+ classKey: mockModule,
179
+ }),
180
+ { wrapper }
181
+ );
182
+
183
+ await act(async () => {
184
+ await result.current.mutate({
185
+ endpoint: "/articles/1",
186
+ body: { title: "Updated" },
187
+ });
188
+ });
189
+
190
+ expect(JsonApiPut).toHaveBeenCalled();
191
+ expect(result.current.data).toBe(mockData);
192
+ });
193
+ });
194
+
195
+ describe("PATCH mutations", () => {
196
+ it("should execute PATCH mutation", async () => {
197
+ const { JsonApiPatch } = await import("../../../unified/JsonApiRequest");
198
+ const mockResponse = createMockResponse({ data: mockData, ok: true });
199
+ (JsonApiPatch as any).mockResolvedValue(mockResponse);
200
+
201
+ const { result } = renderHook(
202
+ () =>
203
+ useJsonApiMutation({
204
+ method: "PATCH",
205
+ classKey: mockModule,
206
+ }),
207
+ { wrapper }
208
+ );
209
+
210
+ await act(async () => {
211
+ await result.current.mutate({
212
+ endpoint: "/articles/1",
213
+ body: { title: "Patched" },
214
+ });
215
+ });
216
+
217
+ expect(JsonApiPatch).toHaveBeenCalled();
218
+ expect(result.current.data).toBe(mockData);
219
+ });
220
+ });
221
+
222
+ describe("DELETE mutations", () => {
223
+ it("should execute DELETE mutation", async () => {
224
+ const { JsonApiDelete } = await import("../../../unified/JsonApiRequest");
225
+ const mockResponse = createMockResponse({ data: mockData, ok: true });
226
+ (JsonApiDelete as any).mockResolvedValue(mockResponse);
227
+
228
+ const { result } = renderHook(
229
+ () =>
230
+ useJsonApiMutation({
231
+ method: "DELETE",
232
+ classKey: mockModule,
233
+ }),
234
+ { wrapper }
235
+ );
236
+
237
+ await act(async () => {
238
+ await result.current.mutate({
239
+ endpoint: "/articles/1",
240
+ });
241
+ });
242
+
243
+ expect(JsonApiDelete).toHaveBeenCalled();
244
+ });
245
+ });
246
+
247
+ describe("reset", () => {
248
+ it("should reset state", async () => {
249
+ const { JsonApiPost } = await import("../../../unified/JsonApiRequest");
250
+ const mockResponse = createMockResponse({ data: mockData, ok: true });
251
+ (JsonApiPost as any).mockResolvedValue(mockResponse);
252
+
253
+ const { result } = renderHook(
254
+ () =>
255
+ useJsonApiMutation({
256
+ method: "POST",
257
+ classKey: mockModule,
258
+ }),
259
+ { wrapper }
260
+ );
261
+
262
+ await act(async () => {
263
+ await result.current.mutate({
264
+ endpoint: "/articles",
265
+ body: {},
266
+ });
267
+ });
268
+
269
+ expect(result.current.data).not.toBeNull();
270
+
271
+ act(() => {
272
+ result.current.reset();
273
+ });
274
+
275
+ expect(result.current.data).toBeNull();
276
+ expect(result.current.error).toBeNull();
277
+ expect(result.current.response).toBeNull();
278
+ expect(result.current.loading).toBe(false);
279
+ });
280
+ });
281
+
282
+ describe("loading state", () => {
283
+ it("should set loading during mutation", async () => {
284
+ const { JsonApiPost } = await import("../../../unified/JsonApiRequest");
285
+ let resolvePromise: any;
286
+ const pendingPromise = new Promise((resolve) => {
287
+ resolvePromise = resolve;
288
+ });
289
+ (JsonApiPost as any).mockReturnValue(pendingPromise);
290
+
291
+ const { result } = renderHook(
292
+ () =>
293
+ useJsonApiMutation({
294
+ method: "POST",
295
+ classKey: mockModule,
296
+ }),
297
+ { wrapper }
298
+ );
299
+
300
+ // Start mutation without awaiting
301
+ act(() => {
302
+ result.current.mutate({
303
+ endpoint: "/articles",
304
+ body: {},
305
+ });
306
+ });
307
+
308
+ // Should be loading
309
+ expect(result.current.loading).toBe(true);
310
+
311
+ // Resolve the promise
312
+ await act(async () => {
313
+ resolvePromise(createMockResponse({ data: mockData, ok: true }));
314
+ });
315
+
316
+ // Loading should be false after completion
317
+ await waitFor(() => {
318
+ expect(result.current.loading).toBe(false);
319
+ });
320
+ });
321
+ });
322
+
323
+ describe("error handling", () => {
324
+ it("should handle thrown exceptions", async () => {
325
+ const { JsonApiPost } = await import("../../../unified/JsonApiRequest");
326
+ (JsonApiPost as any).mockRejectedValue(new Error("Network error"));
327
+
328
+ const { result } = renderHook(
329
+ () =>
330
+ useJsonApiMutation({
331
+ method: "POST",
332
+ classKey: mockModule,
333
+ }),
334
+ { wrapper }
335
+ );
336
+
337
+ await act(async () => {
338
+ await result.current.mutate({
339
+ endpoint: "/articles",
340
+ body: {},
341
+ });
342
+ });
343
+
344
+ expect(result.current.error).toBe("Network error");
345
+ expect(result.current.data).toBeNull();
346
+ });
347
+ });
348
+ });