Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 20 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
<a href="https://github.com/its-me-abhishek/ccsync/pulls?q=is%3Apr+is%3Aclosed">
<img src="https://img.shields.io/github/issues-pr-closed-raw/its-me-abhishek/ccsync?style=flat-square&logo=github&logoColor=white"
alt="Closed pull requests"></a>
<img src="https://img.shields.io/badge/dynamic/json?url=https://raw.githubusercontent.com/its-me-abhishek/ccsync/main/frontend/coverage-report.json&query=$.frontend&label=frontend coverage"
alt="Dynamic JSON Badge" >

</p>
<p align="center">
<a href="">Website</a> •
Expand All @@ -23,7 +26,6 @@

---


## Guide to setup the frontend for development purposes:

- ```bash
Expand All @@ -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

Expand All @@ -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"
Expand All @@ -91,16 +93,18 @@

```
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

use

```
FRONTEND_ORIGIN_DEV="http://localhost:"
CONTAINER_ORIGIN="http://localhost:8080/"
```

if you want to run by `npm run dev`

- Run the application:
Expand All @@ -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)
Expand Down Expand Up @@ -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:

Expand Down
26 changes: 26 additions & 0 deletions frontend/badge.py
Original file line number Diff line number Diff line change
@@ -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}")
3 changes: 3 additions & 0 deletions frontend/coverage-report.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"frontend": "80.33%"
}
1 change: 1 addition & 0 deletions frontend/jest.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module.exports = {
testEnvironment: 'jsdom',
moduleNameMapper: {
'^@/(.*)$': ['<rootDir>/src/$1', '<rootDir>/lib/$1'],
"\\.(scss|sass|css)$": "identity-obj-proxy"
},
transformIgnorePatterns: [
"/node_modules/(?!react-toastify)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {
handleDate,
sortTasks,
sortTasksById,
markTaskAsCompleted,
markTaskAsDeleted,
} from "../tasks-utils";
import { Task } from "@/components/utils/types";

Expand Down Expand Up @@ -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,
}
);
});
});
98 changes: 98 additions & 0 deletions frontend/src/components/__tests__/HomePage.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { render, screen, waitFor } from "@testing-library/react";
import { HomePage } from "../HomePage";

// Mock dependencies
jest.mock("../HomeComponents/Navbar/Navbar", () => ({
Navbar: () => <div>Mocked Navbar</div>,
}));
jest.mock("../HomeComponents/Hero/Hero", () => ({
Hero: () => <div>Mocked Hero</div>,
}));
jest.mock("../HomeComponents/Footer/Footer", () => ({
Footer: () => <div>Mocked Footer</div>,
}));
jest.mock("../HomeComponents/SetupGuide/SetupGuide", () => ({
SetupGuide: () => <div>Mocked SetupGuide</div>,
}));
jest.mock("../HomeComponents/FAQ/FAQ", () => ({
FAQ: () => <div>Mocked FAQ</div>,
}));
jest.mock("../HomeComponents/Tasks/Tasks", () => ({
Tasks: () => <div>Mocked Tasks</div>,
}));

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(
<HomePage />
);

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(
<HomePage />
);

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(
<HomePage />
);

await waitFor(() => {
expect(mockedNavigate).toHaveBeenCalledWith("/");
});
});
});
Loading