Skip to content

Commit a612458

Browse files
Copilotacbart
andcommitted
Add SolidJS quiz system implementation with tests
- Create Quiz model with reactive state management (signals, memos) - Implement Quizzer main component with quiz flow control - Add QuestionComponent with support for 4 question types: * Multiple choice (radio buttons) * True/false (boolean selection) * Short answer (text input) * Multiple answers (checkboxes) - Implement quiz state machine (Ready → Attempting → Completed) - Add attempt tracking and limits - Support 3 feedback modes (Immediate, Summary, None) - Create instructor and student view modes - Add 2 comprehensive test fixtures (simple and mixed types) - Implement 34 new tests for quiz system (all passing) - Create quiz demo page - Add comprehensive documentation - Build output: 37.4 KB total (12 KB gzipped) - Total: 100 tests passing (66 watcher + 34 quiz) Co-authored-by: acbart <897227+acbart@users.noreply.github.com>
1 parent 70c3d52 commit a612458

12 files changed

Lines changed: 1887 additions & 0 deletions

File tree

frontend-solid/quiz-demo.html

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>BlockPy Quiz Demo - SolidJS</title>
7+
8+
<!-- Bootstrap CSS -->
9+
<link
10+
href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
11+
rel="stylesheet"
12+
>
13+
14+
<!-- Font Awesome -->
15+
<link
16+
rel="stylesheet"
17+
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"
18+
>
19+
</head>
20+
<body>
21+
<div class="container mt-5">
22+
<h1>BlockPy Quiz System - SolidJS Prototype</h1>
23+
<p class="lead">
24+
Demonstration of the SolidJS quiz system conversion.
25+
</p>
26+
27+
<div class="row">
28+
<div class="col-md-12">
29+
<div class="alert alert-info">
30+
<h5>Quiz Features Demonstrated:</h5>
31+
<ul>
32+
<li>Multiple question types (Multiple Choice, True/False, Short Answer, Multiple Answers)</li>
33+
<li>Quiz state management (Ready → Attempting → Completed)</li>
34+
<li>Attempt limits and tracking</li>
35+
<li>Immediate feedback display</li>
36+
<li>Instructor/Student view modes</li>
37+
<li>Reactive state with SolidJS signals</li>
38+
</ul>
39+
</div>
40+
</div>
41+
</div>
42+
43+
<div class="row mt-4">
44+
<div class="col-md-6">
45+
<h3>Simple Multiple Choice Quiz</h3>
46+
<div id="quiz-1" class="border p-3 rounded"></div>
47+
</div>
48+
49+
<div class="col-md-6">
50+
<h3>Mixed Question Types</h3>
51+
<div id="quiz-2" class="border p-3 rounded"></div>
52+
</div>
53+
</div>
54+
55+
<div class="row mt-5">
56+
<div class="col-md-12">
57+
<h3>Instructor View</h3>
58+
<div id="quiz-instructor" class="border p-3 rounded"></div>
59+
</div>
60+
</div>
61+
</div>
62+
63+
<script type="module" src="/src/app.tsx"></script>
64+
65+
<!-- Example initialization -->
66+
<script type="module">
67+
import { initQuizzer, QuizFeedbackType, QuizPoolRandomness } from '/src/app.tsx';
68+
69+
// Quiz 1: Simple Multiple Choice
70+
const simpleQuiz = {
71+
assignmentId: 201,
72+
courseId: 1,
73+
userId: 1,
74+
instructions: {
75+
settings: {
76+
attemptLimit: 2,
77+
coolDown: -1,
78+
feedbackType: QuizFeedbackType.IMMEDIATE,
79+
questionsPerPage: -1,
80+
poolRandomness: QuizPoolRandomness.SEED,
81+
readingId: null
82+
},
83+
pools: [],
84+
questions: {
85+
'q1': {
86+
id: 'q1',
87+
type: 'multiple_choice_question',
88+
body: '<p>What is 2 + 2?</p>',
89+
points: 1,
90+
answers: ['3', '4', '5', '6']
91+
},
92+
'q2': {
93+
id: 'q2',
94+
type: 'multiple_choice_question',
95+
body: '<p>Which programming language is this course about?</p>',
96+
points: 1,
97+
answers: ['Java', 'Python', 'C++', 'JavaScript']
98+
}
99+
}
100+
},
101+
submission: {
102+
studentAnswers: {},
103+
attempt: {
104+
attempting: false,
105+
count: 0,
106+
mulligans: 0
107+
},
108+
feedback: {}
109+
}
110+
};
111+
112+
// Quiz 2: Mixed Types
113+
const mixedQuiz = {
114+
assignmentId: 202,
115+
courseId: 1,
116+
userId: 2,
117+
instructions: {
118+
settings: {
119+
attemptLimit: 3,
120+
coolDown: -1,
121+
feedbackType: QuizFeedbackType.IMMEDIATE,
122+
questionsPerPage: -1,
123+
poolRandomness: QuizPoolRandomness.ATTEMPT,
124+
readingId: null
125+
},
126+
pools: [],
127+
questions: {
128+
'tf1': {
129+
id: 'tf1',
130+
type: 'true_false_question',
131+
body: '<p>Python is a compiled language.</p>',
132+
points: 1
133+
},
134+
'sa1': {
135+
id: 'sa1',
136+
type: 'short_answer_question',
137+
body: '<p>What keyword is used to define a function in Python?</p>',
138+
points: 1
139+
},
140+
'ma1': {
141+
id: 'ma1',
142+
type: 'multiple_answers_question',
143+
body: '<p>Which of the following are valid Python data types?</p>',
144+
points: 2,
145+
answers: ['int', 'str', 'boolean', 'list']
146+
}
147+
}
148+
},
149+
submission: {
150+
studentAnswers: {},
151+
attempt: {
152+
attempting: false,
153+
count: 0,
154+
mulligans: 0
155+
},
156+
feedback: {}
157+
}
158+
};
159+
160+
// Initialize quizzes
161+
document.addEventListener('DOMContentLoaded', () => {
162+
initQuizzer('#quiz-1', simpleQuiz, false);
163+
initQuizzer('#quiz-2', mixedQuiz, false);
164+
initQuizzer('#quiz-instructor', simpleQuiz, true);
165+
});
166+
</script>
167+
</body>
168+
</html>

frontend-solid/src/app.tsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,24 @@
44

55
import { render } from 'solid-js/web';
66
import { Watcher } from './components/watcher/Watcher';
7+
import { Quizzer } from './components/quizzes/Quizzer';
78
import { WatchMode } from './components/watcher/SubmissionState';
9+
import { QuizData } from './components/quizzes/types';
810

911
// Export components for external use
1012
export { Watcher } from './components/watcher/Watcher';
1113
export { SubmissionHistory } from './components/watcher/SubmissionHistory';
1214
export { WatchMode, FeedbackMode } from './components/watcher/SubmissionState';
1315

16+
// Export quiz components
17+
export { Quizzer } from './components/quizzes/Quizzer';
18+
export type { QuizData } from './components/quizzes/types';
19+
export {
20+
QuizMode,
21+
QuizFeedbackType,
22+
QuizQuestionType
23+
} from './components/quizzes/types';
24+
1425
// Export models
1526
export { User } from './models/user';
1627
export { Assignment } from './models/assignment';
@@ -47,11 +58,37 @@ export function initWatcher(
4758
render(() => <Watcher {...props} />, element);
4859
}
4960

61+
/**
62+
* Initialize a Quizzer component in the given container
63+
* @param container - DOM element or selector where the component should be mounted
64+
* @param quizData - Quiz data including instructions and submission
65+
* @param isInstructor - Whether the user is an instructor
66+
*/
67+
export function initQuizzer(
68+
container: HTMLElement | string,
69+
quizData: QuizData,
70+
isInstructor: boolean = false
71+
) {
72+
const element = typeof container === 'string'
73+
? document.querySelector(container)
74+
: container;
75+
76+
if (!element) {
77+
console.error('Container element not found:', container);
78+
return;
79+
}
80+
81+
render(() => <Quizzer quizData={quizData} isInstructor={isInstructor} />, element);
82+
}
83+
5084
// Make it available globally for template usage
5185
if (typeof window !== 'undefined') {
5286
(window as any).frontendSolid = {
5387
initWatcher,
88+
initQuizzer,
5489
Watcher,
90+
Quizzer,
5591
WatchMode,
5692
};
5793
}
94+

0 commit comments

Comments
 (0)