diff --git a/README.md b/README.md
index dfab616..f3f2693 100644
--- a/README.md
+++ b/README.md
@@ -170,18 +170,22 @@ This software is licensed under AGPL-3.0 or later, at your convenience.
- stats
- when all values are 0, it doesn't show nicely
- features
+ - add other user to owner of course
- show stats for 7 / 14 / 31 days
- get all quizzes from a github repo
- enter the name of a github / gitlab
- compatibility with Delibay - probably difficult
- add "delete" button to clean students
- modes
- - exam mode: only once the admin switch is flicked will the students see if they answered correctly
- - live mode: after every question, students see if they answered correctly
- - updatable: students can update their questions
+ - groups of students for race
# CHANGELOG
+2024-05-23:
+- regroup wrong answers in regexps
+- when changing quiz for dojo, got the previous quiz in the dojo multiple times
+- twice quiz name in corrections
+
2024-05-19:
- add statistics
- show statistics
diff --git a/frontend/src/app/components/quiz/quiz.component.html b/frontend/src/app/components/quiz/quiz.component.html
index 9d86f25..1990edc 100644
--- a/frontend/src/app/components/quiz/quiz.component.html
+++ b/frontend/src/app/components/quiz/quiz.component.html
@@ -46,7 +46,7 @@
@for (match of answer.question.options.regexp!.match; track $index ) {
diff --git a/frontend/src/app/course/corrections/corrections.component.html b/frontend/src/app/course/corrections/corrections.component.html
index 870c92e..23cbedd 100644
--- a/frontend/src/app/course/corrections/corrections.component.html
+++ b/frontend/src/app/course/corrections/corrections.component.html
@@ -1,5 +1,4 @@
-
{{quiz.title}}
No results available
@@ -68,12 +67,13 @@
style="margin: auto 0 auto 1em; font-size: 120%;">{{results.chosen[sorted[qIndex][0]][$index]}}
}
- @for (wrong of results.texts[sorted[qIndex][0]]; track $index) {
+ @for (wrong of wrongRegexps; track $index) {
-
-
+
+
- 1
+ {{ wrong[1] }}
}
diff --git a/frontend/src/app/course/corrections/corrections.component.ts b/frontend/src/app/course/corrections/corrections.component.ts
index d492a88..a974c59 100644
--- a/frontend/src/app/course/corrections/corrections.component.ts
+++ b/frontend/src/app/course/corrections/corrections.component.ts
@@ -36,6 +36,7 @@ export class CorrectionsComponent {
results!: ResultsSummary;
qIndex = 0;
sorted: number[][] = [];
+ wrongRegexps: [string, number][] = [];
constructor(private livequiz: LivequizStorageService, private storage: StorageService,
private bcs: BreadcrumbService, private stats: StatsService) {
@@ -92,22 +93,31 @@ export class CorrectionsComponent {
updateClasses() {
for (let question = 0; question < this.quiz.questions.length; question++) {
const score = Math.round(this.sorted[question][1] * 8);
+ console.log(question, score);
this.tileClasses[question] = "questionTile" +
(question % 2 === 1 ? " questionTileOdd" : "") +
` questionTileColor${score}`;
}
this.tileClasses[this.qIndex] += " questionTileChosen";
+ this.wrongRegexps = [];
const question = this.quiz.questions[this.sorted[this.qIndex][0]];
if (question.options.multi !== undefined) {
this.resultClasses = question.options.multi.correct.map((_) => 'resultCorrect')
.concat(question.options.multi!.wrong.map((_) => 'resultWrong'));
} else {
- this.resultClasses = question.options.regexp!.match.map((_) => 'resultCorrect')
+ this.resultClasses = question.options.regexp!.match.map((_) => 'resultCorrect');
+ const wrongAnswers = this.results.texts[this.sorted[this.qIndex][0]];
+ [...new Set(wrongAnswers)].map((wrong, i) =>
+ this.wrongRegexps[i] = [wrong, wrongAnswers.filter((ans) => ans === wrong).length]);
}
+
if (this.results.chosen.length > 0) {
this.resultWidth = this.results.chosen[this.sorted[this.qIndex][0]]
.map((s) => `${Math.round(s / this.users.length * 50) + 50}%`);
+ if (this.wrongRegexps.length > 0) {
+ this.resultWidth = this.resultWidth.concat(this.wrongRegexps.map(([_, n]) => `${Math.round(n / this.users.length * 50) + 50}%`));
+ }
}
}
diff --git a/frontend/src/app/course/course-manage/course-manage.component.ts b/frontend/src/app/course/course-manage/course-manage.component.ts
index 5932b7c..8eab3fa 100644
--- a/frontend/src/app/course/course-manage/course-manage.component.ts
+++ b/frontend/src/app/course/course-manage/course-manage.component.ts
@@ -33,14 +33,7 @@ export class CourseManageComponent {
private stats: StatsService) { }
async ngOnChanges() {
- this.quizzes = [];
- for (const id of this.course.quizIds) {
- this.quizzes.push(await this.storage.getNomad(id, new Quiz()));
- }
- if (this.course.state.state !== CourseStateEnum.Idle) {
- const dojo = await this.livequiz.getDojo(this.course.state.getDojoID());
- this.quiz = await this.livequiz.getQuiz(dojo.quizId);
- }
+ await this.updateQuizzes();
if (!this.isStudent()) {
this.user.courses.set(this.course.id.toHex(), this.course.name);
this.stats.add(StatsService.course_join);
@@ -49,6 +42,21 @@ export class CourseManageComponent {
this.user.addCourse(this.course);
}
this.user.update();
+ await this.updateDojo();
+ }
+
+ async updateDojo(){
+ if (this.course.state.state !== CourseStateEnum.Idle) {
+ const dojo = await this.livequiz.getDojo(this.course.state.getDojoID());
+ this.quiz = await this.livequiz.getQuiz(dojo.quizId);
+ }
+ }
+
+ async updateQuizzes(){
+ this.quizzes = [];
+ for (const id of this.course.quizIds) {
+ this.quizzes.push(await this.storage.getNomad(id, new Quiz()));
+ }
}
isAdmin(): boolean {
@@ -62,6 +70,7 @@ export class CourseManageComponent {
async dojoQuiz(id: QuizID) {
this.stats.add(StatsService.dojo_start);
await this.livequiz.setDojoQuiz(this.course.id, id);
+ await this.updateDojo();
}
async dojoCorrections() {
@@ -71,6 +80,7 @@ export class CourseManageComponent {
async dojoIdle() {
this.stats.add(StatsService.dojo_stop);
await this.livequiz.setDojoIdle(this.course.id);
+ await this.updateDojo();
}
async deleteQuiz(id: QuizID) {
@@ -83,7 +93,7 @@ export class CourseManageComponent {
this.course.state.state = CourseStateEnum.Idle;
}
this.stats.add(StatsService.quiz_delete);
- this.ngOnChanges();
+ await this.updateDojo();
}
}
@@ -103,7 +113,7 @@ export class CourseManageComponent {
this.course.quizIds.push(q.id);
this.stats.add(StatsService.quiz_create_upload);
}
- this.ngOnChanges();
+ this.updateQuizzes();
} catch (e) {
await ModalModule.openOKCancel(this.dialog, 'Error',
`While reading quiz: ${e}`
diff --git a/frontend/src/test/livequiz.ts b/frontend/src/test/livequiz.ts
index b5edb7d..4654eca 100644
--- a/frontend/src/test/livequiz.ts
+++ b/frontend/src/test/livequiz.ts
@@ -38,12 +38,18 @@ export class Livequiz {
return this.by(By.xpath(xp));
}
- text(t: string): WEP {
- return this.xpath(`//*[contains(text(), "${t}")]`);
+ text(t: string, occurence = 1): WEP {
+ return this.xpath(`(//*[contains(text(), "${t}")])[${occurence}]`);
}
- async click(t: string) {
- await this.text(t).click();
+ async click(...texts: string[]) {
+ for (const text of texts) {
+ await this.text(text).click();
+ }
+ }
+
+ async find(t: string){
+ await this.text(t).find();
}
}
export class WEP {
diff --git a/frontend/src/test/newUser.spec.ts b/frontend/src/test/newUser.spec.ts
index bbc679b..8fd360c 100644
--- a/frontend/src/test/newUser.spec.ts
+++ b/frontend/src/test/newUser.spec.ts
@@ -1,7 +1,7 @@
import { Livequiz } from './livequiz';
import { readFileSync } from 'fs';
-describe("Logging in", () => {
+describe("E2E tests", () => {
it("Correctly identifies 2 users", async () => {
const admin = await Livequiz.reset();
await admin.id('cname').sendKeys('Testing');
@@ -22,6 +22,7 @@ describe("Logging in", () => {
await user1.id("userName").sendKeys("user1");
let user2 = await Livequiz.init(courseUrl);
+ await Livequiz.wait(200);
await user2.id("userName").clear();
await user2.id("userName").sendKeys("user2");
await user2.click("LiveQuiz");
@@ -51,7 +52,7 @@ describe("Logging in", () => {
await Livequiz.wait(100);
admin.browser.quit();
- });
+ }, 10000);
it("Stores stats", async () => {
const admin = await Livequiz.reset();
@@ -59,14 +60,86 @@ describe("Logging in", () => {
await Livequiz.wait(200);
let user1 = await Livequiz.init();
await user1.id('cname').sendKeys('Testing');
- await user1.css('button').click();
- await user1.click('Testing');
- await user1.click('Create Quiz');
- await user1.click('Save');
- await user1.click('Start Quiz');
- await user1.click('Enter Dojo');
+ await user1.click('Add a course', 'Testing', 'Create Quiz', 'Save', 'Start Quiz', 'Enter Dojo');
await Livequiz.wait(200);
await admin.click("Stats");
- })
-})
+
+ user1.browser.quit();
+ admin.browser.quit();
+ });
+
+ it("Corrections", async () => {
+ // Verify that the fully-wrong answer comes before the half-correct, which comes
+ // before the fully-correct one.
+ const admin = await Livequiz.reset();
+ await admin.id('cname').sendKeys('Testing');
+ await admin.click('Add a course', 'Testing');
+ await admin.id('fileInput').sendKeys(`${__dirname}/quiz1.md`);
+ await admin.click('Start Quiz', 'Enter Dojo', 'wrong', 'Next', 'correct', 'Next');
+ await admin.id('quizInput').sendKeys('one');
+ await admin.click('Testing', 'Start Corrections');
+
+ await admin.find("Question 1");
+ await admin.click('Next');
+ await admin.find("Question 2");
+ await admin.click('Next');
+ await admin.find("Question 3");
+ admin.browser.quit();
+ });
+
+ it("Change dojo", async () => {
+ // Create two quizzes and change the quiz in the dojo
+ const admin = await Livequiz.reset();
+ await admin.id('cname').sendKeys('Testing');
+ await admin.click('Add a course', 'Testing');
+ await admin.id('fileInput').sendKeys(`${__dirname}/quiz1.md`);
+ await admin.click('Create Quiz', 'Save');
+ await admin.click('Start Quiz');
+ await admin.find('Quiz in Dojo: Test Quiz');
+ await admin.text('Start Quiz', 2).click();
+ await admin.find('Quiz in Dojo: Title of Quiz');
+ admin.browser.quit();
+ });
+
+ it("Sorts wrong regexps", async () => {
+ // Create two quizzes and change the quiz in the dojo
+ const admin = await Livequiz.reset();
+ await admin.id('cname').sendKeys('Testing');
+ await admin.click('Add a course', 'Testing');
+ await admin.id('fileInput').sendKeys(`${__dirname}/quiz1.md`);
+ await admin.click('Start Quiz');
+ const courseUrl = await admin.browser.getCurrentUrl() + "/dojo";
+
+ const user1 = await Livequiz.init(courseUrl);
+ const user2 = await Livequiz.init(courseUrl);
+ const user3 = await Livequiz.init(courseUrl);
+ await Livequiz.wait(200);
+ await user1.click('Next', 'Next');
+ await user1.id('quizInput').sendKeys(' three!!');
+ await user2.click('Next', 'Next');
+ await user2.id('quizInput').sendKeys('four');
+ await user3.click('Next', 'Next');
+ await user3.id('quizInput').sendKeys('three!!');
+
+ await Livequiz.wait(300);
+ await admin.click("Corrections", "Next", "Next");
+ await admin.text('three').find();
+ await throwError(() => admin.text('three!!', 2).find());
+ await admin.text('four').find();
+
+ await admin.browser.quit();
+ await user1.browser.quit();
+ await user2.browser.quit();
+ await user3.browser.quit();
+ }, 10000)
+});
+
+async function throwError(f: () => Promise
): Promise {
+ try {
+ await f();
+ } catch (_) {
+ return;
+ }
+ return Promise.reject("Didn't throw error");
+}