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" > - Logo + Light-Logo ({ + 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" > - Logo + Logo-Light
'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, };