@strivacity/sdk-react 3.0.3 → 4.0.0-beta.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 (59) hide show
  1. package/CHANGELOG.md +1 -1
  2. package/README.md +1562 -406
  3. package/dist/assets/rolldown-runtime.cjs +1 -0
  4. package/dist/assets/rolldown-runtime.mjs +1 -0
  5. package/dist/components.cjs +2 -0
  6. package/dist/components.cjs.map +1 -0
  7. package/dist/components.d.ts +11 -0
  8. package/dist/components.mjs +2 -0
  9. package/dist/components.mjs.map +1 -0
  10. package/dist/errors.cjs +2 -0
  11. package/dist/errors.cjs.map +1 -0
  12. package/dist/errors.d.ts +1 -0
  13. package/dist/errors.mjs +2 -0
  14. package/dist/errors.mjs.map +1 -0
  15. package/dist/hooks.cjs +2 -0
  16. package/dist/hooks.cjs.map +1 -0
  17. package/dist/hooks.d.ts +33 -0
  18. package/dist/hooks.mjs +2 -0
  19. package/dist/hooks.mjs.map +1 -0
  20. package/dist/index.cjs +2 -2
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.d.ts +9 -13
  23. package/dist/index.mjs +2 -2
  24. package/dist/index.mjs.map +1 -1
  25. package/dist/storages.cjs +1 -0
  26. package/dist/storages.d.ts +1 -0
  27. package/dist/storages.mjs +1 -0
  28. package/dist/types.cjs +1 -2
  29. package/dist/types.d.ts +147 -110
  30. package/dist/types.mjs +1 -2
  31. package/dist/utils.cjs +1 -0
  32. package/dist/utils.d.ts +1 -0
  33. package/dist/utils.mjs +1 -0
  34. package/package.json +51 -7
  35. package/testing/tests/components.spec.tsx +146 -0
  36. package/testing/tests/errors.spec.ts +10 -0
  37. package/testing/tests/guard.spec.tsx +119 -0
  38. package/testing/tests/hooks.spec.tsx +418 -0
  39. package/testing/tests/index.spec.ts +105 -0
  40. package/testing/tests/storages.spec.ts +10 -0
  41. package/testing/tests/utils.spec.ts +10 -0
  42. package/testing/utils/common.tsx +63 -0
  43. package/dist/AuthProvider.cjs +0 -2
  44. package/dist/AuthProvider.cjs.map +0 -1
  45. package/dist/AuthProvider.d.ts +0 -7
  46. package/dist/AuthProvider.mjs +0 -2
  47. package/dist/AuthProvider.mjs.map +0 -1
  48. package/dist/LoginRenderer.cjs +0 -2
  49. package/dist/LoginRenderer.cjs.map +0 -1
  50. package/dist/LoginRenderer.d.ts +0 -20
  51. package/dist/LoginRenderer.mjs +0 -2
  52. package/dist/LoginRenderer.mjs.map +0 -1
  53. package/dist/composables.cjs +0 -2
  54. package/dist/composables.cjs.map +0 -1
  55. package/dist/composables.d.ts +0 -12
  56. package/dist/composables.mjs +0 -2
  57. package/dist/composables.mjs.map +0 -1
  58. package/dist/types.cjs.map +0 -1
  59. package/dist/types.mjs.map +0 -1
@@ -0,0 +1,119 @@
1
+ import type { RedirectFlow } from '../../src/types';
2
+ import { describe, test, expect, vi, afterEach } from 'vitest';
3
+ import { STRIVACITY_SDK, withAuthGuard } from '../../src/hooks';
4
+ import { createMockFlow } from '@strivacity/testing/mocks/sdk';
5
+ import { mount, cleanupMounts, flush, act, withContext } from '../utils/common';
6
+
7
+ afterEach(() => {
8
+ cleanupMounts();
9
+ });
10
+
11
+ function Protected({ label }: { label: string }) {
12
+ return <div data-testid="protected">{label}</div>;
13
+ }
14
+
15
+ describe('withAuthGuard', () => {
16
+ test('renders nothing while the sdk is still loading', () => {
17
+ const sdk = createMockFlow<RedirectFlow>();
18
+ const Guarded = withAuthGuard(Protected);
19
+
20
+ const { container } = mount(
21
+ <STRIVACITY_SDK.Provider value={withContext(sdk, true)}>
22
+ <Guarded label="secret" />
23
+ </STRIVACITY_SDK.Provider>,
24
+ );
25
+
26
+ expect(container.textContent).toBe('');
27
+ expect(sdk.checkAuthentication).not.toHaveBeenCalled();
28
+ });
29
+
30
+ test('renders the given onLoading placeholder while loading', () => {
31
+ const sdk = createMockFlow<RedirectFlow>();
32
+ const Guarded = withAuthGuard(Protected, { onLoading: () => <div>loading…</div> });
33
+
34
+ const { container } = mount(
35
+ <STRIVACITY_SDK.Provider value={withContext(sdk, true)}>
36
+ <Guarded label="secret" />
37
+ </STRIVACITY_SDK.Provider>,
38
+ );
39
+
40
+ expect(container.textContent).toBe('loading…');
41
+ });
42
+
43
+ test('checks authentication through the sdk once loading finishes, and renders the wrapped component when authenticated', async () => {
44
+ const sdk = createMockFlow<RedirectFlow>({ checkAuthentication: vi.fn().mockResolvedValue(true) });
45
+ const Guarded = withAuthGuard(Protected);
46
+
47
+ const { container } = mount(
48
+ <STRIVACITY_SDK.Provider value={withContext(sdk, false)}>
49
+ <Guarded label="secret" />
50
+ </STRIVACITY_SDK.Provider>,
51
+ );
52
+ await flush();
53
+
54
+ expect(sdk.checkAuthentication).toHaveBeenCalledTimes(1);
55
+ expect(container.textContent).toBe('secret');
56
+ });
57
+
58
+ test('redirects to the default login URI when checkAuthentication resolves false', async () => {
59
+ const originalHref = globalThis.location.href;
60
+ const sdk = createMockFlow<RedirectFlow>({ checkAuthentication: vi.fn().mockResolvedValue(false) });
61
+ const Guarded = withAuthGuard(Protected);
62
+
63
+ mount(
64
+ <STRIVACITY_SDK.Provider value={withContext(sdk, false)}>
65
+ <Guarded label="secret" />
66
+ </STRIVACITY_SDK.Provider>,
67
+ );
68
+ await flush();
69
+
70
+ expect(globalThis.location.href).toContain('/login');
71
+
72
+ globalThis.location.href = originalHref;
73
+ });
74
+
75
+ test('redirects to a custom loginUri when configured', async () => {
76
+ const originalHref = globalThis.location.href;
77
+ const sdk = createMockFlow<RedirectFlow>({ checkAuthentication: vi.fn().mockResolvedValue(false) });
78
+ const Guarded = withAuthGuard(Protected, { loginUri: '/custom-login' });
79
+
80
+ mount(
81
+ <STRIVACITY_SDK.Provider value={withContext(sdk, false)}>
82
+ <Guarded label="secret" />
83
+ </STRIVACITY_SDK.Provider>,
84
+ );
85
+ await flush();
86
+
87
+ expect(globalThis.location.href).toContain('/custom-login');
88
+
89
+ globalThis.location.href = originalHref;
90
+ });
91
+
92
+ test('ignores a checkAuthentication result that resolves after the component has unmounted', async () => {
93
+ let resolveCheck!: (value: boolean) => void;
94
+ const sdk = createMockFlow<RedirectFlow>({
95
+ checkAuthentication: vi.fn(
96
+ () =>
97
+ new Promise<boolean>((resolve) => {
98
+ resolveCheck = resolve;
99
+ }),
100
+ ),
101
+ });
102
+ const Guarded = withAuthGuard(Protected);
103
+
104
+ mount(
105
+ <STRIVACITY_SDK.Provider value={withContext(sdk, false)}>
106
+ <Guarded label="secret" />
107
+ </STRIVACITY_SDK.Provider>,
108
+ );
109
+ await flush();
110
+
111
+ cleanupMounts();
112
+
113
+ expect(() => {
114
+ act(() => {
115
+ resolveCheck(true);
116
+ });
117
+ }).not.toThrow();
118
+ });
119
+ });
@@ -0,0 +1,418 @@
1
+ import type { SDKContext, LoginContext, UseNativeLoginOptions, NativeFlow, NativeFlowState, SessionData } from '../../src/types';
2
+ import { describe, test, expect, vi, afterEach } from 'vitest';
3
+ import { FallbackError } from '@strivacity/sdk-core/utils/errors';
4
+ import { STRIVACITY_SDK, useStrivacity, useNativeLoginContext, useNativeLogin } from '../../src/hooks';
5
+ import { createMockFlow } from '@strivacity/testing/mocks/sdk';
6
+ import { mount, cleanupMounts, flush, act } from '../utils/common';
7
+
8
+ afterEach(() => {
9
+ cleanupMounts();
10
+ });
11
+
12
+ function mountNativeLogin(sdk: NativeFlow, options: UseNativeLoginOptions = {}) {
13
+ let latest!: LoginContext;
14
+
15
+ function Probe() {
16
+ latest = useNativeLogin(options);
17
+ return null;
18
+ }
19
+
20
+ mount(
21
+ <STRIVACITY_SDK.Provider value={{ sdk } as unknown as SDKContext<NativeFlow>}>
22
+ <Probe />
23
+ </STRIVACITY_SDK.Provider>,
24
+ );
25
+
26
+ return { getLogin: () => latest };
27
+ }
28
+
29
+ describe('useStrivacity', () => {
30
+ test('throws when used outside of a Strivacity SDK provider', () => {
31
+ function Consumer() {
32
+ useStrivacity();
33
+ return null;
34
+ }
35
+
36
+ expect(() => mount(<Consumer />)).toThrow('Missing SDK context');
37
+ });
38
+
39
+ test('returns the context provided by the nearest STRIVACITY_SDK provider', () => {
40
+ const context = { sdk: {} } as unknown as SDKContext<NativeFlow>;
41
+ let received: SDKContext<NativeFlow> | undefined;
42
+
43
+ function Consumer() {
44
+ received = useStrivacity<NativeFlow>();
45
+ return null;
46
+ }
47
+
48
+ mount(
49
+ <STRIVACITY_SDK.Provider value={context}>
50
+ <Consumer />
51
+ </STRIVACITY_SDK.Provider>,
52
+ );
53
+
54
+ expect(received).toBe(context);
55
+ });
56
+ });
57
+
58
+ describe('useNativeLoginContext', () => {
59
+ test('returns the default context when no native login session is active', () => {
60
+ function Consumer() {
61
+ const ctx = useNativeLoginContext();
62
+ return <div data-testid="loading">{String(ctx.loading)}</div>;
63
+ }
64
+
65
+ const { container } = mount(<Consumer />);
66
+
67
+ expect(container.textContent).toBe('true');
68
+ });
69
+
70
+ test('resolves to the live context published by an active useNativeLogin call', async () => {
71
+ const sdk = createMockFlow<NativeFlow>();
72
+ vi.mocked(sdk.startSession).mockResolvedValue({ screen: 'identifier' });
73
+
74
+ let injectedLoading: string | undefined;
75
+
76
+ function Owner() {
77
+ useNativeLogin();
78
+ return null;
79
+ }
80
+ function Consumer() {
81
+ const ctx = useNativeLoginContext();
82
+ injectedLoading = String(ctx.loading);
83
+ return null;
84
+ }
85
+
86
+ mount(
87
+ <STRIVACITY_SDK.Provider value={{ sdk } as unknown as SDKContext<NativeFlow>}>
88
+ <Owner />
89
+ <Consumer />
90
+ </STRIVACITY_SDK.Provider>,
91
+ );
92
+ await flush();
93
+
94
+ expect(injectedLoading).toBe('false');
95
+ });
96
+
97
+ test('resets to the default context once the owning useNativeLogin component unmounts', async () => {
98
+ const sdk = createMockFlow<NativeFlow>();
99
+ vi.mocked(sdk.startSession).mockResolvedValue({ screen: 'identifier' });
100
+
101
+ function Owner() {
102
+ useNativeLogin();
103
+ return null;
104
+ }
105
+
106
+ mount(
107
+ <STRIVACITY_SDK.Provider value={{ sdk } as unknown as SDKContext<NativeFlow>}>
108
+ <Owner />
109
+ </STRIVACITY_SDK.Provider>,
110
+ );
111
+ await flush();
112
+
113
+ cleanupMounts();
114
+
115
+ function Consumer() {
116
+ const ctx = useNativeLoginContext();
117
+ return <div>{String(ctx.loading)}</div>;
118
+ }
119
+
120
+ const { container } = mount(<Consumer />);
121
+
122
+ expect(container.textContent).toBe('true');
123
+ });
124
+ });
125
+
126
+ describe('useNativeLogin', () => {
127
+ test('initializes the sdk and starts a session on mount, defaulting params to an empty object', async () => {
128
+ const sdk = createMockFlow<NativeFlow>();
129
+ mountNativeLogin(sdk);
130
+
131
+ await flush();
132
+
133
+ expect(sdk.init).toHaveBeenCalledTimes(1);
134
+ expect(sdk.startSession).toHaveBeenCalledWith({});
135
+ });
136
+
137
+ test('does nothing when there is no sdk instance yet', async () => {
138
+ let latest!: LoginContext;
139
+
140
+ function Probe() {
141
+ latest = useNativeLogin();
142
+ return null;
143
+ }
144
+
145
+ mount(
146
+ <STRIVACITY_SDK.Provider value={{ sdk: undefined } as unknown as SDKContext<NativeFlow>}>
147
+ <Probe />
148
+ </STRIVACITY_SDK.Provider>,
149
+ );
150
+ await flush();
151
+
152
+ expect(latest.loading).toBe(true);
153
+ });
154
+
155
+ test('routes an sdk.init() rejection to onError, logs it, and stops loading', async () => {
156
+ const sdk = createMockFlow<NativeFlow>();
157
+ const error = new Error('init failed');
158
+ vi.mocked(sdk.init).mockRejectedValue(error);
159
+ const onError = vi.fn();
160
+
161
+ const { getLogin } = mountNativeLogin(sdk, { onError });
162
+ await flush();
163
+
164
+ expect(onError).toHaveBeenCalledWith(error);
165
+ expect(sdk.logging!.error).toHaveBeenCalledWith('Error initializing SDK', error);
166
+ expect(getLogin().loading).toBe(false);
167
+ expect(sdk.startSession).not.toHaveBeenCalled();
168
+ });
169
+
170
+ test('forwards the given login params to sdk.startSession', async () => {
171
+ const sdk = createMockFlow<NativeFlow>();
172
+ mountNativeLogin(sdk, { params: { sessionId: 'session-1', language: 'hu-HU' } });
173
+
174
+ await flush();
175
+
176
+ expect(sdk.startSession).toHaveBeenCalledWith({ sessionId: 'session-1', language: 'hu-HU' });
177
+ });
178
+
179
+ test('is loading until sdk.startSession resolves with a state that has no pending finalizeUrl', async () => {
180
+ const sdk = createMockFlow<NativeFlow>();
181
+ vi.mocked(sdk.startSession).mockResolvedValue({ screen: 'identifier' });
182
+ const { getLogin } = mountNativeLogin(sdk);
183
+
184
+ expect(getLogin().loading).toBe(true);
185
+
186
+ await flush();
187
+
188
+ expect(getLogin().loading).toBe(false);
189
+ });
190
+
191
+ test('stays loading forever if sdk.startSession resolves with no state at all', async () => {
192
+ const sdk = createMockFlow<NativeFlow>();
193
+ vi.mocked(sdk.startSession).mockResolvedValue(undefined);
194
+ const { getLogin } = mountNativeLogin(sdk);
195
+
196
+ await flush();
197
+
198
+ expect(getLogin().loading).toBe(true);
199
+ });
200
+
201
+ test('keeps loading while a finalizeUrl is still pending', async () => {
202
+ const sdk = createMockFlow<NativeFlow>();
203
+ vi.mocked(sdk.startSession).mockResolvedValue({ finalizeUrl: 'https://brandtegrity.io/finalize' });
204
+ const { getLogin } = mountNativeLogin(sdk);
205
+
206
+ await flush();
207
+
208
+ expect(getLogin().loading).toBe(true);
209
+ });
210
+
211
+ test('applies the returned flow state: resets per-form data, routes the global message, and keeps per-widget messages', async () => {
212
+ const state: NativeFlowState = {
213
+ hostedUrl: 'https://brandtegrity.io/hosted',
214
+ screen: 'identifier',
215
+ forms: [{ id: 'form1', type: 'form', widgets: [] }],
216
+ messages: {
217
+ global: { type: 'info', text: 'Welcome' },
218
+ form1: { field1: { type: 'error', text: 'Required' } },
219
+ },
220
+ };
221
+ const sdk = createMockFlow<NativeFlow>();
222
+ vi.mocked(sdk.startSession).mockResolvedValue(state);
223
+ const onGlobalMessage = vi.fn();
224
+
225
+ const { getLogin } = mountNativeLogin(sdk, { onGlobalMessage });
226
+ await flush();
227
+
228
+ expect(onGlobalMessage).toHaveBeenCalledWith({ type: 'info', text: 'Welcome' });
229
+ expect(getLogin().forms).toEqual({ form1: {} });
230
+ expect(getLogin().messages).toEqual({ form1: { field1: { type: 'error', text: 'Required' } } });
231
+ expect(getLogin().state).toEqual(state);
232
+ });
233
+
234
+ test('calls onLogin and leaves the tracked state untouched when the sdk already has a session', async () => {
235
+ const session = { access_token: 'access-token' } as unknown as SessionData;
236
+ const sdk = createMockFlow<NativeFlow>({ session });
237
+ vi.mocked(sdk.startSession).mockResolvedValue({ screen: 'identifier' });
238
+ const onLogin = vi.fn();
239
+
240
+ const { getLogin } = mountNativeLogin(sdk, { onLogin });
241
+ await flush();
242
+
243
+ expect(onLogin).toHaveBeenCalledWith(session);
244
+ expect(getLogin().state).toEqual({});
245
+ });
246
+
247
+ test('routes a FallbackError from sdk.startSession to onFallback and logs it', async () => {
248
+ const sdk = createMockFlow<NativeFlow>();
249
+ const fallbackError = new FallbackError(new URL('https://brandtegrity.io/hosted'));
250
+ vi.mocked(sdk.startSession).mockRejectedValue(fallbackError);
251
+ const onFallback = vi.fn();
252
+
253
+ mountNativeLogin(sdk, { onFallback });
254
+ await flush();
255
+
256
+ expect(onFallback).toHaveBeenCalledWith(fallbackError);
257
+ expect(sdk.logging!.error).toHaveBeenCalledWith('Fallback error occurred', fallbackError);
258
+ });
259
+
260
+ test('routes a non-fallback error from sdk.startSession to onError and logs it', async () => {
261
+ const sdk = createMockFlow<NativeFlow>();
262
+ const error = new Error('network down');
263
+ vi.mocked(sdk.startSession).mockRejectedValue(error);
264
+ const onError = vi.fn();
265
+
266
+ mountNativeLogin(sdk, { onError });
267
+ await flush();
268
+
269
+ expect(onError).toHaveBeenCalledWith(error);
270
+ expect(sdk.logging!.error).toHaveBeenCalledWith('Error starting session', error);
271
+ });
272
+
273
+ describe('submitForm', () => {
274
+ test('unflattens the tracked form values and calls through to sdk.submitForm', async () => {
275
+ const sdk = createMockFlow<NativeFlow>();
276
+ const initialForms = [{ id: 'form1', type: 'form' as const, widgets: [] }];
277
+ vi.mocked(sdk.startSession).mockResolvedValue({ screen: 'identifier', forms: initialForms });
278
+ vi.mocked(sdk.submitForm).mockResolvedValue({ screen: 'done' });
279
+
280
+ const { getLogin } = mountNativeLogin(sdk);
281
+ await flush();
282
+
283
+ act(() => {
284
+ getLogin().setFormValue('form1', 'address.city', 'Budapest');
285
+ getLogin().setFormValue('form1', 'address.zip', '');
286
+ });
287
+
288
+ await act(async () => {
289
+ await getLogin().submitForm('form1');
290
+ });
291
+
292
+ expect(sdk.submitForm).toHaveBeenCalledWith('form1', { address: { city: 'Budapest', zip: null } });
293
+ expect(getLogin().state).toEqual({
294
+ hostedUrl: undefined,
295
+ finalizeUrl: undefined,
296
+ screen: 'done',
297
+ forms: initialForms,
298
+ layout: undefined,
299
+ messages: {},
300
+ branding: undefined,
301
+ });
302
+ });
303
+
304
+ test('uses a given custom body instead of the tracked form values', async () => {
305
+ const sdk = createMockFlow<NativeFlow>();
306
+ vi.mocked(sdk.submitForm).mockResolvedValue({ screen: 'done' });
307
+
308
+ const { getLogin } = mountNativeLogin(sdk);
309
+ await flush();
310
+
311
+ await act(async () => {
312
+ await getLogin().submitForm('form1', { raw: true });
313
+ });
314
+
315
+ expect(sdk.submitForm).toHaveBeenCalledWith('form1', { raw: true });
316
+ });
317
+
318
+ test('routes a FallbackError to onFallback', async () => {
319
+ const sdk = createMockFlow<NativeFlow>();
320
+ const fallbackError = new FallbackError(new URL('https://brandtegrity.io/hosted'));
321
+ vi.mocked(sdk.submitForm).mockRejectedValue(fallbackError);
322
+ const onFallback = vi.fn();
323
+
324
+ const { getLogin } = mountNativeLogin(sdk, { onFallback });
325
+ await flush();
326
+
327
+ await act(async () => {
328
+ await getLogin().submitForm('form1');
329
+ });
330
+
331
+ expect(onFallback).toHaveBeenCalledWith(fallbackError);
332
+ expect(sdk.logging!.error).toHaveBeenCalledWith('Fallback error occurred', fallbackError);
333
+ });
334
+
335
+ test('routes a non-fallback error to onError', async () => {
336
+ const sdk = createMockFlow<NativeFlow>();
337
+ const error = new Error('submit failed');
338
+ vi.mocked(sdk.submitForm).mockRejectedValue(error);
339
+ const onError = vi.fn();
340
+
341
+ const { getLogin } = mountNativeLogin(sdk, { onError });
342
+ await flush();
343
+
344
+ await act(async () => {
345
+ await getLogin().submitForm('form1');
346
+ });
347
+
348
+ expect(onError).toHaveBeenCalledWith(error);
349
+ expect(sdk.logging!.error).toHaveBeenCalledWith('Error submitting form', error);
350
+ });
351
+ });
352
+
353
+ test('setFormValue coerces empty strings to null and lazily creates the form bucket', async () => {
354
+ const sdk = createMockFlow<NativeFlow>();
355
+ const { getLogin } = mountNativeLogin(sdk);
356
+ await flush();
357
+
358
+ act(() => {
359
+ getLogin().setFormValue('newForm', 'field', 'value');
360
+ });
361
+ act(() => {
362
+ getLogin().setFormValue('newForm', 'other', '');
363
+ });
364
+
365
+ expect(getLogin().forms.newForm).toEqual({ field: 'value', other: null });
366
+ });
367
+
368
+ test('setMessage lazily creates the message bucket for a form', async () => {
369
+ const sdk = createMockFlow<NativeFlow>();
370
+ const { getLogin } = mountNativeLogin(sdk);
371
+ await flush();
372
+
373
+ act(() => {
374
+ getLogin().setMessage('newForm', 'field', { type: 'error', text: 'Required' });
375
+ });
376
+
377
+ expect(getLogin().messages.newForm).toEqual({ field: { type: 'error', text: 'Required' } });
378
+ });
379
+
380
+ describe('triggerFallback', () => {
381
+ test('throws when no hosted URL is known yet', async () => {
382
+ const sdk = createMockFlow<NativeFlow>();
383
+ vi.mocked(sdk.startSession).mockResolvedValue({ screen: 'identifier' });
384
+ const { getLogin } = mountNativeLogin(sdk);
385
+ await flush();
386
+
387
+ expect(() => getLogin().triggerFallback()).toThrow('No hosted URL provided');
388
+ expect(sdk.logging!.error).toHaveBeenCalledWith('Fallback error', expect.any(Error));
389
+ });
390
+
391
+ test('calls onFallback with a FallbackError built from the known hosted URL', async () => {
392
+ const sdk = createMockFlow<NativeFlow>();
393
+ vi.mocked(sdk.startSession).mockResolvedValue({ hostedUrl: 'https://brandtegrity.io/hosted', screen: 'identifier' });
394
+ const onFallback = vi.fn();
395
+
396
+ const { getLogin } = mountNativeLogin(sdk, { onFallback });
397
+ await flush();
398
+
399
+ getLogin().triggerFallback('manual trigger');
400
+
401
+ expect(sdk.logging!.warn).toHaveBeenCalledWith('Triggering fallback due to: manual trigger');
402
+ expect(onFallback).toHaveBeenCalledWith(expect.any(FallbackError));
403
+ expect((onFallback.mock.calls[0][0] as FallbackError).url.toString()).toBe('https://brandtegrity.io/hosted');
404
+ });
405
+ });
406
+
407
+ test('triggerClose calls onClose and logs', async () => {
408
+ const sdk = createMockFlow<NativeFlow>();
409
+ const onClose = vi.fn();
410
+ const { getLogin } = mountNativeLogin(sdk, { onClose });
411
+ await flush();
412
+
413
+ getLogin().triggerClose();
414
+
415
+ expect(onClose).toHaveBeenCalledTimes(1);
416
+ expect(sdk.logging!.debug).toHaveBeenCalledWith('Triggering close');
417
+ });
418
+ });
@@ -0,0 +1,105 @@
1
+ import { test, expect } from 'vitest';
2
+ import * as index from '../../src/index';
3
+
4
+ test('should export the correct things', () => {
5
+ const expectedExports = [
6
+ 'createBaseFlow',
7
+ 'createEmbeddedFlow',
8
+ 'createNativeFlow',
9
+ 'createPopupFlow',
10
+ 'createRedirectFlow',
11
+ 'popupCallbackHandler',
12
+ 'popupUrlHandler',
13
+ 'redirectCallbackHandler',
14
+ 'redirectUrlHandler',
15
+ 'BACKCHANNEL_LOGOUT_EVENT',
16
+ 'ConfigurationError',
17
+ 'DEFAULT_COOKIE_MAX_AGE',
18
+ 'DISALLOWED_PROXY_HEADERS',
19
+ 'FallbackError',
20
+ 'InternalError',
21
+ 'NetworkError',
22
+ 'OidcError',
23
+ 'PopupBlockedError',
24
+ 'PopupClosedError',
25
+ 'ProtocolError',
26
+ 'RETURN_TO_COOKIE',
27
+ 'ServerError',
28
+ 'SessionExpiredError',
29
+ 'UnsupportedFlowError',
30
+ 'assertWebAuthnCredential',
31
+ 'buildAuthorizationUrl',
32
+ 'buildCookieString',
33
+ 'buildEndSessionUrl',
34
+ 'createDefaultLogging',
35
+ 'createHttpClient',
36
+ 'createSession',
37
+ 'createState',
38
+ 'createWebAuthnCredential',
39
+ 'decodeBase64URL',
40
+ 'decodeJwt',
41
+ 'decryptString',
42
+ 'defaultOptions',
43
+ 'encodeBase64URL',
44
+ 'encryptString',
45
+ 'exchangeCode',
46
+ 'fetchFlowEntry',
47
+ 'finalizeLoginSession',
48
+ 'flushSetCookies',
49
+ 'generateCodeChallenge',
50
+ 'generateCodeVerifier',
51
+ 'generateJwt',
52
+ 'generateRandomHex',
53
+ 'getDefaultFlowState',
54
+ 'getDiscoveryDocument',
55
+ 'getSDKOptions',
56
+ 'getServerSDKOptions',
57
+ 'injectScript',
58
+ 'isSessionExpired',
59
+ 'loadSession',
60
+ 'parseCookieHeader',
61
+ 'parseState',
62
+ 'proxyResponse',
63
+ 'pushSetCookie',
64
+ 'refreshToken',
65
+ 'revokeToken',
66
+ 'serializeCookie',
67
+ 'serializeSession',
68
+ 'serializeState',
69
+ 'submitLoginForm',
70
+ 'throwHttpError',
71
+ 'throwTokenEndpointError',
72
+ 'timestamp',
73
+ 'toSafeRedirect',
74
+ 'unflattenObject',
75
+ 'verifyIdToken',
76
+ 'verifyJwt',
77
+ 'StyAuthProvider',
78
+ 'STRIVACITY_SDK',
79
+ 'useNativeLoginContext',
80
+ 'useStrivacity',
81
+ 'useNativeLogin',
82
+ 'withAuthGuard',
83
+ 'COOKIE_CHUNK_SIZE',
84
+ 'COOKIE_CONTEXT',
85
+ 'createCacheAPIStorage',
86
+ 'createEncryptedCookieStorage',
87
+ 'createIndexedDBStorage',
88
+ 'createLocalStorage',
89
+ 'createMemoryStorage',
90
+ 'createServerMemoryStorage',
91
+ 'createServerStateStorage',
92
+ 'createSessionIdCookieStorage',
93
+ 'createSessionStorage',
94
+ 'createWorkerStorage',
95
+ 'handleWorkerStorageRequests',
96
+ 'AlgorithmTypeList',
97
+ 'GrantTypeList',
98
+ 'ResponseModeList',
99
+ 'ResponseTypeList',
100
+ 'SubjectTypeList',
101
+ 'TokenEndpointAuthMethodList',
102
+ ];
103
+
104
+ expect(Object.keys(index)).toEqual(expectedExports);
105
+ });
@@ -0,0 +1,10 @@
1
+ import { test, expect } from 'vitest';
2
+ import * as reactStorages from '../../src/storages';
3
+ import * as coreStorages from '@strivacity/sdk-core/storages';
4
+
5
+ test('re-exports storages from the core sdk', () => {
6
+ const coreKeys = Object.keys(coreStorages);
7
+
8
+ expect(coreKeys.length).toBeGreaterThan(0);
9
+ expect(Object.keys(reactStorages).sort()).toEqual(coreKeys.sort());
10
+ });
@@ -0,0 +1,10 @@
1
+ import { test, expect } from 'vitest';
2
+ import * as reactUtils from '../../src/utils';
3
+ import * as coreUtils from '@strivacity/sdk-core/utils';
4
+
5
+ test('re-exports utility from the core sdk', () => {
6
+ const coreKeys = Object.keys(coreUtils);
7
+
8
+ expect(coreKeys.length).toBeGreaterThan(0);
9
+ expect(Object.keys(reactUtils).sort()).toEqual(coreKeys.sort());
10
+ });