11import {
2- IsaacQuizDTO ,
2+ DetailedQuizSummaryDTO ,
33 IsaacQuizSectionDTO ,
44 QuestionDTO ,
55 QuizAttemptDTO ,
@@ -16,6 +16,8 @@ import {
1616 isTeacherOrAbove ,
1717 QUIZ_VIEW_STUDENT_ANSWERS_RELEASE_TIMESTAMP ,
1818 siteSpecific ,
19+ SUBJECTS ,
20+ TAG_ID ,
1921 useDeviceSize
2022} from "../../../services" ;
2123import { Spacer } from "../Spacer" ;
@@ -33,25 +35,38 @@ import {Markup} from "../markup";
3335import classNames from "classnames" ;
3436
3537type PageLinkCreator = ( page ?: number ) => string ;
38+ type QuizView = { quiz ?: DetailedQuizSummaryDTO & { subjectId : SUBJECTS | TAG_ID } , quizId : string | undefined } ;
3639
37- export interface QuizAttemptProps {
38- attempt : QuizAttemptDTO ;
40+ interface QuizProps {
3941 user : RegisteredUserDTO ;
40- page : number | null ;
41- questions : QuestionDTO [ ] ;
42- sections : { [ id : string ] : IsaacQuizSectionDTO } ;
43- pageLink : PageLinkCreator ;
4442 pageHelp : React . ReactElement ;
45- preview ?: boolean ;
4643 studentUser ?: UserSummaryDTO ;
4744 quizAssignmentId ?: string ;
4845}
46+ export interface QuizAttemptProps extends QuizProps {
47+ attempt : QuizAttemptDTO
48+ view ?: undefined ;
49+ preview ?: boolean ;
50+ page : number | null ;
51+ pageLink : PageLinkCreator ;
52+ questions : QuestionDTO [ ] ;
53+ sections : { [ id : string ] : IsaacQuizSectionDTO } ;
54+ }
55+ interface QuizViewProps extends QuizProps {
56+ attempt ?: undefined ;
57+ view : QuizView ;
58+ preview ?: undefined ;
59+ page ?: undefined ;
60+ pageLink ?: undefined ;
61+ questions ?: undefined ;
62+ sections ?: undefined ;
63+ }
4964
5065function inSection ( section : IsaacQuizSectionDTO , questions : QuestionDTO [ ] ) {
5166 return questions . filter ( q => q . id ?. startsWith ( section . id as string + "|" ) ) ;
5267}
5368
54- function QuizContents ( { attempt, sections, questions, pageLink} : QuizAttemptProps ) {
69+ function QuizDetails ( { attempt, sections, questions, pageLink} : QuizAttemptProps ) {
5570 if ( isDefined ( attempt . completedDate ) ) {
5671 return attempt . feedbackMode === "NONE" ?
5772 < h4 > No feedback available</ h4 >
@@ -71,7 +86,7 @@ function QuizContents({attempt, sections, questions, pageLink}: QuizAttemptProps
7186 const section = sections [ k ] ;
7287 return < tr key = { k } >
7388 { attempt . feedbackMode === 'DETAILED_FEEDBACK' ?
74- < td > < Link replace to = { pageLink ( index + 1 ) } > { section . title } </ Link > </ td > :
89+ < td > < Link to = { pageLink ( index + 1 ) } > { section . title } </ Link > </ td > :
7590 < td > { section . title } </ td >
7691 }
7792 < td >
@@ -94,7 +109,7 @@ function QuizContents({attempt, sections, questions, pageLink}: QuizAttemptProps
94109 const answerCount = questionsInSection . filter ( q => q . bestAttempt !== undefined ) . length ;
95110 const completed = questionsInSection . length === answerCount ;
96111 return < li key = { k } >
97- < Link replace to = { pageLink ( index + 1 ) } > { section . title } </ Link >
112+ < Link to = { pageLink ( index + 1 ) } > { section . title } </ Link >
98113 { " " }
99114 < small className = "text-muted" > { completed ? "Completed" : anyStarted ? `${ answerCount } / ${ questionsInSection . length } ` : "" } </ small >
100115 </ li > ;
@@ -104,19 +119,20 @@ function QuizContents({attempt, sections, questions, pageLink}: QuizAttemptProps
104119 }
105120}
106121
107- function QuizHeader ( { attempt, preview, user} : QuizAttemptProps ) {
122+ function QuizHeader ( { attempt, preview, view , user} : QuizAttemptProps | QuizViewProps ) {
108123 const dispatch = useAppDispatch ( ) ;
109- const assignment = attempt . quizAssignment ;
110- if ( preview ) {
124+ if ( preview || view ) {
125+ const quiz = view ? view . quiz : attempt . quiz ;
111126 return < >
112- < EditContentButton doc = { attempt . quiz } />
113- < div className = "d-flex" >
114- < span > You are previewing this test.</ span >
127+ { preview && < EditContentButton doc = { attempt . quiz } /> }
128+ < div data-testid = "quiz-action" className = "d-flex" >
129+ < p > { preview ? " You are previewing this test." : "You are viewing the rubric for this test." } </ p >
115130 < Spacer />
116- { isTeacherOrAbove ( user ) && < Button onClick = { ( ) => dispatch ( showQuizSettingModal ( attempt . quiz as IsaacQuizDTO ) ) } > Set Test</ Button > }
131+ { isTeacherOrAbove ( user ) && < Button onClick = { ( ) => dispatch ( showQuizSettingModal ( quiz ! ) ) } > Set Test</ Button > }
117132 </ div >
118133 </ > ;
119- } else if ( isDefined ( assignment ) ) {
134+ } else if ( isDefined ( attempt . quizAssignment ) ) {
135+ const assignment = attempt . quizAssignment ;
120136 return < >
121137 < p className = "d-flex" >
122138 < span >
@@ -139,12 +155,12 @@ function QuizHeader({attempt, preview, user}: QuizAttemptProps) {
139155 </ Alert > }
140156 </ > ;
141157 } else {
142- return < p > You { attempt . completedDate ? "freely attempted" : "are freely attempting" } this test.</ p > ;
158+ return < p data-testid = "quiz-action" > You { attempt . completedDate ? "freely attempted" : "are freely attempting" } this test.</ p > ;
143159 }
144160}
145161
146- function QuizRubric ( { attempt} : { attempt : QuizAttemptDTO } ) {
147- const rubric = attempt . quiz ?. rubric ;
162+ function QuizRubric ( { attempt, view } : Pick < QuizAttemptProps | QuizViewProps , " attempt" | "view" > ) {
163+ const rubric = attempt ? attempt . quiz ?. rubric : view ? .quiz ?. rubric ;
148164 const renderRubric = ( rubric ?. children || [ ] ) . length > 0 ;
149165 return < div >
150166 { rubric && renderRubric && < div >
@@ -207,9 +223,20 @@ function QuizSection({attempt, page, studentUser, user, quizAssignmentId}: QuizA
207223
208224export const myQuizzesCrumbs = [ { title : siteSpecific ( "My Tests" , "My tests" ) , to : `/tests` } ] ;
209225export const teacherQuizzesCrumbs = [ { title : siteSpecific ( "Set / Manage Tests" , "Set tests" ) , to : `/set_tests` } ] ;
210- const QuizTitle = ( { attempt, page, pageLink, pageHelp, preview, studentUser, user} : QuizAttemptProps ) => {
211- let quizTitle = attempt . quiz ?. title || attempt . quiz ?. id || "Test" ;
212- if ( isDefined ( attempt . completedDate ) ) {
226+ export const rubricCrumbs = [ { title : siteSpecific ( "Practice Tests" , "Practice tests" ) , to : "/practice_tests" } ] ;
227+ const getCrumbs = ( preview : boolean | undefined , view : boolean | undefined , user : RegisteredUserDTO ) => {
228+ if ( preview && isTeacherOrAbove ( user ) ) {
229+ return teacherQuizzesCrumbs ;
230+ } if ( view ) {
231+ return rubricCrumbs ;
232+ }
233+ return myQuizzesCrumbs ;
234+ } ;
235+
236+ const QuizTitle = ( { attempt, view, page, pageLink, pageHelp, preview, studentUser, user} : QuizAttemptProps | QuizViewProps ) => {
237+ const quiz = attempt ? attempt . quiz : view . quiz ;
238+ let quizTitle = quiz ?. title || quiz ?. id || "Test" ;
239+ if ( isDefined ( attempt ?. completedDate ) ) {
213240 quizTitle += " Feedback" ;
214241 }
215242 if ( isDefined ( studentUser ) ) {
@@ -218,8 +245,9 @@ const QuizTitle = ({attempt, page, pageLink, pageHelp, preview, studentUser, use
218245 if ( preview ) {
219246 quizTitle += " Preview" ;
220247 }
221- const crumbs = preview && isTeacherOrAbove ( user ) ? teacherQuizzesCrumbs : myQuizzesCrumbs ;
222- if ( page === null ) {
248+
249+ const crumbs = getCrumbs ( preview , ! ! view , user ) ;
250+ if ( page === null || page === undefined ) {
223251 return < TitleAndBreadcrumb currentPageTitle = { quizTitle } help = { pageHelp }
224252 intermediateCrumbs = { crumbs }
225253 /> ;
@@ -246,31 +274,38 @@ export function QuizPagination({page, sections, pageLink, finalLabel}: QuizAttem
246274 const nextLink = pageLink ( ! finalSection ? page + 1 : undefined ) ;
247275
248276 return < div className = "d-flex w-100 justify-content-between align-items-center" >
249- < Button color = "primary" outline size = { below [ "sm" ] ( deviceSize ) ? "sm" : "" } tag = { Link } replace to = { backLink } > Back</ Button >
277+ < Button color = "primary" outline size = { below [ "sm" ] ( deviceSize ) ? "sm" : "" } tag = { Link } to = { backLink } > Back</ Button >
250278 < div className = "d-none d-md-block" > Section { page } / { sectionCount } </ div >
251- < Button color = "secondary" size = { below [ "sm" ] ( deviceSize ) ? "sm" : "" } tag = { Link } replace to = { nextLink } > { finalSection ? finalLabel : "Next" } </ Button >
279+ < Button color = "secondary" size = { below [ "sm" ] ( deviceSize ) ? "sm" : "" } tag = { Link } to = { nextLink } > { finalSection ? finalLabel : "Next" } </ Button >
280+ </ div > ;
281+ }
282+
283+ function QuizOverview ( props : QuizAttemptProps | QuizViewProps ) {
284+ const { studentUser, user, quizAssignmentId} = props ;
285+ const viewingAsSomeoneElse = isDefined ( studentUser ) && studentUser ?. id !== user ?. id ;
286+ return < div className = "mt-4" >
287+ { ! isDefined ( studentUser ?. id ) && < QuizHeader { ...props } /> }
288+ { viewingAsSomeoneElse && < div className = "mb-2" >
289+ You are viewing this test as < b > { studentUser ?. givenName } { studentUser ?. familyName } </ b > .{ quizAssignmentId && < > < Link to = { `/test/assignment/${ quizAssignmentId } /feedback` } > Click here</ Link > to return to the teacher test feedback page.</ > }
290+ </ div > }
291+ < QuizRubric { ...props } />
292+ { props . attempt && < QuizDetails { ...props } /> }
252293 </ div > ;
253294}
254295
255- export function QuizAttemptComponent ( props : QuizAttemptProps ) {
256- const { page, questions, studentUser, user, quizAssignmentId} = props ;
296+ function QuizQuestions ( props : Omit < QuizAttemptProps , 'page' > & { page : number } ) {
257297 // Assumes that ids of questions are defined - I don't know why this is not enforced in the editor/backend, because
258298 // we do unchecked casts of "possibly undefined" content ids to strings almost everywhere
259- const questionNumbers = Object . assign ( { } , ...questions . map ( ( q , i ) => ( { [ q . id as string ] : i + 1 } ) ) ) ;
260- const viewingAsSomeoneElse = isDefined ( studentUser ) && studentUser ?. id !== user ?. id ;
299+ const questionNumbers = Object . assign ( { } , ...props . questions . map ( ( q , i ) => ( { [ q . id as string ] : i + 1 } ) ) ) ;
300+
261301 return < QuizAttemptContext . Provider value = { { quizAttempt : props . attempt , questionNumbers} } >
262- < QuizTitle { ...props } />
263- { page === null ?
264- < div className = "mt-4" >
265- { ! isDefined ( studentUser ?. id ) && < QuizHeader { ...props } /> }
266- { viewingAsSomeoneElse && < div className = "mb-2" >
267- You are viewing this test as < b > { studentUser ?. givenName } { studentUser ?. familyName } </ b > .{ quizAssignmentId && < > < Link to = { `/test/assignment/${ quizAssignmentId } /feedback` } > Click here</ Link > to return to the teacher test feedback page.</ > }
268- </ div > }
269- < QuizRubric { ...props } />
270- < QuizContents { ...props } />
271- </ div >
272- :
273- < QuizSection { ...props } page = { page } />
274- }
302+ < QuizSection { ...props } page = { props . page } />
275303 </ QuizAttemptContext . Provider > ;
276304}
305+
306+ export function QuizContentsComponent ( props : QuizAttemptProps | QuizViewProps ) {
307+ return < >
308+ < QuizTitle { ...props } />
309+ { props . page === null || props . page === undefined ? < QuizOverview { ...props } /> : < QuizQuestions { ...props } page = { props . page } /> }
310+ </ > ;
311+ }
0 commit comments