@@ -17,6 +17,7 @@ import {
1717 ResolveActions ,
1818 ResolveState ,
1919 Execution ,
20+ ResolveMockActions ,
2021} from './internalTypes'
2122import { proxifyEffects } from './proxyfyEffects'
2223import {
@@ -61,6 +62,76 @@ export const makeStringifySafeMutations = (mutations: IMutation[]) => {
6162 } ) )
6263}
6364
65+ function deepCopy ( obj ) {
66+ if ( isPlainObject ( obj ) ) {
67+ return Object . keys ( obj ) . reduce ( ( aggr , key ) => {
68+ aggr [ key ] = deepCopy ( obj [ key ] )
69+
70+ return aggr
71+ } , { } )
72+ } else if ( Array . isArray ( obj ) ) {
73+ return obj . map ( ( item ) => deepCopy ( item ) )
74+ }
75+
76+ return obj
77+ }
78+
79+ export function createMock < Config extends Configuration > (
80+ config : Config ,
81+ effectsCallback ?: (
82+ effect : {
83+ path : string
84+ args : any [ ]
85+ } ,
86+ index : number
87+ ) => any
88+ ) : {
89+ actions : ResolveMockActions < Config [ 'actions' ] >
90+ state : ResolveState < Config [ 'state' ] >
91+ } {
92+ let effectCount = 0
93+ let effectsCalled = [ ]
94+ const mock = new Overmind (
95+ Object . assign ( { } , config , {
96+ state : deepCopy ( config . state ) ,
97+ } ) ,
98+ {
99+ devtools : false ,
100+ testMode : {
101+ effectsCallback : ( effect ) => {
102+ if ( ! effectsCallback ) {
103+ return
104+ }
105+
106+ return effectsCallback (
107+ {
108+ path : effect . name + '.' + effect . method ,
109+ args : effect . args ,
110+ } ,
111+ effectCount ++
112+ )
113+ } ,
114+ actionCallback : ( execution ) => {
115+ const effects = effectsCalled . slice ( )
116+
117+ effectCount = 0
118+ effectsCalled . length = 0
119+
120+ return {
121+ mutations : execution . flush ( ) . mutations ,
122+ effects,
123+ }
124+ } ,
125+ } ,
126+ }
127+ )
128+
129+ return {
130+ actions : mock . actions as any ,
131+ state : mock . state ,
132+ }
133+ }
134+
64135const hotReloadingCache = { }
65136
66137// We do not use TConfig<Config> directly to type the class in order to avoid
@@ -70,6 +141,7 @@ export class Overmind<Config extends Configuration> implements Configuration {
70141 private proxyStateTree : ProxyStateTree < object >
71142 private actionReferences : Function [ ] = [ ]
72143 private nextExecutionId : number = 0
144+ private options : Options
73145 initialized : Promise < any >
74146 eventHub : EventEmitter < Events >
75147 devtools : Devtools
@@ -108,6 +180,7 @@ export class Overmind<Config extends Configuration> implements Configuration {
108180 this . effects = configuration . effects || { }
109181 this . proxyStateTree = proxyStateTree
110182 this . eventHub = eventHub
183+ this . options = options
111184
112185 if ( ! IS_PRODUCTION && typeof window !== 'undefined' ) {
113186 let warning = 'OVERMIND: You are running in DEVELOPMENT mode.'
@@ -157,7 +230,7 @@ export class Overmind<Config extends Configuration> implements Configuration {
157230 nextTick && clearTimeout ( nextTick )
158231 nextTick = setTimeout ( flushTree , 0 )
159232 } )
160- } else {
233+ } else if ( ! options . testMode ) {
161234 eventHub . on ( EventType . OPERATOR_ASYNC , ( execution ) => {
162235 const flushData = execution . flush ( )
163236 if ( this . devtools && flushData ) {
@@ -264,7 +337,7 @@ export class Overmind<Config extends Configuration> implements Configuration {
264337 }
265338 private createAction ( name , action ) {
266339 this . actionReferences . push ( action )
267- return ( value ?) => {
340+ const actionFunc = ( value ?) => {
268341 if ( IS_PRODUCTION || action [ IS_OPERATOR ] ) {
269342 return new Promise ( ( resolve , reject ) => {
270343 const execution = this . createExecution ( name , action )
@@ -288,15 +361,19 @@ export class Overmind<Config extends Configuration> implements Configuration {
288361 operatorId : finalContext . execution . operatorId - 1 ,
289362 } )
290363 if ( err ) reject ( err )
291- else resolve ( finalContext . value )
364+ else resolve ( this . options . testMode && finalContext . execution )
292365 }
293366 )
294- : action (
295- this . createContext (
296- value ,
297- execution ,
298- execution . getMutationTree ( )
367+ : resolve (
368+ action (
369+ this . createContext (
370+ value ,
371+ execution ,
372+ execution . getMutationTree ( )
373+ )
299374 )
375+ ? undefined
376+ : undefined
300377 )
301378 } )
302379 } else {
@@ -332,32 +409,94 @@ export class Overmind<Config extends Configuration> implements Configuration {
332409 } )
333410 } )
334411
335- const result = action (
336- this . createContext (
337- this . scopeValue ( value , mutationTree ) ,
338- execution ,
339- mutationTree
340- )
412+ const context = this . createContext (
413+ this . scopeValue ( value , mutationTree ) ,
414+ execution ,
415+ mutationTree
341416 )
417+ const result = action ( context )
418+
342419 this . eventHub . emit ( EventType . OPERATOR_END , {
343420 ...execution ,
344421 isAsync : result instanceof Promise ,
345422 result : undefined ,
346423 } )
347424 this . eventHub . emit ( EventType . ACTION_END , execution )
348425
349- return Promise . resolve ( result )
426+ return Promise . resolve ( this . options . testMode && execution )
350427 }
351428 }
429+
430+ if ( this . options . testMode ) {
431+ const actionCallback = this . options . testMode . actionCallback
432+
433+ return ( value ?) => ( actionFunc as any ) ( value ) . then ( actionCallback )
434+ }
435+
436+ return actionFunc
352437 }
353438 private trackEffects ( effects = { } , execution ) {
354439 if ( IS_PRODUCTION ) {
355440 return effects
356441 }
357442
358- return proxifyEffects ( this . effects , ( effect ) =>
359- this . eventHub . emit ( EventType . EFFECT , { ...execution , ...effect } )
360- )
443+ return proxifyEffects ( this . effects , ( effect ) => {
444+ let result
445+ try {
446+ result = this . options . testMode
447+ ? this . options . testMode . effectsCallback ( effect )
448+ : effect . func . apply ( this , effect . args )
449+ } catch ( error ) {
450+ // eslint-disable-next-line standard/no-callback-literal
451+ this . eventHub . emit ( EventType . EFFECT , {
452+ ...execution ,
453+ ...effect ,
454+ isPending : false ,
455+ error : error . message ,
456+ } )
457+ return
458+ }
459+
460+ if ( result instanceof Promise ) {
461+ // eslint-disable-next-line standard/no-callback-literal
462+ this . eventHub . emit ( EventType . EFFECT , {
463+ ...execution ,
464+ ...effect ,
465+ isPending : true ,
466+ error : false ,
467+ } )
468+ result
469+ . then ( ( promisedResult ) => {
470+ // eslint-disable-next-line standard/no-callback-literal
471+ this . eventHub . emit ( EventType . EFFECT , {
472+ ...execution ,
473+ ...effect ,
474+ result : promisedResult ,
475+ isPending : false ,
476+ error : false ,
477+ } )
478+ } )
479+ . catch ( ( error ) => {
480+ this . eventHub . emit ( EventType . EFFECT , {
481+ ...execution ,
482+ ...effect ,
483+ isPending : false ,
484+ error : error . message ,
485+ } )
486+ } )
487+ } else {
488+ // eslint-disable-next-line standard/no-callback-literal
489+ this . eventHub . emit ( EventType . EFFECT , {
490+ ...execution ,
491+ ...effect ,
492+ result,
493+ isPending : false ,
494+ error : false ,
495+ } )
496+ }
497+
498+ return result
499+ } )
361500 }
362501 private initializeDevtools ( host , eventHub , proxyStateTree ) {
363502 const devtools = new Devtools (
0 commit comments