diff --git a/src/constants/request.ts b/src/constants/request.ts index fe328769fc..5f8b85c745 100644 --- a/src/constants/request.ts +++ b/src/constants/request.ts @@ -126,12 +126,12 @@ export const URLs = { patch: '/quizzes/:id', delete: '/quizzes/:id' }, - finishedQuizzes: { - add: '/finished-quizzes', - patch: '/finished-quizzes/:id', - get: '/finished-quizzes', - getById: '/finished-quizzes/:id', - getByQuizId: '/finished-quizzes/:cooperationId/:quizId' + attempts: { + add: '/quizzes/attempts', + patch: '/quizzes/attempts/:id', + get: '/quizzes/attempts', + getById: '/quizzes/attempts/:id', + getByQuizId: '/quizzes/attempts/:cooperationId/:quizId' }, attachments: { post: '/attachments' diff --git a/src/containers/quiz/quiz-header/QuizHeader.tsx b/src/containers/quiz/quiz-header/QuizHeader.tsx index 656fbedce2..8517765992 100644 --- a/src/containers/quiz/quiz-header/QuizHeader.tsx +++ b/src/containers/quiz/quiz-header/QuizHeader.tsx @@ -2,7 +2,7 @@ import Box from '@mui/material/Box' import { ActiveQuizInfo, - FinishedQuizInfo, + AttemptInfo, TutorQuizInfo } from '~/containers/quiz/quiz-info/QuizInfo' import TitleWithDescription from '~/components/title-with-description/TitleWithDescription' @@ -51,7 +51,7 @@ const QuizHeader: React.FC = ({ if (type === 'finished') { return ( - = ({ ) } -type FinishedQuizInfoProps = { +type AttemptInfoProps = { points: number totalPoints: number createdAt: string updatedAt: string } -const FinishedQuizInfo: React.FC = ({ +const AttemptInfo: React.FC = ({ points, totalPoints, createdAt, @@ -254,4 +254,4 @@ const StartViewQuizInfo: React.FC = ({ ) } -export { ActiveQuizInfo, FinishedQuizInfo, TutorQuizInfo, StartViewQuizInfo } +export { ActiveQuizInfo, AttemptInfo, TutorQuizInfo, StartViewQuizInfo } diff --git a/src/pages/quiz-attempts/QuizAttempts.tsx b/src/pages/quiz-attempts/QuizAttempts.tsx index ad000c2e19..e3b163acf9 100644 --- a/src/pages/quiz-attempts/QuizAttempts.tsx +++ b/src/pages/quiz-attempts/QuizAttempts.tsx @@ -49,13 +49,13 @@ const QuizAttemptsPage: React.FC = () => { const isTimeLimitNeeded = (timeLimit as string) != 'No limit' - const getFinishedQuizzes = useCallback(() => { - return ResourceService.getFinishedQuizzesByQuizId(cooperationId, quizId) + const getAttempts = useCallback(() => { + return ResourceService.getAttemptByQuizId(cooperationId, quizId) }, [cooperationId, quizId]) - const { data: finishedQuizzes = [] } = useQuery({ - queryKey: ['finished-quizzes', cooperationId, quizId], - queryFn: getFinishedQuizzes, + const { data: attempts = [] } = useQuery({ + queryKey: ['attempts', cooperationId, quizId], + queryFn: getAttempts, options: { staleTime: ONE_HOUR } @@ -88,8 +88,8 @@ const QuizAttemptsPage: React.FC = () => { } const attemptsList = - finishedQuizzes.length !== 0 ? ( - finishedQuizzes.map((item) => { + attempts.length !== 0 ? ( + attempts.map((item) => { return ( { onStart={isTimeLimitNeeded ? openModal : handleStart} questionsAmount={items.length} timeLimit={timeLimit} - usedAttempts={finishedQuizzes.length} + usedAttempts={attempts.length} /> {attemptsList} diff --git a/src/pages/quiz-review/QuizReview.tsx b/src/pages/quiz-review/QuizReview.tsx index 0e6e09fa9e..afb127fceb 100644 --- a/src/pages/quiz-review/QuizReview.tsx +++ b/src/pages/quiz-review/QuizReview.tsx @@ -1,14 +1,14 @@ import { useParams } from 'react-router-dom' import PageWrapper from '~/components/page-wrapper/PageWrapper' import { styles } from '~/pages/quiz-review/QuizReview.styles' -import { FinishedQuiz } from '~/pages/quiz/QuizVariants' +import { Attempt } from '~/pages/quiz/QuizVariants' const QuizReview: React.FC = () => { const { attemptId = '' } = useParams() return ( - + ) } diff --git a/src/pages/quiz/QuizVariants.tsx b/src/pages/quiz/QuizVariants.tsx index 7a3ef9ab5e..1df461a51d 100644 --- a/src/pages/quiz/QuizVariants.tsx +++ b/src/pages/quiz/QuizVariants.tsx @@ -42,7 +42,7 @@ const ActiveQuiz: React.FC = () => { const { t } = useTranslation() const [isOpen, setIsOpen] = useState(false) - const [finishedQuizId, setFinishedQuizId] = useState('') + const [attemptId, setAttemptId] = useState('') const { handleInputChange, handleNonInputValueChange, data } = useForm< Record @@ -97,8 +97,8 @@ const ActiveQuiz: React.FC = () => { }) }, [data, items]) - const addFinishedQuiz = useCallback(() => { - return ResourceService.addFinishedQuiz({ + const addAttempt = useCallback(() => { + return ResourceService.addAttempt({ cooperation: cooperationId, quiz: quizId, grade, @@ -108,14 +108,14 @@ const ActiveQuiz: React.FC = () => { const { handleErrorAlert, handleAlert } = useSnackbarAlert() - const { mutateAsync: createFinishedQuiz } = useMutation({ - mutationFn: addFinishedQuiz, - queryKey: ['finished-quizzes'], + const { mutateAsync: createAttempt } = useMutation({ + mutationFn: addAttempt, + queryKey: ['attempts'], onError: handleErrorAlert }) - const editFinishedQuiz = useCallback(() => { - return ResourceService.editFinishedQuiz(finishedQuizId, { + const editAttempt = useCallback(() => { + return ResourceService.editAttempt(attemptId, { grade, results: items.map(({ text, answers, _id }) => { return { @@ -130,11 +130,11 @@ const ActiveQuiz: React.FC = () => { } }) }) - }, [data, finishedQuizId, grade, items]) + }, [data, attemptId, grade, items]) - const { mutate: updateFinishedQuiz } = useMutation({ - mutationFn: editFinishedQuiz, - queryKey: ['finished-quizzes'], + const { mutate: updateAttempt } = useMutation({ + mutationFn: editAttempt, + queryKey: ['attempts'], onError: handleErrorAlert }) @@ -146,7 +146,7 @@ const ActiveQuiz: React.FC = () => { handleInputChange={handleInputChange} handleNonInputValueChange={handleNonInputChange} isEditable - onNextButtonClick={updateFinishedQuiz} + onNextButtonClick={updateAttempt} questions={items} sx={styles.selectableQuestionQuizWrapper} /> @@ -165,7 +165,7 @@ const ActiveQuiz: React.FC = () => { }, []) const handleFinish = useCallback(() => { - updateFinishedQuiz() + updateAttempt() setIsOpen(false) handleAlert({ @@ -177,7 +177,7 @@ const ActiveQuiz: React.FC = () => { navigate( getFullUrl({ pathname: authRoutes.cooperationQuizReview.route, - parameters: { id: cooperationId, quizId, attemptId: finishedQuizId } + parameters: { id: cooperationId, quizId, attemptId: attemptId } }) ) } else { @@ -187,22 +187,22 @@ const ActiveQuiz: React.FC = () => { scoredResponses, cooperationId, quizId, - finishedQuizId, + attemptId, handleAlert, navigate, - updateFinishedQuiz + updateAttempt ]) const questionsAnswered = Object.keys(data).length useEffect(() => { - const postFinishedQuiz = async () => { - const finishedQuiz = await createFinishedQuiz() - setFinishedQuizId(finishedQuiz?._id) + const postAttempt = async () => { + const attempt = await createAttempt() + setAttemptId(attempt?._id) } - void postFinishedQuiz() - }, [createFinishedQuiz]) + void postAttempt() + }, [createAttempt]) if (isLoading || !quiz) { return @@ -241,16 +241,16 @@ const ActiveQuiz: React.FC = () => { ) } -type FinishedQuizProps = { - finishedQuizId: string +type AttemptProps = { + attemptId: string } -const FinishedQuiz: React.FC = ({ finishedQuizId }) => { +const Attempt: React.FC = ({ attemptId }) => { const { quizId = '' } = useParams() - const getFinishedQuiz = useCallback(() => { - return ResourceService.getFinishedQuiz(finishedQuizId) - }, [finishedQuizId]) + const getAttempt = useCallback(() => { + return ResourceService.getAttempt(attemptId) + }, [attemptId]) const { handleInputChange, handleNonInputValueChange } = useForm< Record @@ -262,9 +262,9 @@ const FinishedQuiz: React.FC = ({ finishedQuizId }) => { handleNonInputValueChange(key, value) } - const { data: finishedQuiz, isLoading: isFinishedQuizLoading } = useQuery({ - queryKey: ['finished-quizzes', finishedQuizId], - queryFn: getFinishedQuiz + const { data: attempt, isLoading: isAttemptLoading } = useQuery({ + queryKey: ['attempts', attemptId], + queryFn: getAttempt }) const { quiz, isLoading: isQuizLoading } = useQuizQuery(quizId) @@ -280,7 +280,7 @@ const FinishedQuiz: React.FC = ({ finishedQuizId }) => { const mappedResults = useMemo(() => { const result: Record = {} - finishedQuiz?.results?.forEach(({ question, answers }) => { + attempt?.results?.forEach(({ question, answers }) => { const quizQuestion = quiz?.items.find((item) => item.text === question) const quizQuestionId = quizQuestion?._id @@ -294,7 +294,7 @@ const FinishedQuiz: React.FC = ({ finishedQuizId }) => { }) return result - }, [finishedQuiz?.results, quiz?.items]) + }, [attempt?.results, quiz?.items]) const questionsBlock = isStepper ? ( = ({ finishedQuizId }) => { /> ) - if (isFinishedQuizLoading || !finishedQuiz || isQuizLoading) { + if (isAttemptLoading || !attempt || isQuizLoading) { return } @@ -327,13 +327,13 @@ const FinishedQuiz: React.FC = ({ finishedQuizId }) => { {questionsBlock} @@ -405,4 +405,4 @@ const TutorQuiz: React.FC = () => { ) } -export { ActiveQuiz, FinishedQuiz, TutorQuiz } +export { ActiveQuiz, Attempt, TutorQuiz } diff --git a/src/services/resource-service.ts b/src/services/resource-service.ts index d64ea4afa9..0cbffcbc1a 100644 --- a/src/services/resource-service.ts +++ b/src/services/resource-service.ts @@ -20,13 +20,13 @@ import { CreateCategoriesParams, UpdateQuestionParams, CreateQuizParams, - type CreateFinishedQuizParams, - type UpdateFinishedQuizParams, + type CreateAttemptParams, + type UpdateAttemptParams, Quiz, UpdateQuizParams, ApiMethodEnum, GetQuestion, - type FinishedQuiz, + type Attempt, type FinishedAttempts } from '~/types' import { createUrlPath } from '~/utils/helper-functions' @@ -132,37 +132,37 @@ export const ResourceService = { }) }) }, - addFinishedQuiz: async (data: CreateFinishedQuizParams) => { - return baseService.request({ + addAttempt: async (data: CreateAttemptParams) => { + return baseService.request({ method: 'POST', - url: URLs.finishedQuizzes.add, + url: URLs.attempts.add, data }) }, - editFinishedQuiz: (id: string, data: UpdateFinishedQuizParams) => { + editAttempt: (id: string, data: UpdateAttemptParams) => { return baseService.request({ method: 'PATCH', url: getFullUrl({ - pathname: URLs.finishedQuizzes.patch, + pathname: URLs.attempts.patch, parameters: { id } }), data }) }, - getFinishedQuiz: async (id: string) => { - return baseService.request({ + getAttempt: async (id: string) => { + return baseService.request({ method: 'GET', url: getFullUrl({ - pathname: URLs.finishedQuizzes.getById, + pathname: URLs.attempts.getById, parameters: { id } }) }) }, - getFinishedQuizzesByQuizId: (cooperationId: string, quizId: string) => { + getAttemptByQuizId: (cooperationId: string, quizId: string) => { return baseService.request({ method: 'GET', url: getFullUrl({ - pathname: URLs.finishedQuizzes.getByQuizId, + pathname: URLs.attempts.getByQuizId, parameters: { cooperationId, quizId } }) }) diff --git a/src/types/attempt/attempt.index.ts b/src/types/attempt/attempt.index.ts new file mode 100644 index 0000000000..eccb166a62 --- /dev/null +++ b/src/types/attempt/attempt.index.ts @@ -0,0 +1 @@ +export * from './types/attempt.types' diff --git a/src/types/finished-quizzes/types/finishedQuizzes.types.ts b/src/types/attempt/types/attempt.types.ts similarity index 66% rename from src/types/finished-quizzes/types/finishedQuizzes.types.ts rename to src/types/attempt/types/attempt.types.ts index 43d8806a6c..40b37a5146 100644 --- a/src/types/finished-quizzes/types/finishedQuizzes.types.ts +++ b/src/types/attempt/types/attempt.types.ts @@ -11,16 +11,16 @@ export type Result = { answers: Answer[] } -export type CreateFinishedQuizParams = { +export type CreateAttemptParams = { quiz: string cooperation: string grade: number results: Result[] } -export type UpdateFinishedQuizParams = { +export type UpdateAttemptParams = { grade: number results: Result[] } -export type FinishedQuiz = CreateFinishedQuizParams & CommonEntityFields +export type Attempt = CreateAttemptParams & CommonEntityFields diff --git a/src/types/finished-quizzes/finishedQuizzes.index.ts b/src/types/finished-quizzes/finishedQuizzes.index.ts deleted file mode 100644 index 09d2a96c5b..0000000000 --- a/src/types/finished-quizzes/finishedQuizzes.index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './types/finishedQuizzes.types' diff --git a/src/types/index.ts b/src/types/index.ts index b7a1bcc44b..402d6577df 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -22,7 +22,7 @@ export * from '~/types/chat/media/media.index' export * from '~/types/attachment/attachment.index' export * from '~/types/my-attachments/myAttachments.index' export * from '~/types/quizzes/quizzes.index' -export * from '~/types/finished-quizzes/finishedQuizzes.index' +export * from '~/types/attempt/attempt.index' export * from '~/types/lesson/lesson.index' export * from '~/types/questions/questions.index' export * from '~/types/course/course.index' diff --git a/src/types/quizzes/interfaces/quizzes.interface.ts b/src/types/quizzes/interfaces/quizzes.interface.ts index 0bbcd5e9fd..eca30f6516 100644 --- a/src/types/quizzes/interfaces/quizzes.interface.ts +++ b/src/types/quizzes/interfaces/quizzes.interface.ts @@ -1,4 +1,4 @@ -import { type FinishedQuiz } from '~/types/finished-quizzes/types/finishedQuizzes.types' +import { type Attempt } from '~/types/attempt/types/attempt.types' import { CommonEntityFields, UserResponse, @@ -40,4 +40,4 @@ export interface UpdateQuizParams id: string } -export type FinishedAttempts = FinishedQuiz[] +export type FinishedAttempts = Attempt[] diff --git a/tests/unit/pages/quiz-attempt/QuizAttempts.spec.jsx b/tests/unit/pages/quiz-attempt/QuizAttempts.spec.jsx index 2e72747d3d..1f9ffa02f7 100644 --- a/tests/unit/pages/quiz-attempt/QuizAttempts.spec.jsx +++ b/tests/unit/pages/quiz-attempt/QuizAttempts.spec.jsx @@ -1,12 +1,14 @@ import { beforeAll, beforeEach, expect, vi } from 'vitest' import { screen, fireEvent } from '@testing-library/react' import QuizAttemptsPage from '~/pages/quiz-attempts/QuizAttempts' +import { Attempt } from '~/pages/quiz/QuizVariants' import { ResourcesTypesEnum as ResourceType, UserRoleEnum } from '~/types' import { mockAxiosClient, renderWithProviders } from '~tests/test-utils' import { URLs } from '~/constants/request' const mockQuizId = '6641388f36ebdb0432a3a2e5' const mockCooperationId = '67ba3b3e4ab9fe9998c7ca2b' +const mockAttemptId = '67ba3be14ab9fe9998c7cacb' const mockQuiz = { _id: mockQuizId, @@ -41,15 +43,16 @@ const mockQuiz = { description: 'Js' } -const mockFinishedQuizzes = [ +const mockAttempts = [ { - _id: '67ba3be14ab9fe9998c7cacb', - quiz: '67ba3bb14ab9fe9998c7ca7d', + _id: mockAttemptId, + quiz: mockQuizId, cooperation: mockCooperationId, grade: 100, results: [ { - question: 'Question 1', + question: + 'What is the difference between function expression and function declaration?', answers: [ { text: 'Correct', @@ -95,21 +98,19 @@ describe('QuizPage for student', () => { .onGet(new RegExp(URLs.quizzes.getById.replace(':id', mockQuizId))) .reply(200, mockQuiz) mockAxiosClient - .onGet(URLs.finishedQuizzes.getById.replace(':id', '')) + .onGet(URLs.attempts.getById.replace(':id', '')) .reply(200, mockQuiz) mockAxiosClient - .onGet( - new RegExp(URLs.finishedQuizzes.getById.replace(':id', mockQuizId)) - ) + .onGet(new RegExp(URLs.attempts.getById.replace(':id', mockQuizId))) .reply(200, mockQuiz) mockAxiosClient .onGet( - URLs.finishedQuizzes.getByQuizId + URLs.attempts.getByQuizId .replace(':cooperationId', mockCooperationId) .replace(':quizId', mockQuizId) ) - .reply(200, mockFinishedQuizzes) + .reply(200, mockAttempts) }) beforeEach(() => { @@ -132,3 +133,38 @@ describe('QuizPage for student', () => { expect(quizTitle).toBeInTheDocument() }) }) + +describe('Attempt page', () => { + beforeEach(() => { + mockUseParams.mockReturnValue({ + id: mockCooperationId, + quizId: mockQuizId + }) + }) + + beforeAll(() => { + mockAxiosClient + .onGet(new RegExp(URLs.attempts.getById.replace(':id', mockAttemptId))) + .reply(200, mockQuiz) + + mockAxiosClient + .onGet(new RegExp(URLs.quizzes.getById.replace(':id', mockQuizId))) + .reply(200, mockQuiz) + }) + + beforeEach(() => { + renderWithProviders() + }) + + it('should render quiz title and description after loading', async () => { + const quizTitle = await screen.findByText('JS Quiz') + expect(quizTitle).toBeInTheDocument() + }) + + it('should render quiz attempt question', async () => { + const quizQuestion = await screen.findByText( + 'What is the difference between function expression and function declaration?' + ) + expect(quizQuestion).toBeInTheDocument() + }) +}) diff --git a/tests/unit/pages/quiz/Quiz.spec.jsx b/tests/unit/pages/quiz/Quiz.spec.jsx index 8728a8c3d1..042d0d1ea9 100644 --- a/tests/unit/pages/quiz/Quiz.spec.jsx +++ b/tests/unit/pages/quiz/Quiz.spec.jsx @@ -53,14 +53,12 @@ describe('QuizPage for student', () => { .onGet(new RegExp(URLs.quizzes.getById.replace(':id', mockQuizId))) .reply(200, mockQuiz) mockAxiosClient - .onGet(new RegExp(URLs.finishedQuizzes.getById.replace(':id', ''))) + .onGet(new RegExp(URLs.attempts.getById.replace(':id', ''))) .reply(200, mockQuiz) mockAxiosClient - .onGet( - new RegExp(URLs.finishedQuizzes.getById.replace(':id', mockQuizId)) - ) + .onGet(new RegExp(URLs.attempts.getById.replace(':id', mockQuizId))) .reply(200, mockQuiz) - mockAxiosClient.onPost(URLs.finishedQuizzes.add).reply(204, mockQuiz) + mockAxiosClient.onPost(URLs.attempts.add).reply(204, mockQuiz) }) beforeEach(() => { @@ -129,7 +127,7 @@ describe('Quiz tutor variant for tutor', () => { mockAxiosClient .onGet(new RegExp(URLs.quizzes.getById.replace(':id', mockQuizId))) .reply(200, mockQuiz) - mockAxiosClient.onPost(URLs.finishedQuizzes.add).reply(204, mockQuiz) + mockAxiosClient.onPost(URLs.attempts.add).reply(204, mockQuiz) }) beforeEach(() => {