diff --git a/package-lock.json b/package-lock.json
index e79b5dd..221b66f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -20,6 +20,7 @@
"@tanstack/react-query": "^5.32.0",
"@tanstack/react-query-devtools": "^5.32.0",
"@testing-library/react": "^15.0.7",
+ "@testing-library/user-event": "^14.5.2",
"axios": "^1.6.8",
"connect-redis": "^7.1.1",
"cors": "^2.8.5",
@@ -2655,6 +2656,18 @@
}
}
},
+ "node_modules/@testing-library/user-event": {
+ "version": "14.5.2",
+ "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.5.2.tgz",
+ "integrity": "sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ==",
+ "engines": {
+ "node": ">=12",
+ "npm": ">=6"
+ },
+ "peerDependencies": {
+ "@testing-library/dom": ">=7.21.4"
+ }
+ },
"node_modules/@tootallnate/once": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
diff --git a/package.json b/package.json
index 95e30f8..e8b9776 100644
--- a/package.json
+++ b/package.json
@@ -13,7 +13,7 @@
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --testMatch '**/server/**/*.test.*' --setupFilesAfterEnv ./setupTests.ts",
"test:watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --testMatch '**/server/**/*.test.*' --setupFilesAfterEnv ./setupTests.ts --watchAll",
"test:front": "node --experimental-vm-modules node_modules/jest/bin/jest.js --testMatch '**/client/**/*.test.*' --setupFilesAfterEnv ./setupTests.ts",
- "test:front:watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --testMatch '**/client/**/*.test.*' --setupFilesAfterEnv ./setupTests.ts --watchAll",
+ "test:front:watch": "DEBUG_PRINT_LIMIT=100000 node --experimental-vm-modules node_modules/jest/bin/jest.js --testMatch '**/client/**/*.test.*' --setupFilesAfterEnv ./setupTests.ts --watchAll",
"test:integration": "concurrently \"docker run -e PGDATA=/test-data -e POSTGRES_PASSWORD=postgres -p 5433:5432 postgres:15.6\" \"sleep 3 && NODE_ENV=test REACT_APP_E2E=true PORT=8001 DATABASE_URL=postgres://postgres:postgres@localhost:5433/postgres node --experimental-vm-modules node_modules/jest/bin/jest.js --setupFilesAfterEnv ./setupIntegrationTests.ts --forceExit\" --kill-others --success command-1 --hide 0 --prefix none",
"test:integration:watch": "concurrently \"docker run -e PGDATA=/test-data -e POSTGRES_PASSWORD=postgres -p 5433:5432 postgres:15.6\" \"NODE_ENV=test PORT=8001 DATABASE_URL=postgres://postgres:postgres@localhost:5433/postgres node --experimental-vm-modules node_modules/jest/bin/jest.js --watchAll --setupFilesAfterEnv ./setupIntegrationTests.ts\" --hide 0 --prefix none",
"lint": "eslint 'src/**/*.{ts,tsx}'",
@@ -44,6 +44,7 @@
"@tanstack/react-query": "^5.32.0",
"@tanstack/react-query-devtools": "^5.32.0",
"@testing-library/react": "^15.0.7",
+ "@testing-library/user-event": "^14.5.2",
"axios": "^1.6.8",
"connect-redis": "^7.1.1",
"cors": "^2.8.5",
diff --git a/src/client/components/ThesisPage/SupervisorSelect.test.jsx b/src/client/components/ThesisPage/SupervisorSelect.test.jsx
index 606d5ae..97c7f6c 100644
--- a/src/client/components/ThesisPage/SupervisorSelect.test.jsx
+++ b/src/client/components/ThesisPage/SupervisorSelect.test.jsx
@@ -5,6 +5,7 @@ import React from 'react'
import { render, screen } from '@testing-library/react'
import SupervisorSelect from './SupervisorSelect'
+import initializeI18n from '../../util/il18n'
describe('SupervisorSelect', () => {
const supervisors = [
@@ -16,6 +17,8 @@ describe('SupervisorSelect', () => {
beforeEach(() => {
setSupervisorSelections = jest.fn()
+
+ initializeI18n()
})
it('renders the SupervisorSelect component', () => {
@@ -27,7 +30,7 @@ describe('SupervisorSelect', () => {
/>
)
- expect(screen.getByText('Add Supervisor')).toBeInTheDocument()
+ expect(screen.getByText('Lisää ohjaaja')).toBeInTheDocument()
})
it('renders the SupervisorSelect component with a supervisor', () => {
@@ -40,8 +43,11 @@ describe('SupervisorSelect', () => {
setSupervisorSelections={setSupervisorSelections}
/>
)
+
+ expect(screen.getByText('Valitse ohjaaja')).toBeInTheDocument()
+ expect(screen.getByText('Osuus')).toBeInTheDocument()
expect(screen.getByText('John Doe')).toBeInTheDocument()
- expect(screen.getByText('Add Supervisor')).toBeInTheDocument()
+ expect(screen.getByText('Lisää ohjaaja')).toBeInTheDocument()
})
it('renders the SupervisorSelect component with multiple supervisors', () => {
@@ -57,7 +63,7 @@ describe('SupervisorSelect', () => {
)
expect(screen.getByText('John Doe')).toBeInTheDocument()
expect(screen.getByText('Jane Smith')).toBeInTheDocument()
- expect(screen.getByText('Add Supervisor')).toBeInTheDocument()
+ expect(screen.getByText('Lisää ohjaaja')).toBeInTheDocument()
})
describe('interactions', () => {
@@ -70,7 +76,7 @@ describe('SupervisorSelect', () => {
/>
)
- const select = screen.getByText('Add Supervisor')
+ const select = screen.getByText('Lisää ohjaaja')
select.click()
expect(setSupervisorSelections).toHaveBeenCalledTimes(1)
@@ -90,7 +96,7 @@ describe('SupervisorSelect', () => {
/>
)
- const removeButton = screen.getByText('Remove')
+ const removeButton = screen.getByText('Poista')
removeButton.click()
expect(setSupervisorSelections).toHaveBeenCalledTimes(1)
diff --git a/src/client/components/ThesisPage/SupervisorSelect.tsx b/src/client/components/ThesisPage/SupervisorSelect.tsx
index 108bcb1..e801781 100644
--- a/src/client/components/ThesisPage/SupervisorSelect.tsx
+++ b/src/client/components/ThesisPage/SupervisorSelect.tsx
@@ -10,12 +10,15 @@ import {
} from '@mui/material'
import { SupervisionData, User } from '@backend/types'
import { SupervisorSelection } from '@frontend/types'
+import { useTranslation } from 'react-i18next'
const SupervisorSelect: React.FC<{
supervisors: User[]
supervisorSelections: SupervisorSelection[]
setSupervisorSelections: (newSupervisions: SupervisionData[]) => void
}> = ({ supervisors, supervisorSelections, setSupervisorSelections }) => {
+ const { t } = useTranslation()
+
const handleSupervisorChange = (index: number, supervisorId: string) => {
const updatedSelections = [...supervisorSelections]
updatedSelections[index].userId = supervisorId
@@ -59,14 +62,14 @@ const SupervisorSelect: React.FC<{
// eslint-disable-next-line react/no-array-index-key
-
- Select supervisor
+
+ {t('thesisForm:selectSupervisor')}
)
@@ -99,7 +102,7 @@ const SupervisorSelect: React.FC<{
disabled={supervisorSelections.length === supervisors.length}
onClick={handleAddSupervisor}
>
- Add Supervisor
+ {t('thesisForm:addSupervisor')}
)
diff --git a/src/client/components/ThesisPage/ThesisEditForm.test.jsx b/src/client/components/ThesisPage/ThesisEditForm.test.jsx
new file mode 100644
index 0000000..0703a6b
--- /dev/null
+++ b/src/client/components/ThesisPage/ThesisEditForm.test.jsx
@@ -0,0 +1,207 @@
+/**
+ * @jest-environment jsdom
+ */
+import * as React from 'react'
+import dayjs from 'dayjs'
+import userEvent from '@testing-library/user-event'
+import {
+ fireEvent,
+ render,
+ screen,
+ waitFor,
+} from '@testing-library/react'
+
+import initializeI18n from '../../util/il18n'
+
+jest.unstable_mockModule('./src/client/hooks/useUsers', () => ({
+ default: jest.fn().mockReturnValue({
+ users: [
+ { id: 1, firstName: 'John', lastName: 'Doe', username: 'johndoe' },
+ { id: 2, firstName: 'Jane', lastName: 'Smith', username: 'janesmith' },
+ {
+ id: 3,
+ firstName: 'Bob',
+ lastName: 'Luukkainen',
+ username: 'bobluukkainen',
+ },
+ ],
+ }),
+}))
+jest.unstable_mockModule('@mui/icons-material/CloudUpload', () => ({
+ default: jest.fn().mockReturnValue('CloudUploadIcon'),
+}))
+jest.unstable_mockModule('@mui/icons-material/UploadFile', () => ({
+ default: jest.fn().mockReturnValue('UploadFileIcon'),
+}))
+jest.unstable_mockModule('@mui/icons-material/Error', () => ({
+ default: jest.fn().mockReturnValue('ErrorIcon'),
+}))
+
+const ThesisEditForm = (await import('./ThesisEditForm')).default
+
+describe('ThesisEditForm', () => {
+ let mockOnClose
+ let mockOnSubmit
+
+ const programs = [{ key: 'KH50_001', name: 'Test Program' }]
+
+ beforeEach(() => {
+ mockOnClose = jest.fn()
+ mockOnSubmit = jest.fn()
+ })
+
+ describe('when initialThesis is a new thesis', () => {
+ beforeEach(() => {
+ const initialThesis = {
+ programId: programs[0].key,
+ supervisions: [{ userId: 1, percentage: 100 }],
+ authors: [],
+ topic: '',
+ status: 'PLANNING',
+ startDate: dayjs().format('YYYY-MM-DD'),
+ targetDate: dayjs().add(1, 'year').format('YYYY-MM-DD'),
+ }
+
+ initializeI18n()
+
+ render(
+
+ )
+ })
+
+ it('renders ThesisEditForm correctly, renders all validation error, and submit button is disabled', () => {
+ expect(screen.getByText('Muokkaa gradu')).toBeInTheDocument()
+ expect(screen.getByText('Aihe')).toBeInTheDocument()
+ expect(screen.getByText('Ohjelma')).toBeInTheDocument()
+ expect(screen.getByText('Tekijä')).toBeInTheDocument()
+ expect(screen.getByText('Vaihe')).toBeInTheDocument()
+ expect(screen.getByText('Lataa tutkimussuunnitelma')).toBeInTheDocument()
+ expect(screen.getByText('Lataa työskentelysopimus')).toBeInTheDocument()
+ expect(screen.getByText('Lisää ohjaaja')).toBeInTheDocument()
+
+ expect(
+ screen.getByText('Tutkimussuunnitelma puuttuu')
+ ).toBeInTheDocument()
+ expect(screen.getByText('Työskentelysopimus puuttuu')).toBeInTheDocument()
+
+ expect(screen.getByRole('button', { name: 'Tallenna' })).toBeDisabled()
+ })
+
+ describe('when all required fields are filled', () => {
+ beforeEach(async () => {
+ const user = userEvent.setup()
+
+ const topicInput = screen.getByRole('textbox', { name: 'Aihe' })
+ const programSelect = screen.getAllByRole('combobox')[0]
+ const authorSelect = screen.getAllByRole('combobox')[1]
+ const statusSelect = screen.getAllByRole('combobox')[2]
+
+ const researchPlanInput = screen
+ .getByRole('button', {
+ name: 'CloudUploadIcon Lataa tutkimussuunnitelma',
+ })
+ .querySelector('input')
+
+ const waysOfWorkingInput = screen
+ .getByRole('button', {
+ name: 'CloudUploadIcon Lataa työskentelysopimus',
+ })
+ .querySelector('input')
+
+ // Fill all required fields
+ await user.type(topicInput, 'Test')
+ await user.click(programSelect)
+ await user.click(
+ screen.getAllByText(
+ "Bachelor's Programme in Mathematical Sciences"
+ )[0]
+ )
+
+ await user.click(authorSelect)
+ await waitFor(() => {
+ expect(screen.getByText('janesmith')).toBeInTheDocument()
+ })
+ await user.click(screen.getByText('janesmith'))
+
+ await user.click(statusSelect)
+ await user.click(screen.getAllByText('Planning')[0])
+
+ const testFile = new File(['test'], 'researchPlan.pdf', {
+ type: 'application/pdf',
+ })
+ fireEvent.change(researchPlanInput, {
+ target: { files: { item: () => testFile, length: 1, 0: testFile } },
+ })
+ expect(researchPlanInput.files[0]).toBe(testFile)
+
+ fireEvent.change(waysOfWorkingInput, {
+ target: { files: { item: () => testFile, length: 1, 0: testFile } },
+ })
+ expect(researchPlanInput.files[0]).toBe(testFile)
+ })
+
+ it('renders Submit button enabled and when clicked, calls onSubmit', async () => {
+ expect(
+ screen.queryByText('Tutkimussuunnitelma puuttuu')
+ ).not.toBeInTheDocument()
+ expect(
+ screen.queryByText('Työskentelysopimus puuttuu')
+ ).not.toBeInTheDocument()
+ expect(
+ screen.queryByText('Varmista, että ohjaajien yhteisprosentti on 100')
+ ).not.toBeInTheDocument()
+
+ const submitButton = screen.getByText('Tallenna').closest('button')
+
+ expect(submitButton).toBeEnabled()
+
+ await userEvent.click(submitButton)
+
+ expect(mockOnSubmit).toHaveBeenCalledTimes(1)
+ })
+ })
+ })
+
+ describe('when initialThesis is an existing thesis', () => {
+ beforeEach(() => {
+ const initialThesis = {
+ programId: programs[0].key,
+ supervisions: [{ userId: 1, percentage: 100 }],
+ authors: [{ userId: 2 }],
+ topic: 'Test',
+ status: 'PLANNING',
+ startDate: dayjs().format('YYYY-MM-DD'),
+ targetDate: dayjs().add(1, 'year').format('YYYY-MM-DD'),
+ researchPlan: {},
+ waysOfWorking: {},
+ }
+
+ initializeI18n()
+
+ render(
+
+ )
+ })
+
+ it('renders ThesisEditForm correctly, renders no validation error, and submit button is enabled', () => {
+ expect(screen.getByText('Muokkaa gradu')).toBeInTheDocument()
+ expect(screen.getByText('Aihe')).toBeInTheDocument()
+ expect(screen.getByText('Ohjelma')).toBeInTheDocument()
+ expect(screen.getByText('Tekijä')).toBeInTheDocument()
+ expect(screen.getByText('Vaihe')).toBeInTheDocument()
+ expect(screen.getByText('Lataa tutkimussuunnitelma')).toBeInTheDocument()
+ expect(screen.getByText('Lataa työskentelysopimus')).toBeInTheDocument()
+ expect(screen.getByText('Lisää ohjaaja')).toBeInTheDocument()
+
+ expect(screen.getByRole('button', { name: 'Tallenna' })).toBeEnabled()
+ })
+ })
+})
diff --git a/src/client/components/ThesisPage/ThesisEditForm.tsx b/src/client/components/ThesisPage/ThesisEditForm.tsx
index cc4fda6..f73c8f4 100644
--- a/src/client/components/ThesisPage/ThesisEditForm.tsx
+++ b/src/client/components/ThesisPage/ThesisEditForm.tsx
@@ -24,6 +24,7 @@ import { DatePicker, LocalizationProvider } from '@mui/x-date-pickers'
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'
import dayjs from 'dayjs'
import { useState } from 'react'
+import * as React from 'react'
import { useTranslation } from 'react-i18next'
import programs from '../mockPorgrams'
import SupervisorSelect from './SupervisorSelect'
@@ -61,7 +62,7 @@ const ThesisEditForm: React.FC<{
if (!users) {
return (