From c111273365361f68ddb12938baf2ffaddf79e423 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Thu, 7 Dec 2023 17:49:49 +0100 Subject: [PATCH] Notify the `error` listener when subscribing to an errored actor (#4570) --- .changeset/rich-ears-grow.md | 5 +++ packages/core/src/interpreter.ts | 25 ++++++++++-- packages/core/test/interpreter.test.ts | 53 ++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 .changeset/rich-ears-grow.md diff --git a/.changeset/rich-ears-grow.md b/.changeset/rich-ears-grow.md new file mode 100644 index 0000000000..7ed2adbe7d --- /dev/null +++ b/.changeset/rich-ears-grow.md @@ -0,0 +1,5 @@ +--- +'xstate': patch +--- + +Fixed an issue that caused a `complete` listener to be called instead of the `error` one when the actor was subscribed after being stopped. diff --git a/packages/core/src/interpreter.ts b/packages/core/src/interpreter.ts index a1138b703b..46728387ba 100644 --- a/packages/core/src/interpreter.ts +++ b/packages/core/src/interpreter.ts @@ -365,10 +365,27 @@ export class Actor if (this._processingStatus !== ProcessingStatus.Stopped) { this.observers.add(observer); } else { - try { - observer.complete?.(); - } catch (err) { - reportUnhandledError(err); + switch ((this._snapshot as any).status) { + case 'done': + try { + observer.complete?.(); + } catch (err) { + reportUnhandledError(err); + } + break; + case 'error': { + const err = (this._snapshot as any).error; + if (!observer.error) { + reportUnhandledError(err); + } else { + try { + observer.error(err); + } catch (err) { + reportUnhandledError(err); + } + } + break; + } } } diff --git a/packages/core/test/interpreter.test.ts b/packages/core/test/interpreter.test.ts index 593eb87528..e66da8a123 100644 --- a/packages/core/test/interpreter.test.ts +++ b/packages/core/test/interpreter.test.ts @@ -1849,3 +1849,56 @@ it('should not process events sent directly to own actor ref before initial entr 'EV transition' ]); }); + +it('should not notify the completion observer for an active logic when it gets subscribed before starting', () => { + const spy = jest.fn(); + + const machine = createMachine({}); + createActor(machine).subscribe({ complete: spy }); + + expect(spy).not.toHaveBeenCalled(); +}); + +it('should not notify the completion observer for an errored logic when it gets subscribed after it errors', () => { + const spy = jest.fn(); + + const machine = createMachine({ + entry: () => { + throw new Error('error'); + } + }); + const actorRef = createActor(machine); + actorRef.subscribe({ error: () => {} }); + actorRef.start(); + + actorRef.subscribe({ + complete: spy + }); + + expect(spy).not.toHaveBeenCalled(); +}); + +it('should notify the error observer for an errored logic when it gets subscribed after it errors', () => { + const spy = jest.fn(); + + const machine = createMachine({ + entry: () => { + throw new Error('error'); + } + }); + const actorRef = createActor(machine); + actorRef.subscribe({ error: () => {} }); + actorRef.start(); + + actorRef.subscribe({ + error: spy + }); + + expect(spy).toMatchMockCallsInlineSnapshot(` + [ + [ + [Error: error], + ], + ] + `); +});