From 0e9034f4bf00b1d3035e58db85b11a07f70b4b50 Mon Sep 17 00:00:00 2001
From: Madhur Saluja <114940822+MadhurSaluja@users.noreply.github.com>
Date: Thu, 28 Nov 2024 05:08:05 +0000
Subject: [PATCH 1/2] Solved issue-148
---
.../ChatBotBody/ChatMessagePrompt.test.tsx | 116 +++++++++++++++
__tests__/services/VoiceService.test.ts | 138 ++++++++++++++++++
package-lock.json | 9 +-
package.json | 2 +-
4 files changed, 260 insertions(+), 5 deletions(-)
create mode 100644 __tests__/components/ChatBotBody/ChatMessagePrompt.test.tsx
create mode 100644 __tests__/services/VoiceService.test.ts
diff --git a/__tests__/components/ChatBotBody/ChatMessagePrompt.test.tsx b/__tests__/components/ChatBotBody/ChatMessagePrompt.test.tsx
new file mode 100644
index 0000000..4745dc1
--- /dev/null
+++ b/__tests__/components/ChatBotBody/ChatMessagePrompt.test.tsx
@@ -0,0 +1,116 @@
+import React from "react";
+import { render, screen, fireEvent } from "@testing-library/react";
+import "@testing-library/jest-dom";
+import ChatMessagePrompt from "../../../src/components/ChatBotBody/ChatMessagePrompt/ChatMessagePrompt";
+
+// Mock contexts
+jest.mock("../../../src/context/BotRefsContext", () => ({
+ useBotRefsContext: jest.fn(() => ({
+ chatBodyRef: {
+ current: {
+ scrollTop: 0,
+ scrollHeight: 1000,
+ clientHeight: 400,
+ },
+ },
+ })),
+}));
+
+const mockSetIsScrolling = jest.fn();
+let unreadCountMock = 0;
+let isScrollingMock = false;
+
+jest.mock("../../../src/context/BotStatesContext", () => ({
+ useBotStatesContext: jest.fn(() => ({
+ unreadCount: unreadCountMock,
+ isScrolling: isScrollingMock,
+ setIsScrolling: mockSetIsScrolling,
+ })),
+}));
+
+jest.mock("../../../src/context/SettingsContext", () => ({
+ useSettingsContext: jest.fn(() => ({
+ settings: {
+ general: { primaryColor: "#000" },
+ chatWindow: {
+ showMessagePrompt: true,
+ messagePromptText: "Scroll to new messages",
+ },
+ },
+ })),
+}));
+
+jest.mock("../../../src/context/StylesContext", () => ({
+ useStylesContext: jest.fn(() => ({
+ styles: {
+ chatMessagePromptStyle: { color: "#fff", borderColor: "#ccc" },
+ chatMessagePromptHoveredStyle: { color: "#000", borderColor: "#000" },
+ },
+ })),
+}));
+
+describe("ChatMessagePrompt Component", () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ jest.useFakeTimers();
+ });
+
+ afterEach(() => {
+ jest.useRealTimers();
+ });
+
+ const renderComponent = () => render();
+
+ it("renders with the correct message prompt text", () => {
+ renderComponent();
+ const messagePrompt = screen.getByText("Scroll to new messages");
+ expect(messagePrompt).toBeInTheDocument();
+ });
+
+ it("applies visible class when conditions are met", () => {
+ unreadCountMock = 2;
+ isScrollingMock = true;
+
+ renderComponent();
+ const messagePrompt = screen.getByText("Scroll to new messages");
+ expect(messagePrompt.parentElement).toHaveClass("rcb-message-prompt-container visible");
+ });
+
+ it("applies hidden class when conditions are not met", () => {
+ unreadCountMock = 0;
+ isScrollingMock = false;
+
+ renderComponent();
+ const messagePromptContainer = screen.queryByText("Scroll to new messages")?.parentElement;
+ expect(messagePromptContainer).toHaveClass("rcb-message-prompt-container hidden");
+ });
+
+ it("applies hover styles when hovered", () => {
+ renderComponent();
+ const messagePrompt = screen.getByText("Scroll to new messages");
+
+ // Before hover
+ expect(messagePrompt).toHaveStyle({ color: "#fff", borderColor: "#ccc" });
+
+ // Hover
+ fireEvent.mouseEnter(messagePrompt);
+ expect(messagePrompt).toHaveStyle({ color: "#000", borderColor: "#000" });
+
+ // Leave hover
+ fireEvent.mouseLeave(messagePrompt);
+ expect(messagePrompt).toHaveStyle({ color: "#fff", borderColor: "#ccc" });
+ });
+
+ it("scrolls to the bottom when clicked", () => {
+ renderComponent();
+ const messagePrompt = screen.getByText("Scroll to new messages");
+
+ fireEvent.mouseDown(messagePrompt);
+
+ // Simulate scrolling completion
+ jest.advanceTimersByTime(600);
+
+ // Verify that setIsScrolling was called
+ expect(mockSetIsScrolling).toHaveBeenCalledWith(false);
+ });
+});
diff --git a/__tests__/services/VoiceService.test.ts b/__tests__/services/VoiceService.test.ts
new file mode 100644
index 0000000..2413c91
--- /dev/null
+++ b/__tests__/services/VoiceService.test.ts
@@ -0,0 +1,138 @@
+import { jest, SpyInstance } from "@jest/globals";
+import * as VoiceService from "../../src/services/VoiceService";
+
+describe("VoiceService", () => {
+ let mockRecognition: jest.Mocked;
+ let mockMediaRecorder: jest.Mocked;
+ let mockStopVoiceRecording: jest.SpyInstance;
+
+ beforeAll(() => {
+ // Mock navigator.mediaDevices if undefined
+ if (!navigator.mediaDevices) {
+ Object.defineProperty(navigator, "mediaDevices", {
+ value: {
+ getUserMedia: jest.fn(),
+ },
+ writable: true,
+ });
+ }
+ });
+
+ beforeEach(() => {
+ // Mock SpeechRecognition
+ mockRecognition = {
+ start: jest.fn(),
+ stop: jest.fn(),
+ onresult: null,
+ onend: null,
+ } as unknown as jest.Mocked;
+
+ window.SpeechRecognition = jest.fn(() => mockRecognition) as any;
+
+ // Mock MediaRecorder
+ mockMediaRecorder = {
+ start: jest.fn(),
+ stop: jest.fn(),
+ ondataavailable: null,
+ onstop: null,
+ state: "inactive",
+ } as unknown as jest.Mocked;
+
+ global.MediaRecorder = jest.fn(() => mockMediaRecorder) as any;
+
+ // Mock getUserMedia
+ jest.spyOn(navigator.mediaDevices, "getUserMedia").mockResolvedValue({
+ active: true,
+ id: "mockStreamId",
+ onaddtrack: null,
+ onremovetrack: null,
+ getTracks: jest.fn(() => [{ kind: "audio", stop: jest.fn() }]),
+ addTrack: jest.fn(),
+ removeTrack: jest.fn(),
+ getAudioTracks: jest.fn(() => []),
+ getVideoTracks: jest.fn(() => []),
+ clone: jest.fn(),
+ addEventListener: jest.fn(),
+ removeEventListener: jest.fn(),
+ dispatchEvent: jest.fn(),
+ } as unknown as MediaStream);
+
+ mockStopVoiceRecording = jest.spyOn(VoiceService, "stopVoiceRecording");
+ });
+
+ afterEach(() => {
+ jest.restoreAllMocks(); // Clean up mocks after each test
+ });
+
+ describe("startVoiceRecording", () => {
+ it("should start SpeechRecognition when sendAsAudio is false", () => {
+ const settings = { voice: { sendAsAudio: false, language: "en-US" } } as any;
+ const mockToggleVoice = jest.fn(() => Promise.resolve());
+
+ VoiceService.startVoiceRecording(
+ settings,
+ mockToggleVoice,
+ jest.fn(),
+ jest.fn(),
+ jest.fn(),
+ { current: [] },
+ { current: null }
+ );
+
+ expect(mockRecognition.start).toHaveBeenCalled();
+ });
+
+ it("should start MediaRecorder when sendAsAudio is true", async () => {
+ const settings = { voice: { sendAsAudio: true } } as any;
+ const mockToggleVoice = jest.fn(() => Promise.resolve());
+
+ await VoiceService.startVoiceRecording(
+ settings,
+ mockToggleVoice,
+ jest.fn(),
+ jest.fn(),
+ jest.fn(),
+ { current: [] },
+ { current: null }
+ );
+
+ expect(navigator.mediaDevices.getUserMedia).toHaveBeenCalledWith({ audio: true });
+ expect(mockMediaRecorder.start).toHaveBeenCalled();
+ });
+ });
+
+ describe("stopVoiceRecording", () => {
+ it("should stop SpeechRecognition and MediaRecorder", () => {
+ VoiceService.stopVoiceRecording();
+
+ expect(mockRecognition.stop).toHaveBeenCalled();
+ expect(mockMediaRecorder.stop).toHaveBeenCalled();
+ });
+ });
+
+ describe("syncVoiceWithChatInput", () => {
+ it("should start MediaRecorder if keepVoiceOn is true and sendAsAudio is enabled", () => {
+ const settings = { voice: { sendAsAudio: true, disabled: false } } as any;
+
+ VoiceService.syncVoiceWithChatInput(true, settings);
+
+ expect(mockMediaRecorder.start).toHaveBeenCalled();
+ });
+
+ it("should start SpeechRecognition if keepVoiceOn is true and sendAsAudio is disabled", () => {
+ const settings = { voice: { sendAsAudio: false, disabled: false } } as any;
+
+ VoiceService.syncVoiceWithChatInput(true, settings);
+
+ expect(mockRecognition.start).toHaveBeenCalled();
+ });
+
+ it("should stop all voice recording if keepVoiceOn is false", () => {
+ const settings = { voice: { sendAsAudio: false, disabled: false } } as any;
+
+ VoiceService.syncVoiceWithChatInput(false, settings);
+
+ expect(mockStopVoiceRecording).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/package-lock.json b/package-lock.json
index 5b46189..6ae7367 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,7 +12,7 @@
"@testing-library/jest-dom": "^6.6.2",
"@testing-library/react": "^16.0.1",
"@types/dom-speech-recognition": "^0.0.4",
- "@types/jest": "^29.5.13",
+ "@types/jest": "^29.5.14",
"@types/react": "^18.3.6",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^5.60.1",
@@ -2320,10 +2320,11 @@
}
},
"node_modules/@types/jest": {
- "version": "29.5.13",
- "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.13.tgz",
- "integrity": "sha512-wd+MVEZCHt23V0/L642O5APvspWply/rGY5BcW4SUETo2UzPU3Z26qr8jC2qxpimI2jjx9h7+2cj2FwIr01bXg==",
+ "version": "29.5.14",
+ "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz",
+ "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"expect": "^29.0.0",
"pretty-format": "^29.0.0"
diff --git a/package.json b/package.json
index 8548f7d..7ce8ac0 100644
--- a/package.json
+++ b/package.json
@@ -54,7 +54,7 @@
"@testing-library/jest-dom": "^6.6.2",
"@testing-library/react": "^16.0.1",
"@types/dom-speech-recognition": "^0.0.4",
- "@types/jest": "^29.5.13",
+ "@types/jest": "^29.5.14",
"@types/react": "^18.3.6",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^5.60.1",
From 375ebaed9b490b31c82787ac28c2f94b57cb7907 Mon Sep 17 00:00:00 2001
From: Madhur Saluja <114940822+MadhurSaluja@users.noreply.github.com>
Date: Thu, 28 Nov 2024 05:30:45 +0000
Subject: [PATCH 2/2] fixed the error casuing test failure
---
.../ChatBotBody/ChatMessagePrompt.test.tsx | 46 ++++--
__tests__/services/VoiceService.test.ts | 138 ------------------
2 files changed, 33 insertions(+), 151 deletions(-)
delete mode 100644 __tests__/services/VoiceService.test.ts
diff --git a/__tests__/components/ChatBotBody/ChatMessagePrompt.test.tsx b/__tests__/components/ChatBotBody/ChatMessagePrompt.test.tsx
index 4745dc1..a484fcd 100644
--- a/__tests__/components/ChatBotBody/ChatMessagePrompt.test.tsx
+++ b/__tests__/components/ChatBotBody/ChatMessagePrompt.test.tsx
@@ -2,6 +2,7 @@ import React from "react";
import { render, screen, fireEvent } from "@testing-library/react";
import "@testing-library/jest-dom";
import ChatMessagePrompt from "../../../src/components/ChatBotBody/ChatMessagePrompt/ChatMessagePrompt";
+import { useBotStatesContext } from "../../../src/context/BotStatesContext";
// Mock contexts
jest.mock("../../../src/context/BotRefsContext", () => ({
@@ -16,16 +17,8 @@ jest.mock("../../../src/context/BotRefsContext", () => ({
})),
}));
-const mockSetIsScrolling = jest.fn();
-let unreadCountMock = 0;
-let isScrollingMock = false;
-
jest.mock("../../../src/context/BotStatesContext", () => ({
- useBotStatesContext: jest.fn(() => ({
- unreadCount: unreadCountMock,
- isScrolling: isScrollingMock,
- setIsScrolling: mockSetIsScrolling,
- })),
+ useBotStatesContext: jest.fn(),
}));
jest.mock("../../../src/context/SettingsContext", () => ({
@@ -50,26 +43,38 @@ jest.mock("../../../src/context/StylesContext", () => ({
}));
describe("ChatMessagePrompt Component", () => {
+ const mockSetIsScrolling = jest.fn();
+
beforeEach(() => {
jest.clearAllMocks();
jest.useFakeTimers();
});
afterEach(() => {
+ jest.runOnlyPendingTimers();
jest.useRealTimers();
});
const renderComponent = () => render();
it("renders with the correct message prompt text", () => {
+ (useBotStatesContext as jest.Mock).mockReturnValue({
+ unreadCount: 0,
+ isScrolling: false,
+ setIsScrolling: mockSetIsScrolling,
+ });
+
renderComponent();
const messagePrompt = screen.getByText("Scroll to new messages");
expect(messagePrompt).toBeInTheDocument();
});
it("applies visible class when conditions are met", () => {
- unreadCountMock = 2;
- isScrollingMock = true;
+ (useBotStatesContext as jest.Mock).mockReturnValue({
+ unreadCount: 2,
+ isScrolling: true,
+ setIsScrolling: mockSetIsScrolling,
+ });
renderComponent();
const messagePrompt = screen.getByText("Scroll to new messages");
@@ -77,8 +82,11 @@ describe("ChatMessagePrompt Component", () => {
});
it("applies hidden class when conditions are not met", () => {
- unreadCountMock = 0;
- isScrollingMock = false;
+ (useBotStatesContext as jest.Mock).mockReturnValue({
+ unreadCount: 0,
+ isScrolling: false,
+ setIsScrolling: mockSetIsScrolling,
+ });
renderComponent();
const messagePromptContainer = screen.queryByText("Scroll to new messages")?.parentElement;
@@ -86,6 +94,12 @@ describe("ChatMessagePrompt Component", () => {
});
it("applies hover styles when hovered", () => {
+ (useBotStatesContext as jest.Mock).mockReturnValue({
+ unreadCount: 2,
+ isScrolling: true,
+ setIsScrolling: mockSetIsScrolling,
+ });
+
renderComponent();
const messagePrompt = screen.getByText("Scroll to new messages");
@@ -102,6 +116,12 @@ describe("ChatMessagePrompt Component", () => {
});
it("scrolls to the bottom when clicked", () => {
+ (useBotStatesContext as jest.Mock).mockReturnValue({
+ unreadCount: 2,
+ isScrolling: true,
+ setIsScrolling: mockSetIsScrolling,
+ });
+
renderComponent();
const messagePrompt = screen.getByText("Scroll to new messages");
diff --git a/__tests__/services/VoiceService.test.ts b/__tests__/services/VoiceService.test.ts
deleted file mode 100644
index 2413c91..0000000
--- a/__tests__/services/VoiceService.test.ts
+++ /dev/null
@@ -1,138 +0,0 @@
-import { jest, SpyInstance } from "@jest/globals";
-import * as VoiceService from "../../src/services/VoiceService";
-
-describe("VoiceService", () => {
- let mockRecognition: jest.Mocked;
- let mockMediaRecorder: jest.Mocked;
- let mockStopVoiceRecording: jest.SpyInstance;
-
- beforeAll(() => {
- // Mock navigator.mediaDevices if undefined
- if (!navigator.mediaDevices) {
- Object.defineProperty(navigator, "mediaDevices", {
- value: {
- getUserMedia: jest.fn(),
- },
- writable: true,
- });
- }
- });
-
- beforeEach(() => {
- // Mock SpeechRecognition
- mockRecognition = {
- start: jest.fn(),
- stop: jest.fn(),
- onresult: null,
- onend: null,
- } as unknown as jest.Mocked;
-
- window.SpeechRecognition = jest.fn(() => mockRecognition) as any;
-
- // Mock MediaRecorder
- mockMediaRecorder = {
- start: jest.fn(),
- stop: jest.fn(),
- ondataavailable: null,
- onstop: null,
- state: "inactive",
- } as unknown as jest.Mocked;
-
- global.MediaRecorder = jest.fn(() => mockMediaRecorder) as any;
-
- // Mock getUserMedia
- jest.spyOn(navigator.mediaDevices, "getUserMedia").mockResolvedValue({
- active: true,
- id: "mockStreamId",
- onaddtrack: null,
- onremovetrack: null,
- getTracks: jest.fn(() => [{ kind: "audio", stop: jest.fn() }]),
- addTrack: jest.fn(),
- removeTrack: jest.fn(),
- getAudioTracks: jest.fn(() => []),
- getVideoTracks: jest.fn(() => []),
- clone: jest.fn(),
- addEventListener: jest.fn(),
- removeEventListener: jest.fn(),
- dispatchEvent: jest.fn(),
- } as unknown as MediaStream);
-
- mockStopVoiceRecording = jest.spyOn(VoiceService, "stopVoiceRecording");
- });
-
- afterEach(() => {
- jest.restoreAllMocks(); // Clean up mocks after each test
- });
-
- describe("startVoiceRecording", () => {
- it("should start SpeechRecognition when sendAsAudio is false", () => {
- const settings = { voice: { sendAsAudio: false, language: "en-US" } } as any;
- const mockToggleVoice = jest.fn(() => Promise.resolve());
-
- VoiceService.startVoiceRecording(
- settings,
- mockToggleVoice,
- jest.fn(),
- jest.fn(),
- jest.fn(),
- { current: [] },
- { current: null }
- );
-
- expect(mockRecognition.start).toHaveBeenCalled();
- });
-
- it("should start MediaRecorder when sendAsAudio is true", async () => {
- const settings = { voice: { sendAsAudio: true } } as any;
- const mockToggleVoice = jest.fn(() => Promise.resolve());
-
- await VoiceService.startVoiceRecording(
- settings,
- mockToggleVoice,
- jest.fn(),
- jest.fn(),
- jest.fn(),
- { current: [] },
- { current: null }
- );
-
- expect(navigator.mediaDevices.getUserMedia).toHaveBeenCalledWith({ audio: true });
- expect(mockMediaRecorder.start).toHaveBeenCalled();
- });
- });
-
- describe("stopVoiceRecording", () => {
- it("should stop SpeechRecognition and MediaRecorder", () => {
- VoiceService.stopVoiceRecording();
-
- expect(mockRecognition.stop).toHaveBeenCalled();
- expect(mockMediaRecorder.stop).toHaveBeenCalled();
- });
- });
-
- describe("syncVoiceWithChatInput", () => {
- it("should start MediaRecorder if keepVoiceOn is true and sendAsAudio is enabled", () => {
- const settings = { voice: { sendAsAudio: true, disabled: false } } as any;
-
- VoiceService.syncVoiceWithChatInput(true, settings);
-
- expect(mockMediaRecorder.start).toHaveBeenCalled();
- });
-
- it("should start SpeechRecognition if keepVoiceOn is true and sendAsAudio is disabled", () => {
- const settings = { voice: { sendAsAudio: false, disabled: false } } as any;
-
- VoiceService.syncVoiceWithChatInput(true, settings);
-
- expect(mockRecognition.start).toHaveBeenCalled();
- });
-
- it("should stop all voice recording if keepVoiceOn is false", () => {
- const settings = { voice: { sendAsAudio: false, disabled: false } } as any;
-
- VoiceService.syncVoiceWithChatInput(false, settings);
-
- expect(mockStopVoiceRecording).toHaveBeenCalled();
- });
- });
-});