diff --git a/src/index.test.ts b/src/index.test.ts index bd75450..2d3a463 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -1,5 +1,5 @@ -import { describe, test, expect, vi } from "vitest"; -import { fork, allSettled } from "effector"; +import { describe, test, expect, vi, beforeEach, afterEach } from "vitest"; +import { fork, allSettled, createEffect, attach } from "effector"; import { buttonClicked } from "../demo-app/counter/model"; @@ -15,6 +15,10 @@ vi.stubGlobal("__REDUX_DEVTOOLS_EXTENSION__", { }); describe("Redux DevTools Effector adapter", () => { + afterEach(() => { + init.mockClear(); + send.mockClear(); + }); test("should work", async () => { const scope = fork(); @@ -2048,4 +2052,73 @@ describe("Redux DevTools Effector adapter", () => { ] `); }); + + test("attached effect shown correctly", async () => { + const rootFx = createEffect(() => { + // ok + }); + + const attachedFx = attach({ + effect: rootFx, + }); + + const scope = fork(); + + attachReduxDevTools({ + name: "Attached fx", + scope, + batch: false, + }); + + await allSettled(attachedFx, { scope }); + + expect(send.mock.calls).toMatchInlineSnapshot(` + [ + [ + { + "id": "114", + "loc": { + "column": 23, + "file": "/src/index.test.ts", + "line": 2061, + }, + "params": undefined, + "type": "☄️ [effect] attachedFx", + }, + {}, + ], + [ + { + "id": "91", + "loc": { + "column": 19, + "file": "/src/index.test.ts", + "line": 2057, + }, + "params": undefined, + "type": "☄️ [effect] rootFx", + }, + {}, + ], + [ + { + "id": "92", + "params": undefined, + "result": undefined, + "type": "✅ [effect] rootFx.done", + }, + {}, + ], + [ + { + "id": "115", + "params": undefined, + "result": undefined, + "type": "✅ [effect] attachedFx.done", + }, + {}, + ], + ] + `); + }); }); diff --git a/src/index.ts b/src/index.ts index cabd4b4..dad6e56 100644 --- a/src/index.ts +++ b/src/index.ts @@ -97,8 +97,21 @@ export function attachReduxDevTools({ }, }); + // handling of buttons + const unsub = controller?.subscribe?.((message: any) => { + if (message?.payload?.type === "COMMIT") { + /** + * Committing the state to the devtools, + * so it is possible to cleanup all the logs + */ + controller.init(state); + return; + } + }); + return () => { uninspect(); + unsub?.(); }; } @@ -122,8 +135,6 @@ function getInstanceName(name?: string): string { } // reporting -const fxIdMap = new Map(); - function createReporter(state: ReturnType) { return (m: Message): Record | void => { // errors @@ -139,8 +150,7 @@ function createReporter(state: ReturnType) { // effects if (isEffectCall(m)) { - const name = getName(m); - fxIdMap.set(m.stack.fxID, name); + saveEffectCall(m); return { type: `☄️ [effect] ${m.name || "unknown"}`, params: m.value, @@ -152,8 +162,7 @@ function createReporter(state: ReturnType) { } if (isEffectFinally(m)) { - const name = fxIdMap.get(m.stack.fxID)!; - fxIdMap.delete(m.stack.fxID); + const name = getParentEffectName(m); if ((m.value as any).status === "done") { return { @@ -253,10 +262,45 @@ function isForward(m: Message) { return m.kind === "forward"; } +// effects tracking +const fxIdMap = new Map(); + +function getCallsById(id: unknown) { + if (!fxIdMap.has(id)) { + fxIdMap.set(id, []); + } + return fxIdMap.get(id)!; +} + +function saveEffectCall(m: Message) { + const name = getName(m); + const callId = getEffectCallId(m); + const calls = getCallsById(callId); + calls.push(name); +} + +function getParentEffectName(m: Message) { + const callId = getEffectCallId(m); + const calls = getCallsById(callId); + const name = calls.pop(); + + if (calls.length === 0) { + fxIdMap.delete(callId); + } + + return name; +} + // util function isEffectorInternal(m: Message) { return !!m.meta.named; } +function getEffectCallId(m: Message) { + if (!isEffectCall(m)) return null; + if (!isEffectFinally(m)) return null; + + return m.stack.fxID; +} function getName(m: Message) { return m.name || locToString(m.loc) || `unknown_${m.id}`; }