diff --git a/frontend/src/components/HomeComponents/BottomBar/__tests__/BottomBar.test.tsx b/frontend/src/components/HomeComponents/BottomBar/__tests__/BottomBar.test.tsx
new file mode 100644
index 00000000..93be6284
--- /dev/null
+++ b/frontend/src/components/HomeComponents/BottomBar/__tests__/BottomBar.test.tsx
@@ -0,0 +1,25 @@
+import { render, screen } from '@testing-library/react';
+import BottomBar from '../BottomBar';
+
+describe('BottomBar Component', () => {
+ const mockSetSelectedProject = jest.fn();
+ const mockSetSelectedStatus = jest.fn();
+ const projects = ['Project A', 'Project B'];
+ const status = ['Status A', 'Status B'];
+
+ test('renders BottomBar component', () => {
+ render(
+
+ );
+
+ expect(screen.getByText('Home')).toBeInTheDocument();
+ expect(screen.getByText('Tasks')).toBeInTheDocument();
+ });
+});
diff --git a/frontend/src/components/HomeComponents/BottomBar/__tests__/bottom-bar-utils.test.ts b/frontend/src/components/HomeComponents/BottomBar/__tests__/bottom-bar-utils.test.ts
new file mode 100644
index 00000000..21411f17
--- /dev/null
+++ b/frontend/src/components/HomeComponents/BottomBar/__tests__/bottom-bar-utils.test.ts
@@ -0,0 +1,33 @@
+import { RouteProps, routeList } from '../bottom-bar-utils';
+
+describe('RouteProps interface', () => {
+ it('should have href and label properties', () => {
+ const exampleRoute: RouteProps = { href: '#', label: 'Example' };
+ expect(exampleRoute).toHaveProperty('href');
+ expect(exampleRoute).toHaveProperty('label');
+ });
+});
+
+describe('routeList array', () => {
+ it('should be an array', () => {
+ expect(Array.isArray(routeList)).toBe(true);
+ });
+
+ it('should contain objects with href and label properties', () => {
+ routeList.forEach(route => {
+ expect(route).toHaveProperty('href');
+ expect(route).toHaveProperty('label');
+ });
+ });
+
+ it('should contain correct number of routes', () => {
+ expect(routeList.length).toBe(2);
+ });
+
+ it('should match the predefined routes', () => {
+ expect(routeList).toEqual([
+ { href: "#", label: "Home" },
+ { href: "#tasks", label: "Tasks" },
+ ]);
+ });
+});
diff --git a/frontend/src/components/HomeComponents/Navbar/Navbar.tsx b/frontend/src/components/HomeComponents/Navbar/Navbar.tsx
index a350bdb2..502370f0 100644
--- a/frontend/src/components/HomeComponents/Navbar/Navbar.tsx
+++ b/frontend/src/components/HomeComponents/Navbar/Navbar.tsx
@@ -23,7 +23,7 @@ export const Navbar = (props: Props) => {
href="/"
className="ml-2 font-bold text-xl flex items-center dark:hidden"
>
-
+
({
+ NavbarMobile: ({ isOpen }: { isOpen: boolean; setIsOpen: (isOpen: boolean) => void }) => (
+
+ )
+}));
+
+jest.mock('../NavbarDesktop', () => ({
+ NavbarDesktop: (_props: Props) =>
+}));
+
+// Mocking the logo images
+jest.mock('../../../../assets/logo.png', () => 'logo.png');
+jest.mock('../../../../assets/logo_light.png', () => 'logo_light.png');
+
+describe('Navbar Component', () => {
+ const props: Props = {
+ imgurl: '',
+ email: '',
+ encryptionSecret: '',
+ origin: '',
+ UUID: ''
+ };
+
+ test('renders Navbar component with correct elements', () => {
+ render();
+
+ const logoLightImage = screen.getByAltText('Light-Logo');
+ const logoImage = screen.getByAltText('Logo');
+
+ expect(logoLightImage).toBeInTheDocument();
+
+ expect(logoImage).toBeInTheDocument();
+
+ // Check for NavbarDesktop and NavbarMobile components
+ expect(screen.getByTestId('navbar-desktop')).toBeInTheDocument();
+ expect(screen.getByTestId('navbar-mobile')).toBeInTheDocument();
+ });
+
+ test('NavbarMobile component receives correct props', () => {
+ render();
+
+ const navbarMobile = screen.getByTestId('navbar-mobile');
+ expect(navbarMobile).toHaveAttribute('data-isopen', 'false');
+ });
+});
diff --git a/frontend/src/components/HomeComponents/Navbar/__tests__/NavbarDesktop.test.tsx b/frontend/src/components/HomeComponents/Navbar/__tests__/NavbarDesktop.test.tsx
new file mode 100644
index 00000000..dffa3d78
--- /dev/null
+++ b/frontend/src/components/HomeComponents/Navbar/__tests__/NavbarDesktop.test.tsx
@@ -0,0 +1,51 @@
+import { render, screen, fireEvent } from "@testing-library/react";
+import { NavbarDesktop } from "../NavbarDesktop";
+import { syncTasksWithTwAndDb, Props, routeList } from "../navbar-utils";
+
+// Mock external dependencies
+jest.mock("../navbar-utils", () => ({
+ syncTasksWithTwAndDb: jest.fn(),
+ deleteAllTasks: jest.fn(),
+ handleLogout: jest.fn(),
+ routeList: [
+ { href: "#", label: "Home" },
+ { href: "#tasks", label: "Tasks" },
+ { href: "#setup-guide", label: "Setup Guide" },
+ { href: "#faq", label: "FAQ" },
+ ],
+}));
+
+describe("NavbarDesktop", () => {
+ const mockProps: Props = {
+ imgurl: "http://example.com/image.png",
+ email: "test@example.com",
+ encryptionSecret: "secret",
+ origin: "http://localhost:3000",
+ UUID: "1234-5678",
+ };
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it("renders the navigation links correctly", () => {
+ render();
+
+ routeList.forEach((route) => {
+ expect(screen.getByText(route.label)).toBeInTheDocument();
+ });
+ });
+
+ it("calls syncTasksWithTwAndDb when 'Sync Tasks' is clicked", () => {
+ render();
+ const syncButton = screen.getByText("Sync Tasks");
+
+ fireEvent.click(syncButton);
+
+ expect(syncTasksWithTwAndDb).toHaveBeenCalledWith(mockProps);
+ });
+
+ it("displays user email and handles dropdown menu actions", () => {
+ render();
+ });
+});
diff --git a/frontend/src/components/HomeComponents/Navbar/__tests__/NavbarMobile.test.tsx b/frontend/src/components/HomeComponents/Navbar/__tests__/NavbarMobile.test.tsx
new file mode 100644
index 00000000..f1a6664f
--- /dev/null
+++ b/frontend/src/components/HomeComponents/Navbar/__tests__/NavbarMobile.test.tsx
@@ -0,0 +1,92 @@
+import { render, screen, fireEvent } from "@testing-library/react";
+import { NavbarMobile } from "../NavbarMobile";
+import { syncTasksWithTwAndDb, deleteAllTasks, handleLogout, Props, routeList } from "../navbar-utils";
+
+jest.mock("../navbar-utils", () => ({
+ syncTasksWithTwAndDb: jest.fn(),
+ deleteAllTasks: jest.fn(),
+ handleLogout: jest.fn(),
+ routeList: [
+ { href: "#", label: "Home" },
+ { href: "#tasks", label: "Tasks" },
+ { href: "#setup-guide", label: "Setup Guide" },
+ { href: "#faq", label: "FAQ" },
+ ],
+}));
+
+describe("NavbarMobile", () => {
+ const mockProps: Props & { setIsOpen: (isOpen: boolean) => void; isOpen: boolean } = {
+ imgurl: "http://example.com/image.png",
+ email: "test@example.com",
+ encryptionSecret: "secret",
+ origin: "http://localhost:3000",
+ UUID: "1234-5678",
+ setIsOpen: jest.fn(),
+ isOpen: false,
+ };
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it("renders the ModeToggle and Menu button", () => {
+ render();
+ });
+
+ it("opens the menu when Menu button is clicked", () => {
+ render();
+ const menuButton = screen.getByRole("button", { name: /menu icon/i });
+
+ fireEvent.click(menuButton);
+ expect(mockProps.setIsOpen).toHaveBeenCalledWith(true);
+ });
+
+ it("displays the navigation links and buttons correctly when menu is open", () => {
+ const openProps = { ...mockProps, isOpen: true };
+ render();
+
+ routeList.forEach((route) => {
+ expect(screen.getByText(route.label)).toBeInTheDocument();
+ });
+ expect(screen.getByText("Github")).toBeInTheDocument();
+ expect(screen.getByText("Sync Tasks")).toBeInTheDocument();
+ expect(screen.getByText("Delete All Tasks")).toBeInTheDocument();
+ expect(screen.getByText("Log out")).toBeInTheDocument();
+ });
+
+ it("closes the menu when a navigation link is clicked", () => {
+ const openProps = { ...mockProps, isOpen: true };
+ render();
+
+ const homeLink = screen.getByText("Home");
+ fireEvent.click(homeLink);
+ expect(mockProps.setIsOpen).toHaveBeenCalledWith(false);
+ });
+
+ it("calls syncTasksWithTwAndDb when 'Sync Tasks' is clicked", () => {
+ const openProps = { ...mockProps, isOpen: true };
+ render();
+ const syncButton = screen.getByText("Sync Tasks");
+
+ fireEvent.click(syncButton);
+ expect(syncTasksWithTwAndDb).toHaveBeenCalledWith(openProps);
+ });
+
+ it("calls deleteAllTasks when 'Delete All Tasks' is clicked", () => {
+ const openProps = { ...mockProps, isOpen: true };
+ render();
+ const deleteButton = screen.getByText("Delete All Tasks");
+
+ fireEvent.click(deleteButton);
+ expect(deleteAllTasks).toHaveBeenCalledWith(openProps);
+ });
+
+ it("calls handleLogout when 'Log out' is clicked", () => {
+ const openProps = { ...mockProps, isOpen: true };
+ render();
+ const logoutButton = screen.getByText("Log out");
+
+ fireEvent.click(logoutButton);
+ expect(handleLogout).toHaveBeenCalled();
+ });
+});
diff --git a/frontend/src/components/HomeComponents/Navbar/__tests__/navbar-utils.test.ts b/frontend/src/components/HomeComponents/Navbar/__tests__/navbar-utils.test.ts
new file mode 100644
index 00000000..115a2948
--- /dev/null
+++ b/frontend/src/components/HomeComponents/Navbar/__tests__/navbar-utils.test.ts
@@ -0,0 +1,155 @@
+import { toast } from "react-toastify";
+import { tasksCollection } from "@/lib/controller";
+import { deleteDoc, getDocs} from "firebase/firestore";
+import { handleLogout, syncTasksWithTwAndDb, deleteAllTasks, Props } from "../navbar-utils";
+
+// Mock external dependencies
+jest.mock("react-toastify", () => ({
+ toast: {
+ success: jest.fn(),
+ error: jest.fn(),
+ info: jest.fn(),
+ update: jest.fn(),
+ },
+}));
+
+jest.mock("firebase/firestore", () => ({
+ deleteDoc: jest.fn(),
+ doc: jest.fn(),
+ getDocs: jest.fn(),
+ setDoc: jest.fn(),
+ updateDoc: jest.fn(),
+}));
+
+jest.mock("@/lib/controller", () => ({
+ tasksCollection: {},
+}));
+
+jest.mock("@/lib/URLs", () => ({
+ url: {
+ backendURL: "http://localhost:3000/",
+ },
+}));
+
+global.fetch = jest.fn();
+
+describe("navbar-utils", () => {
+ const mockProps: Props = {
+ imgurl: "http://example.com/image.png",
+ email: "test@example.com",
+ encryptionSecret: "secret",
+ origin: "http://localhost:3000",
+ UUID: "1234-5678",
+ };
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe("handleLogout", () => {
+ it("should call fetch with correct URL and redirect on success", async () => {
+ (fetch as jest.Mock).mockResolvedValue({ ok: true });
+
+ await handleLogout();
+
+ expect(fetch).toHaveBeenCalledWith("http://localhost:3000/auth/logout", {
+ method: "POST",
+ credentials: "include",
+ });
+ expect(window.location.href).toBe("http://localhost/");
+ });
+
+ it("should log an error if fetch fails", async () => {
+ const consoleErrorSpy = jest.spyOn(console, "error").mockImplementation();
+
+ (fetch as jest.Mock).mockResolvedValue({ ok: false });
+
+ await handleLogout();
+
+ expect(consoleErrorSpy).toHaveBeenCalledWith("Failed to logout");
+ consoleErrorSpy.mockRestore();
+ });
+
+ it("should log an error if fetch throws an exception", async () => {
+ const consoleErrorSpy = jest.spyOn(console, "error").mockImplementation();
+
+ (fetch as jest.Mock).mockRejectedValue(new Error("Network error"));
+
+ await handleLogout();
+
+ expect(consoleErrorSpy).toHaveBeenCalledWith("Error logging out:", expect.any(Error));
+ consoleErrorSpy.mockRestore();
+ });
+ });
+
+ describe("syncTasksWithTwAndDb", () => {
+ it("should sync tasks and show success toast", async () => {
+ (getDocs as jest.Mock).mockResolvedValue({
+ docs: [{ id: "1", data: () => ({}) }],
+ });
+ (fetch as jest.Mock).mockResolvedValue({
+ ok: true,
+ json: jest.fn().mockResolvedValue([{ uuid: "1" }]),
+ });
+
+ await syncTasksWithTwAndDb(mockProps);
+
+ expect(getDocs).toHaveBeenCalledWith(tasksCollection);
+ expect(fetch).toHaveBeenCalledWith(
+ "http://localhost:3000/tasks?email=test%40example.com&encryptionSecret=secret&UUID=1234-5678",
+ {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ }
+ );
+ expect(toast.success).toHaveBeenCalledWith("Tasks synced succesfully!", expect.any(Object));
+ });
+
+ it("should show error toast if server is down", async () => {
+ (getDocs as jest.Mock).mockResolvedValue({
+ docs: [{ id: "1", data: () => ({}) }],
+ });
+ (fetch as jest.Mock).mockResolvedValue({ ok: false });
+
+ await syncTasksWithTwAndDb(mockProps);
+
+ expect(toast.error).toHaveBeenCalledWith("Server is down. Failed to sync tasks", expect.any(Object));
+ });
+
+ it("should log an error if there is an exception", async () => {
+ const consoleErrorSpy = jest.spyOn(console, "error").mockImplementation();
+
+ (getDocs as jest.Mock).mockRejectedValue(new Error("Firestore error"));
+
+ await syncTasksWithTwAndDb(mockProps);
+
+ consoleErrorSpy.mockRestore();
+ });
+ });
+
+ describe("deleteAllTasks", () => {
+ it("should delete all tasks and show success toast", async () => {
+ (getDocs as jest.Mock).mockResolvedValue({
+ docs: [{ id: "1", data: () => ({ email: "test@example.com" }) }],
+ });
+
+ await deleteAllTasks(mockProps);
+
+ expect(getDocs).toHaveBeenCalledWith(tasksCollection);
+ expect(deleteDoc).toHaveBeenCalled();
+ });
+
+ it("should show error toast if there is an exception", async () => {
+ const consoleErrorSpy = jest.spyOn(console, "error").mockImplementation();
+
+ (getDocs as jest.Mock).mockRejectedValue(new Error("Firestore error"));
+
+ await deleteAllTasks(mockProps);
+
+ expect(consoleErrorSpy).toHaveBeenCalledWith("Error deleting tasks for test@example.com:", expect.any(Error));
+ consoleErrorSpy.mockRestore();
+ });
+ });
+});
diff --git a/frontend/src/components/HomeComponents/SetupGuide/__tests__/SetupGuide.test.tsx b/frontend/src/components/HomeComponents/SetupGuide/__tests__/SetupGuide.test.tsx
new file mode 100644
index 00000000..5579719e
--- /dev/null
+++ b/frontend/src/components/HomeComponents/SetupGuide/__tests__/SetupGuide.test.tsx
@@ -0,0 +1,41 @@
+import { render, screen } from '@testing-library/react';
+import { SetupGuide } from '../SetupGuide';
+import { Props } from '../../../utils/types';
+import { url } from '@/lib/URLs';
+
+// Mocking the CopyableCode component
+jest.mock('../CopyableCode', () => ({
+ CopyableCode: ({ text, copyText }: { text: string; copyText: string }) => (
+ {text}
+ )
+}));
+
+describe('SetupGuide Component', () => {
+ const props: Props = {
+ name: "test-name",
+ encryption_secret: 'test-encryption-secret',
+ uuid: 'test-uuid'
+ };
+
+ test('renders SetupGuide component with correct text', () => {
+ render();
+ });
+
+ test('renders CopyableCode components with correct props', () => {
+ render();
+
+ // Check for CopyableCode components
+ const copyableCodeElements = screen.getAllByTestId('copyable-code');
+ expect(copyableCodeElements.length).toBe(5);
+
+ // Validate the text and copyText props of each CopyableCode component
+ expect(copyableCodeElements[0]).toHaveAttribute('data-text', 'task --version');
+ expect(copyableCodeElements[0]).toHaveAttribute('data-copytext', 'task --version');
+ expect(copyableCodeElements[1]).toHaveAttribute('data-text', `task config sync.encryption_secret ${props.encryption_secret}`);
+ expect(copyableCodeElements[1]).toHaveAttribute('data-copytext', `task config sync.encryption_secret ${props.encryption_secret}`);
+ expect(copyableCodeElements[2]).toHaveAttribute('data-text', `task config sync.server.origin ${url.containerOrigin}`);
+ expect(copyableCodeElements[2]).toHaveAttribute('data-copytext', `task config sync.server.origin ${url.containerOrigin}`);
+ expect(copyableCodeElements[3]).toHaveAttribute('data-text', `task config sync.server.client_id ${props.uuid}`);
+ expect(copyableCodeElements[3]).toHaveAttribute('data-copytext', `task config sync.server.client_id ${props.uuid}`);
+ });
+});
diff --git a/frontend/src/components/HomeComponents/Tasks/Tasks.tsx b/frontend/src/components/HomeComponents/Tasks/Tasks.tsx
index ee959e33..277ffb7b 100644
--- a/frontend/src/components/HomeComponents/Tasks/Tasks.tsx
+++ b/frontend/src/components/HomeComponents/Tasks/Tasks.tsx
@@ -18,7 +18,7 @@ import { ArrowUpDown, CheckIcon, CopyIcon, Folder, PencilIcon, Tag, Trash2Icon,
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import CopyToClipboard from "react-copy-to-clipboard";
-import { formattedDate, getDisplayedPages, handleCopy, markTaskAsCompleted, markTaskAsDeleted, Props, sortTasks, sortTasksById } from "./tasks-utils";
+import { formattedDate, getDisplayedPages, handleCopy, handleDate, markTaskAsCompleted, markTaskAsDeleted, Props, sortTasks, sortTasksById } from "./tasks-utils";
import Pagination from "./Pagination";
import { url } from "@/lib/URLs";
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select";
@@ -302,23 +302,6 @@ export const Tasks = (props: Props) => {
}
};
- const handleDate = (v: string) => {
- const date = v;
- const isValid = /^\d{4}-\d{2}-\d{2}$/.test(date);
- if (!isValid) {
- toast.error("Invalid Date Format. Please use the YYYY-MM-DD format.", {
- position: "bottom-left",
- autoClose: 3000,
- hideProgressBar: false,
- closeOnClick: true,
- pauseOnHover: true,
- draggable: true,
- progress: undefined,
- });
- return false
- } return true;
- };
-
const handleProjectChange = (value: string) => {
setSelectedProject(value);
};
@@ -356,7 +339,7 @@ export const Tasks = (props: Props) => {
selectedStatus={selectedStatus}
setSelectedStatus={setSelectedStatus}
/>
-
+
Tasks
diff --git a/frontend/src/components/HomeComponents/Tasks/__tests__/Tasks.test.tsx b/frontend/src/components/HomeComponents/Tasks/__tests__/Tasks.test.tsx
new file mode 100644
index 00000000..f281be78
--- /dev/null
+++ b/frontend/src/components/HomeComponents/Tasks/__tests__/Tasks.test.tsx
@@ -0,0 +1,19 @@
+import { render, screen } from '@testing-library/react';
+import { Tasks } from '../Tasks'; // Ensure correct path to Tasks component
+
+// Mock props for the Tasks component
+const mockProps = {
+ email: 'test@example.com',
+ encryptionSecret: 'mockEncryptionSecret',
+ UUID: 'mockUUID',
+};
+
+// Mock fetch function to simulate API calls
+global.fetch = jest.fn().mockResolvedValue({ ok: true });
+
+describe('Tasks Component', () => {
+ test('renders tasks component', async () => {
+ render();
+ expect(screen.getByTestId("tasks")).toBeInTheDocument();
+ });
+});
diff --git a/frontend/src/components/HomeComponents/Tasks/__tests__/tasks-utils.test.ts b/frontend/src/components/HomeComponents/Tasks/__tests__/tasks-utils.test.ts
index d8b2d233..f4202a17 100644
--- a/frontend/src/components/HomeComponents/Tasks/__tests__/tasks-utils.test.ts
+++ b/frontend/src/components/HomeComponents/Tasks/__tests__/tasks-utils.test.ts
@@ -1,5 +1,12 @@
import { toast } from "react-toastify";
-import { formattedDate, getDisplayedPages, handleCopy, sortTasks, sortTasksById } from "../tasks-utils";
+import {
+ formattedDate,
+ getDisplayedPages,
+ handleCopy,
+ handleDate,
+ sortTasks,
+ sortTasksById,
+} from "../tasks-utils";
import { Task } from "@/components/utils/types";
describe("sortTasks", () => {
@@ -318,18 +325,19 @@ describe("sortTasksById", () => {
});
});
-jest.mock('react-toastify', () => ({
+jest.mock("react-toastify", () => ({
toast: {
success: jest.fn(),
+ error: jest.fn(),
},
}));
-describe('handleCopy', () => {
- it('shows success toast with correct message', () => {
- const text = 'Sample text';
+describe("handleCopy", () => {
+ it("shows success toast with correct message", () => {
+ const text = "Sample text";
handleCopy(text);
expect(toast.success).toHaveBeenCalledWith(`${text} copied to clipboard!`, {
- position: 'bottom-left',
+ position: "bottom-left",
autoClose: 3000,
hideProgressBar: false,
closeOnClick: true,
@@ -340,8 +348,8 @@ describe('handleCopy', () => {
});
});
-describe('getDisplayedPages', () => {
- it('returns all pages if totalPages is less than or equal to 3', () => {
+describe("getDisplayedPages", () => {
+ it("returns all pages if totalPages is less than or equal to 3", () => {
expect(getDisplayedPages(1, 1)).toEqual([1]);
expect(getDisplayedPages(2, 1)).toEqual([1, 2]);
expect(getDisplayedPages(2, 2)).toEqual([1, 2]);
@@ -350,19 +358,19 @@ describe('getDisplayedPages', () => {
expect(getDisplayedPages(3, 3)).toEqual([1, 2, 3]);
});
- it('returns first three pages if currentPage is 1', () => {
+ it("returns first three pages if currentPage is 1", () => {
expect(getDisplayedPages(4, 1)).toEqual([1, 2, 3]);
expect(getDisplayedPages(5, 1)).toEqual([1, 2, 3]);
expect(getDisplayedPages(6, 1)).toEqual([1, 2, 3]);
});
- it('returns last three pages if currentPage is the last page', () => {
+ it("returns last three pages if currentPage is the last page", () => {
expect(getDisplayedPages(4, 4)).toEqual([2, 3, 4]);
expect(getDisplayedPages(5, 5)).toEqual([3, 4, 5]);
expect(getDisplayedPages(6, 6)).toEqual([4, 5, 6]);
});
- it('returns three consecutive pages centered around the currentPage if it is in the middle', () => {
+ it("returns three consecutive pages centered around the currentPage if it is in the middle", () => {
expect(getDisplayedPages(5, 2)).toEqual([1, 2, 3]);
expect(getDisplayedPages(5, 3)).toEqual([2, 3, 4]);
expect(getDisplayedPages(5, 4)).toEqual([3, 4, 5]);
@@ -370,3 +378,58 @@ describe('getDisplayedPages', () => {
expect(getDisplayedPages(6, 4)).toEqual([3, 4, 5]);
});
});
+
+describe("handleDate", () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it("should return true for valid date format YYYY-MM-DD", () => {
+ const validDate = "2023-06-21";
+ const result = handleDate(validDate);
+ expect(result).toBe(true);
+ expect(toast.error).not.toHaveBeenCalled();
+ });
+
+ it("should return false and show error toast for invalid date format", () => {
+ const invalidDate = "06/21/2023";
+ const result = handleDate(invalidDate);
+ expect(result).toBe(false);
+ expect(toast.error).toHaveBeenCalledWith(
+ "Invalid Date Format. Please use the YYYY-MM-DD format.",
+ {
+ position: "bottom-left",
+ autoClose: 3000,
+ hideProgressBar: false,
+ closeOnClick: true,
+ pauseOnHover: true,
+ draggable: true,
+ progress: undefined,
+ }
+ );
+ });
+
+ it("should return true and show no toast for empty date string", () => {
+ const emptyDate = "";
+ const result = handleDate(emptyDate);
+ expect(result).toBe(true);
+ });
+
+ it("should return false and show error toast for date with invalid characters", () => {
+ const invalidDate = "2023-06-21a";
+ const result = handleDate(invalidDate);
+ expect(result).toBe(false);
+ expect(toast.error).toHaveBeenCalledWith(
+ "Invalid Date Format. Please use the YYYY-MM-DD format.",
+ {
+ position: "bottom-left",
+ autoClose: 3000,
+ hideProgressBar: false,
+ closeOnClick: true,
+ pauseOnHover: true,
+ draggable: true,
+ progress: undefined,
+ }
+ );
+ });
+});
diff --git a/frontend/src/components/HomeComponents/Tasks/tasks-utils.ts b/frontend/src/components/HomeComponents/Tasks/tasks-utils.ts
index d3d611bb..62527516 100644
--- a/frontend/src/components/HomeComponents/Tasks/tasks-utils.ts
+++ b/frontend/src/components/HomeComponents/Tasks/tasks-utils.ts
@@ -159,3 +159,21 @@ export const handleCopy = (text: string) => {
progress: undefined,
});
};
+
+export const handleDate = (v: string) => {
+ const date = v;
+ const isValid = date === "" || /^\d{4}-\d{2}-\d{2}$/.test(date);
+ if (!isValid) {
+ toast.error("Invalid Date Format. Please use the YYYY-MM-DD format.", {
+ position: "bottom-left",
+ autoClose: 3000,
+ hideProgressBar: false,
+ closeOnClick: true,
+ pauseOnHover: true,
+ draggable: true,
+ progress: undefined,
+ });
+ return false;
+ }
+ return true;
+};
diff --git a/frontend/src/components/LandingComponents/Hero/__tests__/Hero.test.tsx b/frontend/src/components/LandingComponents/Hero/__tests__/Hero.test.tsx
new file mode 100644
index 00000000..07899fbd
--- /dev/null
+++ b/frontend/src/components/LandingComponents/Hero/__tests__/Hero.test.tsx
@@ -0,0 +1,36 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import { Hero } from '../Hero';
+import { url } from '@/lib/URLs';
+
+// mocking the HeroCards component
+jest.mock('../HeroCards', () => ({
+ HeroCards: () =>
HeroCards Component
+}));
+
+// mocking the buttonVariants function
+jest.mock('../../../ui/button', () => ({
+ Button: ({ children, className }: { children: React.ReactNode; className?: string }) => ,
+ buttonVariants: ({ variant }: { variant: string }) => `btn-${variant}`
+}));
+
+describe('Hero Component', () => {
+ test('renders Hero component with correct text and elements', () => {
+ render();
+ // check for buttons and links
+ const signInButton = screen.getByText(/Sign in to get started/i);
+ expect(signInButton.closest('a')).toHaveAttribute('href', url.backendURL + 'auth/oauth');
+ expect(signInButton).toHaveClass('w-full md:w-1/3 bg-blue-400 hover:bg-blue-500');
+
+ const githubButton = screen.getByText(/Github Repository/i);
+ expect(githubButton.closest('a')).toHaveAttribute('href', 'https://github.com/its-me-abhishek/ccsync');
+ expect(githubButton.closest('a')).toHaveAttribute('target', '_blank');
+ expect(githubButton).toHaveClass('w-full md:w-1/3 btn-outline');
+ expect(screen.getByTestId('hero-cards')).toBeInTheDocument();
+ });
+
+ test('renders HeroCards component', () => {
+ render();
+ expect(screen.getByTestId('hero-cards')).toBeInTheDocument();
+ });
+});
diff --git a/frontend/src/components/LandingComponents/Navbar/Navbar.tsx b/frontend/src/components/LandingComponents/Navbar/Navbar.tsx
index 1e9691f8..f42b3cc9 100644
--- a/frontend/src/components/LandingComponents/Navbar/Navbar.tsx
+++ b/frontend/src/components/LandingComponents/Navbar/Navbar.tsx
@@ -19,7 +19,7 @@ import {
href="/"
className="ml-2 font-bold text-xl flex items-center dark:hidden"
>
-
+
'mocked-logo.png');
+jest.mock('../../../../assets/logo_light.png', () => 'mocked-logo-light.png');
+
+describe('Navbar component', () => {
+ it('renders navbar with desktop and mobile navigation', () => {
+ const { getByAltText, getByText } = render();
+
+ const logoLight = getByAltText('Logo-Light');
+ expect(logoLight).toBeInTheDocument();
+
+ const logoRegular = getByAltText('Logo');
+ expect(logoRegular).toBeInTheDocument();
+
+ const homeLink = getByText('Home');
+ expect(homeLink).toBeInTheDocument();
+
+ const aboutLink = getByText('About');
+ expect(aboutLink).toBeInTheDocument();
+
+ const howItWorksLink = getByText('How it works');
+ expect(howItWorksLink).toBeInTheDocument();
+
+ const contactUsLink = getByText('Contact Us');
+ expect(contactUsLink).toBeInTheDocument();
+
+ const faqLink = getByText('FAQ');
+ expect(faqLink).toBeInTheDocument();
+ });
+});
diff --git a/frontend/src/components/LandingComponents/Navbar/__tests__/NavbarDesktop.test.tsx b/frontend/src/components/LandingComponents/Navbar/__tests__/NavbarDesktop.test.tsx
new file mode 100644
index 00000000..2267f3cf
--- /dev/null
+++ b/frontend/src/components/LandingComponents/Navbar/__tests__/NavbarDesktop.test.tsx
@@ -0,0 +1,24 @@
+import { render } from "@testing-library/react";
+import { NavbarDesktop } from "../NavbarDesktop";
+
+// Mock external dependencies
+jest.mock("../navbar-utils", () => ({
+ syncTasksWithTwAndDb: jest.fn(),
+ deleteAllTasks: jest.fn(),
+ handleLogout: jest.fn(),
+ routeList: [
+ { href: "#", label: "Home" },
+ ],
+}));
+
+describe("NavbarDesktop", () => {
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+
+ it("renders correctly", () => {
+ render();
+ });
+});
diff --git a/frontend/src/components/LandingComponents/Navbar/__tests__/navbar-utils.test.ts b/frontend/src/components/LandingComponents/Navbar/__tests__/navbar-utils.test.ts
new file mode 100644
index 00000000..70372592
--- /dev/null
+++ b/frontend/src/components/LandingComponents/Navbar/__tests__/navbar-utils.test.ts
@@ -0,0 +1,25 @@
+import { RouteProps, routeList } from "../navbar-utils";
+
+describe("routeList", () => {
+ it("should be an array of RouteProps", () => {
+ routeList.forEach((route) => {
+ expect(route).toHaveProperty("href");
+ expect(route).toHaveProperty("label");
+
+ expect(typeof route.href).toBe("string");
+ expect(typeof route.label).toBe("string");
+ });
+ });
+
+ it("should contain the correct routes", () => {
+ const expectedRoutes: RouteProps[] = [
+ { href: "#", label: "Home" },
+ { href: "#about", label: "About" },
+ { href: "#howItWorks", label: "How it works" },
+ { href: "#contact", label: "Contact Us" },
+ { href: "#faq", label: "FAQ" },
+ ];
+
+ expect(routeList).toEqual(expectedRoutes);
+ });
+});
diff --git a/frontend/src/components/LandingComponents/Navbar/navbar-utils.ts b/frontend/src/components/LandingComponents/Navbar/navbar-utils.ts
index bdf4f9b8..26eaf782 100644
--- a/frontend/src/components/LandingComponents/Navbar/navbar-utils.ts
+++ b/frontend/src/components/LandingComponents/Navbar/navbar-utils.ts
@@ -1,28 +1,27 @@
export interface RouteProps {
- href: string;
- label: string;
- }
-
- export const routeList: RouteProps[] = [
- {
- href: "#",
- label: "Home",
- },
- {
- href: "#about",
- label: "About",
- },
- {
- href: "#howItWorks",
- label: "How it works",
- },
- {
- href: "#contact",
- label: "Contact Us",
- },
- {
- href: "#faq",
- label: "FAQ",
- },
- ];
-
\ No newline at end of file
+ href: string;
+ label: string;
+}
+
+export const routeList: RouteProps[] = [
+ {
+ href: "#",
+ label: "Home",
+ },
+ {
+ href: "#about",
+ label: "About",
+ },
+ {
+ href: "#howItWorks",
+ label: "How it works",
+ },
+ {
+ href: "#contact",
+ label: "Contact Us",
+ },
+ {
+ href: "#faq",
+ label: "FAQ",
+ },
+];
diff --git a/frontend/src/lib/URLs.ts b/frontend/src/lib/URLs.ts
index efa97c25..98b6ea11 100644
--- a/frontend/src/lib/URLs.ts
+++ b/frontend/src/lib/URLs.ts
@@ -1,5 +1,11 @@
-export const url = {
+const isTesting = true;
+
+export const url = isTesting ? {
+ backendURL: '',
+ frontendURL: '',
+ containerOrigin: '',
+} : {
backendURL: import.meta.env.VITE_BACKEND_URL,
frontendURL: import.meta.env.VITE_FRONTEND_URL,
- containerOrigin: import.meta.env.VITE_CONATINER_ORIGIN,
+ containerOrigin: import.meta.env.VITE_CONTAINER_ORIGIN,
};