Compare commits

...

4 Commits

Author SHA1 Message Date
379843404f improve: adjust footer layout 2024-12-09 09:25:56 +00:00
ba2274c76e Merge remote-tracking branch 'origin/master'
# Conflicts:
#	tests/ConfigProvider.test.js
2024-12-09 08:53:31 +00:00
3f6461d17e add: tests 2024-12-09 08:53:03 +00:00
931ade90a3 add: tests 2024-12-09 08:46:20 +00:00
9 changed files with 3925 additions and 27 deletions

15
jest.config.js Normal file
View File

@@ -0,0 +1,15 @@
module.exports = {
testEnvironment: "jsdom",
setupFilesAfterEnv: ["@testing-library/jest-dom", "<rootDir>/jest.setup.js"],
moduleNameMapper: {
"\\.(css|less|scss|sass)$": "identity-obj-proxy",
},
transform: {
"^.+\\.[t|j]sx?$": "babel-jest",
},
transformIgnorePatterns: [
'node_modules/(?!(@thi.ng)/)',
"node_modules/(?!node-fetch|katex|react-markdown|remark-math|rehype-katex|rehype-raw)"
],
};

6
jest.setup.js Normal file
View File

@@ -0,0 +1,6 @@
const { TextEncoder, TextDecoder } = require("util");
global.TextEncoder = TextEncoder;
global.TextDecoder = TextDecoder;
const fetch = (...args) =>
import("node-fetch").then(({ default: fetch }) => fetch(...args));
global.fetch = fetch;

3791
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -12,6 +12,7 @@
"author": "",
"license": "ISC",
"dependencies": {
"axios": "^1.7.9",
"bulma": "^1.0.2",
"katex": "^0.16.11",
"oidc-client-ts": "^3.1.0",
@@ -23,16 +24,26 @@
"react-syntax-highlighter": "^15.6.1",
"rehype-katex": "^7.0.1",
"rehype-raw": "^7.0.0",
"remark-math": "^6.0.0"
"remark-math": "^6.0.0",
"util": "^0.12.5"
},
"devDependencies": {
"@babel/core": "^7.26.0",
"@babel/preset-env": "^7.26.0",
"@babel/preset-react": "^7.25.9",
"@babel/preset-react": "^7.26.3",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.1.0",
"@testing-library/user-event": "^14.5.2",
"axios-mock-adapter": "^2.1.0",
"babel-jest": "^29.7.0",
"babel-loader": "^9.2.1",
"css-loader": "^7.1.2",
"dotenv-webpack": "^8.1.0",
"html-webpack-plugin": "^5.6.3",
"identity-obj-proxy": "^3.0.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"node-fetch": "^3.3.2",
"sass": "^1.81.0",
"sass-loader": "^16.0.3",
"style-loader": "^4.0.0",

View File

@@ -33,14 +33,22 @@
.footer-content {
margin-bottom: auto;
text-align: center;
width: 100%;
}
.footer-content a {
color: #fff;
font-size: 1.2rem;
text-decoration: underline;
}
.footer-icons {
display: flex;
flex-direction: column;
gap: 1rem;
align-items: flex-end;
margin-right: 1rem;
align-items: flex-start;
margin-left: 1rem;
width: 100%;
}
.footer-icon {

View File

@@ -25,7 +25,15 @@ const Footer = () => {
{isExpanded ? "↓" : "↑"}
</button>
<div className={`footer-content`}>
<p>&copy; {new Date().getFullYear()} Hangman Lab. All rights reserved.</p>
<p>&copy; {new Date().getFullYear()} Hangman Lab. {!isVisible && (<span>
&nbsp;&nbsp;&nbsp;&nbsp;
<a href="mailto:hzhang@hangman-lab.top">email</a>
&nbsp;&nbsp;&nbsp;&nbsp;
<a href="https://git.hangman-lab.top/hzhang/HangmanLab">git</a>
&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;
</span>
)}</p>
{
isVisible && (
<div
@@ -52,10 +60,10 @@ const Footer = () => {
>
<img
src="/icons/git.png"
alt="GitHub"
alt="Git"
className="footer-icon"
/>
GitHub
Git
</a>
<a href="mailto:hzhang@hangman-lab.top">
<img

View File

@@ -0,0 +1,23 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import AuthProvider, { AuthContext } from "../src/AuthProvider";
import "@testing-library/jest-dom";
describe("AuthProvider", () => {
it("should provide default user and roles as null and empty array", () => {
render(
<AuthProvider>
<AuthContext.Consumer>
{({ user, roles }) => (
<>
<div data-testid="user">{user ? user.name : "null"}</div>
<div data-testid="roles">{roles.length}</div>
</>
)}
</AuthContext.Consumer>
</AuthProvider>
);
expect(screen.getByTestId("user")).toHaveTextContent("null");
expect(screen.getByTestId("roles")).toHaveTextContent("0");
});
});

View File

@@ -0,0 +1,45 @@
import React from "react";
import { render, screen, act } from "@testing-library/react";
import ConfigProvider from "../src/ConfigProvider";
global.fetch = jest.fn(() =>
new Promise((resolve) =>
setTimeout(() => {
resolve({
ok: true,
json: () =>
Promise.resolve({
BACKEND_HOST: "http://localhost:5000",
FRONTEND_HOST: "http://localhost:3000",
}),
});
}, 100)
)
);
describe("ConfigProvider", () => {
it("should display loading initially", async () => {
await act(async () => {
render(
<ConfigProvider>
<div>Loaded</div>
</ConfigProvider>
);
});
expect(screen.getByText(/Loading configuration/i)).toBeInTheDocument();
});
it("should display children after loading", async () => {
await act(async () => {
render(
<ConfigProvider>
<div>Loaded</div>
</ConfigProvider>
);
});
const loadedText = await screen.findByText("Loaded");
expect(loadedText).toBeInTheDocument();
});
});

View File

@@ -0,0 +1,31 @@
import React from "react";
import PermissionGuard from "../src/components/PermissionGuard";
import { AuthContext } from "../src/AuthProvider";
import { render, screen } from "@testing-library/react";
describe("PermissionGuard", () => {
it("should render children for users with required roles", () => {
const roles = ["admin"];
render(
<AuthContext.Provider value={{ roles }}>
<PermissionGuard rolesRequired={["admin"]}>
<div data-testid="protected-content">Protected Content</div>
</PermissionGuard>
</AuthContext.Provider>
);
expect(screen.getByTestId("protected-content")).toBeInTheDocument();
});
it("should not render children for users without required roles", () => {
const roles = ["user"];
render(
<AuthContext.Provider value={{ roles }}>
<PermissionGuard rolesRequired={["admin"]}>
<div data-testid="protected-content">Protected Content</div>
</PermissionGuard>
</AuthContext.Provider>
);
expect(screen.queryByTestId("protected-content")).not.toBeInTheDocument();
});
});