Skip to content

Commit

Permalink
Fixed an issue with invoke.onDone/invoke.onError actions being in…
Browse files Browse the repository at this point in the history
…correctly ignored (#388)

* Fixed an issue with `invoke.onDone`/`invoke.onError` actions being incorrectly ignored

* add test cases

* add changeset
  • Loading branch information
Andarist authored Oct 13, 2023
1 parent e2f3d9e commit 0562617
Show file tree
Hide file tree
Showing 7 changed files with 305 additions and 7 deletions.
6 changes: 6 additions & 0 deletions .changeset/cuddly-files-teach.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@xstate/tools-shared': patch
'@xstate/cli': patch
---

Fixed an issue that could result in loss of information about multiple actions within `invoke.onDone`/`invoke.onError` transitions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { createMachine } from 'xstate';

const machine = createMachine(
{
schema: {
services: {} as {
getData: {
data: unknown;
};
},
},
tsTypes: {} as import('./actor-ondone-multiple-actions.typegen').Typegen0,
predictableActionArguments: true,

initial: 'A',
context: {
value: '',
},
states: {
A: {
invoke: {
src: 'getData',
onDone: {
actions: ['actionA', 'actionB'],
target: 'DONE',
},
},
},
DONE: {},
},
},
{
actions: {
actionA: () => {},
actionB: () => {},
},
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { createMachine } from 'xstate';

const machine = createMachine(
{
schema: {
services: {} as {
getData: {
data: unknown;
};
},
},
tsTypes: {} as import('./actor-ondone-single-action.typegen').Typegen0,
predictableActionArguments: true,

initial: 'A',
context: {
value: '',
},
states: {
A: {
invoke: {
src: 'getData',
onDone: {
actions: 'actionA',
target: 'DONE',
},
},
},
DONE: {},
},
},
{
actions: {
actionA: () => {},
},
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { createMachine } from 'xstate';

const machine = createMachine(
{
schema: {
services: {} as {
getData: {
data: unknown;
};
},
},
tsTypes: {} as import('./actor-onerror-multiple-actions.typegen').Typegen0,
predictableActionArguments: true,

initial: 'A',
context: {
value: '',
},
states: {
A: {
invoke: {
src: 'getData',
onError: {
actions: ['actionA', 'actionB'],
target: 'ERROR',
},
},
},
ERROR: {},
},
},
{
actions: {
actionA: () => {},
actionB: () => {},
},
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { createMachine } from 'xstate';

const machine = createMachine(
{
schema: {
services: {} as {
getData: {
data: unknown;
};
},
},
tsTypes: {} as import('./actor-onerror-single-action.typegen').Typegen0,
predictableActionArguments: true,

initial: 'A',
context: {
value: '',
},
states: {
A: {
invoke: {
src: 'getData',
onError: {
actions: 'actionA',
target: 'ERROR',
},
},
},
ERROR: {},
},
},
{
actions: {
actionA: () => {},
},
},
);
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,150 @@ export interface Typegen0 {
"
`;

exports[`getTypegenOutput actor-ondone-multiple-actions 1`] = `
"// This file was automatically generated. Edits will be overwritten
export interface Typegen0 {
"@@xstate/typegen": true;
internalEvents: {
"done.invoke.(machine).A:invocation[0]": {
type: "done.invoke.(machine).A:invocation[0]";
data: unknown;
__tip: "See the XState TS docs to learn how to strongly type this.";
};
"xstate.init": { type: "xstate.init" };
};
invokeSrcNameMap: {
getData: "done.invoke.(machine).A:invocation[0]";
};
missingImplementations: {
actions: never;
delays: never;
guards: never;
services: "getData";
};
eventsCausingActions: {
actionA: "done.invoke.(machine).A:invocation[0]";
actionB: "done.invoke.(machine).A:invocation[0]";
};
eventsCausingDelays: {};
eventsCausingGuards: {};
eventsCausingServices: {
getData: "xstate.init";
};
matchesStates: "A" | "DONE";
tags: never;
}
"
`;

exports[`getTypegenOutput actor-ondone-single-action 1`] = `
"// This file was automatically generated. Edits will be overwritten
export interface Typegen0 {
"@@xstate/typegen": true;
internalEvents: {
"done.invoke.(machine).A:invocation[0]": {
type: "done.invoke.(machine).A:invocation[0]";
data: unknown;
__tip: "See the XState TS docs to learn how to strongly type this.";
};
"xstate.init": { type: "xstate.init" };
};
invokeSrcNameMap: {
getData: "done.invoke.(machine).A:invocation[0]";
};
missingImplementations: {
actions: never;
delays: never;
guards: never;
services: "getData";
};
eventsCausingActions: {
actionA: "done.invoke.(machine).A:invocation[0]";
};
eventsCausingDelays: {};
eventsCausingGuards: {};
eventsCausingServices: {
getData: "xstate.init";
};
matchesStates: "A" | "DONE";
tags: never;
}
"
`;

exports[`getTypegenOutput actor-onerror-multiple-actions 1`] = `
"// This file was automatically generated. Edits will be overwritten
export interface Typegen0 {
"@@xstate/typegen": true;
internalEvents: {
"error.platform.(machine).A:invocation[0]": {
type: "error.platform.(machine).A:invocation[0]";
data: unknown;
};
"xstate.init": { type: "xstate.init" };
};
invokeSrcNameMap: {
getData: "done.invoke.(machine).A:invocation[0]";
};
missingImplementations: {
actions: never;
delays: never;
guards: never;
services: "getData";
};
eventsCausingActions: {
actionA: "error.platform.(machine).A:invocation[0]";
actionB: "error.platform.(machine).A:invocation[0]";
};
eventsCausingDelays: {};
eventsCausingGuards: {};
eventsCausingServices: {
getData: "xstate.init";
};
matchesStates: "A" | "ERROR";
tags: never;
}
"
`;

exports[`getTypegenOutput actor-onerror-single-action 1`] = `
"// This file was automatically generated. Edits will be overwritten
export interface Typegen0 {
"@@xstate/typegen": true;
internalEvents: {
"error.platform.(machine).A:invocation[0]": {
type: "error.platform.(machine).A:invocation[0]";
data: unknown;
};
"xstate.init": { type: "xstate.init" };
};
invokeSrcNameMap: {
getData: "done.invoke.(machine).A:invocation[0]";
};
missingImplementations: {
actions: never;
delays: never;
guards: never;
services: "getData";
};
eventsCausingActions: {
actionA: "error.platform.(machine).A:invocation[0]";
};
eventsCausingDelays: {};
eventsCausingGuards: {};
eventsCausingServices: {
getData: "xstate.init";
};
matchesStates: "A" | "ERROR";
tags: never;
}
"
`;

exports[`getTypegenOutput after-numeric-like-delay 1`] = `
"// This file was automatically generated. Edits will be overwritten
Expand Down
12 changes: 5 additions & 7 deletions packages/shared/src/forEachAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,11 +155,10 @@ const forEachActionRecur = (
replaceOrDeleteActions(doneTransition, 'actions', visitor);
}
}
}
/**
* invoke.onError transitions
*/
if (event.startsWith('error.invoke')) {
} else if (event.startsWith('error.invoke')) {
/**
* invoke.onError transitions
*/
const index = parseInt(
event.replace(/error\.invoke\..+:invocation\[(\d+)\]/, '$1'),
10,
Expand Down Expand Up @@ -189,9 +188,8 @@ const forEachActionRecur = (
}
}
}

// Guarded transitions
if (Array.isArray(tr)) {
else if (Array.isArray(tr)) {
tr.forEach((group) => {
replaceOrDeleteActions(group, 'actions', visitor);
});
Expand Down

0 comments on commit 0562617

Please sign in to comment.