@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.
Files changed (42) hide show
  1. package/.nvmrc +1 -0
  2. package/README-ADMIN.md +1076 -0
  3. package/README.md +195 -0
  4. package/examples/AdminDashboard.tsx +513 -0
  5. package/examples/README.md +163 -0
  6. package/package.json +66 -0
  7. package/src/context.test.tsx +147 -0
  8. package/src/context.tsx +33 -0
  9. package/src/index.test.ts +255 -0
  10. package/src/index.ts +175 -0
  11. package/src/test-setup.ts +22 -0
  12. package/src/test-utils.tsx +215 -0
  13. package/src/use-admin-auth.test.ts +175 -0
  14. package/src/use-admin-auth.ts +187 -0
  15. package/src/use-admin-hooks.test.ts +457 -0
  16. package/src/use-admin-hooks.ts +309 -0
  17. package/src/use-auth-config.test.ts +145 -0
  18. package/src/use-auth-config.ts +101 -0
  19. package/src/use-auth.test.ts +313 -0
  20. package/src/use-auth.ts +164 -0
  21. package/src/use-captcha.test.ts +273 -0
  22. package/src/use-captcha.ts +250 -0
  23. package/src/use-client-keys.test.ts +286 -0
  24. package/src/use-client-keys.ts +185 -0
  25. package/src/use-graphql.test.ts +424 -0
  26. package/src/use-graphql.ts +392 -0
  27. package/src/use-query.test.ts +348 -0
  28. package/src/use-query.ts +211 -0
  29. package/src/use-realtime.test.ts +359 -0
  30. package/src/use-realtime.ts +180 -0
  31. package/src/use-saml.test.ts +269 -0
  32. package/src/use-saml.ts +221 -0
  33. package/src/use-storage.test.ts +549 -0
  34. package/src/use-storage.ts +508 -0
  35. package/src/use-table-export.ts +481 -0
  36. package/src/use-users.test.ts +264 -0
  37. package/src/use-users.ts +198 -0
  38. package/tsconfig.json +28 -0
  39. package/tsconfig.tsbuildinfo +1 -0
  40. package/tsup.config.ts +11 -0
  41. package/typedoc.json +33 -0
  42. 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
+ });