added shorter syntax for multipel choice

This commit is contained in:
2023-12-12 12:09:39 -07:00
parent 95b913ec05
commit a4b260941f
11 changed files with 408 additions and 437 deletions

View File

@@ -0,0 +1,62 @@
using LocalModels;
public class MatchingTests
{
[Test]
public void CanParseMatchingQuestion()
{
var rawMarkdownQuiz = @"
Name: Test Quiz
ShuffleAnswers: true
OneQuestionAtATime: false
DueAt: 2023-08-21T23:59:00
LockAt: 2023-08-21T23:59:00
AssignmentGroup: Assignments
AllowedAttempts: -1
Description:
---
Match the following terms & definitions
^ statement - a single command to be executed
^ identifier - name of a variable
^ keyword - reserved word that has special meaning in a program (e.g. class, void, static, etc.)
";
var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz);
var firstQuestion = quiz.Questions.First();
firstQuestion.QuestionType.Should().Be(QuestionType.MATCHING);
firstQuestion.Text.Should().NotContain("statement");
firstQuestion.Answers.First().MatchedText.Should().Be("a single command to be executed");
}
[Test]
public void CanCreateMarkdownForMatchingQuesiton()
{
var rawMarkdownQuiz = @"
Name: Test Quiz
ShuffleAnswers: true
OneQuestionAtATime: false
DueAt: 2023-08-21T23:59:00
LockAt: 2023-08-21T23:59:00
AssignmentGroup: Assignments
AllowedAttempts: -1
Description:
---
Match the following terms & definitions
^ statement - a single command to be executed
^ identifier - name of a variable
^ keyword - reserved word that has special meaning in a program (e.g. class, void, static, etc.)
";
var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz);
var questionMarkdown = quiz.Questions.First().ToMarkdown();
var expectedMarkdown = @"Points: 1
Match the following terms & definitions
^ statement - a single command to be executed
^ identifier - name of a variable
^ keyword - reserved word that has special meaning in a program (e.g. class, void, static, etc.)";
questionMarkdown.Should().Contain(expectedMarkdown);
}
}

View File

@@ -0,0 +1,83 @@
using LocalModels;
public class MultipleAnswersTests
{
[Test]
public void QuzMarkdownIncludesMultipleAnswerQuestion()
{
var quiz = new LocalQuiz()
{
Name = "Test Quiz",
Description = "desc",
LockAt = DateTime.MaxValue,
DueAt = DateTime.MaxValue,
ShuffleAnswers = true,
OneQuestionAtATime = false,
LocalAssignmentGroupName = "someId",
AllowedAttempts = -1,
Questions = new LocalQuizQuestion[]
{
new()
{
Text = "oneline question",
Points = 1,
QuestionType = QuestionType.MULTIPLE_ANSWERS,
Answers = new LocalQuizQuestionAnswer[]
{
new() { Correct = true, Text = "true" },
new() { Correct = true, Text = "false"},
new() { Correct = false, Text = "neither"},
}
}
}
};
var markdown = quiz.ToMarkdown();
var expectedQuestionString = @"
Points: 1
oneline question
[*] true
[*] false
[ ] neither
";
markdown.Should().Contain(expectedQuestionString);
}
[Test]
public void CanParseQuestionWithMultipleAnswers()
{
var rawMarkdownQuiz = @"
Name: Test Quiz
ShuffleAnswers: true
OneQuestionAtATime: false
DueAt: 2023-08-21T23:59:00
LockAt: 2023-08-21T23:59:00
AssignmentGroup: Assignments
AllowedAttempts: -1
Description: this is the
multi line
description
---
Which events are triggered when the user clicks on an input field?
[*] click
[*] focus
[*] mousedown
[] submit
[] change
[] mouseout
[] keydown
---
";
var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz);
var firstQuestion = quiz.Questions.First();
firstQuestion.Points.Should().Be(1);
firstQuestion.QuestionType.Should().Be(QuestionType.MULTIPLE_ANSWERS);
firstQuestion.Text.Should().Contain("Which events are triggered when the user clicks on an input field?");
firstQuestion.Answers.First().Text.Should().Be("click");
firstQuestion.Answers.First().Correct.Should().BeTrue();
firstQuestion.Answers.ElementAt(3).Correct.Should().BeFalse();
firstQuestion.Answers.ElementAt(3).Text.Should().Be("submit");
}
}

View File

@@ -0,0 +1,73 @@
using LocalModels;
public class MultipleChoiceTests
{
[Test]
public void QuzMarkdownIncludesMultipleChoiceQuestion()
{
var quiz = new LocalQuiz()
{
Name = "Test Quiz",
Description = "desc",
LockAt = DateTime.MaxValue,
DueAt = DateTime.MaxValue,
ShuffleAnswers = true,
OneQuestionAtATime = false,
LocalAssignmentGroupName = "someId",
AllowedAttempts = -1,
Questions = new LocalQuizQuestion[]
{
new LocalQuizQuestion()
{
Points = 2,
Text = @"`some type` of question
with many
```
lines
```
",
QuestionType = QuestionType.MULTIPLE_CHOICE,
Answers = new LocalQuizQuestionAnswer[]
{
new LocalQuizQuestionAnswer() { Correct = true, Text = "true" },
new LocalQuizQuestionAnswer() { Correct = false, Text = "false" + Environment.NewLine +Environment.NewLine + "endline" },
}
}
}
};
var markdown = quiz.ToMarkdown();
var expectedQuestionString = @"
Points: 2
`some type` of question
with many
```
lines
```
*a) true
b) false
endline
";
markdown.Should().Contain(expectedQuestionString);
}
[Test]
public void LetterOptionalForMultipleChoice()
{
var questionMarkdown = @"Points: 2
`some type` of question
*) true
) false
";
var question = LocalQuizQuestion.ParseMarkdown(questionMarkdown, 0);
question.Answers.Count().Should().Be(2);
}
}

View File

@@ -0,0 +1,114 @@
using LocalModels;
public class TextAnswerTests
{
[Test]
public void CanParseEssay()
{
var rawMarkdownQuiz = @"
Name: Test Quiz
ShuffleAnswers: true
OneQuestionAtATime: false
DueAt: 2023-08-21T23:59:00
LockAt: 2023-08-21T23:59:00
AssignmentGroup: Assignments
AllowedAttempts: -1
Description: this is the
multi line
description
---
Which events are triggered when the user clicks on an input field?
essay
";
var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz);
var firstQuestion = quiz.Questions.First();
firstQuestion.Points.Should().Be(1);
firstQuestion.QuestionType.Should().Be(QuestionType.ESSAY);
firstQuestion.Text.Should().NotContain("essay");
}
[Test]
public void CanParseShortAnswer()
{
var rawMarkdownQuiz = @"
Name: Test Quiz
ShuffleAnswers: true
OneQuestionAtATime: false
DueAt: 2023-08-21T23:59:00
LockAt: 2023-08-21T23:59:00
AssignmentGroup: Assignments
AllowedAttempts: -1
Description: this is the
multi line
description
---
Which events are triggered when the user clicks on an input field?
short answer
";
var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz);
var firstQuestion = quiz.Questions.First();
firstQuestion.Points.Should().Be(1);
firstQuestion.QuestionType.Should().Be(QuestionType.SHORT_ANSWER);
firstQuestion.Text.Should().NotContain("short answer");
}
[Test]
public void ShortAnswerToMarkdown_IsCorrect()
{
var rawMarkdownQuiz = @"
Name: Test Quiz
ShuffleAnswers: true
OneQuestionAtATime: false
DueAt: 2023-08-21T23:59:00
LockAt: 2023-08-21T23:59:00
AssignmentGroup: Assignments
AllowedAttempts: -1
Description: this is the
multi line
description
---
Which events are triggered when the user clicks on an input field?
short answer
";
var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz);
var firstQuestion = quiz.Questions.First();
var questionMarkdown = firstQuestion.ToMarkdown();
var expectedMarkdown = @"Points: 1
Which events are triggered when the user clicks on an input field?
short_answer";
questionMarkdown.Should().Contain(expectedMarkdown);
}
[Test]
public void EssayQuestionToMarkdown_IsCorrect()
{
var rawMarkdownQuiz = @"
Name: Test Quiz
ShuffleAnswers: true
OneQuestionAtATime: false
DueAt: 2023-08-21T23:59:00
LockAt: 2023-08-21T23:59:00
AssignmentGroup: Assignments
AllowedAttempts: -1
Description: this is the
multi line
description
---
Which events are triggered when the user clicks on an input field?
essay
";
var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz);
var firstQuestion = quiz.Questions.First();
var questionMarkdown = firstQuestion.ToMarkdown();
var expectedMarkdown = @"Points: 1
Which events are triggered when the user clicks on an input field?
essay";
questionMarkdown.Should().Contain(expectedMarkdown);
}
}

View File

@@ -1,302 +0,0 @@
using LocalModels;
public class QuizQuestionMarkdownTests
{
[Test]
public void QuzMarkdownIncludesMultipleChoiceQuestion()
{
var quiz = new LocalQuiz()
{
Name = "Test Quiz",
Description = "desc",
LockAt = DateTime.MaxValue,
DueAt = DateTime.MaxValue,
ShuffleAnswers = true,
OneQuestionAtATime = false,
LocalAssignmentGroupName = "someId",
AllowedAttempts = -1,
Questions = new LocalQuizQuestion[]
{
new LocalQuizQuestion()
{
Points = 2,
Text = @"`some type` of question
with many
```
lines
```
",
QuestionType = QuestionType.MULTIPLE_CHOICE,
Answers = new LocalQuizQuestionAnswer[]
{
new LocalQuizQuestionAnswer() { Correct = true, Text = "true" },
new LocalQuizQuestionAnswer() { Correct = false, Text = "false" + Environment.NewLine +Environment.NewLine + "endline" },
}
}
}
};
var markdown = quiz.ToMarkdown();
var expectedQuestionString = @"
Points: 2
`some type` of question
with many
```
lines
```
*a) true
b) false
endline
";
markdown.Should().Contain(expectedQuestionString);
}
[Test]
public void QuzMarkdownIncludesMultipleAnswerQuestion()
{
var quiz = new LocalQuiz()
{
Name = "Test Quiz",
Description = "desc",
LockAt = DateTime.MaxValue,
DueAt = DateTime.MaxValue,
ShuffleAnswers = true,
OneQuestionAtATime = false,
LocalAssignmentGroupName = "someId",
AllowedAttempts = -1,
Questions = new LocalQuizQuestion[]
{
new()
{
Text = "oneline question",
Points = 1,
QuestionType = QuestionType.MULTIPLE_ANSWERS,
Answers = new LocalQuizQuestionAnswer[]
{
new() { Correct = true, Text = "true" },
new() { Correct = true, Text = "false"},
new() { Correct = false, Text = "neither"},
}
}
}
};
var markdown = quiz.ToMarkdown();
var expectedQuestionString = @"
Points: 1
oneline question
[*] true
[*] false
[ ] neither
";
markdown.Should().Contain(expectedQuestionString);
}
[Test]
public void CanParseQuestionWithMultipleAnswers()
{
var rawMarkdownQuiz = @"
Name: Test Quiz
ShuffleAnswers: true
OneQuestionAtATime: false
DueAt: 2023-08-21T23:59:00
LockAt: 2023-08-21T23:59:00
AssignmentGroup: Assignments
AllowedAttempts: -1
Description: this is the
multi line
description
---
Which events are triggered when the user clicks on an input field?
[*] click
[*] focus
[*] mousedown
[] submit
[] change
[] mouseout
[] keydown
---
";
var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz);
var firstQuestion = quiz.Questions.First();
firstQuestion.Points.Should().Be(1);
firstQuestion.QuestionType.Should().Be(QuestionType.MULTIPLE_ANSWERS);
firstQuestion.Text.Should().Contain("Which events are triggered when the user clicks on an input field?");
firstQuestion.Answers.First().Text.Should().Be("click");
firstQuestion.Answers.First().Correct.Should().BeTrue();
firstQuestion.Answers.ElementAt(3).Correct.Should().BeFalse();
firstQuestion.Answers.ElementAt(3).Text.Should().Be("submit");
}
[Test]
public void CanParseEssay()
{
var rawMarkdownQuiz = @"
Name: Test Quiz
ShuffleAnswers: true
OneQuestionAtATime: false
DueAt: 2023-08-21T23:59:00
LockAt: 2023-08-21T23:59:00
AssignmentGroup: Assignments
AllowedAttempts: -1
Description: this is the
multi line
description
---
Which events are triggered when the user clicks on an input field?
essay
";
var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz);
var firstQuestion = quiz.Questions.First();
firstQuestion.Points.Should().Be(1);
firstQuestion.QuestionType.Should().Be(QuestionType.ESSAY);
firstQuestion.Text.Should().NotContain("essay");
}
[Test]
public void CanParseShortAnswer()
{
var rawMarkdownQuiz = @"
Name: Test Quiz
ShuffleAnswers: true
OneQuestionAtATime: false
DueAt: 2023-08-21T23:59:00
LockAt: 2023-08-21T23:59:00
AssignmentGroup: Assignments
AllowedAttempts: -1
Description: this is the
multi line
description
---
Which events are triggered when the user clicks on an input field?
short answer
";
var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz);
var firstQuestion = quiz.Questions.First();
firstQuestion.Points.Should().Be(1);
firstQuestion.QuestionType.Should().Be(QuestionType.SHORT_ANSWER);
firstQuestion.Text.Should().NotContain("short answer");
}
[Test]
public void ShortAnswerToMarkdown_IsCorrect()
{
var rawMarkdownQuiz = @"
Name: Test Quiz
ShuffleAnswers: true
OneQuestionAtATime: false
DueAt: 2023-08-21T23:59:00
LockAt: 2023-08-21T23:59:00
AssignmentGroup: Assignments
AllowedAttempts: -1
Description: this is the
multi line
description
---
Which events are triggered when the user clicks on an input field?
short answer
";
var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz);
var firstQuestion = quiz.Questions.First();
var questionMarkdown = firstQuestion.ToMarkdown();
var expectedMarkdown = @"Points: 1
Which events are triggered when the user clicks on an input field?
short_answer";
questionMarkdown.Should().Contain(expectedMarkdown);
}
[Test]
public void EssayQuestionToMarkdown_IsCorrect()
{
var rawMarkdownQuiz = @"
Name: Test Quiz
ShuffleAnswers: true
OneQuestionAtATime: false
DueAt: 2023-08-21T23:59:00
LockAt: 2023-08-21T23:59:00
AssignmentGroup: Assignments
AllowedAttempts: -1
Description: this is the
multi line
description
---
Which events are triggered when the user clicks on an input field?
essay
";
var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz);
var firstQuestion = quiz.Questions.First();
var questionMarkdown = firstQuestion.ToMarkdown();
var expectedMarkdown = @"Points: 1
Which events are triggered when the user clicks on an input field?
essay";
questionMarkdown.Should().Contain(expectedMarkdown);
}
[Test]
public void CanParseMatchingQuestion()
{
var rawMarkdownQuiz = @"
Name: Test Quiz
ShuffleAnswers: true
OneQuestionAtATime: false
DueAt: 2023-08-21T23:59:00
LockAt: 2023-08-21T23:59:00
AssignmentGroup: Assignments
AllowedAttempts: -1
Description:
---
Match the following terms & definitions
^ statement - a single command to be executed
^ identifier - name of a variable
^ keyword - reserved word that has special meaning in a program (e.g. class, void, static, etc.)
";
var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz);
var firstQuestion = quiz.Questions.First();
firstQuestion.QuestionType.Should().Be(QuestionType.MATCHING);
firstQuestion.Text.Should().NotContain("statement");
firstQuestion.Answers.First().MatchedText.Should().Be("a single command to be executed");
}
[Test]
public void CanCreateMarkdownForMatchingQuesiton()
{
var rawMarkdownQuiz = @"
Name: Test Quiz
ShuffleAnswers: true
OneQuestionAtATime: false
DueAt: 2023-08-21T23:59:00
LockAt: 2023-08-21T23:59:00
AssignmentGroup: Assignments
AllowedAttempts: -1
Description:
---
Match the following terms & definitions
^ statement - a single command to be executed
^ identifier - name of a variable
^ keyword - reserved word that has special meaning in a program (e.g. class, void, static, etc.)
";
var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz);
var questionMarkdown = quiz.Questions.First().ToMarkdown();
var expectedMarkdown = @"Points: 1
Match the following terms & definitions
^ statement - a single command to be executed
^ identifier - name of a variable
^ keyword - reserved word that has special meaning in a program (e.g. class, void, static, etc.)";
questionMarkdown.Should().Contain(expectedMarkdown);
}
}

View File

@@ -138,7 +138,12 @@ short_answer
--- ---
points: 4 points: 4
the underscore is optional the underscore is optional
short answer"; short answer
---
this is a matching question
^ left answer - right dropdown
^ other thing shown - another option
";
} }
<div class="d-flex flex-column py-3" style="height: 100vh;"> <div class="d-flex flex-column py-3" style="height: 100vh;">

View File

@@ -1,88 +0,0 @@
@code {
[Parameter, EditorRequired]
public LocalQuizQuestionAnswer Answer { get; set; } = default!;
[Parameter, EditorRequired]
public int AnswerIndex { get; set; } = default!;
[Parameter, EditorRequired]
public int QuestionIndex { get; set; } = default!;
[Parameter, EditorRequired]
public LocalQuizQuestion Question { get; set; } = default!;
[Parameter, EditorRequired]
public Action<LocalQuizQuestionAnswer, int> SaveAnswer { get; set; } = (_, _) => {};
private string label => "question_" + QuestionIndex + "_answer_" + AnswerIndex;
private string _text { get; set; } = string.Empty;
private string text
{
get => _text;
set
{
_text = value;
SaveAnswer(Answer with { Text = _text }, AnswerIndex);
}
}
protected override void OnParametersSet()
{
if(_text == string.Empty)
_text = Answer.Text;
base.OnParametersSet();
}
private void handleOneAnswerChange()
{
SaveAnswer(Answer with {Correct = !Answer.Correct}, AnswerIndex);
}
}
<div class="row">
<div class="col-auto my-auto">
@if(Question.QuestionType == QuestionType.MULTIPLE_ANSWERS)
{
<div class="form-check form-switch">
<input
class="form-check-input"
type="checkbox"
role="switch"
id="@label"
checked="@Answer.Correct"
@onchange="@(() => handleOneAnswerChange())"
>
<label
class="form-check-label" for="@label">
Is Correct
</label>
</div>
}
@if(Question.QuestionType == QuestionType.MULTIPLE_CHOICE)
{
<div class="form-check">
<input
class="form-check-input"
type="radio"
id="@label"
checked="@Answer.Correct"
@onchange="@(() => handleOneAnswerChange())"
>
<label
class="form-check-label" for="@label">
Is Correct
</label>
</div>
}
</div>
<div class="col">
<div class="m-1">
<textarea
class="form-control"
@bind="text"
@bind:event="oninput"
/>
</div>
</div>
</div>

View File

@@ -18,6 +18,26 @@
@((MarkupString)Question.HtmlText) @((MarkupString)Question.HtmlText)
@if(Question.QuestionType == QuestionType.MATCHING)
{
@foreach(var answer in Question.Answers)
{
<div class="mx-3 mb-1 bg-dark px-2 rounded rounded-2 border row">
<div
class="col text-end my-auto p-1"
>
@answer.Text
</div>
<div
class="col my-auto"
>
@answer.MatchedText
</div>
</div>
}
}
else
{
@foreach(var answer in Question.Answers) @foreach(var answer in Question.Answers)
{ {
string answerPreview = answer.HtmlText.StartsWith("<p>") string answerPreview = answer.HtmlText.StartsWith("<p>")
@@ -59,3 +79,4 @@
</div> </div>
</div> </div>
} }
}

View File

@@ -49,7 +49,7 @@ public record LocalQuizQuestion
} }
} }
private static readonly string[] _validFirstAnswerDelimiters = ["*a)", "a)", "[ ]", "[*]", "^"]; private static readonly string[] _validFirstAnswerDelimiters = ["*a)", "a)", "*)", ")", "[ ]", "[*]", "^"];
public static LocalQuizQuestion ParseMarkdown(string input, int questionIndex) public static LocalQuizQuestion ParseMarkdown(string input, int questionIndex)
{ {
@@ -115,15 +115,18 @@ public record LocalQuizQuestion
return "short_answer"; return "short_answer";
var answerLines = getAnswersGroupedByLines(linesWithoutPoints, questionIndex); var answerLines = getAnswersGroupedByLines(linesWithoutPoints, questionIndex);
var firstAnswerLine = answerLines.First();
var isMultipleChoice = var isMultipleChoice =
answerLines.First().StartsWith("a)") firstAnswerLine.StartsWith("a)")
|| answerLines.First().StartsWith("*a)"); || firstAnswerLine.StartsWith("*a)")
|| firstAnswerLine.StartsWith("*)")
|| firstAnswerLine.StartsWith(")");
if (isMultipleChoice) if (isMultipleChoice)
return "multiple_choice"; return "multiple_choice";
var isMultipleAnswer = var isMultipleAnswer =
answerLines.First().StartsWith("[ ]") firstAnswerLine.StartsWith("[ ]")
|| answerLines.First().StartsWith("[*]"); || firstAnswerLine.StartsWith("[*]");
if (isMultipleAnswer) if (isMultipleAnswer)
return "multiple_answers"; return "multiple_answers";
@@ -150,7 +153,7 @@ public record LocalQuizQuestion
var answerLinesRaw = linesWithoutPoints[indexOfAnswerStart..]; var answerLinesRaw = linesWithoutPoints[indexOfAnswerStart..];
var answerStartPattern = @"^(\*?[a-z]\))|\[\s*\]|\[\*\]|\^"; var answerStartPattern = @"^(\*?[a-z]?\))|\[\s*\]|\[\*\]|\^";
var answerLines = answerLinesRaw.Aggregate(new List<string>(), (acc, line) => var answerLines = answerLinesRaw.Aggregate(new List<string>(), (acc, line) =>
{ {
var isNewAnswer = Regex.IsMatch(line, answerStartPattern); var isNewAnswer = Regex.IsMatch(line, answerStartPattern);

View File

@@ -15,7 +15,7 @@ public record LocalQuizQuestionAnswer
public static LocalQuizQuestionAnswer ParseMarkdown(string input, string questionType) public static LocalQuizQuestionAnswer ParseMarkdown(string input, string questionType)
{ {
var isCorrect = input[0] == '*' || input[1] == '*'; var isCorrect = input[0] == '*' || input[1] == '*';
string startingQuestionPattern = @"^(\*?[a-z]\))|\[\s*\]|\[\*\]|\^ "; string startingQuestionPattern = @"^(\*?[a-z]?\))|\[\s*\]|\[\*\]|\^ ";
var text = Regex.Replace(input, startingQuestionPattern, string.Empty).Trim(); var text = Regex.Replace(input, startingQuestionPattern, string.Empty).Trim();
if(questionType == QuestionType.MATCHING) if(questionType == QuestionType.MATCHING)
@@ -23,7 +23,7 @@ public record LocalQuizQuestionAnswer
{ {
Correct = true, Correct = true,
Text = text.Split('-')[0].Trim(), Text = text.Split('-')[0].Trim(),
MatchedText = string.Join("", text.Split('-')[1..]).Trim(), MatchedText = string.Join("-", text.Split('-')[1..]).Trim(),
}; };
return new LocalQuizQuestionAnswer() return new LocalQuizQuestionAnswer()