diff --git a/.changeset/fep-2636-resume-without-queued-events.md b/.changeset/fep-2636-resume-without-queued-events.md new file mode 100644 index 000000000..7ccf6d50d --- /dev/null +++ b/.changeset/fep-2636-resume-without-queued-events.md @@ -0,0 +1,5 @@ +--- +"@stackflow/core": patch +--- + +Fix `resume()` leaving a Stack paused when no navigation event was queued after `pause()`. diff --git a/core/src/activity-utils/makeStackReducer.ts b/core/src/activity-utils/makeStackReducer.ts index 5123890cd..053f61abb 100644 --- a/core/src/activity-utils/makeStackReducer.ts +++ b/core/src/activity-utils/makeStackReducer.ts @@ -128,7 +128,7 @@ export function makeStackReducer(context: { now: number; resumedAt?: number }) { ), Resumed: withActivitiesReducer( (stack: Stack, event: ResumedEvent): Stack => { - if (stack.globalTransitionState !== "paused" || !stack.pausedEvents) { + if (stack.globalTransitionState !== "paused") { return { ...stack, events: [...stack.events, event] }; } @@ -137,10 +137,16 @@ export function makeStackReducer(context: { now: number; resumedAt?: number }) { resumedAt: event.eventDate, }); - const { pausedEvents, ...rest } = stack; + const { pausedEvents = [], ...rest } = stack; const resumedStack = pausedEvents.reduce(reducer, { ...rest, - globalTransitionState: "idle", + globalTransitionState: rest.activities.some( + (activity) => + activity.transitionState === "enter-active" || + activity.transitionState === "exit-active", + ) + ? "loading" + : "idle", }); return { diff --git a/core/src/aggregate.spec.ts b/core/src/aggregate.spec.ts index ec5c0e869..d2e33c56c 100644 --- a/core/src/aggregate.spec.ts +++ b/core/src/aggregate.spec.ts @@ -4132,6 +4132,35 @@ test("aggregate - Pause되면 이벤트가 반영되지 않고, globalTransition }); }); +test("aggregate - queued event 없이 Resumed 되어도 paused 상태를 해제합니다", () => { + const events = [ + initializedEvent({ + transitionDuration: 300, + }), + registeredEvent({ + activityName: "a", + }), + makeEvent("Pushed", { + activityId: "activity-1", + activityName: "a", + eventDate: enoughPastTime(), + activityParams: {}, + }), + makeEvent("Paused", { + eventDate: enoughPastTime(), + }), + makeEvent("Resumed", { + eventDate: enoughPastTime(), + }), + ]; + + const output = aggregate(events, nowTime()); + + expect(output.globalTransitionState).toBe("idle"); + expect(output.pausedEvents).toBeUndefined(); + expect(output.events).toStrictEqual(events); +}); + test("aggregate - Resumed 되면 해당 시간 이후로 Transition이 정상작동합니다", () => { let pushedEvent1: PushedEvent; let pushedEvent2: PushedEvent; diff --git a/core/src/makeCoreStore.spec.ts b/core/src/makeCoreStore.spec.ts index 83603243a..085baca7d 100644 --- a/core/src/makeCoreStore.spec.ts +++ b/core/src/makeCoreStore.spec.ts @@ -281,3 +281,51 @@ test("makeCoreStore - subscribe에 등록한 이후에 아무 Event가 없는 expect(listener1).toHaveBeenCalledTimes(0); }); + +test("makeCoreStore - queued event 없이 resume해도 Stack이 재개됩니다", () => { + const onResumed = jest.fn(); + + const { actions, pullEvents } = makeCoreStore({ + initialEvents: [ + makeEvent("Initialized", { + transitionDuration: 150, + eventDate: enoughPastTime(), + }), + makeEvent("ActivityRegistered", { + activityName: "hello", + eventDate: enoughPastTime(), + }), + makeEvent("Pushed", { + activityId: "a1", + activityName: "hello", + activityParams: {}, + eventDate: enoughPastTime(), + }), + ], + plugins: [ + () => ({ + key: "test", + onResumed, + }), + ], + }); + + actions.pause(); + + expect(actions.getStack().globalTransitionState).toBe("paused"); + expect(actions.getStack().pausedEvents).toBeUndefined(); + + actions.resume(); + + expect(actions.getStack().globalTransitionState).toBe("idle"); + expect(onResumed).toHaveBeenCalledTimes(1); + expect(last(pullEvents())?.name).toBe("Resumed"); + + actions.push({ + activityId: "a2", + activityName: "hello", + activityParams: {}, + }); + + expect(last(actions.getStack().activities)?.id).toBe("a2"); +});