Skip to content

Commit

Permalink
Merge pull request #1661 from davidkpiano/fix/initial-assign-actions
Browse files Browse the repository at this point in the history
Fixed an issue with initial `assign` actions not being resolved
  • Loading branch information
davidkpiano authored Nov 26, 2020
2 parents 227d81b + 8b67065 commit 3cae72f
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 30 deletions.
5 changes: 5 additions & 0 deletions .changeset/purple-taxis-burn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@xstate/fsm': patch
---

Fixed an issue with initial `assign` actions not being resolved and thus context not being updated by them.
85 changes: 55 additions & 30 deletions packages/xstate-fsm/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,42 @@ function createUnchangedState<
};
}

function handleActions<
TContext extends object,
TEvent extends EventObject = EventObject
>(
actions: Array<StateMachine.ActionObject<TContext, TEvent>>,
context: TContext,
eventObject: TEvent
): [Array<StateMachine.ActionObject<TContext, TEvent>>, TContext, boolean] {
let nextContext = context;
let assigned = false;

const nonAssignActions = actions.filter((action) => {
if (action.type === ASSIGN_ACTION) {
assigned = true;
let tmpContext = Object.assign({}, nextContext);

if (typeof action.assignment === 'function') {
tmpContext = action.assignment(nextContext, eventObject);
} else {
Object.keys(action.assignment).forEach((key) => {
tmpContext[key] =
typeof action.assignment[key] === 'function'
? action.assignment[key](nextContext, eventObject)
: action.assignment[key];
});
}

nextContext = tmpContext;
return false;
}
return true;
});

return [nonAssignActions, nextContext, assigned];
}

export function createMachine<
TContext extends object,
TEvent extends EventObject = EventObject,
Expand All @@ -95,15 +131,21 @@ export function createMachine<
});
}

const [initialActions, initialContext] = handleActions(
toArray(fsmConfig.states[fsmConfig.initial].entry).map((action) =>
toActionObject(action, options.actions)
),
fsmConfig.context!,
INIT_EVENT as TEvent
);

const machine = {
config: fsmConfig,
_options: options,
initialState: {
value: fsmConfig.initial,
actions: toArray(
fsmConfig.states[fsmConfig.initial].entry
).map((action) => toActionObject(action, options.actions)),
context: fsmConfig.context!,
actions: initialActions,
context: initialContext,
matches: createMatcher(fsmConfig.initial)
},
transition: (
Expand Down Expand Up @@ -140,44 +182,27 @@ export function createMachine<
? { target: transition }
: transition;

let nextContext = context;

if (cond(context, eventObject)) {
const nextStateConfig = fsmConfig.states[target];
let assigned = false;
const allActions = ([] as any[])
.concat(stateConfig.exit, actions, nextStateConfig.entry)
.filter((a) => a)
.map<StateMachine.ActionObject<TContext, TEvent>>((action) =>
toActionObject(action, (machine as any)._options.actions)
)
.filter((action) => {
if (action.type === ASSIGN_ACTION) {
assigned = true;
let tmpContext = Object.assign({}, nextContext);

if (typeof action.assignment === 'function') {
tmpContext = action.assignment(nextContext, eventObject);
} else {
Object.keys(action.assignment).forEach((key) => {
tmpContext[key] =
typeof action.assignment[key] === 'function'
? action.assignment[key](nextContext, eventObject)
: action.assignment[key];
});
}
);

nextContext = tmpContext;
return false;
}
return true;
});
const [nonAssignActions, nextContext, assigned] = handleActions(
allActions,
context,
eventObject
);

return {
value: target,
context: nextContext,
actions: allActions,
changed: target !== value || allActions.length > 0 || assigned,
actions: nonAssignActions,
changed:
target !== value || nonAssignActions.length > 0 || assigned,
matches: createMatcher(target)
};
}
Expand Down
37 changes: 37 additions & 0 deletions packages/xstate-fsm/test/fsm.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,43 @@ describe('@xstate/fsm', () => {
expect(initialState.value).toEqual('green');
expect(initialState.actions).toEqual([{ type: 'enterGreen' }]);
});
it('should have initial context updated by initial assign actions', () => {
const { initialState } = createMachine({
initial: 'init',
context: {
count: 0
},
states: {
init: {
entry: assign({
count: () => 1
})
}
}
});

expect(initialState.context).toEqual({ count: 1 });
});
it('should have initial actions computed without assign actions', () => {
const { initialState } = createMachine({
initial: 'init',
context: {
count: 0
},
states: {
init: {
entry: [
{ type: 'foo' },
assign({
count: () => 1
})
]
}
}
});

expect(initialState.actions).toEqual([{ type: 'foo' }]);
});
it('should transition correctly', () => {
const nextState = lightFSM.transition('green', 'TIMER');
expect(nextState.value).toEqual('yellow');
Expand Down

0 comments on commit 3cae72f

Please sign in to comment.