@nimbleflux/fluxbase-sdk-react 2026.3.6-rc.1
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.
- package/.nvmrc +1 -0
- package/README-ADMIN.md +1076 -0
- package/README.md +195 -0
- package/examples/AdminDashboard.tsx +513 -0
- package/examples/README.md +163 -0
- package/package.json +66 -0
- package/src/context.test.tsx +147 -0
- package/src/context.tsx +33 -0
- package/src/index.test.ts +255 -0
- package/src/index.ts +175 -0
- package/src/test-setup.ts +22 -0
- package/src/test-utils.tsx +215 -0
- package/src/use-admin-auth.test.ts +175 -0
- package/src/use-admin-auth.ts +187 -0
- package/src/use-admin-hooks.test.ts +457 -0
- package/src/use-admin-hooks.ts +309 -0
- package/src/use-auth-config.test.ts +145 -0
- package/src/use-auth-config.ts +101 -0
- package/src/use-auth.test.ts +313 -0
- package/src/use-auth.ts +164 -0
- package/src/use-captcha.test.ts +273 -0
- package/src/use-captcha.ts +250 -0
- package/src/use-client-keys.test.ts +286 -0
- package/src/use-client-keys.ts +185 -0
- package/src/use-graphql.test.ts +424 -0
- package/src/use-graphql.ts +392 -0
- package/src/use-query.test.ts +348 -0
- package/src/use-query.ts +211 -0
- package/src/use-realtime.test.ts +359 -0
- package/src/use-realtime.ts +180 -0
- package/src/use-saml.test.ts +269 -0
- package/src/use-saml.ts +221 -0
- package/src/use-storage.test.ts +549 -0
- package/src/use-storage.ts +508 -0
- package/src/use-table-export.ts +481 -0
- package/src/use-users.test.ts +264 -0
- package/src/use-users.ts +198 -0
- package/tsconfig.json +28 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/tsup.config.ts +11 -0
- package/typedoc.json +33 -0
- package/vitest.config.ts +22 -0
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for admin hooks (settings, webhooks)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
6
|
+
import { renderHook, waitFor, act } from "@testing-library/react";
|
|
7
|
+
import {
|
|
8
|
+
useAppSettings,
|
|
9
|
+
useSystemSettings,
|
|
10
|
+
useWebhooks,
|
|
11
|
+
} from "./use-admin-hooks";
|
|
12
|
+
import { createMockClient, createWrapper } from "./test-utils";
|
|
13
|
+
|
|
14
|
+
describe("useAppSettings", () => {
|
|
15
|
+
it("should fetch settings on mount when autoFetch is true", async () => {
|
|
16
|
+
const mockSettings = { features: { darkMode: true } };
|
|
17
|
+
const getMock = vi.fn().mockResolvedValue(mockSettings);
|
|
18
|
+
|
|
19
|
+
const client = createMockClient({
|
|
20
|
+
admin: {
|
|
21
|
+
settings: {
|
|
22
|
+
app: { get: getMock, update: vi.fn() },
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
} as any);
|
|
26
|
+
|
|
27
|
+
const { result } = renderHook(() => useAppSettings({ autoFetch: true }), {
|
|
28
|
+
wrapper: createWrapper(client),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
32
|
+
expect(result.current.settings).toEqual(mockSettings);
|
|
33
|
+
expect(getMock).toHaveBeenCalled();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("should not fetch settings on mount when autoFetch is false", async () => {
|
|
37
|
+
const getMock = vi.fn();
|
|
38
|
+
|
|
39
|
+
const client = createMockClient({
|
|
40
|
+
admin: {
|
|
41
|
+
settings: {
|
|
42
|
+
app: { get: getMock, update: vi.fn() },
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
} as any);
|
|
46
|
+
|
|
47
|
+
const { result } = renderHook(() => useAppSettings({ autoFetch: false }), {
|
|
48
|
+
wrapper: createWrapper(client),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
52
|
+
expect(getMock).not.toHaveBeenCalled();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("should update settings", async () => {
|
|
56
|
+
const mockSettings = { features: { darkMode: true } };
|
|
57
|
+
const getMock = vi.fn().mockResolvedValue(mockSettings);
|
|
58
|
+
const updateMock = vi.fn().mockResolvedValue({});
|
|
59
|
+
|
|
60
|
+
const client = createMockClient({
|
|
61
|
+
admin: {
|
|
62
|
+
settings: {
|
|
63
|
+
app: { get: getMock, update: updateMock },
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
} as any);
|
|
67
|
+
|
|
68
|
+
const { result } = renderHook(() => useAppSettings({ autoFetch: true }), {
|
|
69
|
+
wrapper: createWrapper(client),
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
73
|
+
|
|
74
|
+
await act(async () => {
|
|
75
|
+
await result.current.updateSettings({
|
|
76
|
+
features: { enable_realtime: false },
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
expect(updateMock).toHaveBeenCalledWith({
|
|
81
|
+
features: { enable_realtime: false },
|
|
82
|
+
});
|
|
83
|
+
// Should refetch after update
|
|
84
|
+
expect(getMock).toHaveBeenCalledTimes(2);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("should handle errors", async () => {
|
|
88
|
+
const error = new Error("Failed to fetch");
|
|
89
|
+
const getMock = vi.fn().mockRejectedValue(error);
|
|
90
|
+
|
|
91
|
+
const client = createMockClient({
|
|
92
|
+
admin: {
|
|
93
|
+
settings: {
|
|
94
|
+
app: { get: getMock, update: vi.fn() },
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
} as any);
|
|
98
|
+
|
|
99
|
+
const { result } = renderHook(() => useAppSettings({ autoFetch: true }), {
|
|
100
|
+
wrapper: createWrapper(client),
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
104
|
+
expect(result.current.error).toBe(error);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("should refetch on demand", async () => {
|
|
108
|
+
const mockSettings = { features: {} };
|
|
109
|
+
const getMock = vi.fn().mockResolvedValue(mockSettings);
|
|
110
|
+
|
|
111
|
+
const client = createMockClient({
|
|
112
|
+
admin: {
|
|
113
|
+
settings: {
|
|
114
|
+
app: { get: getMock, update: vi.fn() },
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
} as any);
|
|
118
|
+
|
|
119
|
+
const { result } = renderHook(() => useAppSettings({ autoFetch: false }), {
|
|
120
|
+
wrapper: createWrapper(client),
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
await act(async () => {
|
|
124
|
+
await result.current.refetch();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
expect(getMock).toHaveBeenCalledTimes(1);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe("useSystemSettings", () => {
|
|
132
|
+
it("should fetch settings on mount when autoFetch is true", async () => {
|
|
133
|
+
const mockSettings = [{ key: "theme", value: "dark" }];
|
|
134
|
+
const listMock = vi.fn().mockResolvedValue({ settings: mockSettings });
|
|
135
|
+
|
|
136
|
+
const client = createMockClient({
|
|
137
|
+
admin: {
|
|
138
|
+
settings: {
|
|
139
|
+
system: { list: listMock, update: vi.fn(), delete: vi.fn() },
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
} as any);
|
|
143
|
+
|
|
144
|
+
const { result } = renderHook(
|
|
145
|
+
() => useSystemSettings({ autoFetch: true }),
|
|
146
|
+
{ wrapper: createWrapper(client) },
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
150
|
+
expect(result.current.settings).toEqual(mockSettings);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("should get setting by key", async () => {
|
|
154
|
+
const mockSettings = [
|
|
155
|
+
{ key: "theme", value: "dark" },
|
|
156
|
+
{ key: "language", value: "en" },
|
|
157
|
+
];
|
|
158
|
+
const listMock = vi.fn().mockResolvedValue({ settings: mockSettings });
|
|
159
|
+
|
|
160
|
+
const client = createMockClient({
|
|
161
|
+
admin: {
|
|
162
|
+
settings: {
|
|
163
|
+
system: { list: listMock, update: vi.fn(), delete: vi.fn() },
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
} as any);
|
|
167
|
+
|
|
168
|
+
const { result } = renderHook(
|
|
169
|
+
() => useSystemSettings({ autoFetch: true }),
|
|
170
|
+
{ wrapper: createWrapper(client) },
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
174
|
+
|
|
175
|
+
const setting = result.current.getSetting("theme");
|
|
176
|
+
expect(setting).toEqual({ key: "theme", value: "dark" });
|
|
177
|
+
|
|
178
|
+
const notFound = result.current.getSetting("nonexistent");
|
|
179
|
+
expect(notFound).toBeUndefined();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("should update setting", async () => {
|
|
183
|
+
const mockSettings = [{ key: "theme", value: "dark" }];
|
|
184
|
+
const listMock = vi.fn().mockResolvedValue({ settings: mockSettings });
|
|
185
|
+
const updateMock = vi.fn().mockResolvedValue({});
|
|
186
|
+
|
|
187
|
+
const client = createMockClient({
|
|
188
|
+
admin: {
|
|
189
|
+
settings: {
|
|
190
|
+
system: { list: listMock, update: updateMock, delete: vi.fn() },
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
} as any);
|
|
194
|
+
|
|
195
|
+
const { result } = renderHook(
|
|
196
|
+
() => useSystemSettings({ autoFetch: true }),
|
|
197
|
+
{ wrapper: createWrapper(client) },
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
201
|
+
|
|
202
|
+
await act(async () => {
|
|
203
|
+
await result.current.updateSetting("theme", {
|
|
204
|
+
value: { theme: "light" },
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
expect(updateMock).toHaveBeenCalledWith("theme", {
|
|
209
|
+
value: { theme: "light" },
|
|
210
|
+
});
|
|
211
|
+
// Should refetch after update
|
|
212
|
+
expect(listMock).toHaveBeenCalledTimes(2);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it("should delete setting", async () => {
|
|
216
|
+
const mockSettings = [{ key: "theme", value: "dark" }];
|
|
217
|
+
const listMock = vi.fn().mockResolvedValue({ settings: mockSettings });
|
|
218
|
+
const deleteMock = vi.fn().mockResolvedValue({});
|
|
219
|
+
|
|
220
|
+
const client = createMockClient({
|
|
221
|
+
admin: {
|
|
222
|
+
settings: {
|
|
223
|
+
system: { list: listMock, update: vi.fn(), delete: deleteMock },
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
} as any);
|
|
227
|
+
|
|
228
|
+
const { result } = renderHook(
|
|
229
|
+
() => useSystemSettings({ autoFetch: true }),
|
|
230
|
+
{ wrapper: createWrapper(client) },
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
234
|
+
|
|
235
|
+
await act(async () => {
|
|
236
|
+
await result.current.deleteSetting("theme");
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
expect(deleteMock).toHaveBeenCalledWith("theme");
|
|
240
|
+
// Should refetch after delete
|
|
241
|
+
expect(listMock).toHaveBeenCalledTimes(2);
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
describe("useWebhooks", () => {
|
|
246
|
+
it("should fetch webhooks on mount when autoFetch is true", async () => {
|
|
247
|
+
const mockWebhooks = [{ id: "1", url: "https://example.com/webhook" }];
|
|
248
|
+
const listMock = vi.fn().mockResolvedValue({ webhooks: mockWebhooks });
|
|
249
|
+
|
|
250
|
+
const client = createMockClient({
|
|
251
|
+
admin: {
|
|
252
|
+
management: {
|
|
253
|
+
webhooks: {
|
|
254
|
+
list: listMock,
|
|
255
|
+
create: vi.fn(),
|
|
256
|
+
update: vi.fn(),
|
|
257
|
+
delete: vi.fn(),
|
|
258
|
+
test: vi.fn(),
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
} as any);
|
|
263
|
+
|
|
264
|
+
const { result } = renderHook(() => useWebhooks({ autoFetch: true }), {
|
|
265
|
+
wrapper: createWrapper(client),
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
269
|
+
expect(result.current.webhooks).toEqual(mockWebhooks);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it("should create webhook", async () => {
|
|
273
|
+
const mockWebhooks: any[] = [];
|
|
274
|
+
const listMock = vi.fn().mockResolvedValue({ webhooks: mockWebhooks });
|
|
275
|
+
const createMock = vi
|
|
276
|
+
.fn()
|
|
277
|
+
.mockResolvedValue({ id: "1", url: "https://example.com/webhook" });
|
|
278
|
+
|
|
279
|
+
const client = createMockClient({
|
|
280
|
+
admin: {
|
|
281
|
+
management: {
|
|
282
|
+
webhooks: {
|
|
283
|
+
list: listMock,
|
|
284
|
+
create: createMock,
|
|
285
|
+
update: vi.fn(),
|
|
286
|
+
delete: vi.fn(),
|
|
287
|
+
test: vi.fn(),
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
} as any);
|
|
292
|
+
|
|
293
|
+
const { result } = renderHook(() => useWebhooks({ autoFetch: true }), {
|
|
294
|
+
wrapper: createWrapper(client),
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
298
|
+
|
|
299
|
+
await act(async () => {
|
|
300
|
+
await result.current.createWebhook({
|
|
301
|
+
url: "https://example.com/webhook",
|
|
302
|
+
events: ["user.created"],
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
expect(createMock).toHaveBeenCalledWith({
|
|
307
|
+
url: "https://example.com/webhook",
|
|
308
|
+
events: ["user.created"],
|
|
309
|
+
});
|
|
310
|
+
// Should refetch after create
|
|
311
|
+
expect(listMock).toHaveBeenCalledTimes(2);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it("should update webhook", async () => {
|
|
315
|
+
const mockWebhooks = [{ id: "1", url: "https://example.com/webhook" }];
|
|
316
|
+
const listMock = vi.fn().mockResolvedValue({ webhooks: mockWebhooks });
|
|
317
|
+
const updateMock = vi
|
|
318
|
+
.fn()
|
|
319
|
+
.mockResolvedValue({ id: "1", url: "https://new.com/webhook" });
|
|
320
|
+
|
|
321
|
+
const client = createMockClient({
|
|
322
|
+
admin: {
|
|
323
|
+
management: {
|
|
324
|
+
webhooks: {
|
|
325
|
+
list: listMock,
|
|
326
|
+
create: vi.fn(),
|
|
327
|
+
update: updateMock,
|
|
328
|
+
delete: vi.fn(),
|
|
329
|
+
test: vi.fn(),
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
} as any);
|
|
334
|
+
|
|
335
|
+
const { result } = renderHook(() => useWebhooks({ autoFetch: true }), {
|
|
336
|
+
wrapper: createWrapper(client),
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
340
|
+
|
|
341
|
+
await act(async () => {
|
|
342
|
+
await result.current.updateWebhook("1", {
|
|
343
|
+
url: "https://new.com/webhook",
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
expect(updateMock).toHaveBeenCalledWith("1", {
|
|
348
|
+
url: "https://new.com/webhook",
|
|
349
|
+
});
|
|
350
|
+
// Should refetch after update
|
|
351
|
+
expect(listMock).toHaveBeenCalledTimes(2);
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it("should delete webhook", async () => {
|
|
355
|
+
const mockWebhooks = [{ id: "1", url: "https://example.com/webhook" }];
|
|
356
|
+
const listMock = vi.fn().mockResolvedValue({ webhooks: mockWebhooks });
|
|
357
|
+
const deleteMock = vi.fn().mockResolvedValue({});
|
|
358
|
+
|
|
359
|
+
const client = createMockClient({
|
|
360
|
+
admin: {
|
|
361
|
+
management: {
|
|
362
|
+
webhooks: {
|
|
363
|
+
list: listMock,
|
|
364
|
+
create: vi.fn(),
|
|
365
|
+
update: vi.fn(),
|
|
366
|
+
delete: deleteMock,
|
|
367
|
+
test: vi.fn(),
|
|
368
|
+
},
|
|
369
|
+
},
|
|
370
|
+
},
|
|
371
|
+
} as any);
|
|
372
|
+
|
|
373
|
+
const { result } = renderHook(() => useWebhooks({ autoFetch: true }), {
|
|
374
|
+
wrapper: createWrapper(client),
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
378
|
+
|
|
379
|
+
await act(async () => {
|
|
380
|
+
await result.current.deleteWebhook("1");
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
expect(deleteMock).toHaveBeenCalledWith("1");
|
|
384
|
+
// Should refetch after delete
|
|
385
|
+
expect(listMock).toHaveBeenCalledTimes(2);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it("should test webhook", async () => {
|
|
389
|
+
const mockWebhooks = [{ id: "1", url: "https://example.com/webhook" }];
|
|
390
|
+
const listMock = vi.fn().mockResolvedValue({ webhooks: mockWebhooks });
|
|
391
|
+
const testMock = vi.fn().mockResolvedValue({});
|
|
392
|
+
|
|
393
|
+
const client = createMockClient({
|
|
394
|
+
admin: {
|
|
395
|
+
management: {
|
|
396
|
+
webhooks: {
|
|
397
|
+
list: listMock,
|
|
398
|
+
create: vi.fn(),
|
|
399
|
+
update: vi.fn(),
|
|
400
|
+
delete: vi.fn(),
|
|
401
|
+
test: testMock,
|
|
402
|
+
},
|
|
403
|
+
},
|
|
404
|
+
},
|
|
405
|
+
} as any);
|
|
406
|
+
|
|
407
|
+
const { result } = renderHook(() => useWebhooks({ autoFetch: true }), {
|
|
408
|
+
wrapper: createWrapper(client),
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
412
|
+
|
|
413
|
+
await act(async () => {
|
|
414
|
+
await result.current.testWebhook("1");
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
expect(testMock).toHaveBeenCalledWith("1");
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
it("should set up refetch interval", async () => {
|
|
421
|
+
vi.useFakeTimers();
|
|
422
|
+
const mockWebhooks: any[] = [];
|
|
423
|
+
const listMock = vi.fn().mockResolvedValue({ webhooks: mockWebhooks });
|
|
424
|
+
|
|
425
|
+
const client = createMockClient({
|
|
426
|
+
admin: {
|
|
427
|
+
management: {
|
|
428
|
+
webhooks: {
|
|
429
|
+
list: listMock,
|
|
430
|
+
create: vi.fn(),
|
|
431
|
+
update: vi.fn(),
|
|
432
|
+
delete: vi.fn(),
|
|
433
|
+
test: vi.fn(),
|
|
434
|
+
},
|
|
435
|
+
},
|
|
436
|
+
},
|
|
437
|
+
} as any);
|
|
438
|
+
|
|
439
|
+
const { unmount } = renderHook(
|
|
440
|
+
() => useWebhooks({ autoFetch: true, refetchInterval: 5000 }),
|
|
441
|
+
{ wrapper: createWrapper(client) },
|
|
442
|
+
);
|
|
443
|
+
|
|
444
|
+
// Initial fetch
|
|
445
|
+
expect(listMock).toHaveBeenCalledTimes(1);
|
|
446
|
+
|
|
447
|
+
// Advance timer
|
|
448
|
+
await act(async () => {
|
|
449
|
+
vi.advanceTimersByTime(5000);
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
expect(listMock).toHaveBeenCalledTimes(2);
|
|
453
|
+
|
|
454
|
+
unmount();
|
|
455
|
+
vi.useRealTimers();
|
|
456
|
+
});
|
|
457
|
+
});
|