From 57aafed7892df5e5e2dfed88c54f21891583fb98 Mon Sep 17 00:00:00 2001 From: Asko Nõmm Date: Mon, 21 Apr 2025 14:55:16 +0300 Subject: Fixes #2 --- src/shapex.test.ts | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/shapex.ts | 44 ++++++++++++++++++++++---------------------- 2 files changed, 76 insertions(+), 22 deletions(-) diff --git a/src/shapex.test.ts b/src/shapex.test.ts index 55e38ae..2acaa29 100644 --- a/src/shapex.test.ts +++ b/src/shapex.test.ts @@ -170,6 +170,60 @@ describe("dispatch", () => { args: [{ counter: 1 }, "arg-value"], }); }); + + it("supports different data types for event callback and dispatch", () => { + type AppState = { + counter: number; + }; + + type ParentEventData = { + id: number; + }; + + type ChildEventData = { + message: string; + }; + + const $ = ShapeX({ counter: 1 }); + + // This callback receives ChildEventData + const childEventCb: EventCallback = ( + state, + data + ) => ({ + state: data ? { ...state, counter: data.message.length } : state, + }); + + const spyChildCb = spy(childEventCb); + + $.subscribe("child-event", spyChildCb); + + // This callback receives ParentEventData but dispatches ChildEventData + const parentEventCb: EventCallback< + AppState, + ParentEventData, + ChildEventData + > = (state, data) => ({ + state, + dispatch: { + to: "child-event", + with: { message: `ID ${data?.id ?? 0} processed` }, + }, + }); + + $.subscribe("parent-event", parentEventCb); + + // Dispatch with parent event data + $.dispatch("parent-event", { id: 123 }); + + // Child event should be called with the child event data + assertSpyCall(spyChildCb, 0, { + args: [{ counter: 1 }, { message: "ID 123 processed" }], + }); + + // State should be updated based on the message length + assertEquals($.state().counter, 16); + }); }); describe("state change detection", () => { diff --git a/src/shapex.ts b/src/shapex.ts index 0366934..f15916d 100644 --- a/src/shapex.ts +++ b/src/shapex.ts @@ -12,11 +12,11 @@ export type SubscriptionResponseDispatch = { * if you want to update state, and/or optionally also any events you * might want to dispatch. */ -export type SubscriptionResponse = { +export type SubscriptionResponse = { state?: T; dispatch?: - | SubscriptionResponseDispatch - | SubscriptionResponseDispatch[]; + | SubscriptionResponseDispatch + | SubscriptionResponseDispatch[]; }; const isSubscriptionResponseList = ( @@ -27,14 +27,14 @@ const isSubscriptionResponseList = ( * A callback passed to subcriptions, called when the event * that the subscription is listening to is called. */ -export type EventCallback = ( +export type EventCallback = ( state: T, data?: W -) => SubscriptionResponse; +) => SubscriptionResponse; -type Subscription = { +type Subscription = { listener: string; - callback: EventCallback; + callback: EventCallback; once: boolean; }; @@ -45,16 +45,16 @@ export type ShapeXInstance = { /** * Subcribe to an event. */ - subscribe: ( + subscribe: ( listener: string, - callback: EventCallback + callback: EventCallback ) => number; /** * Subscribe to an event once. */ - subscribeOnce: ( + subscribeOnce: ( listener: string, - callback: EventCallback + callback: EventCallback ) => number; /** @@ -95,7 +95,7 @@ export default function ShapeX( let _state = initialState; const _subscriptions: Map< string, - Array> + Array> > = new Map(); let subscriptionId = 0; @@ -103,12 +103,12 @@ export default function ShapeX( * Subcribe to an event. * * @param {string} listener - * @param {EventCallback} callback + * @param {EventCallback} callback * @returns */ - const subscribe = ( + const subscribe = ( listener: string, - callback: EventCallback + callback: EventCallback ): number => { if (!_subscriptions.has(listener)) { _subscriptions.set(listener, []); @@ -118,7 +118,7 @@ export default function ShapeX( if (subscriptions) { subscriptions.push({ listener, - callback: callback as unknown as EventCallback, + callback: callback as unknown as EventCallback, once: false, }); } @@ -130,12 +130,12 @@ export default function ShapeX( * Subcribe to an event, once. * * @param {string} listener - * @param {EventCallback} callback + * @param {EventCallback} callback * @returns */ - const subscribeOnce = ( + const subscribeOnce = ( listener: string, - callback: EventCallback + callback: EventCallback ): number => { if (!_subscriptions.has(listener)) { _subscriptions.set(listener, []); @@ -145,7 +145,7 @@ export default function ShapeX( if (subscriptions) { subscriptions.push({ listener, - callback: callback as unknown as EventCallback, + callback: callback as unknown as EventCallback, once: true, }); } @@ -236,11 +236,11 @@ export default function ShapeX( } const scopedSubsriptions = _subscriptions.get(to) ?? []; - const remainingSubscriptions = [] as Array>; + const remainingSubscriptions = [] as Array>; let callbackCount = 0; for (const subscription of scopedSubsriptions) { - const callback = subscription.callback as unknown as EventCallback; + const callback = subscription.callback as unknown as EventCallback; const response = withData ? callback(_state, withData) : callback(_state); // Updates state, and checks for state changes, and if any changes present, -- cgit v1.2.3