- migration: open question type, rubric, max_length, quiz grader and the grading and review columns of attempt answers - open answers are checked, stored as pending and never scored on answer; a changed text resets the grading state - student JSON gains text, grading_status, grader_feedback, review_comment, max_length, needs_review and open_answers; the rubric is never serialized
74 lines
2.7 KiB
Go
74 lines
2.7 KiB
Go
package models
|
|
|
|
import (
|
|
"slices"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.golem15.com/golem15/sm-quizzes-plugin/graders"
|
|
)
|
|
|
|
func TestQuizDefaults(t *testing.T) {
|
|
zero, negative := 0, -1
|
|
for name, hook := range map[string]func(*Quiz) error{
|
|
"BeforeSave": func(q *Quiz) error { return q.BeforeSave(nil) },
|
|
"BeforeValidate": func(q *Quiz) error { return q.BeforeValidate(nil) },
|
|
} {
|
|
for _, limit := range []*int{&zero, &negative} {
|
|
q := &Quiz{Title: "Q", MaxAttempts: limit}
|
|
if err := hook(q); err != nil {
|
|
t.Fatalf("%s: %v", name, err)
|
|
}
|
|
if q.Mode != ModeLearning || q.Navigation != NavigationFree || q.MaxAttempts != nil {
|
|
t.Errorf("%s with max_attempts %d = %q %q %v, want learning free nil", name, *limit, q.Mode, q.Navigation, q.MaxAttempts)
|
|
}
|
|
if q.Grader != graders.Manual {
|
|
t.Errorf("%s left grader %q, want %q", name, q.Grader, graders.Manual)
|
|
}
|
|
}
|
|
two := 2
|
|
q := &Quiz{Title: "Q", Mode: ModeTest, Navigation: NavigationOneWay, MaxAttempts: &two, Grader: "ai"}
|
|
if err := hook(q); err != nil {
|
|
t.Fatalf("%s: %v", name, err)
|
|
}
|
|
if q.Mode != ModeTest || q.Navigation != NavigationOneWay || q.MaxAttempts == nil || *q.MaxAttempts != 2 {
|
|
t.Errorf("%s changed a test quiz: %q %q %v", name, q.Mode, q.Navigation, q.MaxAttempts)
|
|
}
|
|
if q.Grader != "ai" {
|
|
t.Errorf("%s changed an explicit grader to %q", name, q.Grader)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestOpenQuestionModel(t *testing.T) {
|
|
if !slices.Contains(QuestionTypes, TypeOpen) || len(QuestionTypes) != 4 {
|
|
t.Errorf("QuestionTypes = %v, want the open type appended", QuestionTypes)
|
|
}
|
|
if QuestionTypes[0] != TypeSingle || QuestionTypes[1] != TypeMultiple || QuestionTypes[2] != TypeSelfCheck {
|
|
t.Errorf("QuestionTypes reordered the existing types: %v", QuestionTypes)
|
|
}
|
|
rules := Question{}.Rules()
|
|
if !strings.Contains(rules["type"], "open") || !strings.Contains(rules["type"], "self_check") {
|
|
t.Errorf("type rule = %q, want every type including open", rules["type"])
|
|
}
|
|
if rules["max_length"] != "nullable|integer|between:1,20000" {
|
|
t.Errorf("max_length rule = %q", rules["max_length"])
|
|
}
|
|
fill := Question{}.Fillable()
|
|
if !slices.Contains(fill, "rubric") || !slices.Contains(fill, "max_length") {
|
|
t.Errorf("Fillable = %v, want rubric and max_length", fill)
|
|
}
|
|
if DefaultMaxLength != 2000 || MaxMaxLength != 20000 {
|
|
t.Errorf("max length limits = %d / %d", DefaultMaxLength, MaxMaxLength)
|
|
}
|
|
if len(GradingStatuses) != 4 || GradingStatuses[0] != GradingPending {
|
|
t.Errorf("GradingStatuses = %v", GradingStatuses)
|
|
}
|
|
if !slices.Contains(Quiz{}.Fillable(), "grader") {
|
|
t.Error("Quiz.Fillable lacks grader")
|
|
}
|
|
if len(AttemptAnswer{}.Fillable()) != 0 {
|
|
t.Error("AttemptAnswer.Fillable must stay empty")
|
|
}
|
|
}
|