feat: add test mode to quizzes and withhold its solutions
- mode, navigation and max_attempts columns with Go-side defaults - classes.Withheld gates every solution field of the answer feedback and the attempt result while a test attempt is open - a test's quiz detail lists no questions before the attempt starts - admin fields and lang keys for the three settings
This commit is contained in:
15
classes/policy.go
Normal file
15
classes/policy.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package classes
|
||||
|
||||
import "git.golem15.com/golem15/sm-quizzes-plugin/models"
|
||||
|
||||
// IsTest reports whether the quiz runs as a test. A zero or unknown mode is
|
||||
// learning, so a quiz written before test mode existed keeps its behaviour.
|
||||
func IsTest(q *models.Quiz) bool { return q != nil && q.Mode == models.ModeTest }
|
||||
|
||||
// Withheld reports whether an attempt's solutions must stay hidden: it is
|
||||
// an attempt at a test that is not finished yet. Every student serializer
|
||||
// drops is_correct, points, correct answers, explanations and model answers
|
||||
// while it is true.
|
||||
func Withheld(a *models.Attempt) bool {
|
||||
return a != nil && IsTest(a.Quiz) && a.CompletedAt == nil
|
||||
}
|
||||
39
classes/policy_test.go
Normal file
39
classes/policy_test.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package classes_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.golem15.com/golem15/sm-quizzes-plugin/classes"
|
||||
"git.golem15.com/golem15/sm-quizzes-plugin/models"
|
||||
)
|
||||
|
||||
func TestIsTestAndWithheld(t *testing.T) {
|
||||
if classes.IsTest(nil) {
|
||||
t.Error("IsTest(nil) = true")
|
||||
}
|
||||
for mode, want := range map[string]bool{"": false, models.ModeLearning: false, "exam": false, models.ModeTest: true} {
|
||||
if got := classes.IsTest(&models.Quiz{Mode: mode}); got != want {
|
||||
t.Errorf("IsTest(mode %q) = %v, want %v", mode, got, want)
|
||||
}
|
||||
}
|
||||
done := time.Now()
|
||||
test, learning := &models.Quiz{Mode: models.ModeTest}, &models.Quiz{Mode: models.ModeLearning}
|
||||
cases := []struct {
|
||||
name string
|
||||
a *models.Attempt
|
||||
want bool
|
||||
}{
|
||||
{"open test", &models.Attempt{Quiz: test}, true},
|
||||
{"completed test", &models.Attempt{Quiz: test, CompletedAt: &done}, false},
|
||||
{"open learning", &models.Attempt{Quiz: learning}, false},
|
||||
{"zero mode", &models.Attempt{Quiz: &models.Quiz{}}, false},
|
||||
{"nil attempt", nil, false},
|
||||
{"nil quiz", &models.Attempt{}, false},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
if got := classes.Withheld(tc.a); got != tc.want {
|
||||
t.Errorf("Withheld(%s) = %v, want %v", tc.name, got, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -258,6 +258,10 @@ type Feedback struct {
|
||||
Question models.Question
|
||||
Result ScoreResult
|
||||
CorrectAnswerIDs []uint
|
||||
// Withheld is set for an open test attempt: the solutions stay hidden
|
||||
// and the serializer must drop Result, CorrectAnswerIDs and every
|
||||
// solution field of Question.
|
||||
Withheld bool
|
||||
}
|
||||
|
||||
// Answer records or overwrites the user's answer to one question of an
|
||||
@@ -334,7 +338,7 @@ func (s *Service) Answer(ctx context.Context, userID, attemptID uint, in AnswerI
|
||||
}).Create(&row).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
fb = &Feedback{Question: question, Result: res, CorrectAnswerIDs: correct}
|
||||
fb = &Feedback{Question: question, Result: res, CorrectAnswerIDs: correct, Withheld: Withheld(attempt)}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user