@@ -12,12 +12,8 @@ import {fromPromise} from 'xstate';
1212
1313type CapturedCallback = ( ) => void ;
1414type NavigationTransitionOverrides = Pick < typeof Navigation , 'runAfterTransition' | 'runAfterUpcomingTransition' > ;
15- type PendingValidateDevice = {
16- resolve : ( result : MFAResult ) => void ;
17- reject : ( error : Error ) => void ;
18- } ;
19- type PendingReadHasAcceptedSoftPrompt = {
20- resolve : ( accepted : boolean ) => void ;
15+ type PendingCall < TOutput > = {
16+ resolve : ( output : TOutput ) => void ;
2117 reject : ( error : Error ) => void ;
2218} ;
2319
@@ -54,68 +50,52 @@ const biometricsMock: Pick<UseBiometricsReturn, 'serverKnownCredentialIDs' | 'ar
5450 areLocalCredentialsKnownToServer : ( ) => Promise . resolve ( false ) ,
5551} ;
5652
57- let pendingValidateDeviceCall : PendingValidateDevice | undefined ;
58- let pendingSoftPromptAcceptanceCall : PendingReadHasAcceptedSoftPrompt | undefined ;
59-
60- function takePendingValidateDeviceCall ( ) : PendingValidateDevice {
61- const pendingCall = pendingValidateDeviceCall ;
62- pendingValidateDeviceCall = undefined ;
63- if ( ! pendingCall ) {
64- throw new Error ( 'No pending validateDevice call is available.' ) ;
65- }
66- return pendingCall ;
67- }
68-
69- function resolveValidateDevice ( result : MFAResult ) {
70- takePendingValidateDeviceCall ( ) . resolve ( result ) ;
71- }
72-
73- function rejectValidateDevice ( ) {
74- takePendingValidateDeviceCall ( ) . reject ( new Error ( 'Mock validateDevice actor rejected for this path' ) ) ;
75- }
76-
77- function takePendingSoftPromptAcceptanceCall ( ) : PendingReadHasAcceptedSoftPrompt {
78- const pendingCall = pendingSoftPromptAcceptanceCall ;
79- pendingSoftPromptAcceptanceCall = undefined ;
80- if ( ! pendingCall ) {
81- throw new Error ( 'No pending readHasAcceptedSoftPrompt call is available.' ) ;
53+ /**
54+ * Builds a controlled deferred mock for one invoked machine actor. Each machine invocation parks a
55+ * pending promise that the test settles later through `resolve` or `reject`, at the exact path step
56+ * where the machine expects the actor outcome.
57+ */
58+ function createControlledActor < TOutput , TInput > ( actorID : string ) {
59+ let pendingCall : PendingCall < TOutput > | undefined ;
60+
61+ function takePendingCall ( ) : PendingCall < TOutput > {
62+ const call = pendingCall ;
63+ pendingCall = undefined ;
64+ if ( ! call ) {
65+ throw new Error ( `No pending ${ actorID } call is available.` ) ;
66+ }
67+ return call ;
8268 }
83- return pendingCall ;
84- }
8569
86- function resolveSoftPromptAcceptance ( accepted : boolean ) {
87- takePendingSoftPromptAcceptanceCall ( ) . resolve ( accepted ) ;
70+ return {
71+ actor : fromPromise < TOutput , TInput > (
72+ ( ) =>
73+ new Promise < TOutput > ( ( resolve , reject ) => {
74+ pendingCall = { resolve, reject} ;
75+ } ) ,
76+ ) ,
77+ resolve : ( output : TOutput ) => takePendingCall ( ) . resolve ( output ) ,
78+ reject : ( ) => takePendingCall ( ) . reject ( new Error ( `Mock ${ actorID } actor rejected for this path` ) ) ,
79+ reset : ( ) => {
80+ pendingCall = undefined ;
81+ } ,
82+ } ;
8883}
8984
90- function rejectSoftPromptAcceptanceRead ( ) {
91- takePendingSoftPromptAcceptanceCall ( ) . reject ( new Error ( 'Mock readHasAcceptedSoftPrompt actor rejected for this path' ) ) ;
92- }
85+ const validateDeviceControl = createControlledActor < MFAResult , ValidateDeviceInput > ( 'validateDevice' ) ;
86+ const readHasAcceptedSoftPromptControl = createControlledActor < boolean , ReadHasAcceptedSoftPromptInput > ( 'readHasAcceptedSoftPrompt' ) ;
9387
9488function resetMfaUiMocks ( ) {
9589 pendingModalClose . clear ( ) ;
96- pendingValidateDeviceCall = undefined ;
97- pendingSoftPromptAcceptanceCall = undefined ;
90+ validateDeviceControl . reset ( ) ;
91+ readHasAcceptedSoftPromptControl . reset ( ) ;
9892}
9993
100- const validateDeviceMock = fromPromise < MFAResult , ValidateDeviceInput > (
101- ( ) =>
102- new Promise < MFAResult > ( ( resolve , reject ) => {
103- pendingValidateDeviceCall = { resolve, reject} ;
104- } ) ,
105- ) ;
106-
107- const readHasAcceptedSoftPromptMock = fromPromise < boolean , ReadHasAcceptedSoftPromptInput > (
108- ( ) =>
109- new Promise < boolean > ( ( resolve , reject ) => {
110- pendingSoftPromptAcceptanceCall = { resolve, reject} ;
111- } ) ,
112- ) ;
113-
11494/** Replaces the machine's side-effect actors with controlled test implementations. */
11595function mfaActorsMock ( ) {
11696 const actors = {
117- validateDevice : validateDeviceMock ,
118- readHasAcceptedSoftPrompt : readHasAcceptedSoftPromptMock ,
97+ validateDevice : validateDeviceControl . actor ,
98+ readHasAcceptedSoftPrompt : readHasAcceptedSoftPromptControl . actor ,
11999 } satisfies ReturnType < typeof createActors > ;
120100
121101 return {
@@ -173,16 +153,4 @@ function navigationMock() {
173153 } ;
174154}
175155
176- export {
177- pendingModalClose ,
178- resolveValidateDevice ,
179- rejectValidateDevice ,
180- resolveSoftPromptAcceptance ,
181- rejectSoftPromptAcceptanceRead ,
182- resetMfaUiMocks ,
183- mfaActorsMock ,
184- biometricsHookMock ,
185- renderHtmlMock ,
186- syncHistoryMock ,
187- navigationMock ,
188- } ;
156+ export { pendingModalClose , validateDeviceControl , readHasAcceptedSoftPromptControl , resetMfaUiMocks , mfaActorsMock , biometricsHookMock , renderHtmlMock , syncHistoryMock , navigationMock } ;
0 commit comments