Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
ac034a8
refactor: update linting rules and remove max-warnings restriction
Ali-Sdg90 Nov 27, 2025
7371964
refactor(calendar): simplify today badge implementation and adjust st…
Ali-Sdg90 Nov 27, 2025
a8d6ff1
feat(EventPopup): enhance popup animations and add unit tests for ren…
Ali-Sdg90 Nov 28, 2025
1a58fb8
fix(EventPopup): enhance arrow styling and transition effects for imp…
Ali-Sdg90 Nov 28, 2025
e8ec80b
fix(CSCalendar): add effect to strip native titles from calendar cell…
Ali-Sdg90 Dec 7, 2025
da83502
refactor(EventPopup): simplify event title display and adjust layout …
Ali-Sdg90 Dec 7, 2025
8837819
refactor(EventPopup): update layout and styling for improved readabil…
Ali-Sdg90 Dec 7, 2025
b755fb9
feat(CalendarIntro): add introductory card for calendar with guidance…
Ali-Sdg90 Dec 7, 2025
5977e6e
feat(Header): update header title to reflect new calendar name in Per…
Ali-Sdg90 Dec 7, 2025
fe5292a
refactor(ESLint): enhance ESLint configuration for better React suppo…
Ali-Sdg90 Dec 7, 2025
96d4725
refactor(CalendarIntro): improve text clarity and adjust styles for b…
Ali-Sdg90 Dec 8, 2025
fdfef2e
feat(CalendarIntro): enhance introductory card with detailed guidance…
Ali-Sdg90 Dec 8, 2025
24e14c8
refactor(events): update event titles for consistency and add short t…
Ali-Sdg90 Dec 9, 2025
557c7b9
feat(calendar): add theming support and update event styling
Ali-Sdg90 Dec 9, 2025
336c4ac
refactor(App): remove Header component and update related tests
Ali-Sdg90 Dec 10, 2025
41a3cee
feat(tests): enhance unit tests for EventPopup, FloatButtonSection, T…
Ali-Sdg90 Dec 10, 2025
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
24 changes: 20 additions & 4 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,22 +1,38 @@
// Temp ESLint config for testing only
{
"env": {
"browser": true,
"es2021": true,
"node": true,
"jest": true
},
"extends": ["eslint:recommended", "plugin:prettier/recommended"],
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:prettier/recommended"
],
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
}
},
"settings": {
"react": {
"version": "detect"
}
},
"plugins": ["react", "react-hooks"],
"rules": {
"no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }],
"no-console": "off",
"quotes": ["error", "double"],
"semi": ["error", "always"],
"react/react-in-jsx-scope": "off",
"react/prop-types": "off",
"prettier/prettier": [
"error",
"warn",
{
"endOfLine": "auto"
}
Expand Down
2 changes: 2 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
# npm run lint
npm run format:check
npm test
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@
"format": "prettier --write .",
"format:check": "prettier --check .",
"test": "react-scripts test --watchAll=false --coverage",
"lint": "eslint src --ext .js,.jsx --format stylish --max-warnings=0",
"lint:fix": "eslint src --ext .js,.jsx --format stylish --max-warnings=0 --fix",
"lint": "eslint src --ext .js,.jsx --format stylish",
"lint:fix": "eslint src --ext .js,.jsx --format stylish --fix",
"act": "act --env-file .env.act --quiet",
"prepare": "husky"
"prepare": "husky",
"check": "npm run format:check && npm run test"
},
"repository": {
"type": "git",
Expand Down
3 changes: 0 additions & 3 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React, { useContext, useEffect, useState } from "react";
import { ConfigProvider, theme } from "antd";
import Header from "./components/Header";
import Footer from "./components/Footer";
import CSCalendar from "./components/CSCalendar";
import FloatButtonSection from "./components/FloatButtonSection";
Expand Down Expand Up @@ -43,8 +42,6 @@ const App = () => {
>
<Toastify toastifyObj={toastifyObj} />

<Header />

<CSCalendar
setAnnouncementData={setAnnouncementData}
addToCurrentWeek={addToCurrentWeek}
Expand Down
182 changes: 125 additions & 57 deletions src/__tests__/App.test.jsx
Original file line number Diff line number Diff line change
@@ -1,96 +1,164 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import App from "../App";
import { ThemeContext } from "../store/Theme/ThemeContext";

// Mock child components
jest.mock("../components/Header", () => {
return function DummyHeader() {
return <div data-testid="header">Header</div>;
jest.mock("antd", () => {
const React = require("react");
const theme = {
defaultAlgorithm: "defaultAlgorithm",
darkAlgorithm: "darkAlgorithm",
};
});

jest.mock("../components/Footer", () => {
return function DummyFooter() {
return <div data-testid="footer">Footer</div>;
};
const ConfigProvider = ({ children, theme: themeProp }) => (
<div
data-testid="config-provider"
data-theme={JSON.stringify(themeProp)}
>
{children}
</div>
);

return { ConfigProvider, theme };
});

const mockCsCalendar = jest.fn();
let mockAnnouncementProps = null;

jest.mock("../components/CSCalendar", () => {
return function DummyCSCalendar() {
return <div data-testid="calendar">Calendar</div>;
const React = require("react");
return function MockCSCalendar(props) {
mockCsCalendar(props);
React.useEffect(() => {
props.setAnnouncementData({
startWeekDate: "2025/01/13",
endWeekDate: "2025/01/20",
firstEventDate: "2025/01/15",
secondEventDate: "2025/01/19",
firstEvent: "First",
secondEvent: "Second",
});
}, [props.setAnnouncementData]);
return <div data-testid="calendar" />;
};
});

jest.mock("../components/Footer", () => () => (
<div data-testid="footer">footer</div>
));

jest.mock("../components/FloatButtonSection", () => {
return function DummyFloatButtonSection() {
return <div data-testid="float-button">Float Button</div>;
return function MockFloatButtonSection({ setIsModalOpen }) {
return (
<button
data-testid="float-toggle"
onClick={() => setIsModalOpen(true)}
>
toggle
</button>
);
};
});

jest.mock("../components/AnnouncementModule", () => {
return function DummyAnnouncementModule() {
return <div data-testid="announcement">Announcement</div>;
return function MockAnnouncementModule(props) {
mockAnnouncementProps = props;
return (
<div
data-testid="announcement"
onClick={() => props.setAddToCurrentWeek((prev) => prev + 1)}
>
announcement
</div>
);
};
});

jest.mock("../components/Toastify", () => {
return function DummyToastify() {
return <div data-testid="toastify">Toastify</div>;
};
});

jest.mock("../store/StoreProvider", () => {
return function DummyStoreProvider({ children }) {
return <div data-testid="store-provider">{children}</div>;
};
});

// Mock ThemeContext
jest.mock("../store/Theme/ThemeContext", () => {
const React = require("react");
const mockThemeContext = {
theme: "light",
toggleTheme: jest.fn(),
};
return {
__esModule: true,
default: React.createContext(mockThemeContext),
ThemeContext: React.createContext(mockThemeContext),
return function MockToastify({ toastifyObj }) {
return (
<div data-testid="toastify" data-mode={toastifyObj?.mode || ""} />
);
};
});

describe("App", () => {
it("should render without crashing", () => {
render(<App />);
beforeEach(() => {
document.body.className = "";
jest.useFakeTimers();
jest.clearAllMocks();
mockAnnouncementProps = null;
mockCsCalendar.mockClear();
});

it("should render Header component", () => {
render(<App />);
expect(screen.getByTestId("header")).toBeInTheDocument();
afterEach(() => {
jest.useRealTimers();
});

it("should render Footer component", () => {
render(<App />);
const renderWithTheme = (themeValue = "light") =>
render(
<ThemeContext.Provider
value={{ theme: themeValue, toggleTheme: jest.fn() }}
>
<App />
</ThemeContext.Provider>
);

it("applies the correct theme algorithm and mounts children for light mode", () => {
renderWithTheme("light");
jest.runAllTimers();

const providers = screen.getAllByTestId("config-provider");
expect(providers.length).toBeGreaterThan(0);
const outerTheme = JSON.parse(providers[0].dataset.theme);
expect(outerTheme.algorithm).toBe("defaultAlgorithm");
expect(screen.getByTestId("calendar")).toBeInTheDocument();
expect(screen.getByTestId("footer")).toBeInTheDocument();
});

it("should render CSCalendar component", () => {
render(<App />);
expect(screen.getByTestId("calendar")).toBeInTheDocument();
it("switches to dark algorithm when theme context is dark", () => {
renderWithTheme("dark");
jest.runAllTimers();

const providers = screen.getAllByTestId("config-provider");
const outerTheme = JSON.parse(providers[0].dataset.theme);
expect(outerTheme.algorithm).toBe("darkAlgorithm");
});

it("adds the loaded class to body after the initial effect", () => {
renderWithTheme();
expect(document.body.classList.contains("loaded")).toBe(false);
jest.runAllTimers();
expect(document.body.classList.contains("loaded")).toBe(true);
});

it("should render FloatButtonSection component", () => {
render(<App />);
expect(screen.getByTestId("float-button")).toBeInTheDocument();
it("propagates announcement data from calendar to announcement module", async () => {
renderWithTheme();
await waitFor(() =>
expect(mockAnnouncementProps?.announcementData?.firstEvent).toBe(
"First"
)
);
expect(mockCsCalendar).toHaveBeenCalled();
});

it("should render AnnouncementModule component", () => {
render(<App />);
expect(screen.getByTestId("announcement")).toBeInTheDocument();
it("updates addToCurrentWeek state when announcement module requests it", async () => {
renderWithTheme();
fireEvent.click(screen.getByTestId("announcement"));

await waitFor(() => {
const lastCall =
mockCsCalendar.mock.calls[mockCsCalendar.mock.calls.length - 1];
expect(lastCall[0].addToCurrentWeek).toBe(1);
});
});

it("should render Toastify component", () => {
render(<App />);
expect(screen.getByTestId("toastify")).toBeInTheDocument();
it("opens the announcement modal via float button toggle", async () => {
renderWithTheme();
fireEvent.click(screen.getByTestId("float-toggle"));

await waitFor(() =>
expect(mockAnnouncementProps?.isModalOpen).toBe(true)
);
});
});
Loading