Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ISSUE-282 keep history messages in sync with storage #283

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
196 changes: 104 additions & 92 deletions __tests__/hooks/internal/useFlowInternal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,106 +7,118 @@ import { usePathsContext } from "../../../src/context/PathsContext";
import { useToastsContext } from "../../../src/context/ToastsContext";
import { useBotStatesContext } from "../../../src/context/BotStatesContext";
import { useBotRefsContext } from "../../../src/context/BotRefsContext";
import { useSettingsContext } from "../../../src/context/SettingsContext";
import { setHistoryStorageValues } from "../../../src/services/ChatHistoryService";

jest.mock("../../../src/context/MessagesContext");
jest.mock("../../../src/context/PathsContext");
jest.mock("../../../src/context/ToastsContext");
jest.mock("../../../src/context/BotStatesContext");
jest.mock("../../../src/context/BotRefsContext");
jest.mock("../../../src/context/SettingsContext");
jest.mock("../../../src/services/ChatHistoryService");

describe("useFlowInternal Hook", () => {
const setMessagesMock = jest.fn();
const setPathsMock = jest.fn();
const setPathsMock = jest.fn();
const setToastsMock = jest.fn();
const flowRefMock = { current: { id: "test-flow" } };
const hasFlowStartedMock = true;

beforeEach(() => {
jest.clearAllMocks();


(useMessagesContext as jest.Mock).mockReturnValue({ setMessages: setMessagesMock });
(usePathsContext as jest.Mock).mockReturnValue({ setPaths: setPathsMock });
(useToastsContext as jest.Mock).mockReturnValue({ setToasts: setToastsMock });
(useBotRefsContext as jest.Mock).mockReturnValue({ flowRef: flowRefMock });
(useBotStatesContext as jest.Mock).mockReturnValue({ hasFlowStarted: hasFlowStartedMock });
});


// Test to ensure initial values (hasFlowStarted and flowRef) are returned correctly from the hook
it("should return initial values from context", () => {
const { result } = renderHook(() => useFlowInternal());

expect(result.current.hasFlowStarted).toBe(true);
expect(result.current.getFlow()).toEqual({ id: "test-flow" });
});

// Test to ensure that restartFlow clears messages, toasts, and resets paths
it("should restart the flow by clearing messages, toasts, and resetting paths", () => {
const { result } = renderHook(() => useFlowInternal());

act(() => {
result.current.restartFlow();
});

expect(setMessagesMock).toHaveBeenCalledWith([]);
expect(setToastsMock).toHaveBeenCalledWith([]);
expect(setPathsMock).toHaveBeenCalledWith(["start"]);
});

// Test to ensure that getFlow returns the current flow from flowRef
it("should get the current flow from flowRef", () => {
const { result } = renderHook(() => useFlowInternal());

const flow = result.current.getFlow();
expect(flow).toEqual({ id: "test-flow" });
});

// Test to ensure that calling restartFlow multiple times works correctly
it("should handle multiple restarts correctly", () => {
const { result } = renderHook(() => useFlowInternal());

act(() => {
result.current.restartFlow();
});

act(() => {
result.current.restartFlow();
});

expect(setMessagesMock).toHaveBeenCalledTimes(2);
expect(setToastsMock).toHaveBeenCalledTimes(2);
expect(setPathsMock).toHaveBeenCalledTimes(2);
});

// Test to check flow state when hasFlowStarted is false
it("should correctly reflect flow state when it hasn't started", () => {
(useBotStatesContext as jest.Mock).mockReturnValue({ hasFlowStarted: false });
const { result } = renderHook(() => useFlowInternal());

expect(result.current.hasFlowStarted).toBe(false);
});

// Test to ensure messages, toasts, and paths are initialized correctly when restarting the flow
it("should initialize messages, toasts, and paths correctly", () => {
const { result } = renderHook(() => useFlowInternal());

act(() => {
result.current.restartFlow();
});

expect(setMessagesMock).toHaveBeenCalledWith([]);
expect(setToastsMock).toHaveBeenCalledWith([]);
expect(setPathsMock).toHaveBeenCalledWith(["start"]);
});

// Test to check that getFlow returns different flowRef values correctly
it("should handle different flowRef values", () => {
const differentFlowRefMock = { current: { id: "different-flow" } };
(useBotRefsContext as jest.Mock).mockReturnValue({ flowRef: differentFlowRefMock });
const { result } = renderHook(() => useFlowInternal());

const flow = result.current.getFlow();
expect(flow).toEqual({ id: "different-flow" });
});
const flowRefMock = { current: { id: "test-flow" } };
const hasFlowStartedMock = true;
const mockSettings = {
chatHistory: {
storageType: "localStorage",
storageKey: "rcb-history",
},
};

beforeEach(() => {
jest.clearAllMocks();

(useMessagesContext as jest.Mock).mockReturnValue({ setMessages: setMessagesMock });
(usePathsContext as jest.Mock).mockReturnValue({ setPaths: setPathsMock });
(useToastsContext as jest.Mock).mockReturnValue({ setToasts: setToastsMock });
(useBotRefsContext as jest.Mock).mockReturnValue({ flowRef: flowRefMock });
(useBotStatesContext as jest.Mock).mockReturnValue({ hasFlowStarted: hasFlowStartedMock });
(useSettingsContext as jest.Mock).mockReturnValue({ settings: mockSettings });
});


// Test to ensure initial values (hasFlowStarted and flowRef) are returned correctly from the hook
it("should return initial values from context", () => {
const { result } = renderHook(() => useFlowInternal());

expect(result.current.hasFlowStarted).toBe(true);
expect(result.current.getFlow()).toEqual({ id: "test-flow" });
});

// Test to ensure that restartFlow clears messages, toasts, and resets paths
it("should restart the flow by clearing messages, toasts, resetting paths and loading history", () => {
const { result } = renderHook(() => useFlowInternal());

act(() => {
result.current.restartFlow();
});

expect(setMessagesMock).toHaveBeenCalledWith([]);
expect(setToastsMock).toHaveBeenCalledWith([]);
expect(setPathsMock).toHaveBeenCalledWith(["start"]);
expect(setHistoryStorageValues)
.toHaveBeenCalledWith({ chatHistory: { storageType: "localStorage", storageKey: "rcb-history" } });
});

// Test to ensure that getFlow returns the current flow from flowRef
it("should get the current flow from flowRef", () => {
const { result } = renderHook(() => useFlowInternal());

const flow = result.current.getFlow();
expect(flow).toEqual({ id: "test-flow" });
});

// Test to ensure that calling restartFlow multiple times works correctly
it("should handle multiple restarts correctly", () => {
const { result } = renderHook(() => useFlowInternal());

act(() => {
result.current.restartFlow();
});

act(() => {
result.current.restartFlow();
});

expect(setMessagesMock).toHaveBeenCalledTimes(2);
expect(setToastsMock).toHaveBeenCalledTimes(2);
expect(setPathsMock).toHaveBeenCalledTimes(2);
});

// Test to check flow state when hasFlowStarted is false
it("should correctly reflect flow state when it hasn't started", () => {
(useBotStatesContext as jest.Mock).mockReturnValue({ hasFlowStarted: false });
const { result } = renderHook(() => useFlowInternal());

expect(result.current.hasFlowStarted).toBe(false);
});

// Test to ensure messages, toasts, and paths are initialized correctly when restarting the flow
it("should initialize messages, toasts, and paths correctly", () => {
const { result } = renderHook(() => useFlowInternal());

act(() => {
result.current.restartFlow();
});

expect(setMessagesMock).toHaveBeenCalledWith([]);
expect(setToastsMock).toHaveBeenCalledWith([]);
expect(setPathsMock).toHaveBeenCalledWith(["start"]);
});

// Test to check that getFlow returns different flowRef values correctly
it("should handle different flowRef values", () => {
const differentFlowRefMock = { current: { id: "different-flow" } };
(useBotRefsContext as jest.Mock).mockReturnValue({ flowRef: differentFlowRefMock });
const { result } = renderHook(() => useFlowInternal());

const flow = result.current.getFlow();
expect(flow).toEqual({ id: "different-flow" });
});
});
2 changes: 1 addition & 1 deletion __tests__/services/ChatHistoryService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ describe("ChatHistoryService", () => {
it("should save history if not disabled", async () => {
setHistoryStorageValues(mockSettings(storageType));
await saveChatHistory([mockMessage]);

expect(storage.setItem).toHaveBeenCalled();
});

Expand Down
22 changes: 15 additions & 7 deletions src/hooks/internal/useFlowInternal.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { useCallback } from "react";
import {useCallback} from "react";

import { useMessagesInternal } from "./useMessagesInternal";
import { usePathsInternal } from "./usePathsInternal";
import { useToastsInternal } from "./useToastsInternal";
import { useBotRefsContext } from "../../context/BotRefsContext";
import { useBotStatesContext } from "../../context/BotStatesContext";
import {useMessagesInternal} from "./useMessagesInternal";
import {usePathsInternal} from "./usePathsInternal";
import {useToastsInternal} from "./useToastsInternal";
import {useBotRefsContext} from "../../context/BotRefsContext";
import {useBotStatesContext} from "../../context/BotStatesContext";
import {setHistoryStorageValues} from "../../services/ChatHistoryService";
import {useSettingsContext} from "../../context/SettingsContext";

/**
* Internal custom hook for managing flow.
Expand All @@ -24,15 +26,21 @@ export const useFlowInternal = () => {

// handles bot refs
const { flowRef } = useBotRefsContext();

// handles bot settings
const { settings } = useSettingsContext();

/**
* Restarts the conversation flow for the chatbot.
*/
const restartFlow = useCallback(() => {
//reload the chat history from storage
setHistoryStorageValues(settings)

replaceMessages([]);
replaceToasts([]);
replacePaths(["start"]);
}, [replaceMessages, replaceToasts, replacePaths]);
}, [replaceMessages, replaceToasts, replacePaths, setHistoryStorageValues]);

/**
* Retrieves the conversation flow for the chatbot.
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/internal/useMessagesInternal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ export const useMessagesInternal = () => {
// defer update to next event loop, handles edge case where messages are sent too fast
// and the scrolling does not properly reach the bottom
setTimeout(() => {
if (!chatBodyRef.current) {
if (!chatBodyRef?.current) {
return;
}

Expand Down
Loading