Repository: tjtanjin/react-chatbotify (ts)

Issue:
[Task] Add unit test cases for ChatBotButton component
**Task Description:**
Currently, we run unit tests using jest which can be found [**here**](https://github.com/tjtanjin/react-chatbotify/tree/main/__tests__). 

However, the test cases are not yet comprehensive enough and work can be done to cover more grounds. Specifically for this issue, we hope to add test cases for the `ChatBotButton` component.

**Deliverable(s):**
New unit test cases that cover the testing of the `ChatBotButton` component.

**Additional Context:**
- The `ChatBotButton` components can be found [**here**](https://github.com/tjtanjin/react-chatbotify/tree/main/src/components/ChatBotButton)
- An example for `AudioButton` can be found [**here**](https://github.com/tjtanjin/react-chatbotify/blob/main/__tests__/components/AudioButton.test.tsx) which may serve as a good source for reference


Pull request description:
Added unit test cases for ChatBotButton component
### **Description:**

This PR adds unit tests for the `ChatBotButton` component, located in `__tests__/components/buttons/ChatBotButton.test.tsx`, to address issue #145. These tests verify that the component renders correctly, applies the proper CSS classes, and toggles visibility when clicked.

Closes #145

---

### **What change does this PR introduce?**

- [x] New feature (non-breaking change which adds functionality)

---

### **What is the proposed approach?**

This PR introduces unit tests for the following cases:
- **Basic Render Test**: Ensures that the `ChatBotButton` renders correctly within the `TestChatBotProvider`.
- **CSS Class Application**: Verifies that the correct CSS class (`rcb-toggle-button`, `rcb-button-show`, `rcb-button-hide`) is applied based on the button's state.
- **Visibility Class Toggle**: Simulates a click event on the button to verify that the visibility toggles between `rcb-button-show` (visible) and `rcb-button-hide` (hidden). This assumes that the button manages its visibility internally.

The tests ensure that the `ChatBotButton` behaves as expected in terms of rendering, class handling, and visibility toggling.

---

### **Checklist:**
- [x] The commit message follows the [guidelines](https://www.conventionalcommits.org/en/v1.0.0/).
- [x] Unit tests have been written and successfully run for the added functionality.
- [x] Relevant documentation or comments have been updated (if needed).

---

### **Testing Done:**
- Used `@testing-library/react` to ensure the component renders correctly and applies the appropriate CSS classes.
- Simulated user interactions (clicks) to confirm the visibility toggle functionality.
- All tests passed, confirming correct rendering and CSS class application.

---

### **Hacktoberfest Note:**
Please consider labeling this PR with **`hacktoberfest-accepted`** so that it counts towards the Hacktoberfest contest. Thank you!


Patch:
diff --git a/src/components/ChatBotTooltip/ChatBotTooltip.tsx b/src/components/ChatBotTooltip/ChatBotTooltip.tsx
index 0e4b676..28d2cc9 100644
--- a/src/components/ChatBotTooltip/ChatBotTooltip.tsx
+++ b/src/components/ChatBotTooltip/ChatBotTooltip.tsx
@@ -87,6 +87,7 @@ const ChatBotTooltip = () => {
 		<>
 			{!settings.general?.embedded &&
 				<div 
+					data-testid="chat-tooltip"
 					style={tooltipStyle}
 					className={`rcb-chat-tooltip ${showTooltip ? "rcb-tooltip-show" : "rcb-tooltip-hide"}`}
 					onClick={() => openChat(true)}
diff --git a/src/components/ChatHistoryLineBreak/ChatHistoryLineBreak.tsx b/src/components/ChatHistoryLineBreak/ChatHistoryLineBreak.tsx
index 94d9fd2..5d7bfd6 100644
--- a/src/components/ChatHistoryLineBreak/ChatHistoryLineBreak.tsx
+++ b/src/components/ChatHistoryLineBreak/ChatHistoryLineBreak.tsx
@@ -16,7 +16,11 @@ const ChatHistoryLineBreak = () => {
 
 	return (
 		<div className="rcb-line-break-container">
-			<div style={styles.chatHistoryLineBreakStyle} className="rcb-line-break-text">
+			<div
+				style={styles.chatHistoryLineBreakStyle}
+				className="rcb-line-break-text"
+				data-testid="chat-history-line-break-text"
+			>
 				{settings.chatHistory?.chatHistoryLineBreakText}
 			</div>
 		</div>


Test patch:
diff --git a/__tests__/components/ChatBotFooter/ChatBotFooter.test.tsx b/__tests__/components/ChatBotFooter/ChatBotFooter.test.tsx
index af8da60..99bb2e5 100644
--- a/__tests__/components/ChatBotFooter/ChatBotFooter.test.tsx
+++ b/__tests__/components/ChatBotFooter/ChatBotFooter.test.tsx
@@ -21,7 +21,7 @@ import ChatBotFooter from "../../../src/components/ChatBotFooter/ChatBotFooter"
 const renderChatBotFooter = ({ buttons }: { buttons: JSX.Element[] }) => {
 	return render(
 
-			<ChatBotFooter buttons={buttons}></ChatBotFooter>
+		<ChatBotFooter buttons={buttons}></ChatBotFooter>
 
 	);
 };
diff --git a/__tests__/components/ChatBotTooltip/ChatBotTooltip.test.tsx b/__tests__/components/ChatBotTooltip/ChatBotTooltip.test.tsx
new file mode 100644
index 0000000..961633d
--- /dev/null
+++ b/__tests__/components/ChatBotTooltip/ChatBotTooltip.test.tsx
@@ -0,0 +1,121 @@
+import React from "react";
+import "@testing-library/jest-dom"; 
+import { render, screen, fireEvent } from "@testing-library/react";
+import ChatBotTooltip from "../../../src/components/ChatBotTooltip/ChatBotTooltip";
+import { useIsDesktopInternal } from "../../../src/hooks/internal/useIsDesktopInternal";
+import { useChatWindowInternal } from "../../../src/hooks/internal/useChatWindowInternal";
+import { useSettingsContext } from "../../../src/context/SettingsContext";
+import { useStylesContext } from "../../../src/context/StylesContext";
+
+// Mock the contexts and hooks
+jest.mock("../../../src/hooks/internal/useIsDesktopInternal");
+jest.mock("../../../src/hooks/internal/useChatWindowInternal");
+jest.mock("../../../src/context/SettingsContext");
+jest.mock("../../../src/context/StylesContext");
+
+describe("ChatBotTooltip Component", () => {
+	// Set up default mock return values before each test
+	beforeEach(() => {
+		(useIsDesktopInternal as jest.Mock).mockReturnValue(true); 
+		(useChatWindowInternal as jest.Mock).mockReturnValue({
+			isChatWindowOpen: false,
+			openChat: jest.fn(), 
+		});
+		(useSettingsContext as jest.Mock).mockReturnValue({
+			settings: {
+				general: { primaryColor: "blue", secondaryColor: "#000", embedded: false },
+				tooltip: { text: "Chatbot Tooltip" }, 
+			},
+		});
+		(useStylesContext as jest.Mock).mockReturnValue({
+			styles: {
+				chatButtonStyle: { width: 75 },
+				chatWindowStyle: { width: 375 },
+				tooltipStyle: {}, 
+			},
+		});
+	}); 
+
+	// Clear all mocks after each test case to avoid test interference
+	afterEach(() => {
+		jest.clearAllMocks();
+	});
+
+	// Test: Render the tooltip when mode is "ALWAYS" and on desktop
+	it('renders tooltip when mode is "ALWAYS" and isDesktop is true', () => {
+		// Mock the context to return settings with mode "ALWAYS"
+		(useSettingsContext as jest.Mock).mockReturnValueOnce({
+			settings: {
+				tooltip: { mode: "ALWAYS", text: "Chatbot Tooltip" },
+			},
+		});
+
+		// Render the component and ensure the tooltip is shown
+		render(<ChatBotTooltip />);
+		const tooltip = screen.getByTestId("chat-tooltip");
+		expect(tooltip).toBeInTheDocument(); 
+		expect(tooltip).toHaveClass("rcb-tooltip-show"); 
+	});
+
+	// Test: Ensure the tooltip is hidden when mode is "NEVER"
+	it('hides tooltip when mode is "NEVER"', () => {
+		// Mock the context to return settings with mode "NEVER"
+		(useSettingsContext as jest.Mock).mockReturnValueOnce({
+			settings: {
+				tooltip: { mode: "NEVER", text: "Chatbot Tooltip" },
+			},
+		});
+
+		// Render the component and ensure the tooltip is rendered but hidden
+		render(<ChatBotTooltip />);
+		const tooltip = screen.getByTestId("chat-tooltip");
+		expect(tooltip).toBeInTheDocument();
+		expect(tooltip).toHaveClass("rcb-tooltip-hide");
+	});
+
+	// Test: Show the tooltip when mode is "START" (when component is rendered for the first time)
+	it('shows tooltip when mode is "START" and shownTooltipOnStart is false', () => {
+		// Mock the context to return settings with mode "START"
+		(useSettingsContext as jest.Mock).mockReturnValueOnce({
+			settings: {
+				tooltip: { mode: "START", text: "Chatbot Tooltip" },
+			},
+		});
+
+		// Render the component and ensure the tooltip is shown
+		render(<ChatBotTooltip />);
+		const tooltip = screen.getByText("Chatbot Tooltip");
+		expect(tooltip).toBeInTheDocument(); 
+	});
+
+	// Test: Ensure the tooltip shows when mode is "CLOSE"
+	it('shows tooltip when mode is "CLOSE"', () => {
+		// Mock the context to return settings with mode "CLOSE"
+		(useSettingsContext as jest.Mock).mockReturnValueOnce({
+			settings: {
+				tooltip: { mode: "CLOSE", text: "Chatbot Tooltip" },
+			},
+		});
+
+		// Render the component and ensure the tooltip is shown
+		render(<ChatBotTooltip />);
+		const tooltip = screen.getByText("Chatbot Tooltip");
+		expect(tooltip).toBeInTheDocument();
+	});
+
+	// Test: Ensure clicking the tooltip calls the openChat function
+	it("calls openChat function when tooltip is clicked", () => {
+		// Mock the function that opens the chat
+		const mockOpenChat = jest.fn();
+		(useChatWindowInternal as jest.Mock).mockReturnValue({
+			isChatWindowOpen: false,
+			openChat: mockOpenChat,
+		});
+
+		// Render the component, simulate click event and verify that openChat is called
+		render(<ChatBotTooltip />);
+		const tooltip = screen.getByText("Chatbot Tooltip");
+		fireEvent.click(tooltip); 
+		expect(mockOpenChat).toHaveBeenCalledWith(true); 
+	});
+});
diff --git a/__tests__/components/ChatHistoryLineBreak/ChatHistoryLineBreak.test.tsx b/__tests__/components/ChatHistoryLineBreak/ChatHistoryLineBreak.test.tsx
new file mode 100644
index 0000000..e2deab7
--- /dev/null
+++ b/__tests__/components/ChatHistoryLineBreak/ChatHistoryLineBreak.test.tsx
@@ -0,0 +1,87 @@
+import React from "react";
+
+import { expect } from "@jest/globals";
+import { render, screen} from "@testing-library/react";
+import "@testing-library/jest-dom/jest-globals";
+
+import ChatHistoryLineBreak from "../../../src/components/ChatHistoryLineBreak/ChatHistoryLineBreak";
+import { useSettingsContext } from "../../../src/context/SettingsContext";
+import { useStylesContext } from "../../../src/context/StylesContext";
+import { DefaultStyles } from "../../../src/constants/internal/DefaultStyles";
+import { DefaultSettings } from "../../../src/constants/internal/DefaultSettings";
+
+jest.mock("../../../src/context/SettingsContext");
+jest.mock("../../../src/context/StylesContext");
+
+/**
+ * Test for ChatHistoryLineBreak component.
+ */
+describe("ChatHistoryLineBreak Component", () => {
+	const mockText = DefaultSettings.chatHistory?.chatHistoryLineBreakText ?? "test break line";
+	const mockColor = DefaultStyles.chatHistoryLineBreakStyle?.color ?? "blue";
+	
+	// Mock default settings and styles before each test
+	beforeEach(() => {
+		(useSettingsContext as jest.Mock).mockReturnValue({
+			settings: {
+				chatHistory: {
+					chatHistoryLineBreakText: mockText
+				}
+			}
+		});
+
+		(useStylesContext as jest.Mock).mockReturnValue({
+			styles: {
+				chatHistoryLineBreakStyle: {
+					color: mockColor
+				}
+			}
+		});
+	});
+
+	it("renders mock line break text and style", () => {
+		render(<ChatHistoryLineBreak />);
+
+		// check if the default text is rendered
+		const lineBreak = screen.getByText(mockText);
+		expect(lineBreak).toBeInTheDocument();
+
+		// check if line break color is correct
+		expect(lineBreak).toHaveStyle(`color: ${mockColor}`);
+	});
+
+	it("renders empty div when chatHistoryLineBreakText is not provided", () => {
+		// Mock settings without chatHistoryLineBreakText
+		(useSettingsContext as jest.Mock).mockReturnValue({
+			settings: {
+				chatHistory: {} // Simulate missing chatHistoryLineBreakText
+			}
+		});
+
+		render(<ChatHistoryLineBreak />);
+
+		// check that line break div exists but text is empty
+		const lineBreak = screen.getByTestId("chat-history-line-break-text");
+		expect(lineBreak).toBeInTheDocument();
+		expect(lineBreak).toBeEmptyDOMElement();
+
+		// check if line break color is applied even when chatHistoryLineBreakText is empty
+		expect(lineBreak).toHaveStyle(`color: ${DefaultStyles.chatHistoryLineBreakStyle?.color ?? mockColor}`);
+	});
+
+	it("renders empty when chatHistory is not provided", () => {
+		(useSettingsContext as jest.Mock).mockReturnValue({
+			settings: {}
+		});
+
+		render(<ChatHistoryLineBreak />);
+
+		// check that line break div exists but text is empty
+		const lineBreak = screen.getByTestId("chat-history-line-break-text");
+		expect(lineBreak).toBeInTheDocument();
+		expect(lineBreak).toBeEmptyDOMElement();
+
+		// check line break color is applied even when chatHistory is empty
+		expect(lineBreak).toHaveStyle(`color: ${DefaultStyles.chatHistoryLineBreakStyle?.color ?? mockColor}`);
+	});
+});
diff --git a/__tests__/components/buttons/ChatBotButton.test.tsx b/__tests__/components/buttons/ChatBotButton.test.tsx
new file mode 100644
index 0000000..7db25c1
--- /dev/null
+++ b/__tests__/components/buttons/ChatBotButton.test.tsx
@@ -0,0 +1,37 @@
+import React from 'react';
+import { expect } from "@jest/globals";
+import { render, screen, fireEvent } from '@testing-library/react';
+import "@testing-library/jest-dom/jest-globals";
+import ChatBotButton from '../../../src/components/ChatBotButton/ChatBotButton'; 
+import { TestChatBotProvider } from '../../__mocks__/TestChatBotContext';
+import { DefaultSettings } from "../../../src/constants/internal/DefaultSettings";
+
+// Helper function to render ChatBotButton within TestChatBotProvider
+const renderChatBotButton = () => {
+  return render(
+    <TestChatBotProvider initialSettings={DefaultSettings}>
+      <ChatBotButton />  
+    </TestChatBotProvider>
+  );
+};
+
+describe('ChatBotButton', () => {
+  it('renders ChatBotButton correctly', () => {
+    renderChatBotButton();
+    const button = screen.getByRole('button');
+    expect(button).toBeInTheDocument();
+  });
+
+  // Mock visibility toggle function (assuming it's triggered by a button click)
+  it('toggles visibility classes correctly based on internal function', () => {
+    renderChatBotButton();
+    const button = screen.getByRole('button');
+
+    // Initially visible
+    expect(button).toHaveClass('rcb-button-show');
+
+    // Simulate state change or function that hides the button
+    fireEvent.click(button); // Assuming the button click triggers visibility toggle
+    expect(button).toHaveClass('rcb-button-hide');  // Check if the class changes to hidden
+  });
+});