Skip to content

Commit

Permalink
Merge pull request #54 from hotungkhanh/kan-65/frontend-tests
Browse files Browse the repository at this point in the history
Kan 65/frontend tests
  • Loading branch information
dh-giang-vu authored Oct 23, 2024
2 parents 2ee423d + b33fa04 commit 490aa7c
Show file tree
Hide file tree
Showing 14 changed files with 1,492 additions and 110 deletions.
1,086 changes: 984 additions & 102 deletions frontend/package-lock.json

Large diffs are not rendered by default.

10 changes: 8 additions & 2 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,20 @@
},
"devDependencies": {
"@eslint/js": "^9.9.0",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.2",
"@testing-library/react": "^16.0.1",
"@testing-library/user-event": "^14.5.2",
"@types/file-saver": "^2.0.7",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@types/react": "^18.3.11",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.3.1",
"@vitest/ui": "^2.1.3",
"eslint": "^9.9.0",
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
"eslint-plugin-react-refresh": "^0.4.9",
"globals": "^15.9.0",
"jsdom": "^25.0.1",
"moment": "^2.30.1",
"typescript": "^5.5.3",
"typescript-eslint": "^8.0.1",
Expand Down
21 changes: 20 additions & 1 deletion frontend/src/components/SkipButton.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,29 @@
import Button from "@mui/material/Button";
import { useNavigate } from "react-router-dom";
import ArrowForwardIcon from "@mui/icons-material/ArrowForward";
import { useEffect, useState } from "react";

export default function SkipButton() {
const navigate = useNavigate();
if (sessionStorage.getItem("campusSolutions") === undefined) {
const [hasSolution, setHasSolution] = useState(false);

useEffect(() => {
const checkHasSolution = () => {
const campusSolutions = sessionStorage.getItem("campusSolutions");
setHasSolution(campusSolutions !== null);
};

checkHasSolution();
window.addEventListener("fetchSolutionFinished", checkHasSolution);

return () => {
window.removeEventListener("fetchSolutionFinished", checkHasSolution);
};

}, []);


if (!hasSolution) {
return (
<Button
disabled
Expand Down
8 changes: 6 additions & 2 deletions frontend/src/pages/Enrolment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,11 @@ export default function StarterPage() {
"campusSolutions",
JSON.stringify(timetableSolutions)
);
});
})
.finally(() => {
// Dispatch custom event to notify other components of the change
window.dispatchEvent(new Event("fetchSolutionFinished"));
})
}, []);
return (
<Box className="app-container">
Expand Down Expand Up @@ -88,7 +92,7 @@ export default function StarterPage() {
gap: '50%',
marginRight: "5%",
}}>
<SkipButton/>
<SkipButton />
</div>
</Footer>
</Box>
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/tests/api.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest';
import { fetchTimetableSolution, LOCAL_API_URL, TimetableProblem } from '../scripts/api';
import { fetchTimetableSolution, REMOTE_API_URL, TimetableProblem } from '../scripts/api';
import moment from 'moment';
import { AuthHeader } from '../security/AuthContext';

Expand All @@ -23,7 +23,7 @@ describe('fetchTimetableSolution', { timeout: 200000 }, () => {

const authHeader: AuthHeader = `Basic ${btoa(`${import.meta.env.VITE_FRONTEND_USERNAME}:${import.meta.env.VITE_FRONTEND_PASSWORD}`)}`;

const solution = await fetchTimetableSolution(problem, authHeader, LOCAL_API_URL);
const solution = await fetchTimetableSolution(problem, authHeader, REMOTE_API_URL);
expect(solution).not.toBeNull();
expect(solution?.units[0].dayOfWeek).toEqual(problem.daysOfWeek[0]);
expect(solution?.units[0].startTime).toEqual(problem.startTimes[0]);
Expand Down Expand Up @@ -55,7 +55,7 @@ describe('fetchTimetableSolution', { timeout: 200000 }, () => {
};

const authHeader: AuthHeader = `Basic ${btoa(`${import.meta.env.VITE_FRONTEND_USERNAME}:${import.meta.env.VITE_FRONTEND_PASSWORD}`)}`;
const solutions = await Promise.all([fetchTimetableSolution(problem0, authHeader, LOCAL_API_URL), fetchTimetableSolution(problem1, authHeader, LOCAL_API_URL)]);
const solutions = await Promise.all([fetchTimetableSolution(problem0, authHeader, REMOTE_API_URL), fetchTimetableSolution(problem1, authHeader, REMOTE_API_URL)]);

for (let i = 0; i < solutions.length; i++) {
expect(solutions[i]).not.toBeNull();
Expand Down
38 changes: 38 additions & 0 deletions frontend/src/tests/components/BackButton.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { afterEach, describe, it, expect } from 'vitest';
import { render, screen, cleanup } from '@testing-library/react';
import '@testing-library/jest-dom/vitest';
import BackButton from '../../components/BackButton';

// Automatically clean up the DOM after each test
afterEach(() => {
cleanup();
});

describe('BackButton', () => {
it('renders as an active button by default', () => {
// Render the default BackButton
render(<BackButton />);

const backButton = screen.getByRole('button');

// Check if the button is in the document
expect(backButton).toBeInTheDocument();

// Check if the button is enabled (active by default)
expect(backButton).toBeEnabled();
});

it('renders as a disabled button with props', () => {
// Render the BackButton with the disabled prop set to true
render(<BackButton disabled={true} />);

const backButton = screen.getByRole('button');

// Check if the button is in the document
expect(backButton).toBeInTheDocument();

// Check if the button is disabled
expect(backButton).toBeDisabled();
});

});
30 changes: 30 additions & 0 deletions frontend/src/tests/components/DisplayFile.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { describe, it, expect } from 'vitest';
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom/vitest';
import DisplayFile from '../../components/DisplayFile';

describe('DisplayFile', () => {

it('renders an empty Box when fileChosen is null', () => {
const { container } = render(<DisplayFile fileChosen={null} />);

// The Box is rendered, but nothing else
const emptyBox = container.querySelector('.MuiBox-root');
expect(emptyBox).toBeInTheDocument();
expect(emptyBox).toBeEmptyDOMElement(); // Box should be empty when fileChosen is null
});

it('renders the file name and FilePresentIcon when fileChosen is not null', () => {
const mockFile = new File([''], 'example.txt', { type: 'text/plain' });

render(<DisplayFile fileChosen={mockFile} />);

// Check that the file name is rendered
const fileName = screen.getByText('example.txt');
expect(fileName).toBeInTheDocument();

// Check that the FilePresentIcon is rendered
const fileIcon = screen.getByTestId('FilePresentIcon');
expect(fileIcon).toBeInTheDocument();
});
});
76 changes: 76 additions & 0 deletions frontend/src/tests/components/LoadingButton.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { afterEach, describe, it, expect, vi } from 'vitest';
import { render, screen, cleanup } from '@testing-library/react';
import LoadingButton from '../../components/LoadingButton';
import '@testing-library/jest-dom/vitest';
import userEvent from '@testing-library/user-event';

// Automatically clean up the DOM after each test
afterEach(() => {
cleanup();
});

describe('LoadingButton', () => {

it('shows a disabled button with CircularProgress and no text when loading is true', () => {
render(
<LoadingButton
loading={true}
onClick={() => {}}
text="Submit"
/>
);

// Check that the button is disabled
const button = screen.getByRole('button');
expect(button).toBeDisabled();

// Check that CircularProgress is present
const circularProgress = screen.getByRole('progressbar');
expect(circularProgress).toBeInTheDocument();

// Check that the button does not display the text "Submit"
expect(button).not.toHaveTextContent('Submit');
});

it('shows an enabled button with text and no CircularProgress when loading is false', () => {
render(
<LoadingButton
loading={false}
onClick={() => {}}
text="Submit"
/>
);

// Check that the button is enabled
const button = screen.getByRole('button');
expect(button).toBeEnabled();

// Check that CircularProgress is not present
const circularProgress = screen.queryByRole('progressbar');
expect(circularProgress).not.toBeInTheDocument();

// Check that the button displays the text "Submit"
expect(button).toHaveTextContent('Submit');
});

it('triggers the onClick function when clicked and not loading', async () => {
const handleClick = vi.fn();

render(
<LoadingButton
loading={false}
onClick={handleClick}
text="Submit"
/>
);

// Simulate user clicking the button
const button = screen.getByRole('button');
const user = userEvent.setup();
await user.click(button);

// Ensure the onClick function is called
expect(handleClick).toHaveBeenCalledTimes(1);
});

});
34 changes: 34 additions & 0 deletions frontend/src/tests/components/LoadingModal.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { afterEach, describe, it, expect } from 'vitest';
import { render, screen, cleanup } from '@testing-library/react';
import '@testing-library/jest-dom/vitest'; // For jest-dom matchers
import LoadingModal from '../../components/LoadingModel';

// Automatically clean up the DOM after each test
afterEach(() => {
cleanup();
});

describe('LoadingModal', () => {
it('renders the modal with CircularProgress when open is true', () => {
render(<LoadingModal open={true} />);

// Modal should be present
const modal = screen.getByRole('presentation');
expect(modal).toBeInTheDocument();

// CircularProgress should be present inside the modal
const progress = screen.getByRole('progressbar');
expect(progress).toBeInTheDocument();
});

it('does not render the modal or CircularProgress when open is false', () => {
render(<LoadingModal open={false} />);

// Modal and CircularProgress should not be present
const modal = screen.queryByRole('presentation');
expect(modal).not.toBeInTheDocument();

const progress = screen.queryByRole('progressbar');
expect(progress).not.toBeInTheDocument();
});
});
60 changes: 60 additions & 0 deletions frontend/src/tests/components/ProceedButton.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { afterEach, describe, it, expect, vi } from 'vitest';
import { render, screen, cleanup } from '@testing-library/react';
import ProceedButton from '../../components/ProceedButton';
import { MemoryRouter, useNavigate } from 'react-router-dom';
import '@testing-library/jest-dom/vitest';
import userEvent from '@testing-library/user-event';

// Automatically clean up the DOM after each test
afterEach(() => {
cleanup();
});

// Mock useNavigate to test navigation behavior
vi.mock(import('react-router-dom'), async (importOriginal) => {
const actual = await importOriginal();
return {
...actual,
useNavigate: vi.fn(), // Mock useNavigate only
};
});

afterEach(() => {
vi.clearAllMocks(); // Clear mocks after each test
});

describe('ProceedButton', () => {
it('renders a disabled button when fileChosen is null', () => {
render(
<MemoryRouter>
<ProceedButton fileChosen={null} />
</MemoryRouter>
);

// Check that the button is disabled
const button = screen.getByRole('button', { name: /proceed/i });
expect(button).toBeDisabled();
});

it('renders an enabled button and triggers navigation when fileChosen is provided', async () => {
const navigateMock = vi.fn();
vi.mocked(useNavigate).mockReturnValue(navigateMock);
const user = userEvent.setup();

render(
<MemoryRouter>
<ProceedButton fileChosen={new File(["content"], "testFile.txt")} />
</MemoryRouter>
);

// Check that the button is enabled
const button = screen.getByRole('button', { name: /proceed/i });
expect(button).toBeEnabled();

// Simulate clicking the button
await user.click(button);

// Ensure that navigate is called with the correct route
expect(navigateMock).toHaveBeenCalledWith('/seminfo/room');
});
});
Loading

0 comments on commit 490aa7c

Please sign in to comment.