Skip to content

Commit

Permalink
Correctly use falsy outputs (#4768)
Browse files Browse the repository at this point in the history
  • Loading branch information
Andarist committed Feb 27, 2024
1 parent b421ab9 commit 4a29f8a
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 5 deletions.
5 changes: 5 additions & 0 deletions .changeset/nasty-foxes-move.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'xstate': patch
---

Correctly use falsy outputs (instead of accidentally converting them to `undefined`).
4 changes: 2 additions & 2 deletions packages/core/src/StateMachine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,9 @@ export class StateMachine<

if (
isDevelopment &&
!this.root.output &&
!('output' in this.root) &&
Object.values(this.states).some(
(state) => state.type === 'final' && !!state.output
(state) => state.type === 'final' && 'output' in state
)
) {
console.warn(
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/stateUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1063,12 +1063,12 @@ function getMachineOutput(
rootNode: AnyStateNode,
rootCompletionNode: AnyStateNode
) {
if (!rootNode.output) {
if (rootNode.output === undefined) {
return;
}
const doneStateEvent = createDoneStateEvent(
rootCompletionNode.id,
rootCompletionNode.output && rootCompletionNode.parent
rootCompletionNode.output !== undefined && rootCompletionNode.parent
? resolveOutput(
rootCompletionNode.output,
snapshot.context,
Expand Down Expand Up @@ -1158,7 +1158,7 @@ function enterStates(
internalQueue.push(
createDoneStateEvent(
parent!.id,
stateNodeToEnter.output
stateNodeToEnter.output !== undefined
? resolveOutput(
stateNodeToEnter.output,
nextSnapshot.context,
Expand Down
45 changes: 45 additions & 0 deletions packages/core/test/final.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1241,4 +1241,49 @@ describe('final states', () => {
// if `xstate.done.actor.*` would be delivered first the value would be `completed`
expect(actorRef.getSnapshot().value).toBe('canceled');
});

it('should be possible to complete with a null output (directly on root)', () => {
const machine = createMachine({
initial: 'start',
states: {
start: {
on: {
NEXT: 'end'
}
},
end: {
type: 'final'
}
},
output: null
});

const actorRef = createActor(machine).start();
actorRef.send({ type: 'NEXT' });

expect(actorRef.getSnapshot().output).toBe(null);
});

it("should be possible to complete with a null output (resolving with final state's output)", () => {
const machine = createMachine({
initial: 'start',
states: {
start: {
on: {
NEXT: 'end'
}
},
end: {
type: 'final',
output: null
}
},
output: ({ event }) => event.output
});

const actorRef = createActor(machine).start();
actorRef.send({ type: 'NEXT' });

expect(actorRef.getSnapshot().output).toBe(null);
});
});

0 comments on commit 4a29f8a

Please sign in to comment.