From 259bcc0025ed09fa090cdeabe1aab5582dee45a3 Mon Sep 17 00:00:00 2001
From: Abhishek
Date: Mon, 1 Jul 2024 19:55:17 +0530
Subject: [PATCH 1/5] test: add home and landing page tests
---
frontend/jest.config.cjs | 1 +
.../components/__tests__/HomePage.test.tsx | 98 +++++++++++++++++++
.../components/__tests__/LandingPage.test.tsx | 43 ++++++++
3 files changed, 142 insertions(+)
create mode 100644 frontend/src/components/__tests__/HomePage.test.tsx
create mode 100644 frontend/src/components/__tests__/LandingPage.test.tsx
diff --git a/frontend/jest.config.cjs b/frontend/jest.config.cjs
index a4ba050c..d38e92f4 100644
--- a/frontend/jest.config.cjs
+++ b/frontend/jest.config.cjs
@@ -3,6 +3,7 @@ module.exports = {
testEnvironment: 'jsdom',
moduleNameMapper: {
'^@/(.*)$': ['/src/$1', '/lib/$1'],
+ "\\.(scss|sass|css)$": "identity-obj-proxy"
},
transformIgnorePatterns: [
"/node_modules/(?!react-toastify)"
diff --git a/frontend/src/components/__tests__/HomePage.test.tsx b/frontend/src/components/__tests__/HomePage.test.tsx
new file mode 100644
index 00000000..eabd5837
--- /dev/null
+++ b/frontend/src/components/__tests__/HomePage.test.tsx
@@ -0,0 +1,98 @@
+import { render, screen, waitFor } from "@testing-library/react";
+import { HomePage } from "../HomePage";
+
+// Mock dependencies
+jest.mock("../HomeComponents/Navbar/Navbar", () => ({
+ Navbar: () => Mocked Navbar
,
+}));
+jest.mock("../HomeComponents/Hero/Hero", () => ({
+ Hero: () => Mocked Hero
,
+}));
+jest.mock("../HomeComponents/Footer/Footer", () => ({
+ Footer: () => Mocked Footer
,
+}));
+jest.mock("../HomeComponents/SetupGuide/SetupGuide", () => ({
+ SetupGuide: () => Mocked SetupGuide
,
+}));
+jest.mock("../HomeComponents/FAQ/FAQ", () => ({
+ FAQ: () => Mocked FAQ
,
+}));
+jest.mock("../HomeComponents/Tasks/Tasks", () => ({
+ Tasks: () => Mocked Tasks
,
+}));
+
+const mockedNavigate = jest.fn();
+jest.mock("react-router", () => ({
+ useNavigate: () => mockedNavigate,
+}));
+jest.mock("@/components/utils/URLs", () => ({
+ url: {
+ backendURL: "http://mocked-backend-url/",
+ containerOrigin: "http://mocked-origin/",
+ frontendURL: "http://mocked-frontend-url/",
+ },
+}));
+
+// Mock fetch
+global.fetch = jest.fn(() =>
+ Promise.resolve({
+ ok: true,
+ json: () =>
+ Promise.resolve({
+ picture: "mocked-picture-url",
+ email: "mocked-email",
+ encryption_secret: "mocked-encryption-secret",
+ uuid: "mocked-uuid",
+ name: "mocked-name",
+ }),
+ })
+) as jest.Mock;
+
+describe("HomePage", () => {
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it("renders correctly when user info is fetched successfully", async () => {
+ render(
+
+ );
+
+ await waitFor(() => {
+ expect(screen.getByText("Mocked Navbar")).toBeInTheDocument();
+ expect(screen.getByText("Mocked Hero")).toBeInTheDocument();
+ expect(screen.getByText("Mocked Tasks")).toBeInTheDocument();
+ expect(screen.getByText("Mocked SetupGuide")).toBeInTheDocument();
+ expect(screen.getByText("Mocked FAQ")).toBeInTheDocument();
+ expect(screen.getByText("Mocked Footer")).toBeInTheDocument();
+ });
+ });
+
+ it("renders session expired message when user info fetch fails", async () => {
+ (fetch as jest.Mock).mockImplementationOnce(() =>
+ Promise.resolve({
+ ok: false,
+ })
+ );
+
+ render(
+
+ );
+
+ await waitFor(() => {
+ expect(screen.getByText("Session has been expired.")).toBeInTheDocument();
+ });
+ });
+
+ it("navigates to home page on fetch error", async () => {
+ (fetch as jest.Mock).mockImplementationOnce(() => Promise.reject("Fetch error"));
+
+ render(
+
+ );
+
+ await waitFor(() => {
+ expect(mockedNavigate).toHaveBeenCalledWith("/");
+ });
+ });
+});
diff --git a/frontend/src/components/__tests__/LandingPage.test.tsx b/frontend/src/components/__tests__/LandingPage.test.tsx
new file mode 100644
index 00000000..93464876
--- /dev/null
+++ b/frontend/src/components/__tests__/LandingPage.test.tsx
@@ -0,0 +1,43 @@
+import { render, screen } from "@testing-library/react";
+import { LandingPage } from "../LandingPage";
+
+// Mock dependencies
+jest.mock("../LandingComponents/Navbar/Navbar", () => ({
+ Navbar: () => Mocked Navbar
,
+}));
+jest.mock("../LandingComponents/Hero/Hero", () => ({
+ Hero: () => Mocked Hero
,
+}));
+jest.mock("../LandingComponents/About/About", () => ({
+ About: () => Mocked About
,
+}));
+jest.mock("../LandingComponents/HowItWorks/HowItWorks", () => ({
+ HowItWorks: () => Mocked HowItWorks
,
+}));
+jest.mock("../LandingComponents/Contact/Contact", () => ({
+ Contact: () => Mocked Contact
,
+}));
+jest.mock("../LandingComponents/FAQ/FAQ", () => ({
+ FAQ: () => Mocked FAQ
,
+}));
+jest.mock("../LandingComponents/Footer/Footer", () => ({
+ Footer: () => Mocked Footer
,
+}));
+jest.mock("../../components/utils/ScrollToTop", () => ({
+ ScrollToTop: () => Mocked ScrollToTop
,
+}));
+
+describe("LandingPage", () => {
+ it("renders all components correctly", () => {
+ render();
+
+ expect(screen.getByText("Mocked Navbar")).toBeInTheDocument();
+ expect(screen.getByText("Mocked Hero")).toBeInTheDocument();
+ expect(screen.getByText("Mocked About")).toBeInTheDocument();
+ expect(screen.getByText("Mocked HowItWorks")).toBeInTheDocument();
+ expect(screen.getByText("Mocked Contact")).toBeInTheDocument();
+ expect(screen.getByText("Mocked FAQ")).toBeInTheDocument();
+ expect(screen.getByText("Mocked Footer")).toBeInTheDocument();
+ expect(screen.getByText("Mocked ScrollToTop")).toBeInTheDocument();
+ });
+});
From acf2d8f9045342ed02836a66af7aab36512b82f6 Mon Sep 17 00:00:00 2001
From: Abhishek
Date: Mon, 1 Jul 2024 19:55:33 +0530
Subject: [PATCH 2/5] test: add task tests
---
.../Tasks/__tests__/Tasks.test.tsx | 14 ++-
.../Tasks/__tests__/tasks-utils.test.ts | 100 ++++++++++++++++++
2 files changed, 113 insertions(+), 1 deletion(-)
diff --git a/frontend/src/components/HomeComponents/Tasks/__tests__/Tasks.test.tsx b/frontend/src/components/HomeComponents/Tasks/__tests__/Tasks.test.tsx
index f281be78..34a40c6d 100644
--- a/frontend/src/components/HomeComponents/Tasks/__tests__/Tasks.test.tsx
+++ b/frontend/src/components/HomeComponents/Tasks/__tests__/Tasks.test.tsx
@@ -8,7 +8,19 @@ const mockProps = {
UUID: 'mockUUID',
};
-// Mock fetch function to simulate API calls
+// Mock functions and modules
+jest.mock('react-toastify', () => ({
+ toast: {
+ success: jest.fn(),
+ error: jest.fn(),
+ },
+}));
+
+jest.mock('../tasks-utils', () => ({
+ markTaskAsCompleted: jest.fn(),
+ markTaskAsDeleted: jest.fn(),
+}));
+
global.fetch = jest.fn().mockResolvedValue({ ok: true });
describe('Tasks Component', () => {
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 f4202a17..432e5a4f 100644
--- a/frontend/src/components/HomeComponents/Tasks/__tests__/tasks-utils.test.ts
+++ b/frontend/src/components/HomeComponents/Tasks/__tests__/tasks-utils.test.ts
@@ -6,6 +6,8 @@ import {
handleDate,
sortTasks,
sortTasksById,
+ markTaskAsCompleted,
+ markTaskAsDeleted,
} from "../tasks-utils";
import { Task } from "@/components/utils/types";
@@ -433,3 +435,101 @@ describe("handleDate", () => {
);
});
});
+
+import { url } from "@/components/utils/URLs";
+// Mock fetch and toast
+global.fetch = jest.fn();
+jest.mock("react-toastify", () => ({
+ toast: {
+ success: jest.fn(),
+ error: jest.fn(),
+ },
+}));
+
+describe("markTaskAsCompleted", () => {
+ const email = "test@example.com";
+ const encryptionSecret = "secret";
+ const UUID = "user-uuid";
+ const taskuuid = "task-uuid";
+ const backendURL = `${url.backendURL}complete-task`;
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it("marks task as completed successfully", async () => {
+ (fetch as jest.Mock).mockResolvedValueOnce({ ok: true });
+
+ await markTaskAsCompleted(email, encryptionSecret, UUID, taskuuid);
+
+ expect(fetch).toHaveBeenCalledWith(backendURL, {
+ method: "POST",
+ body: JSON.stringify({
+ email: email,
+ encryptionSecret: encryptionSecret,
+ UUID: UUID,
+ taskuuid: taskuuid,
+ }),
+ });
+ expect(toast.success).toHaveBeenCalledWith(
+ "Task marked as completed successfully!",
+ {
+ position: "bottom-left",
+ autoClose: 3000,
+ hideProgressBar: false,
+ closeOnClick: true,
+ pauseOnHover: true,
+ draggable: true,
+ progress: undefined,
+ }
+ );
+ });
+});
+
+global.fetch = jest.fn();
+jest.mock("react-toastify", () => ({
+ toast: {
+ success: jest.fn(),
+ error: jest.fn(),
+ },
+}));
+
+describe("markTaskAsDeleted", () => {
+ const email = "test@example.com";
+ const encryptionSecret = "secret";
+ const UUID = "user-uuid";
+ const taskuuid = "task-uuid";
+ const backendURL = `${url.backendURL}delete-task`;
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it("marks task as deleted successfully", async () => {
+ (fetch as jest.Mock).mockResolvedValueOnce({ ok: true });
+
+ await markTaskAsDeleted(email, encryptionSecret, UUID, taskuuid);
+
+ expect(fetch).toHaveBeenCalledWith(backendURL, {
+ method: "POST",
+ body: JSON.stringify({
+ email: email,
+ encryptionSecret: encryptionSecret,
+ UUID: UUID,
+ taskuuid: taskuuid,
+ }),
+ });
+ expect(toast.success).toHaveBeenCalledWith(
+ "Task marked as deleted successfully!",
+ {
+ position: "bottom-left",
+ autoClose: 3000,
+ hideProgressBar: false,
+ closeOnClick: true,
+ pauseOnHover: true,
+ draggable: true,
+ progress: undefined,
+ }
+ );
+ });
+});
From 429cf3726d64c3a2518778b21538e922501aad42 Mon Sep 17 00:00:00 2001
From: Abhishek
Date: Mon, 1 Jul 2024 20:49:46 +0530
Subject: [PATCH 3/5] test: add script for generating badge data
---
frontend/badge.py | 26 ++++++++++++++++++++++++++
frontend/coverage-report.json | 3 +++
2 files changed, 29 insertions(+)
create mode 100644 frontend/badge.py
create mode 100644 frontend/coverage-report.json
diff --git a/frontend/badge.py b/frontend/badge.py
new file mode 100644
index 00000000..fd87e9b5
--- /dev/null
+++ b/frontend/badge.py
@@ -0,0 +1,26 @@
+# script for extracting code coverage value for readme
+
+from bs4 import BeautifulSoup
+import json
+
+def extract_coverage_data(file_path):
+ with open(file_path, "r", encoding="utf-8") as file:
+ html_content = file.read()
+ soup = BeautifulSoup(html_content, "lxml")
+ div = soup.find("div", class_="fl pad1y space-right2")
+ percentage = div.find("span", class_="strong").get_text(strip=True)
+ return {"frontend": percentage}
+
+def save_to_json(data, output_path):
+ with open(output_path, "w", encoding="utf-8") as json_file:
+ json.dump(data, json_file, indent=4)
+
+
+html_file_path = "./coverage/lcov-report/index.html"
+json_file_path = "./coverage-report.json"
+
+coverage_data = extract_coverage_data(html_file_path)
+
+save_to_json(coverage_data, json_file_path)
+
+print(f"Coverage data has been saved to {json_file_path}")
diff --git a/frontend/coverage-report.json b/frontend/coverage-report.json
new file mode 100644
index 00000000..234eb343
--- /dev/null
+++ b/frontend/coverage-report.json
@@ -0,0 +1,3 @@
+{
+ "frontend": "80.33%"
+}
\ No newline at end of file
From 4773b8e06cab36410f649c08d9e1b06c174d5086 Mon Sep 17 00:00:00 2001
From: Abhishek
Date: Mon, 1 Jul 2024 22:52:35 +0530
Subject: [PATCH 4/5] docs: add test coverage badge
---
README.md | 35 +++++++++++++++++++----------------
1 file changed, 19 insertions(+), 16 deletions(-)
diff --git a/README.md b/README.md
index 4c82d46b..ed940367 100644
--- a/README.md
+++ b/README.md
@@ -14,6 +14,9 @@
+
+
Website •
@@ -23,7 +26,6 @@
---
-
## Guide to setup the frontend for development purposes:
- ```bash
@@ -36,21 +38,21 @@
### Set environment variables in .env as:
- *For docker usage:*
+_For docker usage:_
- ```bash
- VITE_BACKEND_URL="http://localhost:8000/"
- VITE_FRONTEND_URL="http://localhost:80"
- VITE_CONTAINER_ORIGIN="http://localhost:8080/"
- ```
+```bash
+VITE_BACKEND_URL="http://localhost:8000/"
+VITE_FRONTEND_URL="http://localhost:80"
+VITE_CONTAINER_ORIGIN="http://localhost:8080/"
+```
- *For normal npm usage:*
+_For normal npm usage:_
- ```bash
- VITE_BACKEND_URL="http://localhost:8000/"
- VITE_FRONTEND_URL="http://localhost:5173"
- VITE_CONTAINER_ORIGIN="http://localhost:8080/"
- ```
+```bash
+VITE_BACKEND_URL="http://localhost:8000/"
+VITE_FRONTEND_URL="http://localhost:5173"
+VITE_CONTAINER_ORIGIN="http://localhost:8080/"
+```
- Note: The ports can be changed on demand, and if you want to do so, be sure to change ports of the Dockerfiles as well as the ports in docker-compose.yml
@@ -72,7 +74,7 @@
- Go to [Google cloud credential page](https://console.cloud.google.com/apis/credentials) for generating client id and secret.
- Add the Client ID and secret as an environment variable
-- *Sample .env format:*
+- _Sample .env format:_
```bash
CLIENT_ID="client_ID"
@@ -97,10 +99,12 @@
only while using Docker Container
use
+
```
FRONTEND_ORIGIN_DEV="http://localhost:"
CONTAINER_ORIGIN="http://localhost:8080/"
```
+
if you want to run by `npm run dev`
- Run the application:
@@ -116,7 +120,6 @@
docker-compose up
```
-
## Taskchampion Sync Server:
- Setup the Taskchampion Sync Server "As a Container" by following the [official documentation](https://github.com/GothenburgBitFactory/taskchampion-sync-server/tree/main)
@@ -159,7 +162,7 @@
// Initialize Firebase
export const app = initializeApp(firebaseConfig);
```
-- Download it, and store it at frontend/src/lib/ by the name *firestore.js*
+- Download it, and store it at frontend/src/lib/ by the name _firestore.js_
## Run the Containers:
From 25598b91114fc674e074b82e69dd78b85665be1a Mon Sep 17 00:00:00 2001
From: Abhishek
Date: Mon, 1 Jul 2024 22:56:01 +0530
Subject: [PATCH 5/5] docs: fix typo
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index ed940367..55c2991b 100644
--- a/README.md
+++ b/README.md
@@ -93,7 +93,7 @@ VITE_CONTAINER_ORIGIN="http://localhost:8080/"
```
FRONTEND_ORIGIN_DEV="http://localhost"
- CONTAINER_ORIGIN="http://172.19.0.2:8080/"
+ CONTAINER_ORIGIN="http://YOUR_DOCKER_CONTAINER_IP:8080/"
```
only while using Docker Container