Skip to content

Commit aaa9b99

Browse files
authored
Merge pull request #1 from cs-internship/feature/ui-v2.0
Apply UI v2.0
2 parents d905596 + 41a3cee commit aaa9b99

33 files changed

Lines changed: 2176 additions & 1044 deletions

.eslintrc.json

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,38 @@
1-
// Temp ESLint config for testing only
21
{
32
"env": {
3+
"browser": true,
44
"es2021": true,
55
"node": true,
66
"jest": true
77
},
8-
"extends": ["eslint:recommended", "plugin:prettier/recommended"],
8+
"extends": [
9+
"eslint:recommended",
10+
"plugin:react/recommended",
11+
"plugin:react-hooks/recommended",
12+
"plugin:prettier/recommended"
13+
],
914
"parserOptions": {
1015
"ecmaVersion": "latest",
11-
"sourceType": "module"
16+
"sourceType": "module",
17+
"ecmaFeatures": {
18+
"jsx": true
19+
}
1220
},
21+
"settings": {
22+
"react": {
23+
"version": "detect"
24+
}
25+
},
26+
"plugins": ["react", "react-hooks"],
1327
"rules": {
1428
"no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }],
1529
"no-console": "off",
1630
"quotes": ["error", "double"],
1731
"semi": ["error", "always"],
32+
"react/react-in-jsx-scope": "off",
33+
"react/prop-types": "off",
1834
"prettier/prettier": [
19-
"error",
35+
"warn",
2036
{
2137
"endOfLine": "auto"
2238
}

.husky/pre-commit

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1+
# npm run lint
2+
npm run format:check
13
npm test

package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@
1212
"format": "prettier --write .",
1313
"format:check": "prettier --check .",
1414
"test": "react-scripts test --watchAll=false --coverage",
15-
"lint": "eslint src --ext .js,.jsx --format stylish --max-warnings=0",
16-
"lint:fix": "eslint src --ext .js,.jsx --format stylish --max-warnings=0 --fix",
15+
"lint": "eslint src --ext .js,.jsx --format stylish",
16+
"lint:fix": "eslint src --ext .js,.jsx --format stylish --fix",
1717
"act": "act --env-file .env.act --quiet",
18-
"prepare": "husky"
18+
"prepare": "husky",
19+
"check": "npm run format:check && npm run test"
1920
},
2021
"repository": {
2122
"type": "git",

src/App.jsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import React, { useContext, useEffect, useState } from "react";
22
import { ConfigProvider, theme } from "antd";
3-
import Header from "./components/Header";
43
import Footer from "./components/Footer";
54
import CSCalendar from "./components/CSCalendar";
65
import FloatButtonSection from "./components/FloatButtonSection";
@@ -43,8 +42,6 @@ const App = () => {
4342
>
4443
<Toastify toastifyObj={toastifyObj} />
4544

46-
<Header />
47-
4845
<CSCalendar
4946
setAnnouncementData={setAnnouncementData}
5047
addToCurrentWeek={addToCurrentWeek}

src/__tests__/App.test.jsx

Lines changed: 125 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,96 +1,164 @@
11
import React from "react";
2-
import { render, screen } from "@testing-library/react";
2+
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
33
import App from "../App";
4+
import { ThemeContext } from "../store/Theme/ThemeContext";
45

5-
// Mock child components
6-
jest.mock("../components/Header", () => {
7-
return function DummyHeader() {
8-
return <div data-testid="header">Header</div>;
6+
jest.mock("antd", () => {
7+
const React = require("react");
8+
const theme = {
9+
defaultAlgorithm: "defaultAlgorithm",
10+
darkAlgorithm: "darkAlgorithm",
911
};
10-
});
1112

12-
jest.mock("../components/Footer", () => {
13-
return function DummyFooter() {
14-
return <div data-testid="footer">Footer</div>;
15-
};
13+
const ConfigProvider = ({ children, theme: themeProp }) => (
14+
<div
15+
data-testid="config-provider"
16+
data-theme={JSON.stringify(themeProp)}
17+
>
18+
{children}
19+
</div>
20+
);
21+
22+
return { ConfigProvider, theme };
1623
});
1724

25+
const mockCsCalendar = jest.fn();
26+
let mockAnnouncementProps = null;
27+
1828
jest.mock("../components/CSCalendar", () => {
19-
return function DummyCSCalendar() {
20-
return <div data-testid="calendar">Calendar</div>;
29+
const React = require("react");
30+
return function MockCSCalendar(props) {
31+
mockCsCalendar(props);
32+
React.useEffect(() => {
33+
props.setAnnouncementData({
34+
startWeekDate: "2025/01/13",
35+
endWeekDate: "2025/01/20",
36+
firstEventDate: "2025/01/15",
37+
secondEventDate: "2025/01/19",
38+
firstEvent: "First",
39+
secondEvent: "Second",
40+
});
41+
}, [props.setAnnouncementData]);
42+
return <div data-testid="calendar" />;
2143
};
2244
});
2345

46+
jest.mock("../components/Footer", () => () => (
47+
<div data-testid="footer">footer</div>
48+
));
49+
2450
jest.mock("../components/FloatButtonSection", () => {
25-
return function DummyFloatButtonSection() {
26-
return <div data-testid="float-button">Float Button</div>;
51+
return function MockFloatButtonSection({ setIsModalOpen }) {
52+
return (
53+
<button
54+
data-testid="float-toggle"
55+
onClick={() => setIsModalOpen(true)}
56+
>
57+
toggle
58+
</button>
59+
);
2760
};
2861
});
2962

3063
jest.mock("../components/AnnouncementModule", () => {
31-
return function DummyAnnouncementModule() {
32-
return <div data-testid="announcement">Announcement</div>;
64+
return function MockAnnouncementModule(props) {
65+
mockAnnouncementProps = props;
66+
return (
67+
<div
68+
data-testid="announcement"
69+
onClick={() => props.setAddToCurrentWeek((prev) => prev + 1)}
70+
>
71+
announcement
72+
</div>
73+
);
3374
};
3475
});
3576

3677
jest.mock("../components/Toastify", () => {
37-
return function DummyToastify() {
38-
return <div data-testid="toastify">Toastify</div>;
39-
};
40-
});
41-
42-
jest.mock("../store/StoreProvider", () => {
43-
return function DummyStoreProvider({ children }) {
44-
return <div data-testid="store-provider">{children}</div>;
45-
};
46-
});
47-
48-
// Mock ThemeContext
49-
jest.mock("../store/Theme/ThemeContext", () => {
50-
const React = require("react");
51-
const mockThemeContext = {
52-
theme: "light",
53-
toggleTheme: jest.fn(),
54-
};
55-
return {
56-
__esModule: true,
57-
default: React.createContext(mockThemeContext),
58-
ThemeContext: React.createContext(mockThemeContext),
78+
return function MockToastify({ toastifyObj }) {
79+
return (
80+
<div data-testid="toastify" data-mode={toastifyObj?.mode || ""} />
81+
);
5982
};
6083
});
6184

6285
describe("App", () => {
63-
it("should render without crashing", () => {
64-
render(<App />);
86+
beforeEach(() => {
87+
document.body.className = "";
88+
jest.useFakeTimers();
89+
jest.clearAllMocks();
90+
mockAnnouncementProps = null;
91+
mockCsCalendar.mockClear();
6592
});
6693

67-
it("should render Header component", () => {
68-
render(<App />);
69-
expect(screen.getByTestId("header")).toBeInTheDocument();
94+
afterEach(() => {
95+
jest.useRealTimers();
7096
});
7197

72-
it("should render Footer component", () => {
73-
render(<App />);
98+
const renderWithTheme = (themeValue = "light") =>
99+
render(
100+
<ThemeContext.Provider
101+
value={{ theme: themeValue, toggleTheme: jest.fn() }}
102+
>
103+
<App />
104+
</ThemeContext.Provider>
105+
);
106+
107+
it("applies the correct theme algorithm and mounts children for light mode", () => {
108+
renderWithTheme("light");
109+
jest.runAllTimers();
110+
111+
const providers = screen.getAllByTestId("config-provider");
112+
expect(providers.length).toBeGreaterThan(0);
113+
const outerTheme = JSON.parse(providers[0].dataset.theme);
114+
expect(outerTheme.algorithm).toBe("defaultAlgorithm");
115+
expect(screen.getByTestId("calendar")).toBeInTheDocument();
74116
expect(screen.getByTestId("footer")).toBeInTheDocument();
75117
});
76118

77-
it("should render CSCalendar component", () => {
78-
render(<App />);
79-
expect(screen.getByTestId("calendar")).toBeInTheDocument();
119+
it("switches to dark algorithm when theme context is dark", () => {
120+
renderWithTheme("dark");
121+
jest.runAllTimers();
122+
123+
const providers = screen.getAllByTestId("config-provider");
124+
const outerTheme = JSON.parse(providers[0].dataset.theme);
125+
expect(outerTheme.algorithm).toBe("darkAlgorithm");
126+
});
127+
128+
it("adds the loaded class to body after the initial effect", () => {
129+
renderWithTheme();
130+
expect(document.body.classList.contains("loaded")).toBe(false);
131+
jest.runAllTimers();
132+
expect(document.body.classList.contains("loaded")).toBe(true);
80133
});
81134

82-
it("should render FloatButtonSection component", () => {
83-
render(<App />);
84-
expect(screen.getByTestId("float-button")).toBeInTheDocument();
135+
it("propagates announcement data from calendar to announcement module", async () => {
136+
renderWithTheme();
137+
await waitFor(() =>
138+
expect(mockAnnouncementProps?.announcementData?.firstEvent).toBe(
139+
"First"
140+
)
141+
);
142+
expect(mockCsCalendar).toHaveBeenCalled();
85143
});
86144

87-
it("should render AnnouncementModule component", () => {
88-
render(<App />);
89-
expect(screen.getByTestId("announcement")).toBeInTheDocument();
145+
it("updates addToCurrentWeek state when announcement module requests it", async () => {
146+
renderWithTheme();
147+
fireEvent.click(screen.getByTestId("announcement"));
148+
149+
await waitFor(() => {
150+
const lastCall =
151+
mockCsCalendar.mock.calls[mockCsCalendar.mock.calls.length - 1];
152+
expect(lastCall[0].addToCurrentWeek).toBe(1);
153+
});
90154
});
91155

92-
it("should render Toastify component", () => {
93-
render(<App />);
94-
expect(screen.getByTestId("toastify")).toBeInTheDocument();
156+
it("opens the announcement modal via float button toggle", async () => {
157+
renderWithTheme();
158+
fireEvent.click(screen.getByTestId("float-toggle"));
159+
160+
await waitFor(() =>
161+
expect(mockAnnouncementProps?.isModalOpen).toBe(true)
162+
);
95163
});
96164
});

0 commit comments

Comments
 (0)