Skip to content

Commit

Permalink
fix(nova/react): fix eventing for multiple nova event interceptors (#132
Browse files Browse the repository at this point in the history
)

* fix eventing pointer for multiple interceptors

* Change files

* apply suggestions

* add comment to README

* move creation of internaleventingcontext to function

* remove console logs

* udpate comment
  • Loading branch information
carmenberndt authored Jan 15, 2025
1 parent 6dfd060 commit 7eac482
Show file tree
Hide file tree
Showing 4 changed files with 287 additions and 60 deletions.
7 changes: 7 additions & 0 deletions change/@nova-react-198378aa-bbb8-4a24-8ce3-2d0718002e1e.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "fix eventing pointer for multiple interceptors",
"packageName": "@nova/react",
"email": "[email protected]",
"dependentChangeType": "patch"
}
2 changes: 2 additions & 0 deletions packages/nova-react/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,3 +195,5 @@ const MyComponentWrapper = () => {
```

The `NovaEventingInterceptor` will intercept the event and if you can check it's properties to decide if is should be acted upon. If from `intercept` promise resolving to undefined is returned the event will not be passed to eventing higher up the tree. However, if to process the event further, one should return a promise resolving to the `eventWrapper` object. That also gives a possibility to alter the event and still pass it further up.

You can nest as many interceptors as you need to either handle or pass events further up.
142 changes: 142 additions & 0 deletions packages/nova-react/src/eventing/nova-eventing-provider.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -533,3 +533,145 @@ describe("NovaEventingInterceptor", () => {
expect(bubbleCall.event.data()).toBe("addedData");
});
});

describe("Multiple NovaEventingInterceptors", () => {
const originalError = console.error;
beforeEach(() => {
jest.clearAllMocks();
console.error = originalError;
});
const realMapper = jest.requireActual(
"./react-event-source-mapper",
).mapEventMetadata;

const mapEventMetadataMock = jest.fn().mockImplementation(realMapper);

const bubbleMock = jest.fn();

const parentEventing = {
bubble: bubbleMock,
generateEvent: bubbleMock,
} as unknown as NovaEventing;

const callbackToBeCalledOnFirstIntercept = jest.fn();
const callbackToBeCalledOnSecondIntercept = jest.fn();

const evenOriginatorToBeInterceptedFirst = "toBeInterceptedFirst";
const evenOriginatorToBeInterceptedSecond = "toBeInterceptedSecond";

const firstInterceptor = (eventWrapper: EventWrapper) => {
if (eventWrapper.event.originator === evenOriginatorToBeInterceptedFirst) {
callbackToBeCalledOnFirstIntercept();
return Promise.resolve(undefined);
} else {
return Promise.resolve(eventWrapper);
}
};

const secondInterceptor = (eventWrapper: EventWrapper) => {
if (eventWrapper.event.originator === evenOriginatorToBeInterceptedSecond) {
callbackToBeCalledOnSecondIntercept();
return Promise.resolve(undefined);
} else {
return Promise.resolve(eventWrapper);
}
};

const ComponentWithTwoEvents = ({
name,
eventOriginatorToBeIntercepted,
}: {
name: string;
eventOriginatorToBeIntercepted: string;
}) => {
const eventing = useNovaEventing();
const onInterceptClick = (event: React.SyntheticEvent) => {
eventing.bubble({
reactEvent: event,
event: {
originator: eventOriginatorToBeIntercepted,
type: "TypeToBeIntercepted",
},
});
};

const onNonInterceptClick = (event: React.SyntheticEvent) => {
eventing.bubble({
reactEvent: event,
event: {
originator: "notToBeIntercepted",
type: "TypeNotToBeIntercepted",
},
});
};
return (
<>
Component with two events
<button onClick={onInterceptClick}>
{name}: Fire event to be intercepted
</button>
<button onClick={onNonInterceptClick}>
{name}: Fire event without intercept
</button>
</>
);
};

const MultipleInterceptorsTestComponent: React.FC = () => (
<NovaEventingProvider
eventing={parentEventing}
reactEventMapper={mapEventMetadataMock}
>
<NovaEventingInterceptor interceptor={firstInterceptor}>
<NovaEventingInterceptor interceptor={secondInterceptor}>
<ComponentWithTwoEvents
name="toBeInterceptedInFirstInterceptor"
eventOriginatorToBeIntercepted={evenOriginatorToBeInterceptedFirst}
/>
<ComponentWithTwoEvents
name="toBeInterceptedInSecondInterceptor"
eventOriginatorToBeIntercepted={evenOriginatorToBeInterceptedSecond}
/>
</NovaEventingInterceptor>
</NovaEventingInterceptor>
</NovaEventingProvider>
);

it("intercepts the event in second interceptor and does not bubble it up", async () => {
const { getByText } = render(<MultipleInterceptorsTestComponent />);
const button = getByText(
"toBeInterceptedInSecondInterceptor: Fire event to be intercepted",
);
button.click();

await waitFor(() => expect(mapEventMetadataMock).toHaveBeenCalled());
expect(callbackToBeCalledOnSecondIntercept).toHaveBeenCalled();
expect(bubbleMock).not.toHaveBeenCalled();
expect(callbackToBeCalledOnFirstIntercept).not.toHaveBeenCalled();
});

it("bubbles the event when interceptor returns the event", async () => {
const { getByText } = render(<MultipleInterceptorsTestComponent />);
const button = getByText(
"toBeInterceptedInFirstInterceptor: Fire event without intercept",
);
button.click();

await waitFor(() => expect(bubbleMock).toHaveBeenCalled());
expect(callbackToBeCalledOnSecondIntercept).not.toHaveBeenCalled();
expect(callbackToBeCalledOnFirstIntercept).not.toHaveBeenCalled();
});

it("intercepts the event in first interceptor and does not bubble it up", async () => {
const { getByText } = render(<MultipleInterceptorsTestComponent />);
const button = getByText(
"toBeInterceptedInFirstInterceptor: Fire event to be intercepted",
);
button.click();

await waitFor(() => expect(bubbleMock).not.toHaveBeenCalled());
await waitFor(() => expect(mapEventMetadataMock).toHaveBeenCalled());
expect(callbackToBeCalledOnSecondIntercept).not.toHaveBeenCalled();
expect(callbackToBeCalledOnFirstIntercept).toHaveBeenCalled();
});
});
Loading

0 comments on commit 7eac482

Please sign in to comment.