diff --git a/frontend/__test__/AddButton.test.tsx b/frontend/__test__/AddButton.test.tsx new file mode 100644 index 00000000..2039b895 --- /dev/null +++ b/frontend/__test__/AddButton.test.tsx @@ -0,0 +1,11 @@ +import {fireEvent, render, screen} from "@testing-library/react"; +import React from "react"; +import AddButton from "@app/[locale]/components/AddButton"; + +describe("AddButton", () => { + it("render", async () => { + render(); + const backButton = screen.getByText(/key/i); + fireEvent.click(backButton); + }); +}); diff --git a/frontend/__test__/CourseControls.test.tsx b/frontend/__test__/CourseControls.test.tsx new file mode 100644 index 00000000..d4803e2d --- /dev/null +++ b/frontend/__test__/CourseControls.test.tsx @@ -0,0 +1,21 @@ +import {render, screen} from "@testing-library/react"; +import React from "react"; +import CourseControls from "@app/[locale]/components/CourseControls"; +import {APIError, fetchUserData, UserData} from "@lib/api"; + +jest.mock('../lib/api', () => ({ + fetchUserData: jest.fn(), +})); +describe("CourseControls", () => { + it("render coursecontrols", async () => { + const mockOnYearChange = jest.fn(); + render(); + + expect(screen.getByText(/all_courses/i)).toBeInTheDocument(); + + expect(screen.getByText(/create_course/i)).toBeInTheDocument(); + + expect(screen.getByText(/view_archive/i)).toBeInTheDocument(); + + }); +}); diff --git a/frontend/__test__/CourseDetails.test.tsx b/frontend/__test__/CourseDetails.test.tsx new file mode 100644 index 00000000..88c5fd65 --- /dev/null +++ b/frontend/__test__/CourseDetails.test.tsx @@ -0,0 +1,26 @@ +import {render, screen} from "@testing-library/react"; +import React from "react"; +import CourseDetails from "@app/[locale]/components/CourseDetails"; + +// mock copyclipboardbutton component +jest.mock("../app/[locale]/components/CopyToClipboardButton", () => ({ + __esModule: true, + default: jest.fn(() =>
Mocked CopyToClipboardButton
), // Mock rendering +})); + +jest.mock('../lib/api', () => ({ + getCourse: jest.fn(), + Course: jest.fn(), + UserData: jest.fn(), + getUserData: jest.fn(), +})); +describe("CourseDetails", () => { + it("render CourseDetails", async () => { + render(); + + expect(screen.getByText("no_description")).toBeInTheDocument(); + + expect(screen.getByText("description")).toBeInTheDocument(); + + }); +}); diff --git a/frontend/__test__/CoursesGrid.test.tsx b/frontend/__test__/CoursesGrid.test.tsx new file mode 100644 index 00000000..068d3c20 --- /dev/null +++ b/frontend/__test__/CoursesGrid.test.tsx @@ -0,0 +1,20 @@ +import {render, screen} from "@testing-library/react"; +import React from "react"; +import CoursesGrid from "@app/[locale]/components/CoursesGrid"; + + +jest.mock("../app/[locale]/components/CourseCard", () => ({ + __esModule: true, + default: jest.fn(() =>
Mocked CourseCard
), // Mock rendering +})); + + +jest.mock('../lib/api', () => ({ + getCoursesForUser: jest.fn(), +})); + +describe("CourseDetails", () => { + it("render CourseDetails", async () => { + render(); + }); +}); diff --git a/frontend/__test__/EditUserForm.test.tsx b/frontend/__test__/EditUserForm.test.tsx new file mode 100644 index 00000000..4d619d56 --- /dev/null +++ b/frontend/__test__/EditUserForm.test.tsx @@ -0,0 +1,71 @@ +import React from 'react'; +import { render, screen, waitFor, fireEvent } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import EditUserForm from '../app/[locale]/components/EditUserForm'; +import { getUser, updateUserData } from '@lib/api'; + +jest.mock('../lib/api', () => ({ + getUser: jest.fn(), + updateUserData: jest.fn(), +})); + +jest.mock('react-i18next', () => ({ + useTranslation: () => ({ + t: (key) => key, + }), +})); + +describe('EditUserForm', () => { + const mockUser = { + first_name: 'John', + last_name: 'Doe', + role: 2, + email: 'john.doe@example.com', + }; + + beforeEach(() => { + jest.resetAllMocks(); + getUser.mockResolvedValue(mockUser); + updateUserData.mockResolvedValue({}); + }); + + it('fetches and displays user data correctly', async () => { + render(); + + await waitFor(() => { + expect(screen.getByText('email')).toBeInTheDocument(); + expect(screen.getByText('john.doe@example.com')).toBeInTheDocument(); + expect(screen.getByDisplayValue('John')).toBeInTheDocument(); + expect(screen.getByDisplayValue('Doe')).toBeInTheDocument(); + expect(screen.getByText('teacher')).toBeInTheDocument(); + }); + }); + + it('updates user data correctly', async () => { + render(); + + // Wait for the user data to be fetched and displayed + await waitFor(() => { + expect(screen.getByDisplayValue('John')).toBeInTheDocument(); + }); + + // Change the first name + fireEvent.change(screen.getByDisplayValue('John'), { + target: { value: 'Jane' }, + }); + + // Submit the form + fireEvent.submit(screen.getByRole('button', { name: 'save changes' })); + + // Check if the updateUserData was called with the updated data + await waitFor(() => { + expect(updateUserData).toHaveBeenCalledWith(1, expect.any(FormData)); + }); + + const formData = updateUserData.mock.calls[0][1]; + expect(formData.get('first_name')).toBe('Jane'); + expect(formData.get('last_name')).toBe('Doe'); + expect(formData.get('role')).toBe('2'); + expect(formData.get('email')).toBe('john.doe@example.com'); + }); +}); diff --git a/frontend/__test__/GroupSubmissionList.test.tsx b/frontend/__test__/GroupSubmissionList.test.tsx new file mode 100644 index 00000000..8d1cbf54 --- /dev/null +++ b/frontend/__test__/GroupSubmissionList.test.tsx @@ -0,0 +1,15 @@ +import {render} from "@testing-library/react"; +import React from "react"; +import GroupSubmissionList from "@app/[locale]/components/GroupSubmissionList"; + + +jest.mock("../app/[locale]/components/ListView", () => ({ + __esModule: true, + default: jest.fn(() =>
Mocked ListView
), // Mock rendering +})); + +describe("CourseDetails", () => { + it("render CourseDetails", async () => { + render(); + }); +}); diff --git a/frontend/__test__/ListView.test.tsx b/frontend/__test__/ListView.test.tsx index f42fc6fe..600b4e49 100644 --- a/frontend/__test__/ListView.test.tsx +++ b/frontend/__test__/ListView.test.tsx @@ -110,7 +110,7 @@ const mockLastSubmission = { output_test: 'output', }; -const headers = ['name', +const headers1 = ['name', {" " + 'description'} , @@ -118,7 +118,9 @@ const headers = ['name', {" " + 'join_leave'} ]; -const headers_backend = ['name', 'description', 'open', 'join/leave'] +const headers_backend1 = ['name', 'description', 'open', 'join/leave'] + + describe('ListView', () => { @@ -147,8 +149,8 @@ describe('ListView', () => { act(() => { render( ({ + getProjectsFromCourse: jest.fn(), + getUserData: jest.fn(), +})); + +describe('ProjectTable', () => { + const projects = [ + { + project_id: 1, + name: 'Project 1', + deadline: '2024-06-01T12:00:00Z', + visible: true, + }, + { + project_id: 2, + name: 'Project 2', + deadline: '2024-06-10T12:00:00Z', + visible: false, + }, + ]; + + const userData = { /* Mock user data */}; + + beforeEach(() => { + getProjectsFromCourse.mockResolvedValue(projects); + getUserData.mockResolvedValue(userData); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('renders project table with correct data', async () => { + render(); + + // Wait for async operations to complete + await waitFor(() => { + expect(screen.getByText('Project 1')).toBeInTheDocument(); + expect(screen.getByText('Project 2')).toBeInTheDocument(); + }); + }); + + it('renders correct project data after sorting', async () => { + render(); + + // Wait for async operations to complete + await waitFor(() => { + expect(screen.getByText('Project 1')).toBeInTheDocument(); + expect(screen.getByText('Project 2')).toBeInTheDocument(); + }); + + // Sort the table by project name (descending) + const projectNameHeader = screen.getByText('Project Name'); + projectNameHeader.click(); + projectNameHeader.click(); + + // Wait for table to re-render with sorted data + await waitFor(() => { + expect(screen.getByText('Project 2')).toBeInTheDocument(); // Now Project 2 should appear first + expect(screen.getByText('Project 1')).toBeInTheDocument(); + }); + }); + +}); diff --git a/frontend/__test__/ProjectTableTeacher.test.tsx b/frontend/__test__/ProjectTableTeacher.test.tsx index 503b80d5..8359e571 100644 --- a/frontend/__test__/ProjectTableTeacher.test.tsx +++ b/frontend/__test__/ProjectTableTeacher.test.tsx @@ -1,7 +1,7 @@ import React from 'react'; import {render, screen, fireEvent, within} from '@testing-library/react'; import '@testing-library/jest-dom'; -import ProjectTableTeacher from '@app/[locale]/components/ProjectTable'; // Adjust the import path as necessary +import ProjectTableTeacher from '@app/[locale]/components/ProjectTable'; import * as api from '@lib/api'; // Mocking the necessary modules diff --git a/frontend/__test__/StatusButton.test.tsx b/frontend/__test__/StatusButton.test.tsx new file mode 100644 index 00000000..efec088f --- /dev/null +++ b/frontend/__test__/StatusButton.test.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import StatusButton from '@app/[locale]/components/StatusButton'; + +jest.mock('@mui/icons-material/Check', () => () =>
); +jest.mock('@mui/icons-material/HelpOutline', () => () =>
); +describe('StatusButton', () => { + let files: any[]; + let setFiles: jest.Mock; + + beforeEach(() => { + files = ['+', '~', '-']; + setFiles = jest.fn((newFiles) => { + files = newFiles; + }); + }); + + it('renders the initial status correctly', () => { + render(); + expect(screen.getByTestId('check-icon')).toBeInTheDocument(); + }); + + it('cycles through statuses on click', () => { + render(); + + // Click to change status + fireEvent.click(screen.getByRole('button')); + expect(screen.getByTestId('help-icon')).toBeInTheDocument(); + expect(setFiles).toHaveBeenCalledWith(['~', '~', '-']); + + // Click to change status again + fireEvent.click(screen.getByRole('button')); + expect(setFiles).toHaveBeenCalledWith(['-', '~', '-']); + + // Click to change status back to initial + fireEvent.click(screen.getByRole('button')); + expect(screen.getByTestId('check-icon')).toBeInTheDocument(); + expect(setFiles).toHaveBeenCalledWith(['+', '~', '-']); + }); + + it('renders correct status for fileIndex 1', () => { + render(); + expect(screen.getByTestId('help-icon')).toBeInTheDocument(); + }); + + it('renders correct status for fileIndex 2', () => { + render(); + }); + + it('handles an empty file state correctly', () => { + files = ['', '~', '-']; + render(); + + fireEvent.click(screen.getByRole('button')); + expect(screen.getByTestId('check-icon')).toBeInTheDocument(); + expect(setFiles).toHaveBeenCalledWith(['+', '~', '-']); + }); +}); diff --git a/frontend/__test__/SubmissionDetailsPage.test.tsx b/frontend/__test__/SubmissionDetailsPage.test.tsx new file mode 100644 index 00000000..5fc4527f --- /dev/null +++ b/frontend/__test__/SubmissionDetailsPage.test.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import {render, screen, waitFor} from '@testing-library/react'; +import SubmissionDetailsPage from '@app/[locale]/components/SubmissionDetailsPage'; + +jest.mock('../lib/api', () => ({ + getSubmission: jest.fn().mockResolvedValue({ + submission_nr: 1, + output_simple_test: false, + feedback_simple_test: { + '0': ['Feedback 1'], + '2': ['Feedback 2'] + }, + }), + getProjectFromSubmission: jest.fn().mockResolvedValue(456), +})); + + +describe('SubmissionDetailsPage', () => { + test('renders submission details correctly', async () => { + render(); + + expect(screen.getByRole('progressbar')).toBeInTheDocument(); + + await waitFor(() => expect(screen.queryByRole('progressbar')).not.toBeInTheDocument()); + + // Ensure submission details are rendered + expect(screen.getByText(/submission #/i)).toBeInTheDocument(); + expect(screen.getByText(/evaluation status/i)).toBeInTheDocument(); + expect(screen.getByText(/uploaded_files/i)).toBeInTheDocument(); + expect(screen.getByText(/feedback_simple_test_0/i)).toBeInTheDocument(); + expect(screen.getByText(/Feedback 1/i)).toBeInTheDocument(); + + // Test the feedback for simple test "2" + expect(screen.getByText(/feedback_simple_test_2/i)).toBeInTheDocument(); + expect(screen.getByText(/Feedback 2/i)).toBeInTheDocument(); + }); +}); diff --git a/frontend/__test__/SubmitDetailsPage.test.tsx b/frontend/__test__/SubmitDetailsPage.test.tsx new file mode 100644 index 00000000..329e79a8 --- /dev/null +++ b/frontend/__test__/SubmitDetailsPage.test.tsx @@ -0,0 +1,150 @@ +import React from 'react'; +import {render, screen, fireEvent, waitFor} from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; +import SubmitDetailsPage from '@app/[locale]/components/SubmitDetailsPage'; // Adjust the import path as needed +import {getProject, fetchUserData, uploadSubmissionFile} from '@lib/api'; +import {ThemeProvider} from '@mui/material'; +import baseTheme from '@styles/theme'; + +// Mock the dependencies +jest.mock('../lib/api', () => ({ + getProject: jest.fn(), + fetchUserData: jest.fn(), + uploadSubmissionFile: jest.fn(), +})); + +jest.mock('../app/[locale]/components/ProjectReturnButton', () => () =>
ProjectReturnButton
); +jest.mock('../app/[locale]/components/Tree', () => () =>
TreeComponent
); + +describe('SubmitDetailsPage', () => { + const projectMock = { + project_id: 1, + name: 'Project 1', + description: 'This is a description for project 1', + course_id: 1, + }; + + const userMock = { + course: [1], + }; + + beforeEach(() => { + getProject.mockResolvedValue(projectMock); + fetchUserData.mockResolvedValue(userMock); + uploadSubmissionFile.mockResolvedValue({result: 'ok', submission_id: 1}); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('renders the component correctly', async () => { + render( + + + + ); + + expect(screen.getByRole('progressbar')).toBeInTheDocument(); + + await waitFor(() => { + expect(screen.getByText('Project 1')).toBeInTheDocument(); + expect(screen.getByText('This is a description for project 1')).toBeInTheDocument(); + expect(screen.getByText('ProjectReturnButton')).toBeInTheDocument(); + }); + }); + + it('handles file and folder uploads', async () => { + render( + + + + ); + + await waitFor(() => screen.getByText('Project 1')); + + const fileInput = screen.getByText('upload_folders'); + const files = [new File(['content'], 'file1.txt')]; + + fireEvent.change(fileInput, { + target: {files}, + }); + }); + + it('submits the form successfully', async () => { + render( + + + + ); + + await waitFor(() => screen.getByText('Project 1')); + + const fileInput = screen.getByText('upload_folders'); + const files = [new File(['content'], 'file1.txt')]; + + fireEvent.change(fileInput, { + target: {files}, + }); + + const submitButton = screen.getByText('submit'); + fireEvent.click(submitButton); + + await waitFor(() => { + expect(uploadSubmissionFile).toHaveBeenCalled(); + }); + }); + + it('displays an error message on submission failure', async () => { + uploadSubmissionFile.mockResolvedValue({result: 'error', errorcode: 'submission_failed'}); + + render( + + + + ); + + await waitFor(() => screen.getByText('Project 1')); + + const fileInput = screen.getByText('upload_folders'); + const files = [new File(['content'], 'file1.txt')]; + + fireEvent.change(fileInput, { + target: {files}, + }); + + const submitButton = screen.getByText('submit'); + fireEvent.click(submitButton); + + await waitFor(() => { + expect(uploadSubmissionFile).toHaveBeenCalled(); + }); + }); + + + it('renders project details and handles file input changes', async () => { + render(); + + await waitFor(() => screen.getByText('Project 1')); + + // Simulate folder input change + const folderInput = screen.getByText('upload_folders'); + const folderFiles = [new File(['content'], 'folder/file1.txt', {type: 'text/plain'})]; + + Object.defineProperty(folderInput, 'files', { + value: folderFiles, + }); + + fireEvent.change(folderInput); + + // Simulate file input change + const fileInput = screen.getByText(/files/i); + const files = [new File(['content'], 'file2.txt', {type: 'text/plain'})]; + + Object.defineProperty(fileInput, 'files', { + value: files, + }); + + fireEvent.change(fileInput); + }); +}); diff --git a/frontend/__test__/Tree.test.tsx b/frontend/__test__/Tree.test.tsx new file mode 100644 index 00000000..f2f16199 --- /dev/null +++ b/frontend/__test__/Tree.test.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import Tree from '@app/[locale]/components/Tree'; + +// Mocking the TreeNode component +jest.mock('../app/[locale]/components/TreeNode', () => ({ + __esModule: true, + default: ({ node }: any) =>
{node.name}
, +})); + +describe('Tree', () => { + it('renders correctly with given paths', () => { + const paths = [ + 'root/branch1/leaf1', + 'root/branch1/leaf2', + 'root/branch2/leaf1', + 'root/branch3' + ]; + + render(); + + // Check if the root node is rendered + expect(screen.getByText('root')).toBeInTheDocument();}); +}); diff --git a/frontend/__test__/TreeNode.test.tsx b/frontend/__test__/TreeNode.test.tsx new file mode 100644 index 00000000..df6ef0e4 --- /dev/null +++ b/frontend/__test__/TreeNode.test.tsx @@ -0,0 +1,62 @@ +import React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import TreeNode from '../app/[locale]/components/TreeNode'; // Adjust the import path accordingly + +const mockNode = { + name: 'Parent Node', + level: 0, + isLeaf: false, + children: [ + { + name: 'Child Node 1', + level: 1, + isLeaf: true, + children: [], + }, + { + name: 'Child Node 2', + level: 1, + isLeaf: false, + children: [ + { + name: 'Grandchild Node 1', + level: 2, + isLeaf: true, + children: [], + }, + ], + }, + ], +}; + +const mockPaths = ['path/to/parent', 'path/to/child1', 'path/to/child2', 'path/to/grandchild1']; + +describe('TreeNode', () => { + it('renders correctly', () => { + render(); + + expect(screen.getByText('Parent Node')).toBeInTheDocument(); + expect(screen.queryByText('Child Node 1')).not.toBeInTheDocument(); + expect(screen.queryByText('Child Node 2')).not.toBeInTheDocument(); + }); + + it('toggles the collapse on click', () => { + render(); + + const parentNode = screen.getByText('Parent Node'); + fireEvent.click(parentNode); + + expect(screen.getByText('Child Node 1')).toBeInTheDocument(); + expect(screen.getByText('Child Node 2')).toBeInTheDocument(); + + fireEvent.click(parentNode); + }); + + it('renders child nodes correctly', () => { + render(); + + expect(screen.getByText('Child Node 1')).toBeInTheDocument(); + expect(screen.getByText('Child Node 2')).toBeInTheDocument(); + }); +}); diff --git a/frontend/__test__/admin_components/UserList.test.tsx b/frontend/__test__/admin_components/UserList.test.tsx new file mode 100644 index 00000000..b95e6328 --- /dev/null +++ b/frontend/__test__/admin_components/UserList.test.tsx @@ -0,0 +1,28 @@ +import { render, screen } from "@testing-library/react"; +import React from "react"; +import UserList from "@app/[locale]/components/admin_components/UserList"; + +jest.mock("react-i18next", () => ({ + useTranslation: () => ({ + t: (key) => key + }), +})); + +describe("UserList", () => { + it("renders with translated headers, BackButton text, and ListView props", async () => { + render(); + + // BackButton + const backButton = screen.getByText("back_to home page"); // Match translated button text partially + + expect(backButton).toBeInTheDocument(); + + // ListView headers + const emailHeader = screen.getByText(/email/i); // Match translated email partially + const roleHeader = screen.getByText(/role/i); // Match translated role partially + + expect(emailHeader).toBeInTheDocument(); + expect(roleHeader).toBeInTheDocument(); + + }); +}); diff --git a/frontend/__test__/course_components/ArchiveButton.test.tsx b/frontend/__test__/course_components/ArchiveButton.test.tsx new file mode 100644 index 00000000..455fc1e9 --- /dev/null +++ b/frontend/__test__/course_components/ArchiveButton.test.tsx @@ -0,0 +1,64 @@ +import {render, screen, fireEvent} from "@testing-library/react"; +import React from "react"; +import ArchiveButton from "@app/[locale]/components/course_components/ArchiveButton"; +import {APIError, archiveCourse} from "@lib/api"; + +jest.mock("react-i18next", () => ({ + useTranslation: () => ({ + t: (key) => key + }), +})); + +const mockCourse = + { + course_id: 1, + name: "Course 1", + description: "Description for Course 1", + year: 2023, + open_course: true, + banner: null, + }; + +jest.mock("../../lib/api", () => ({ + archiveCourse: jest.fn(), +})); + + +describe("ArchiveButton", () => { + beforeEach(() => { + jest.resetAllMocks(); + archiveCourse.mockResolvedValueOnce(mockCourse.course_id); + }); + + it("renders correctly and click the button", async () => { + render(); + + const archiveButton = screen.getByRole("button", {name: /archive course/i}); + + expect(archiveButton).toBeInTheDocument(); + + fireEvent.click(archiveButton); + + await expect(archiveCourse).toHaveBeenCalledWith(1); + }); + + it("api error", async () => { + // for some reason mocking the apierror doesnt work? + const mockAPIError = jest.fn(); + mockAPIError.mockImplementation(() => ({ + message: "API Error", + status: 400, + type: "UNKNOWN", + })); + + archiveCourse.mockResolvedValueOnce(mockAPIError); + + render(); + + const archiveButton = screen.getByRole("button", {name: /archive course/i}); + + fireEvent.click(archiveButton); + + }); + +}); diff --git a/frontend/__test__/course_components/CancelButton.test.tsx b/frontend/__test__/course_components/CancelButton.test.tsx new file mode 100644 index 00000000..e134106e --- /dev/null +++ b/frontend/__test__/course_components/CancelButton.test.tsx @@ -0,0 +1,10 @@ +import {render, screen} from "@testing-library/react"; +import React from "react"; +import CancelButton from "@app/[locale]/components/course_components/CancelButton"; + +describe("CancelButton", () => { + it("renders cancel button and click", async () => { + render(); + screen.getByText(/cancel/i).click(); + }); +}); diff --git a/frontend/__test__/course_components/DeleteButton.test.tsx b/frontend/__test__/course_components/DeleteButton.test.tsx new file mode 100644 index 00000000..e94ff56f --- /dev/null +++ b/frontend/__test__/course_components/DeleteButton.test.tsx @@ -0,0 +1,35 @@ +import {render, screen, fireEvent} from "@testing-library/react"; +import React from "react"; +import DeleteButton from "@app/[locale]/components/course_components/DeleteButton"; +import {deleteCourse} from "@lib/api"; + +jest.mock("react-i18next", () => ({ + useTranslation: () => ({ + t: (key) => key + }), +})); + + +jest.mock("../../lib/api", () => ({ + deleteCourse: jest.fn(() => Promise.resolve()), +})); + +describe("DeleteButton", () => { + it("render and delete", async () => { + render(); + + const deleteButton = screen.getByRole("button", {name: /delete course/i}); + expect(deleteButton).toBeInTheDocument(); + + fireEvent.click(deleteButton); + + const dialogTitle = screen.getByText("Are you sure you want to delete this course?"); + const cancelButton = screen.getByRole("button", {name: /cancel/i}); + const deleteButtonInDialog = screen.getByRole("button", {name: /delete/i}); + + fireEvent.click(cancelButton); + + fireEvent.click(deleteButton); + fireEvent.click(deleteButtonInDialog); + }); +}); diff --git a/frontend/__test__/general/ItemList.test.tsx b/frontend/__test__/general/ItemList.test.tsx new file mode 100644 index 00000000..26983ba7 --- /dev/null +++ b/frontend/__test__/general/ItemList.test.tsx @@ -0,0 +1,33 @@ +import {render, screen, fireEvent} from "@testing-library/react"; +import React from "react"; +import ItemList from "@app/[locale]/components/general/ItemsList"; + +jest.mock("react-i18next", () => ({ + useTranslation: () => ({ + t: (key) => key + }), +})); + +const mockCourse = + { + course_id: 1, + name: "Course 1", + description: "Description for Course 1", + year: 2023, + open_course: true, + banner: null, + }; + + + +describe("ItemList", () => { + + it("renders correctly", async () => { + const mockSetItems = jest.fn(); + render(); + }); +}); diff --git a/frontend/__test__/general/RequiredFilesList.test.tsx b/frontend/__test__/general/RequiredFilesList.test.tsx new file mode 100644 index 00000000..76fef109 --- /dev/null +++ b/frontend/__test__/general/RequiredFilesList.test.tsx @@ -0,0 +1,25 @@ +import {render} from "@testing-library/react"; +import React from "react"; +import RequiredFilesList from "@app/[locale]/components/general/RequiredFilesList"; + +jest.mock("react-i18next", () => ({ + useTranslation: () => ({ + t: (key) => key + }), +})); + + +describe("RequiredFilesList", () => { + + it("renders correctly", async () => { + const mockSetItems = jest.fn(); + render(); + }); +}); diff --git a/frontend/__test__/project/edit/BackButton.test.tsx b/frontend/__test__/project/edit/BackButton.test.tsx new file mode 100644 index 00000000..86aab476 --- /dev/null +++ b/frontend/__test__/project/edit/BackButton.test.tsx @@ -0,0 +1,11 @@ +import {fireEvent, render, screen} from "@testing-library/react"; +import React from "react"; +import BackButton from "@app/[locale]/components/project_components/BackButton"; + +describe("BackButton", () => { + it("render", async () => { + render(); + const backButton = screen.getByRole("button", {name: /back/i}); + fireEvent.click(backButton); + }); +}); diff --git a/frontend/__test__/project/edit/Groups.test.tsx b/frontend/__test__/project/edit/Groups.test.tsx index d27667ff..868c8f2c 100644 --- a/frontend/__test__/project/edit/Groups.test.tsx +++ b/frontend/__test__/project/edit/Groups.test.tsx @@ -1,24 +1,65 @@ -import getTranslations from "../../translations"; -import {render, screen} from "@testing-library/react"; -import Groups from "@app/[locale]/components/project_components/groups"; -import React from "react"; +import React from 'react'; +import {render, screen, fireEvent} from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; +import Groups from '@app/[locale]/components/project_components/groups'; + +jest.mock('react-i18next', () => ({ + useTranslation: () => ({t: (key: any) => key}) +})); describe('Groups', () => { - it('renders correctly', async () => { - const {getByText: getByText_en} = render( - + let setGroupAmount: jest.Mock; + let setGroupSize: jest.Mock; + + beforeEach(() => { + setGroupAmount = jest.fn(); + setGroupSize = jest.fn(); + }); + + const renderComponent = (props: any) => { + return render( + ); + }; + + it('renders component correctly', () => { + renderComponent({ + groupAmount: 5, + isGroupAmountEmpty: false, + groupSize: 3, + isGroupSizeEmpty: false, + setGroupAmount, + setGroupSize, + }); - // check that it was rendered properly + expect(screen.getByText('groups')).toBeInTheDocument(); expect(screen.getByText('group_amount')).toBeInTheDocument(); expect(screen.getByText('group_size')).toBeInTheDocument(); + }); + + it('displays error for empty group amount', () => { + renderComponent({ + groupAmount: '', + isGroupAmountEmpty: true, + groupSize: 3, + isGroupSizeEmpty: false, + setGroupAmount, + setGroupSize, + }); + + expect(screen.getByText('group_amount_required')).toBeInTheDocument(); + }); + + it('displays error for empty group size', () => { + renderComponent({ + groupAmount: 5, + isGroupAmountEmpty: false, + groupSize: '', + isGroupSizeEmpty: true, + setGroupAmount, + setGroupSize, + }); + expect(screen.getByText('group_size_required')).toBeInTheDocument(); }); -}); \ No newline at end of file +}); diff --git a/frontend/__test__/project/edit/Requiredfiles.test.tsx b/frontend/__test__/project/edit/Requiredfiles.test.tsx index 8ba02667..b150269a 100644 --- a/frontend/__test__/project/edit/Requiredfiles.test.tsx +++ b/frontend/__test__/project/edit/Requiredfiles.test.tsx @@ -1,27 +1,30 @@ -import getTranslations from "../../translations"; -import {render, screen} from "@testing-library/react"; -import RequiredFiles from "@app/[locale]/components/project_components/requiredFiles"; +import {render, screen, fireEvent} from "@testing-library/react"; import React from "react"; +import RequiredFiles from "@app/[locale]/components/project_components/requiredFiles"; -jest.mock('react-i18next', () => ({ - useTranslation: () => ({t: (key: any) => key}) +// Mock translations with actual translation logic (consider using a mocking library) +jest.mock("react-i18next", () => ({ + useTranslation: () => ({ + t: (key) => key + }), })); -describe('Requiredfiles', () => { - it('renders correctly', async () => { - const translations = await getTranslations(); - const {getByText: getByText_en, getByDisplayValue} = render( +describe("RequiredFiles", () => { + it("renders required files title and list with translations", async () => { + render( + file_status={["+", "-"]} + setFileStatus={jest.fn()} + /> ); - // check that the required files were rendered properly - expect(screen.getByText('required_files')).toBeInTheDocument(); - expect(screen.getByText('First')).toBeInTheDocument(); - expect(screen.getByText('Second')).toBeInTheDocument(); + const title = screen.getByText(/required_files/i); + const fileList = screen.getByRole("list"); + + expect(title).toBeInTheDocument(); + expect(fileList).toBeInTheDocument(); }); -}); \ No newline at end of file +}); diff --git a/frontend/__test__/project/edit/Title.test.tsx b/frontend/__test__/project/edit/Title.test.tsx index db68a9e7..dd789562 100644 --- a/frontend/__test__/project/edit/Title.test.tsx +++ b/frontend/__test__/project/edit/Title.test.tsx @@ -1,29 +1,114 @@ -import {render, screen} from "@testing-library/react"; +import {render, screen, fireEvent} from "@testing-library/react"; import React from "react"; import Title from "@app/[locale]/components/project_components/title"; -import getTranslations from "../../translations"; -jest.mock('react-i18next', () => ({ - useTranslation: () => ({t: (key: any) => key}) -})); +describe("Title", () => { + it("renders title and max score labels with translations", async () => { + render( + + ); + + // Assert translated labels using actual translation logic + expect(screen.getByRole("heading", {name: /title/i})).toBeInTheDocument(); + expect(screen.getByRole("heading", {name: /max_score/i})).toBeInTheDocument(); + }); + + it("renders title input with placeholder and helper text", () => { + render( + <Title + isTitleEmpty={false} + setTitle={jest.fn()} + title="Test title" + score={1} + isScoreEmpty={false} + setScore={jest.fn()} + /> + ); + + const titleInput = screen.getByLabelText("title"); + expect(titleInput).toBeInTheDocument(); + expect(titleInput).toHaveAttribute("placeholder", "title"); + }); + + it("renders score input with error when empty and sets score on change", () => { + render( + <Title + isTitleEmpty={false} + setTitle={jest.fn()} + title="Test title" + score={1} + isScoreEmpty={true} + setScore={jest.fn()} + /> + ); + + const scoreInput = screen.getByRole("spinbutton"); + expect(scoreInput).toHaveAttribute("aria-invalid", "true"); + + fireEvent.change(scoreInput, {target: {value: 50}}); + }); + + it("limits score input between 1 and 100", () => { + render( + <Title + isTitleEmpty={false} + setTitle={jest.fn()} + title="Test title" + score={1} + isScoreEmpty={false} + setScore={jest.fn()} + /> + ); + + const scoreInput = screen.getByRole("spinbutton"); -describe('Title', () => { - it('renders correctly', async () => { + fireEvent.change(scoreInput, {target: {value: 0}}); - const {getByText: getByRole} = render( + fireEvent.change(scoreInput, {target: {value: 150}}); + }); + + it("score no input", () => { + render( <Title isTitleEmpty={false} setTitle={jest.fn()} title="Test title" - score={50} + score={1} + isScoreEmpty={true} // Set initial score empty + setScore={jest.fn()} + /> + ); + + const scoreInput = screen.getByRole("spinbutton"); + + fireEvent.change(scoreInput, {target: {value: ''}}); + }); + + it("updates title state on title input change", () => { + const mockSetTitle = jest.fn(); // Mock the setTitle function + + render( + <Title + isTitleEmpty={false} + setTitle={mockSetTitle} + title="Test title" + score={1} isScoreEmpty={false} setScore={jest.fn()} /> ); - // check that the title and score were rendered properly - expect(screen.getByLabelText('title')).toBeInTheDocument(); - expect(screen.getByText('max_score')).toBeInTheDocument(); + const titleInput = screen.getByLabelText("title"); + fireEvent.change(titleInput, {target: {value: "New Title"}}); + expect(mockSetTitle).toHaveBeenCalledWith("New Title"); // Verify setTitle called with new value }); -}); \ No newline at end of file + +}); diff --git a/frontend/__test__/project/edit/Uploadbutton.test.tsx b/frontend/__test__/project/edit/Uploadbutton.test.tsx index 9d31e6a3..e09a4964 100644 --- a/frontend/__test__/project/edit/Uploadbutton.test.tsx +++ b/frontend/__test__/project/edit/Uploadbutton.test.tsx @@ -1,26 +1,27 @@ -import {fireEvent, render} from "@testing-library/react"; -import React from "react"; -import UploadTestFile from "@app/[locale]/components/project_components/uploadButton"; -import getTranslations from "../../translations"; +import React from 'react'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import UploadTestFile from '@app/[locale]/components/project_components/uploadButton'; -jest.mock('react-i18next', () => ({ - useTranslation: () => ({t: (key: any) => key}) -})); +describe('UploadTestFile', () => { + test('uploads files correctly', async () => { + const setTestfilesName = jest.fn(); + const setTestfilesData = jest.fn(); + const files = [ + new File(['test file 1'], 'testfile1.txt', { type: 'text/plain' }), + new File(['test file 2'], 'testfile2.txt', { type: 'text/plain' }) + ]; -describe('Uploadbutton', () => { - it('renders correctly', async () => { - const translations = await getTranslations(); - const {getByText: getByText_en, getByDisplayValue} = render( - <UploadTestFile - testfilesName={[]} - setTestfilesName={jest.fn()} - testfilesData={[]} - setTestfilesData={jest.fn()} - translations={translations.en} - /> - ); + render( + <UploadTestFile + testfilesName={['testfile1.txt', 'testfile2.txt']} + setTestfilesName={setTestfilesName} + testfilesData={[]} + setTestfilesData={setTestfilesData} + /> + ); - // check that the buttons were rendered properly - expect(getByText_en('upload')).toBeInTheDocument(); - }); -}); \ No newline at end of file + const input = screen.getByText('upload'); + fireEvent.change(input, { target: { files } }); + + }); +}); diff --git a/frontend/__test__/user_components/CancelButton.test.tsx b/frontend/__test__/user_components/CancelButton.test.tsx new file mode 100644 index 00000000..4cf43aa8 --- /dev/null +++ b/frontend/__test__/user_components/CancelButton.test.tsx @@ -0,0 +1,10 @@ +import {render, screen} from "@testing-library/react"; +import React from "react"; +import CancelButton from "@app/[locale]/components/user_components/CancelButton"; + +describe("CancelButton", () => { + it("renders cancel button and click", async () => { + render(<CancelButton/>); + screen.getByText(/cancel/i).click(); + }); +}); diff --git a/frontend/__test__/user_components/DeleteButton.test.tsx b/frontend/__test__/user_components/DeleteButton.test.tsx new file mode 100644 index 00000000..32b22681 --- /dev/null +++ b/frontend/__test__/user_components/DeleteButton.test.tsx @@ -0,0 +1,34 @@ +import {render, screen, fireEvent} from "@testing-library/react"; +import React from "react"; +import DeleteButton from "@app/[locale]/components/user_components/DeleteButton"; + +jest.mock("react-i18next", () => ({ + useTranslation: () => ({ + t: (key) => key + }), +})); + + +jest.mock("../../lib/api", () => ({ + deleteUser: jest.fn(() => Promise.resolve()), +})); + +describe("DeleteButton", () => { + it("render and delete", async () => { + render(<DeleteButton userId={1}/>); + + const deleteButton = screen.getByRole("button", {name: /delete user/i}); + expect(deleteButton).toBeInTheDocument(); + + fireEvent.click(deleteButton); + + const dialogTitle = screen.getByText("Are you sure you want to delete this user?"); + const cancelButton = screen.getByRole("button", {name: /cancel/i}); + const deleteButtonInDialog = screen.getByRole("button", {name: /delete/i}); + + fireEvent.click(cancelButton); + + fireEvent.click(deleteButton); + fireEvent.click(deleteButtonInDialog); + }); +});