@incodetech/core 2.0.0-alpha.2 → 2.0.0-alpha.4
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/dist/OpenViduLogger-BdPfiZO6.esm.js +3 -0
- package/dist/OpenViduLogger-CQyDxBvM.esm.js +803 -0
- package/dist/{addEvent-1Mi5CEiq.esm.js → addEvent-9v4w5iO-.esm.js} +1 -1
- package/dist/email.d.ts +1 -1
- package/dist/email.esm.js +2 -2
- package/dist/{endpoints-D_pUMaqA.esm.js → endpoints-Dn1t57hJ.esm.js} +8 -3
- package/dist/flow.d.ts +3 -3
- package/dist/flow.esm.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.esm.js +2 -2
- package/dist/{lib-CyIAFRfr.esm.js → lib-Bu9XGMBW.esm.js} +1 -800
- package/dist/{permissionServices-CVR0Pq38.esm.js → permissionServices-CCpxd8le.esm.js} +1 -1
- package/dist/phone.d.ts +1 -1
- package/dist/phone.esm.js +2 -2
- package/dist/selfie.d.ts +5 -4
- package/dist/selfie.esm.js +27 -10
- package/package.json +4 -1
- package/.turbo/turbo-build.log +0 -33
- package/.turbo/turbo-coverage.log +0 -22
- package/.turbo/turbo-format.log +0 -6
- package/.turbo/turbo-lint$colon$fix.log +0 -77
- package/.turbo/turbo-lint.log +0 -95
- package/.turbo/turbo-test.log +0 -870
- package/.turbo/turbo-typecheck.log +0 -5
- package/coverage/base.css +0 -224
- package/coverage/block-navigation.js +0 -87
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +0 -221
- package/coverage/prettify.css +0 -1
- package/coverage/prettify.js +0 -2
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +0 -210
- package/coverage/src/camera/cameraService.ts.html +0 -580
- package/coverage/src/camera/cameraServices.ts.html +0 -163
- package/coverage/src/camera/cameraStateMachine.ts.html +0 -877
- package/coverage/src/camera/index.html +0 -146
- package/coverage/src/email/emailActor.ts.html +0 -130
- package/coverage/src/email/emailManager.ts.html +0 -1366
- package/coverage/src/email/emailStateMachine.ts.html +0 -1186
- package/coverage/src/email/index.html +0 -146
- package/coverage/src/flow/flowActor.ts.html +0 -124
- package/coverage/src/flow/flowAnalyzer.ts.html +0 -196
- package/coverage/src/flow/flowManager.ts.html +0 -790
- package/coverage/src/flow/flowServices.ts.html +0 -124
- package/coverage/src/flow/flowStateMachine.ts.html +0 -631
- package/coverage/src/flow/index.html +0 -221
- package/coverage/src/flow/moduleLoader.ts.html +0 -304
- package/coverage/src/flow/orchestratedFlowManager.ts.html +0 -778
- package/coverage/src/flow/orchestratedFlowStateMachine.ts.html +0 -1060
- package/coverage/src/http/api.ts.html +0 -355
- package/coverage/src/http/endpoints.ts.html +0 -136
- package/coverage/src/http/index.html +0 -131
- package/coverage/src/index.html +0 -116
- package/coverage/src/phone/index.html +0 -146
- package/coverage/src/phone/phoneActor.ts.html +0 -130
- package/coverage/src/phone/phoneManager.ts.html +0 -1459
- package/coverage/src/phone/phoneStateMachine.ts.html +0 -1351
- package/coverage/src/recordings/index.html +0 -116
- package/coverage/src/recordings/recordingsRepository.ts.html +0 -229
- package/coverage/src/selfie/index.html +0 -191
- package/coverage/src/selfie/selfieActor.ts.html +0 -136
- package/coverage/src/selfie/selfieErrorUtils.ts.html +0 -283
- package/coverage/src/selfie/selfieManager.ts.html +0 -988
- package/coverage/src/selfie/selfieStateMachine.ts.html +0 -2497
- package/coverage/src/selfie/selfieUploadService.ts.html +0 -328
- package/coverage/src/selfie/types.ts.html +0 -394
- package/coverage/src/setup.ts.html +0 -598
- package/src/camera/cameraActor.ts +0 -21
- package/src/camera/cameraService.test.ts +0 -437
- package/src/camera/cameraService.ts +0 -165
- package/src/camera/cameraServices.test.ts +0 -66
- package/src/camera/cameraServices.ts +0 -26
- package/src/camera/cameraStateMachine.test.ts +0 -602
- package/src/camera/cameraStateMachine.ts +0 -264
- package/src/camera/index.ts +0 -5
- package/src/camera/types.ts +0 -17
- package/src/device/getBrowser.ts +0 -31
- package/src/device/getDeviceClass.ts +0 -29
- package/src/device/index.ts +0 -2
- package/src/email/__mocks__/emailMocks.ts +0 -59
- package/src/email/emailActor.ts +0 -15
- package/src/email/emailManager.test.ts +0 -573
- package/src/email/emailManager.ts +0 -427
- package/src/email/emailServices.ts +0 -66
- package/src/email/emailStateMachine.test.ts +0 -741
- package/src/email/emailStateMachine.ts +0 -367
- package/src/email/index.ts +0 -39
- package/src/email/types.ts +0 -60
- package/src/events/addEvent.ts +0 -20
- package/src/events/types.ts +0 -7
- package/src/flow/__mocks__/flowMocks.ts +0 -84
- package/src/flow/flowActor.ts +0 -13
- package/src/flow/flowAnalyzer.test.ts +0 -266
- package/src/flow/flowAnalyzer.ts +0 -37
- package/src/flow/flowCompletionService.ts +0 -21
- package/src/flow/flowManager.test.ts +0 -560
- package/src/flow/flowManager.ts +0 -235
- package/src/flow/flowServices.test.ts +0 -109
- package/src/flow/flowServices.ts +0 -13
- package/src/flow/flowStateMachine.test.ts +0 -334
- package/src/flow/flowStateMachine.ts +0 -182
- package/src/flow/index.ts +0 -21
- package/src/flow/moduleLoader.test.ts +0 -136
- package/src/flow/moduleLoader.ts +0 -73
- package/src/flow/orchestratedFlowManager.test.ts +0 -240
- package/src/flow/orchestratedFlowManager.ts +0 -231
- package/src/flow/orchestratedFlowStateMachine.test.ts +0 -199
- package/src/flow/orchestratedFlowStateMachine.ts +0 -325
- package/src/flow/types.ts +0 -434
- package/src/http/__mocks__/api.ts +0 -88
- package/src/http/api.test.ts +0 -231
- package/src/http/api.ts +0 -90
- package/src/http/endpoints.ts +0 -17
- package/src/index.ts +0 -33
- package/src/permissions/index.ts +0 -2
- package/src/permissions/permissionServices.ts +0 -31
- package/src/permissions/types.ts +0 -3
- package/src/phone/__mocks__/phoneMocks.ts +0 -71
- package/src/phone/index.ts +0 -39
- package/src/phone/phoneActor.ts +0 -15
- package/src/phone/phoneManager.test.ts +0 -393
- package/src/phone/phoneManager.ts +0 -458
- package/src/phone/phoneServices.ts +0 -98
- package/src/phone/phoneStateMachine.test.ts +0 -918
- package/src/phone/phoneStateMachine.ts +0 -422
- package/src/phone/types.ts +0 -83
- package/src/recordings/recordingsRepository.test.ts +0 -87
- package/src/recordings/recordingsRepository.ts +0 -48
- package/src/recordings/streamingEvents.ts +0 -10
- package/src/selfie/__mocks__/selfieMocks.ts +0 -26
- package/src/selfie/index.ts +0 -14
- package/src/selfie/selfieActor.ts +0 -17
- package/src/selfie/selfieErrorUtils.test.ts +0 -116
- package/src/selfie/selfieErrorUtils.ts +0 -66
- package/src/selfie/selfieManager.test.ts +0 -297
- package/src/selfie/selfieManager.ts +0 -301
- package/src/selfie/selfieServices.ts +0 -362
- package/src/selfie/selfieStateMachine.test.ts +0 -283
- package/src/selfie/selfieStateMachine.ts +0 -804
- package/src/selfie/selfieUploadService.test.ts +0 -90
- package/src/selfie/selfieUploadService.ts +0 -81
- package/src/selfie/types.ts +0 -103
- package/src/session/index.ts +0 -5
- package/src/session/sessionService.ts +0 -78
- package/src/setup.test.ts +0 -61
- package/src/setup.ts +0 -171
- package/tsconfig.json +0 -13
- package/tsdown.config.ts +0 -22
- package/vitest.config.ts +0 -37
- package/vitest.setup.ts +0 -135
- /package/dist/{Manager-6BwbaI_H.d.ts → Manager-BGfxEmyv.d.ts} +0 -0
- /package/dist/{StateMachine-7c1gcu94.d.ts → StateMachine-DRE1oH2B.d.ts} +0 -0
- /package/dist/{types-tq1ypYSL.d.ts → types-kWlqshfM.d.ts} +0 -0
- /package/dist/{warmup-Dr7OcFND.d.ts → warmup-CEJTfxQr.d.ts} +0 -0
package/src/flow/flowManager.ts
DELETED
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type CreateApiOptions,
|
|
3
|
-
createManager,
|
|
4
|
-
type ManagerSnapshot,
|
|
5
|
-
} from '@incodetech/infra';
|
|
6
|
-
import { type CreateFlowActorOptions, createFlowActor } from './flowActor';
|
|
7
|
-
import type { FlowMachine } from './flowStateMachine';
|
|
8
|
-
import type { Flow } from './types';
|
|
9
|
-
|
|
10
|
-
/** Internal snapshot type for the flow state machine */
|
|
11
|
-
type FlowSnapshot = ManagerSnapshot<FlowMachine>;
|
|
12
|
-
|
|
13
|
-
/** Flow manager is waiting to be started */
|
|
14
|
-
type FlowIdleState = {
|
|
15
|
-
status: 'idle';
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
/** Flow is being fetched from the server */
|
|
19
|
-
type FlowLoadingState = {
|
|
20
|
-
status: 'loading';
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
/** Flow is loaded and ready for navigation */
|
|
24
|
-
export type FlowReadyState = {
|
|
25
|
-
status: 'ready';
|
|
26
|
-
/** The complete flow configuration from the server */
|
|
27
|
-
flow: Flow;
|
|
28
|
-
/** Array of module keys in order */
|
|
29
|
-
steps: string[];
|
|
30
|
-
/** Zero-based index of the current step */
|
|
31
|
-
currentStepIndex: number;
|
|
32
|
-
/** The module key of the current step (e.g., 'SELFIE', 'ID', 'FACE_MATCH') */
|
|
33
|
-
currentStep: string | undefined;
|
|
34
|
-
/** The configuration object for the current module. Type varies by module. */
|
|
35
|
-
config: unknown;
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
/** All steps have been completed */
|
|
39
|
-
type FlowFinishedState = {
|
|
40
|
-
status: 'finished';
|
|
41
|
-
/** The complete flow configuration */
|
|
42
|
-
flow: Flow;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
/** An error occurred while loading or processing the flow */
|
|
46
|
-
type FlowErrorState = {
|
|
47
|
-
status: 'error';
|
|
48
|
-
/** The error message */
|
|
49
|
-
error: string;
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
/** Union of all possible flow states */
|
|
53
|
-
export type FlowState =
|
|
54
|
-
| FlowIdleState
|
|
55
|
-
| FlowLoadingState
|
|
56
|
-
| FlowReadyState
|
|
57
|
-
| FlowFinishedState
|
|
58
|
-
| FlowErrorState;
|
|
59
|
-
|
|
60
|
-
function mapState(snapshot: FlowSnapshot): FlowState {
|
|
61
|
-
const { value, context } = snapshot;
|
|
62
|
-
|
|
63
|
-
switch (value) {
|
|
64
|
-
case 'idle':
|
|
65
|
-
return { status: 'idle' };
|
|
66
|
-
case 'loading':
|
|
67
|
-
return { status: 'loading' };
|
|
68
|
-
case 'ready':
|
|
69
|
-
return {
|
|
70
|
-
status: 'ready',
|
|
71
|
-
flow: context.flow ?? ({} as Flow),
|
|
72
|
-
steps: context.steps,
|
|
73
|
-
currentStepIndex: context.currentStepIndex,
|
|
74
|
-
currentStep: context.currentStep,
|
|
75
|
-
config: context.config,
|
|
76
|
-
};
|
|
77
|
-
case 'finished':
|
|
78
|
-
return {
|
|
79
|
-
status: 'finished',
|
|
80
|
-
flow: context.flow ?? ({} as Flow),
|
|
81
|
-
};
|
|
82
|
-
case 'error':
|
|
83
|
-
return {
|
|
84
|
-
status: 'error',
|
|
85
|
-
error: context.error ?? 'Unknown error',
|
|
86
|
-
};
|
|
87
|
-
default:
|
|
88
|
-
return { status: 'idle' };
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function createApi({ actor, getSnapshot }: CreateApiOptions<FlowMachine>) {
|
|
93
|
-
function getCanNext(): boolean {
|
|
94
|
-
const snapshot = getSnapshot();
|
|
95
|
-
const { currentStepIndex, steps } = snapshot.context;
|
|
96
|
-
return (
|
|
97
|
-
snapshot.matches('ready') &&
|
|
98
|
-
currentStepIndex >= 0 &&
|
|
99
|
-
currentStepIndex < steps.length - 1
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function getCanPrev(): boolean {
|
|
104
|
-
const snapshot = getSnapshot();
|
|
105
|
-
return snapshot.matches('ready') && snapshot.context.currentStepIndex > 0;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function getModuleConfig<T = unknown>(moduleKey: string): T | undefined {
|
|
109
|
-
const snapshot = getSnapshot();
|
|
110
|
-
if (!snapshot.matches('ready')) {
|
|
111
|
-
return undefined;
|
|
112
|
-
}
|
|
113
|
-
const mod = snapshot.context.flow?.flowModules.find(
|
|
114
|
-
(m) => m.key === moduleKey,
|
|
115
|
-
);
|
|
116
|
-
return mod?.configuration as T | undefined;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function isModuleEnabled(moduleKey: string): boolean {
|
|
120
|
-
const snapshot = getSnapshot();
|
|
121
|
-
if (!snapshot.matches('ready')) {
|
|
122
|
-
return false;
|
|
123
|
-
}
|
|
124
|
-
return !!snapshot.context.flow?.flowModules.some(
|
|
125
|
-
(m) => m.key === moduleKey,
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return {
|
|
130
|
-
/**
|
|
131
|
-
* Loads the flow from the server.
|
|
132
|
-
* Transitions the state from `idle` to `loading`, then to `ready` on success or `error` on failure.
|
|
133
|
-
* Requires setup() to have been called with a token first.
|
|
134
|
-
*/
|
|
135
|
-
load() {
|
|
136
|
-
actor.send({ type: 'LOAD' });
|
|
137
|
-
},
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Cancels the current loading operation and returns to `idle` state.
|
|
141
|
-
* Only effective when in `loading` state.
|
|
142
|
-
*/
|
|
143
|
-
cancel() {
|
|
144
|
-
actor.send({ type: 'CANCEL' });
|
|
145
|
-
},
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Resets the flow manager to its initial `idle` state.
|
|
149
|
-
* Can be called from `ready`, `finished`, or `error` states.
|
|
150
|
-
*/
|
|
151
|
-
reset() {
|
|
152
|
-
actor.send({ type: 'RESET' });
|
|
153
|
-
},
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Advances to the next step in the flow.
|
|
157
|
-
* If on the last step, transitions to `finished` state.
|
|
158
|
-
* Only effective when in `ready` state.
|
|
159
|
-
*/
|
|
160
|
-
nextStep() {
|
|
161
|
-
actor.send({ type: 'NEXT_STEP' });
|
|
162
|
-
},
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Goes back to the previous step in the flow.
|
|
166
|
-
* Does nothing if already on the first step.
|
|
167
|
-
* Only effective when in `ready` state.
|
|
168
|
-
*/
|
|
169
|
-
prevStep() {
|
|
170
|
-
actor.send({ type: 'PREV_STEP' });
|
|
171
|
-
},
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Whether the flow can advance to the next step.
|
|
175
|
-
* Returns `true` if in `ready` state and not on the last step.
|
|
176
|
-
*/
|
|
177
|
-
get canNext() {
|
|
178
|
-
return getCanNext();
|
|
179
|
-
},
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Whether the flow can go back to the previous step.
|
|
183
|
-
* Returns `true` if in `ready` state and not on the first step.
|
|
184
|
-
*/
|
|
185
|
-
get canPrev() {
|
|
186
|
-
return getCanPrev();
|
|
187
|
-
},
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Gets the configuration for a specific module by its key.
|
|
191
|
-
* Useful for accessing config of modules other than the current one.
|
|
192
|
-
* @param moduleKey - The unique key identifier of the module
|
|
193
|
-
* @returns The module configuration or `undefined` if not found or not in `ready` state
|
|
194
|
-
*/
|
|
195
|
-
getModuleConfig,
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Checks if a module is enabled in the current flow.
|
|
199
|
-
* @param moduleKey - The unique key identifier of the module
|
|
200
|
-
* @returns `true` if the module exists in the flow, `false` otherwise or if not in `ready` state
|
|
201
|
-
*/
|
|
202
|
-
isModuleEnabled,
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Creates a flow manager instance for managing onboarding flow state and navigation.
|
|
208
|
-
*
|
|
209
|
-
* The flow manager provides:
|
|
210
|
-
* - State management with statuses: `idle`, `loading`, `ready`, `finished`, `error`
|
|
211
|
-
* - Step navigation with `nextStep()` and `prevStep()`
|
|
212
|
-
* - Current step info via `state.currentStep` and `state.config` when in `ready` state
|
|
213
|
-
* - Module configuration lookup via `getModuleConfig()`
|
|
214
|
-
*
|
|
215
|
-
* @param options - Optional configuration for the flow actor
|
|
216
|
-
* @param options.getFlow - Custom function to fetch flow data. Defaults to `getOnboardingFlow`
|
|
217
|
-
* @returns A manager instance with state subscription, API methods, and lifecycle controls
|
|
218
|
-
*
|
|
219
|
-
* @example
|
|
220
|
-
* ```ts
|
|
221
|
-
* const flowManager = createFlowManager();
|
|
222
|
-
*
|
|
223
|
-
* flowManager.subscribe((state) => {
|
|
224
|
-
* if (state.status === 'ready') {
|
|
225
|
-
* console.log(state.currentStep, state.config);
|
|
226
|
-
* }
|
|
227
|
-
* });
|
|
228
|
-
*
|
|
229
|
-
* flowManager.load({ token: 'session-token' });
|
|
230
|
-
* ```
|
|
231
|
-
*/
|
|
232
|
-
export function createFlowManager(options?: CreateFlowActorOptions) {
|
|
233
|
-
const actor = createFlowActor(options);
|
|
234
|
-
return createManager({ actor, mapState, createApi });
|
|
235
|
-
}
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
createMockResponse,
|
|
4
|
-
mockClient,
|
|
5
|
-
resetMocks,
|
|
6
|
-
} from '../http/__mocks__/api';
|
|
7
|
-
import { mockFlow } from './__mocks__/flowMocks';
|
|
8
|
-
import { type GetFlow, getFlow } from './flowServices';
|
|
9
|
-
|
|
10
|
-
vi.mock('../http/api', () => import('../http/__mocks__/api'));
|
|
11
|
-
|
|
12
|
-
describe('flowServices', () => {
|
|
13
|
-
beforeEach(() => {
|
|
14
|
-
resetMocks();
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
describe('getFlow', () => {
|
|
18
|
-
it('should call api.get with correct URL', async () => {
|
|
19
|
-
mockClient.get.mockResolvedValue(createMockResponse(mockFlow));
|
|
20
|
-
|
|
21
|
-
const abortController = new AbortController();
|
|
22
|
-
|
|
23
|
-
await getFlow(abortController.signal);
|
|
24
|
-
|
|
25
|
-
expect(mockClient.get).toHaveBeenCalledWith('/omni/onboarding/flow', {
|
|
26
|
-
signal: abortController.signal,
|
|
27
|
-
});
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it('should return flow data on success', async () => {
|
|
31
|
-
mockClient.get.mockResolvedValue(createMockResponse(mockFlow));
|
|
32
|
-
|
|
33
|
-
const abortController = new AbortController();
|
|
34
|
-
|
|
35
|
-
const result = await getFlow(abortController.signal);
|
|
36
|
-
|
|
37
|
-
expect(result).toEqual(mockFlow);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('should throw error when response is not ok', async () => {
|
|
41
|
-
mockClient.get.mockResolvedValue({
|
|
42
|
-
...createMockResponse(null, false),
|
|
43
|
-
status: 401,
|
|
44
|
-
statusText: 'Unauthorized',
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
const abortController = new AbortController();
|
|
48
|
-
|
|
49
|
-
await expect(getFlow(abortController.signal)).rejects.toThrow(
|
|
50
|
-
'GET /flow failed: 401 Unauthorized',
|
|
51
|
-
);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('should throw error when response is 404', async () => {
|
|
55
|
-
mockClient.get.mockResolvedValue({
|
|
56
|
-
...createMockResponse(null, false),
|
|
57
|
-
status: 404,
|
|
58
|
-
statusText: 'Not Found',
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
const abortController = new AbortController();
|
|
62
|
-
|
|
63
|
-
await expect(getFlow(abortController.signal)).rejects.toThrow(
|
|
64
|
-
'GET /flow failed: 404 Not Found',
|
|
65
|
-
);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('should throw error when response is 500', async () => {
|
|
69
|
-
mockClient.get.mockResolvedValue({
|
|
70
|
-
...createMockResponse(null, false),
|
|
71
|
-
status: 500,
|
|
72
|
-
statusText: 'Internal Server Error',
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
const abortController = new AbortController();
|
|
76
|
-
|
|
77
|
-
await expect(getFlow(abortController.signal)).rejects.toThrow(
|
|
78
|
-
'GET /flow failed: 500 Internal Server Error',
|
|
79
|
-
);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it('should pass abort signal to api', async () => {
|
|
83
|
-
mockClient.get.mockResolvedValue(createMockResponse(mockFlow));
|
|
84
|
-
|
|
85
|
-
const abortController = new AbortController();
|
|
86
|
-
|
|
87
|
-
await getFlow(abortController.signal);
|
|
88
|
-
|
|
89
|
-
const callArgs = mockClient.get.mock.calls[0];
|
|
90
|
-
expect(callArgs[1]?.signal).toBe(abortController.signal);
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
describe('GetFlow type', () => {
|
|
95
|
-
it('should be compatible with custom implementations', async () => {
|
|
96
|
-
const customGetFlow: GetFlow = async (_signal) => {
|
|
97
|
-
return {
|
|
98
|
-
flowId: 'custom-flow',
|
|
99
|
-
name: 'Custom Flow',
|
|
100
|
-
flowModules: [],
|
|
101
|
-
};
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
const result = await customGetFlow(new AbortController().signal);
|
|
105
|
-
|
|
106
|
-
expect(result.flowId).toBe('custom-flow');
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
});
|
package/src/flow/flowServices.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { api } from '../http/api';
|
|
2
|
-
import { endpoints } from '../http/endpoints';
|
|
3
|
-
import type { Flow } from './types';
|
|
4
|
-
|
|
5
|
-
export type GetFlow = (signal: AbortSignal) => Promise<Flow>;
|
|
6
|
-
|
|
7
|
-
export const getFlow: GetFlow = async (signal) => {
|
|
8
|
-
const res = await api.get<Flow>(endpoints.flow, { signal });
|
|
9
|
-
if (!res.ok) {
|
|
10
|
-
throw new Error(`GET /flow failed: ${res.status} ${res.statusText}`);
|
|
11
|
-
}
|
|
12
|
-
return res.data as Flow;
|
|
13
|
-
};
|
|
@@ -1,334 +0,0 @@
|
|
|
1
|
-
import { createActor } from '@incodetech/infra';
|
|
2
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
3
|
-
import {
|
|
4
|
-
createFailingGetFlow,
|
|
5
|
-
createMockGetFlow,
|
|
6
|
-
mockFlow,
|
|
7
|
-
} from './__mocks__/flowMocks';
|
|
8
|
-
import { flowMachine } from './flowStateMachine';
|
|
9
|
-
import type { Flow } from './types';
|
|
10
|
-
|
|
11
|
-
describe('flowStateMachine', () => {
|
|
12
|
-
describe('Initial state', () => {
|
|
13
|
-
it('should start in idle state', () => {
|
|
14
|
-
const getFlow = createMockGetFlow();
|
|
15
|
-
const actor = createActor(flowMachine, {
|
|
16
|
-
input: { getFlow },
|
|
17
|
-
}).start();
|
|
18
|
-
|
|
19
|
-
expect(actor.getSnapshot().value).toBe('idle');
|
|
20
|
-
actor.stop();
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('should have initial context', () => {
|
|
24
|
-
const getFlow = createMockGetFlow();
|
|
25
|
-
const actor = createActor(flowMachine, {
|
|
26
|
-
input: { getFlow },
|
|
27
|
-
}).start();
|
|
28
|
-
|
|
29
|
-
const { context } = actor.getSnapshot();
|
|
30
|
-
expect(context.flow).toBeUndefined();
|
|
31
|
-
expect(context.error).toBeUndefined();
|
|
32
|
-
expect(context.steps).toEqual([]);
|
|
33
|
-
expect(context.currentStepIndex).toBe(-1);
|
|
34
|
-
|
|
35
|
-
actor.stop();
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
describe('LOAD event', () => {
|
|
40
|
-
it('should transition to loading state', () => {
|
|
41
|
-
const getFlow = createMockGetFlow();
|
|
42
|
-
const actor = createActor(flowMachine, {
|
|
43
|
-
input: { getFlow },
|
|
44
|
-
}).start();
|
|
45
|
-
|
|
46
|
-
actor.send({ type: 'LOAD' });
|
|
47
|
-
|
|
48
|
-
expect(actor.getSnapshot().value).toBe('loading');
|
|
49
|
-
actor.stop();
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
describe('Loading flow', () => {
|
|
54
|
-
it('should call getFlow with signal', async () => {
|
|
55
|
-
const getFlow = createMockGetFlow();
|
|
56
|
-
const actor = createActor(flowMachine, {
|
|
57
|
-
input: { getFlow },
|
|
58
|
-
}).start();
|
|
59
|
-
|
|
60
|
-
actor.send({ type: 'LOAD' });
|
|
61
|
-
|
|
62
|
-
await vi.waitFor(() => {
|
|
63
|
-
expect(getFlow).toHaveBeenCalledWith(expect.any(AbortSignal));
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
actor.stop();
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('should transition to ready on success', async () => {
|
|
70
|
-
const getFlow = createMockGetFlow();
|
|
71
|
-
const actor = createActor(flowMachine, {
|
|
72
|
-
input: { getFlow },
|
|
73
|
-
}).start();
|
|
74
|
-
|
|
75
|
-
actor.send({ type: 'LOAD' });
|
|
76
|
-
|
|
77
|
-
await vi.waitFor(() => {
|
|
78
|
-
expect(actor.getSnapshot().value).toBe('ready');
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
actor.stop();
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('should set flow data in context on success', async () => {
|
|
85
|
-
const getFlow = createMockGetFlow();
|
|
86
|
-
const actor = createActor(flowMachine, {
|
|
87
|
-
input: { getFlow },
|
|
88
|
-
}).start();
|
|
89
|
-
|
|
90
|
-
actor.send({ type: 'LOAD' });
|
|
91
|
-
|
|
92
|
-
await vi.waitFor(() => {
|
|
93
|
-
expect(actor.getSnapshot().value).toBe('ready');
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
const { context } = actor.getSnapshot();
|
|
97
|
-
expect(context.flow).toEqual(mockFlow);
|
|
98
|
-
expect(context.steps).toEqual(['TUTORIAL_ID', 'SELFIE', 'FACE_MATCH']);
|
|
99
|
-
expect(context.currentStepIndex).toBe(0);
|
|
100
|
-
|
|
101
|
-
actor.stop();
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it('should transition to error on failure', async () => {
|
|
105
|
-
const getFlow = createFailingGetFlow('Network error');
|
|
106
|
-
const actor = createActor(flowMachine, {
|
|
107
|
-
input: { getFlow },
|
|
108
|
-
}).start();
|
|
109
|
-
|
|
110
|
-
actor.send({ type: 'LOAD' });
|
|
111
|
-
|
|
112
|
-
await vi.waitFor(() => {
|
|
113
|
-
expect(actor.getSnapshot().value).toBe('error');
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
expect(actor.getSnapshot().context.error).toContain('Network error');
|
|
117
|
-
|
|
118
|
-
actor.stop();
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
describe('CANCEL event during loading', () => {
|
|
123
|
-
it('should transition back to idle', async () => {
|
|
124
|
-
const getFlow = vi.fn().mockImplementation(() => {
|
|
125
|
-
return new Promise((resolve) =>
|
|
126
|
-
setTimeout(() => resolve(mockFlow), 1000),
|
|
127
|
-
);
|
|
128
|
-
});
|
|
129
|
-
const actor = createActor(flowMachine, {
|
|
130
|
-
input: { getFlow },
|
|
131
|
-
}).start();
|
|
132
|
-
|
|
133
|
-
actor.send({ type: 'LOAD' });
|
|
134
|
-
expect(actor.getSnapshot().value).toBe('loading');
|
|
135
|
-
|
|
136
|
-
actor.send({ type: 'CANCEL' });
|
|
137
|
-
|
|
138
|
-
expect(actor.getSnapshot().value).toBe('idle');
|
|
139
|
-
|
|
140
|
-
actor.stop();
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
describe('Step navigation in ready state', () => {
|
|
145
|
-
const setupReadyActor = async () => {
|
|
146
|
-
const getFlow = createMockGetFlow();
|
|
147
|
-
const actor = createActor(flowMachine, {
|
|
148
|
-
input: { getFlow },
|
|
149
|
-
}).start();
|
|
150
|
-
|
|
151
|
-
actor.send({ type: 'LOAD' });
|
|
152
|
-
|
|
153
|
-
await vi.waitFor(() => {
|
|
154
|
-
expect(actor.getSnapshot().value).toBe('ready');
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
return actor;
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
it('should increment step on NEXT_STEP', async () => {
|
|
161
|
-
const actor = await setupReadyActor();
|
|
162
|
-
|
|
163
|
-
expect(actor.getSnapshot().context.currentStepIndex).toBe(0);
|
|
164
|
-
|
|
165
|
-
actor.send({ type: 'NEXT_STEP' });
|
|
166
|
-
|
|
167
|
-
expect(actor.getSnapshot().context.currentStepIndex).toBe(1);
|
|
168
|
-
expect(actor.getSnapshot().value).toBe('ready');
|
|
169
|
-
|
|
170
|
-
actor.stop();
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
it('should decrement step on PREV_STEP', async () => {
|
|
174
|
-
const actor = await setupReadyActor();
|
|
175
|
-
|
|
176
|
-
actor.send({ type: 'NEXT_STEP' });
|
|
177
|
-
expect(actor.getSnapshot().context.currentStepIndex).toBe(1);
|
|
178
|
-
|
|
179
|
-
actor.send({ type: 'PREV_STEP' });
|
|
180
|
-
expect(actor.getSnapshot().context.currentStepIndex).toBe(0);
|
|
181
|
-
|
|
182
|
-
actor.stop();
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
it('should not go below 0 on PREV_STEP', async () => {
|
|
186
|
-
const actor = await setupReadyActor();
|
|
187
|
-
|
|
188
|
-
expect(actor.getSnapshot().context.currentStepIndex).toBe(0);
|
|
189
|
-
|
|
190
|
-
actor.send({ type: 'PREV_STEP' });
|
|
191
|
-
|
|
192
|
-
expect(actor.getSnapshot().context.currentStepIndex).toBe(0);
|
|
193
|
-
|
|
194
|
-
actor.stop();
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
it('should transition to finished on last step', async () => {
|
|
198
|
-
const actor = await setupReadyActor();
|
|
199
|
-
|
|
200
|
-
actor.send({ type: 'NEXT_STEP' });
|
|
201
|
-
actor.send({ type: 'NEXT_STEP' });
|
|
202
|
-
|
|
203
|
-
expect(actor.getSnapshot().context.currentStepIndex).toBe(2);
|
|
204
|
-
expect(actor.getSnapshot().value).toBe('ready');
|
|
205
|
-
|
|
206
|
-
actor.send({ type: 'NEXT_STEP' });
|
|
207
|
-
|
|
208
|
-
expect(actor.getSnapshot().value).toBe('finished');
|
|
209
|
-
|
|
210
|
-
actor.stop();
|
|
211
|
-
});
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
describe('RESET event', () => {
|
|
215
|
-
it('should reset from ready state to idle', async () => {
|
|
216
|
-
const getFlow = createMockGetFlow();
|
|
217
|
-
const actor = createActor(flowMachine, {
|
|
218
|
-
input: { getFlow },
|
|
219
|
-
}).start();
|
|
220
|
-
|
|
221
|
-
actor.send({ type: 'LOAD' });
|
|
222
|
-
|
|
223
|
-
await vi.waitFor(() => {
|
|
224
|
-
expect(actor.getSnapshot().value).toBe('ready');
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
actor.send({ type: 'RESET' });
|
|
228
|
-
|
|
229
|
-
expect(actor.getSnapshot().value).toBe('idle');
|
|
230
|
-
expect(actor.getSnapshot().context.flow).toBeUndefined();
|
|
231
|
-
expect(actor.getSnapshot().context.steps).toEqual([]);
|
|
232
|
-
|
|
233
|
-
actor.stop();
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
it('should reset from error state to idle', async () => {
|
|
237
|
-
const getFlow = createFailingGetFlow('Error');
|
|
238
|
-
const actor = createActor(flowMachine, {
|
|
239
|
-
input: { getFlow },
|
|
240
|
-
}).start();
|
|
241
|
-
|
|
242
|
-
actor.send({ type: 'LOAD' });
|
|
243
|
-
|
|
244
|
-
await vi.waitFor(() => {
|
|
245
|
-
expect(actor.getSnapshot().value).toBe('error');
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
actor.send({ type: 'RESET' });
|
|
249
|
-
|
|
250
|
-
expect(actor.getSnapshot().value).toBe('idle');
|
|
251
|
-
expect(actor.getSnapshot().context.error).toBeUndefined();
|
|
252
|
-
|
|
253
|
-
actor.stop();
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
it('should reset from finished state to idle', async () => {
|
|
257
|
-
const getFlow = createMockGetFlow();
|
|
258
|
-
const actor = createActor(flowMachine, {
|
|
259
|
-
input: { getFlow },
|
|
260
|
-
}).start();
|
|
261
|
-
|
|
262
|
-
actor.send({ type: 'LOAD' });
|
|
263
|
-
|
|
264
|
-
await vi.waitFor(() => {
|
|
265
|
-
expect(actor.getSnapshot().value).toBe('ready');
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
actor.send({ type: 'NEXT_STEP' });
|
|
269
|
-
actor.send({ type: 'NEXT_STEP' });
|
|
270
|
-
actor.send({ type: 'NEXT_STEP' });
|
|
271
|
-
|
|
272
|
-
expect(actor.getSnapshot().value).toBe('finished');
|
|
273
|
-
|
|
274
|
-
actor.send({ type: 'RESET' });
|
|
275
|
-
|
|
276
|
-
expect(actor.getSnapshot().value).toBe('idle');
|
|
277
|
-
|
|
278
|
-
actor.stop();
|
|
279
|
-
});
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
describe('Empty flow handling', () => {
|
|
283
|
-
it('should handle flow with no modules', async () => {
|
|
284
|
-
const emptyFlow: Flow = {
|
|
285
|
-
flowId: 'empty',
|
|
286
|
-
name: 'Empty Flow',
|
|
287
|
-
flowModules: [],
|
|
288
|
-
};
|
|
289
|
-
const getFlow = createMockGetFlow(emptyFlow);
|
|
290
|
-
const actor = createActor(flowMachine, {
|
|
291
|
-
input: { getFlow },
|
|
292
|
-
}).start();
|
|
293
|
-
|
|
294
|
-
actor.send({ type: 'LOAD' });
|
|
295
|
-
|
|
296
|
-
await vi.waitFor(() => {
|
|
297
|
-
expect(actor.getSnapshot().value).toBe('ready');
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
const { context } = actor.getSnapshot();
|
|
301
|
-
expect(context.steps).toEqual([]);
|
|
302
|
-
expect(context.currentStepIndex).toBe(-1);
|
|
303
|
-
|
|
304
|
-
actor.stop();
|
|
305
|
-
});
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
describe('Subscriptions', () => {
|
|
309
|
-
it('should notify subscribers of state changes', async () => {
|
|
310
|
-
const getFlow = createMockGetFlow();
|
|
311
|
-
const actor = createActor(flowMachine, {
|
|
312
|
-
input: { getFlow },
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
const states: string[] = [];
|
|
316
|
-
actor.subscribe((snapshot) => {
|
|
317
|
-
states.push(snapshot.value as string);
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
actor.start();
|
|
321
|
-
actor.send({ type: 'LOAD' });
|
|
322
|
-
|
|
323
|
-
await vi.waitFor(() => {
|
|
324
|
-
expect(actor.getSnapshot().value).toBe('ready');
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
expect(states).toContain('idle');
|
|
328
|
-
expect(states).toContain('loading');
|
|
329
|
-
expect(states).toContain('ready');
|
|
330
|
-
|
|
331
|
-
actor.stop();
|
|
332
|
-
});
|
|
333
|
-
});
|
|
334
|
-
});
|