Skip to content

Commit

Permalink
Fixed an issue with targeted ancestor reentrancy (#3424)
Browse files Browse the repository at this point in the history
  • Loading branch information
Andarist committed Jun 25, 2022
1 parent e35493f commit 88d540e
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 10 deletions.
5 changes: 5 additions & 0 deletions .changeset/unlucky-sloths-tell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'xstate': patch
---

Fixed an issue with targeted ancestors not being correctly reentered during external transitions.
21 changes: 11 additions & 10 deletions packages/core/src/StateNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -956,8 +956,8 @@ class StateNode<
const reentryNodes: StateNode<any, any, any, any, any>[] = [];

if (!isInternal) {
allNextStateNodes.forEach((n1) => {
reentryNodes.push(...this.getExternalReentryNodes(n1));
nextStateNodes.forEach((targetNode) => {
reentryNodes.push(...this.getExternalReentryNodes(targetNode));
});
}

Expand All @@ -972,23 +972,24 @@ class StateNode<
}

private getExternalReentryNodes(
childStateNode: StateNode<TContext, any, TEvent, any, any, any>
targetNode: StateNode<TContext, any, TEvent, any, any, any>
): Array<StateNode<TContext, any, TEvent, any, any, any>> {
const nodes: Array<StateNode<TContext, any, TEvent, any, any, any>> = [];
let marker:
| StateNode<TContext, any, TEvent, any, any, any>
| undefined = childStateNode;
let [marker, possibleAncestor]: [
StateNode<TContext, any, TEvent, any, any, any> | undefined,
StateNode<TContext, any, TEvent, any, any, any>
] = targetNode.order > this.order ? [targetNode, this] : [this, targetNode];

while (marker && marker !== this) {
while (marker && marker !== possibleAncestor) {
nodes.push(marker);
marker = marker.parent;
}
if (marker !== this) {
// we never got to `this`, therefore the `childStateNode` "escapes" it
if (marker !== possibleAncestor) {
// we never got to `possibleAncestor`, therefore the initial `marker` "escapes" it
// it's in a different part of the tree so no states will be reentered for such an external transition
return [];
}
nodes.push(this);
nodes.push(possibleAncestor);
return nodes;
}

Expand Down
28 changes: 28 additions & 0 deletions packages/core/test/actions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,34 @@ describe('entry/exit actions', () => {
expect(stateA.actions.map((action) => action.type)).toEqual(['D2 Exit']);
});

it("should reenter targeted ancestor (as it's a descendant of the transition domain)", () => {
const actual: string[] = [];
const machine = createMachine({
initial: 'loaded',
states: {
loaded: {
id: 'loaded',
entry: () => actual.push('loaded entry'),
initial: 'idle',
states: {
idle: {
on: {
UPDATE: '#loaded'
}
}
}
}
}
});

const service = interpret(machine).start();

actual.length = 0;
service.send('UPDATE');

expect(actual).toEqual(['loaded entry']);
});

describe('should ignore same-parent state actions (sparse)', () => {
const fooBar = {
initial: 'foo',
Expand Down

0 comments on commit 88d540e

Please sign in to comment.