Skip to content

Commit

Permalink
feat: Add full support for async event handlers
Browse files Browse the repository at this point in the history
  • Loading branch information
tjtanjin committed Nov 3, 2024
1 parent 19b15c1 commit 371a9ee
Show file tree
Hide file tree
Showing 16 changed files with 68 additions and 63 deletions.
6 changes: 3 additions & 3 deletions __tests__/hooks/internal/useAudioInternal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ describe("useAudioInternal Hook", () => {
expect(result.current.audioToggledOn).toBe(!initialAudioToggledOn);
});

it("should prevent toggling when event is defaultPrevented", () => {
it("should prevent toggling when event is defaultPrevented", async () => {
// mocks rcb event handler
const callRcbEventMock = jest.fn().mockReturnValue({ defaultPrevented: true });
mockUseRcbEventInternal.mockReturnValue({
Expand All @@ -69,8 +69,8 @@ describe("useAudioInternal Hook", () => {
expect(result.current.audioToggledOn).toBe(initialAudioToggledOn);

// simulates clicking the toggle action
act(() => {
result.current.toggleAudio();
await act(async () => {
await result.current.toggleAudio();
});

// checks if callRcbEvent was called with rcb-toggle-audio and correct arguments
Expand Down
10 changes: 5 additions & 5 deletions __tests__/hooks/internal/useChatWindowInternal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ describe("useChatWindowInternal Hook", () => {
expect(result.current.isChatWindowOpen).toBe(!initialChatWindowOpen);
});

it("should prevent toggling when event is defaultPrevented", () => {
it("should prevent toggling when event is defaultPrevented", async () => {
// mocks rcb event handler
const callRcbEventMock = jest.fn().mockReturnValue({ defaultPrevented: true });
mockUseRcbEventInternal.mockReturnValue({
Expand All @@ -69,8 +69,8 @@ describe("useChatWindowInternal Hook", () => {
expect(result.current.isChatWindowOpen).toBe(initialChatWindowOpen);

// simulates clicking the toggle action
act(() => {
result.current.toggleChatWindow();
await act(async () => {
await result.current.toggleChatWindow();
});

// checks if callRcbEvent was called with rcb-toggle-chat-window and correct arguments
Expand Down Expand Up @@ -99,8 +99,8 @@ describe("useChatWindowInternal Hook", () => {
expect(result.current.isChatWindowOpen).toBe(initialChatWindowOpen);

// opens the chat window
act(() => {
result.current.openChat(true);
await act(async () => {
await result.current.openChat(true);
});

// checks if callRcbEvent was called with rcb-toggle-chat-window and correct arguments
Expand Down
6 changes: 3 additions & 3 deletions __tests__/hooks/internal/useNotificationsInternal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ describe("useNotificationsInternal Hook", () => {
expect(result.current.notificationsToggledOn).toBe(!initialNotificationsToggledOn);
});

it("should prevent toggling when event is defaultPrevented", () => {
it("should prevent toggling when event is defaultPrevented", async () => {
// mocks rcb event handler
const callRcbEventMock = jest.fn().mockReturnValue({ defaultPrevented: true });
mockUseRcbEventInternal.mockReturnValue({
Expand All @@ -69,8 +69,8 @@ describe("useNotificationsInternal Hook", () => {
expect(result.current.notificationsToggledOn).toBe(initialNotificationsToggledOn);

// simulates clicking the toggle action
act(() => {
result.current.toggleNotifications();
await act(async () => {
await result.current.toggleNotifications();
});

// checks if callRcbEvent was called with rcb-toggle-notifications and correct arguments
Expand Down
4 changes: 2 additions & 2 deletions __tests__/hooks/internal/usePathsInternal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ describe("usePathsInternal Hook", () => {
const { result } = renderHook(() => usePathsInternal());

await act(async () => {
const success = result.current.goToPath("newPath");
const success = await result.current.goToPath("newPath");
expect(success).toBe(true);
});

Expand Down Expand Up @@ -88,7 +88,7 @@ describe("usePathsInternal Hook", () => {
const { result } = renderHook(() => usePathsInternal());

await act(async () => {
const success = result.current.goToPath("blockedPath");
const success = await result.current.goToPath("blockedPath");
expect(success).toBe(false);
});

Expand Down
6 changes: 3 additions & 3 deletions __tests__/hooks/internal/useTextAreaInternal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ describe("useTextAreaInternal Hook", () => {
expect(result.current.getTextAreaValue().length).toBe(1000);
});

it("should prevent setting value if event is defaultPrevented", () => {
it("should prevent setting value if event is defaultPrevented", async () => {
const callRcbEventMock = jest.fn().mockReturnValue({ defaultPrevented: true });
mockUseRcbEventInternal.mockReturnValue({
callRcbEvent: callRcbEventMock,
Expand All @@ -136,8 +136,8 @@ describe("useTextAreaInternal Hook", () => {

const { result } = renderHook(() => useTextAreaInternal());

act(() => {
result.current.setTextAreaValue("Test value");
await act(async () => {
await result.current.setTextAreaValue("Test value");
});

expect(result.current.getTextAreaValue()).toBe("");
Expand Down
36 changes: 18 additions & 18 deletions __tests__/hooks/internal/useToastsInternal.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,14 @@ describe('useToastsInternal', () => {
jest.clearAllMocks();
});

it('should add a new toast when not exceeding maxCount', () => {
it('should add a new toast when not exceeding maxCount', async () => {
// Test adding a toast when maxCount is not reached
mockRcbEventInternal.callRcbEvent.mockReturnValue({ defaultPrevented: false,
data: { toast: { id: 'mocked-uuid', content: 'New toast content', timeout: undefined }
} });
const { result } = renderHook(() => useToastsInternal());
act(() => {
result.current.showToast('New toast content');
await act(async () => {
await result.current.showToast('New toast content');
});
expect(generateSecureUUID).toHaveBeenCalled();
expect(mockToastsContext.setToasts).toHaveBeenCalledWith(expect.any(Function));
Expand All @@ -81,16 +81,16 @@ describe('useToastsInternal', () => {
expect(newToasts).toEqual([{ id: 'mocked-uuid', content: 'New toast content', timeout: undefined }]);
});

it('should not add a new toast if maxCount is reached and forbidOnMax is true', () => {
it('should not add a new toast if maxCount is reached and forbidOnMax is true', async () => {
// Test forbidding new toast if maxCount is reached
mockToastsContext.toasts = [{ id: '1' }, { id: '2' }, { id: '3' }];
const { result } = renderHook(() => useToastsInternal());
const toastId = result.current.showToast('Toast content');
const toastId = await result.current.showToast('Toast content');
expect(toastId).toBeNull();
expect(mockToastsContext.setToasts).not.toHaveBeenCalled();
});

it('should remove the oldest toast and add a new one if maxCount is reached but forbidOnMax is false', () => {
it('should remove the oldest toast and add a new one if maxCount is reached but forbidOnMax is false', async () => {
// Test replacing oldest toast if maxCount reached and forbidOnMax is false
mockSettingsContext.settings.toast.forbidOnMax = false;
mockToastsContext.toasts = [{ id: '1' }, { id: '2' }, { id: '3' }];
Expand All @@ -99,8 +99,8 @@ describe('useToastsInternal', () => {
content: 'New toast content', timeout: undefined }
} });
const { result } = renderHook(() => useToastsInternal());
act(() => {
result.current.showToast('New toast content');
await act(async () => {
await result.current.showToast('New toast content');
});
expect(mockToastsContext.setToasts).toHaveBeenCalledWith(expect.any(Function));

Expand All @@ -114,14 +114,14 @@ describe('useToastsInternal', () => {
]);
});

it('should dismiss a toast by id', () => {
it('should dismiss a toast by id', async () => {
// Test dismissing a toast by ID
const toast = { id: 'toast-1', content: 'Toast to dismiss' };
mockToastsContext.toasts = [toast];
mockRcbEventInternal.callRcbEvent.mockReturnValue({ defaultPrevented: false });
const { result } = renderHook(() => useToastsInternal());
act(() => {
result.current.dismissToast('toast-1');
await act(async () => {
await result.current.dismissToast('toast-1');
});
expect(mockToastsContext.setToasts).toHaveBeenCalledWith(expect.any(Function));

Expand All @@ -131,32 +131,32 @@ describe('useToastsInternal', () => {
expect(updatedToasts).toEqual([]);
});

it('should not dismiss a toast if the id is not found', () => {
it('should not dismiss a toast if the id is not found', async () => {
// Test no dismissal if ID not found
mockToastsContext.toasts = [{ id: 'toast-2', content: 'Another toast' }];
const { result } = renderHook(() => useToastsInternal());
const resultId = result.current.dismissToast('invalid-id');
const resultId = await result.current.dismissToast('invalid-id');
expect(resultId).toBeNull();
expect(mockToastsContext.setToasts).not.toHaveBeenCalled();
});

it('should not show toast if rcbShowToast event is prevented', () => {
it('should not show toast if rcbShowToast event is prevented', async () => {
// Test prevention of toast display by event
mockRcbEventInternal.callRcbEvent.mockReturnValue({ defaultPrevented: true });
const { result } = renderHook(() => useToastsInternal());
const resultId = result.current.showToast('Prevented toast');
const resultId = await result.current.showToast('Prevented toast');
expect(resultId).toBeNull();
expect(mockToastsContext.setToasts).not.toHaveBeenCalled();
});

it('should call rcbDismissToast event when dismissing a toast', () => {
it('should call rcbDismissToast event when dismissing a toast', async () => {
// Test triggering of dismiss event upon toast removal
const toast = { id: 'toast-1', content: 'Toast to dismiss' };
mockToastsContext.toasts = [toast];
mockRcbEventInternal.callRcbEvent.mockReturnValue({ defaultPrevented: false });
const { result } = renderHook(() => useToastsInternal());
act(() => {
result.current.dismissToast('toast-1');
await act(async () => {
await result.current.dismissToast('toast-1');
});
expect(mockRcbEventInternal.callRcbEvent).toHaveBeenCalledWith(RcbEvent.DISMISS_TOAST, { toast });
});
Expand Down
16 changes: 8 additions & 8 deletions __tests__/services/RcbEventService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ describe('emitRcbEvent', () => {
});

// Test for emitting a cancellable event
it('should emit a cancellable event and return the event', () => {
it('should emit a cancellable event and return the event', async () => {
const eventName = RcbEvent.TOGGLE_AUDIO;
const eventDetail: EventDetail = {
botId: 'testBotId',
Expand All @@ -34,7 +34,7 @@ describe('emitRcbEvent', () => {
const data = { someData: 'testData' };

// Call emitRcbEvent and capture the result
const result = emitRcbEvent(eventName, eventDetail, data);
const result = await emitRcbEvent(eventName, eventDetail, data);

// Verify that the event was dispatched correctly
if (dispatchedEvent) {
Expand All @@ -49,7 +49,7 @@ describe('emitRcbEvent', () => {
});

// Test for emitting a non-cancellable event
it('should emit a non-cancellable event and return the event', () => {
it('should emit a non-cancellable event and return the event', async () => {
const eventName = RcbEvent.POST_INJECT_MESSAGE;
const eventDetail: EventDetail = {
botId: 'testBotId',
Expand All @@ -58,7 +58,7 @@ describe('emitRcbEvent', () => {
};
const data = { someData: 'testData' };

const result = emitRcbEvent(eventName, eventDetail, data);
const result = await emitRcbEvent(eventName, eventDetail, data);

// Verify that the event was dispatched as non-cancellable
if (dispatchedEvent) {
Expand All @@ -73,15 +73,15 @@ describe('emitRcbEvent', () => {
});

// Test for handling an event with empty data
it('should handle an event with empty data', () => {
it('should handle an event with empty data', async () => {
const eventName = RcbEvent.TOGGLE_VOICE;
const eventDetail: EventDetail = {
botId: 'testBotId',
currPath: 'testCurrentPath',
prevPath: 'testPreviousPath'
};

const result = emitRcbEvent(eventName, eventDetail, {});
const result = await emitRcbEvent(eventName, eventDetail, {});

// Verify the event was dispatched with empty data
if (dispatchedEvent) {
Expand All @@ -95,10 +95,10 @@ describe('emitRcbEvent', () => {
});

// Test for handling an event with no detail and empty data
it('should handle an event with no detail and data', () => {
it('should handle an event with no detail and data', async () => {
const eventName = RcbEvent.CHANGE_PATH;

const result = emitRcbEvent(eventName, {
const result = await emitRcbEvent(eventName, {
botId: null,
currPath: null,
prevPath: null
Expand Down
4 changes: 2 additions & 2 deletions src/components/Buttons/AudioButton/AudioButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@ const AudioButton = () => {
<div
aria-label={settings.ariaLabel?.audioButton ?? "toggle audio"}
role="button"
onMouseDown={(event: MouseEvent) => {
onMouseDown={async (event: MouseEvent) => {
event.preventDefault();
toggleAudio();
await toggleAudio();
}}
style={audioToggledOn
? styles.audioButtonStyle
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@ const NotificationButton = () => {
<div
aria-label={settings.ariaLabel?.notificationButton ?? "toggle notifications"}
role="button"
onMouseDown={(event: MouseEvent) => {
onMouseDown={async (event: MouseEvent) => {
event.preventDefault();
toggleNotifications();
await toggleNotifications();
}}
style={notificationsToggledOn
? styles.notificationButtonStyle
Expand Down
4 changes: 2 additions & 2 deletions src/components/Buttons/VoiceButton/VoiceButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,12 +126,12 @@ const VoiceButton = () => {
<div
aria-label={settings.ariaLabel?.voiceButton ?? "toggle voice"}
role="button"
onMouseDown={(event: MouseEvent) => {
onMouseDown={async (event: MouseEvent) => {
event.preventDefault();
if (textAreaDisabled) {
return;
}
toggleVoice();
await toggleVoice();
}}
style={voiceToggledOn && !textAreaDisabled
? styles.voiceButtonStyle
Expand Down
4 changes: 2 additions & 2 deletions src/components/ChatBotBody/ToastPrompt/ToastPrompt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,10 @@ const Toast = ({
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
style={isHovered ? toastPromptHoveredStyle : localizedStyles.toastPromptStyle}
onMouseDown={(event: MouseEvent) => {
onMouseDown={async (event: MouseEvent) => {
if (settings.toast?.dismissOnClick) {
event.preventDefault();
dismissToast(id);
await dismissToast(id);
}
}}
className="rcb-toast-prompt"
Expand Down
4 changes: 3 additions & 1 deletion src/hooks/internal/useAudioInternal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ export const useAudioInternal = () => {
const toggleAudio = useCallback(async () => {
// handles toggle audio event
if (settings.event?.rcbToggleAudio) {
const event = await callRcbEvent(RcbEvent.TOGGLE_AUDIO, {currState: audioToggledOn, newState: !audioToggledOn});
const event = await callRcbEvent(
RcbEvent.TOGGLE_AUDIO, {currState: audioToggledOn, newState: !audioToggledOn}
);
if (event.defaultPrevented) {
return;
}
Expand Down
4 changes: 2 additions & 2 deletions src/hooks/internal/useChatWindowInternal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,11 @@ export const useChatWindowInternal = () => {
*
* @param isOpen boolean indicating whether to open/close the chat window
*/
const openChat = useCallback((isOpen: boolean) => {
const openChat = useCallback(async (isOpen: boolean) => {
if (isChatWindowOpen === isOpen) {
return;
}
toggleChatWindow();
await toggleChatWindow();
}, [isChatWindowOpen]);

return {
Expand Down
3 changes: 2 additions & 1 deletion src/services/BlockService/TransitionProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import { Params } from "../../types/Params";
* @param setTimeoutId sets the timeout id for the transition attribute if it is interruptable
*/
export const processTransition = async (flow: Flow, path: keyof Flow, params: Params,
goToPath: (pathToGo: string) => Promise<boolean>, setTimeoutId: (timeoutId: ReturnType<typeof setTimeout>) => void) => {
goToPath: (pathToGo: string) => Promise<boolean>,
setTimeoutId: (timeoutId: ReturnType<typeof setTimeout>) => void) => {

const block = flow[path];

Expand Down
Loading

0 comments on commit 371a9ee

Please sign in to comment.