Skip to content

Commit

Permalink
Issue 334 (#426)
Browse files Browse the repository at this point in the history
- Validation for task names in AdaptivityContentDialog
- create, edit, delete questions
- Validation for questions
- localizer for forms and dialogs
- sidepanel in QuestionDialog for existing questions in current task
- add QuestionPreview in AdaptivityContentDialog and in QuestionDialog for existing questions
  • Loading branch information
andreasweishaupt committed Oct 26, 2023
1 parent 5375111 commit fb4624a
Show file tree
Hide file tree
Showing 35 changed files with 982 additions and 597 deletions.
7 changes: 6 additions & 1 deletion AuthoringTool/Mapping/FormModelEntityMappingProfile.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using AutoMapper;
using BusinessLogic.Entities;
using BusinessLogic.Entities.LearningContent.Adaptivity;
using BusinessLogic.Entities.LearningContent.Adaptivity.Question;
using BusinessLogic.Entities.LearningContent.LinkContent;
using Presentation.Components.Adaptivity.Dialogues;
using Presentation.Components.Adaptivity.Forms.Models;
using Presentation.Components.Forms.Models;

Expand All @@ -28,6 +30,7 @@ private FormModelEntityMappingProfile()
private void CreateContentMap()
{
CreateMap<LinkContentFormModel, LinkContent>();
CreateMap<AdaptivityContentFormModel, AdaptivityContent>();
}

private void CreateElementMap()
Expand All @@ -52,6 +55,8 @@ private void CreateAdaptivityQuestionMap()
? context.Mapper.Map<MultipleChoiceSingleResponseQuestion>(formModel)
: context.Mapper.Map<MultipleChoiceMultipleResponseQuestion>(formModel));
CreateMap<MultipleChoiceQuestionFormModel, MultipleChoiceMultipleResponseQuestion>();
CreateMap<MultipleChoiceQuestionFormModel, MultipleChoiceSingleResponseQuestion>();
CreateMap<MultipleChoiceQuestionFormModel, MultipleChoiceSingleResponseQuestion>()
.ForMember(x => x.CorrectChoices, opt => opt.Ignore())
.ForMember(x => x.CorrectChoice, opt => opt.MapFrom(x => x.CorrectChoices.First()));
}
}
8 changes: 6 additions & 2 deletions AuthoringTool/Mapping/ViewModelFormModelMappingProfile.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using AutoMapper;
using Presentation.Components.Adaptivity.Dialogues;
using Presentation.Components.Adaptivity.Forms.Models;
using Presentation.Components.Forms.Models;
using Presentation.PresentationLogic.LearningContent.AdaptivityContent;
using Presentation.PresentationLogic.LearningContent.AdaptivityContent.Question;
using Presentation.PresentationLogic.LearningContent.LinkContent;
using Presentation.PresentationLogic.LearningElement;
Expand All @@ -16,7 +18,8 @@ private ViewModelFormModelMappingProfile()
CreateWorldMap();
CreateSpaceMap();
CreateElementMap();
CreateLinkContentMap();
CreateContentMap();
CreateAdaptivityQuestionMap();
CreateAdaptivityQuestionMap();
}

Expand All @@ -26,10 +29,11 @@ private ViewModelFormModelMappingProfile()
cfg.AddCollectionMappersOnce();
};

private void CreateLinkContentMap()
private void CreateContentMap()
{
CreateMap<LinkContentViewModel, LinkContentFormModel>()
.ReverseMap();
CreateMap<AdaptivityContentViewModel, AdaptivityContentFormModel>();
}

private void CreateElementMap()
Expand Down
5 changes: 4 additions & 1 deletion AuthoringToolTest/StartupUt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,10 @@ public void Startup_ConfigureServices_CanResolveAllCommandFactoriesServices(Type
private static readonly Type[] ConfigureValidationRequiredTypes =
{
typeof(LearningWorldValidator), typeof(LearningSpaceValidator), typeof(LearningElementValidator),
typeof(LearningContentValidator),
typeof(LearningContentValidator), typeof(FileContentValidator), typeof(LinkContentValidator),
typeof(MultipleChoiceQuestionValidator), typeof(MultipleChoiceMultipleResponseQuestionValidator),
typeof(MultipleChoiceSingleResponseQuestionValidator), typeof(ChoiceValidator),
typeof(AdaptivityContentValidator),
typeof(ILearningSpaceNamesProvider), typeof(ILearningWorldNamesProvider), typeof(ILearningElementNamesProvider)
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ public void Execute()
_memento = AdaptivityTask.GetMemento();

AdaptivityTask.Questions.Add(Question);
if (AdaptivityTask.Questions.Count == 1)
{
AdaptivityTask.MinimumRequiredDifficulty = Question.Difficulty;
}

Logger.LogTrace(
"Created MultipleChoiceMultipleResponseQuestion '{QuestionText}' in AdaptivityTask {AdaptivityTaskName}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ public void Execute()
_memento = AdaptivityTask.GetMemento();

AdaptivityTask.Questions.Add(Question);
if (AdaptivityTask.Questions.Count == 1)
{
AdaptivityTask.MinimumRequiredDifficulty = Question.Difficulty;
}

Logger.LogTrace(
"Created MultipleChoicesSingleResponseQuestion '{QuestionText}' in AdaptivityTask {AdaptivityTaskName}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ public void Execute()
if (questionToDelete != null)
{
Task.Questions.Remove(questionToDelete);
if (Task.MinimumRequiredDifficulty == questionToDelete.Difficulty)
{
Task.MinimumRequiredDifficulty = Task.Questions.Any() ? Task.Questions.Min(x => x.Difficulty) : null;
}

Logger.LogTrace(
"Deleted AdaptivityQuestion {AdaptivityQuestionName} ({AdaptivityQuestionId}) in AdaptivityTask {AdaptivityTaskName}",
Expand Down
11 changes: 11 additions & 0 deletions BusinessLogic/Validation/Validators/AdaptivityContentValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using BusinessLogic.Entities.LearningContent.Adaptivity;
using FluentValidation;

namespace BusinessLogic.Validation.Validators;

public class AdaptivityContentValidator : AbstractValidator<AdaptivityContent>
{
public AdaptivityContentValidator()
{
}
}
18 changes: 18 additions & 0 deletions BusinessLogic/Validation/Validators/ChoiceValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using BusinessLogic.Entities.LearningContent.Adaptivity.Question;
using FluentValidation;
using JetBrains.Annotations;

namespace BusinessLogic.Validation.Validators;

[UsedImplicitly]
public class ChoiceValidator : AbstractValidator<Choice>
{
public ChoiceValidator()
{
RuleFor(x => x.Text)
.NotEmpty()
.WithMessage("Choice text is required.")
.MaximumLength(1000)
.WithMessage("Choice text cannot be longer than 1000 characters.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,8 @@ public MultipleChoiceQuestionValidator()
.WithMessage("Question must have at least two choices.")
.Must(x => x.Count <= 10)
.WithMessage("Question cannot have more than ten choices.");
RuleForEach(x => x.Choices)
.Must(y => !string.IsNullOrWhiteSpace(y.Text))
.WithMessage("Choice text is required.")
.Must(y => y.Text.Length <= 1000)
.WithMessage("Choice text cannot be longer than 1000 characters.");
RuleFor(x => x.CorrectChoices)
.Must(x => x.Count >= 1)
.NotEmpty()
.WithMessage("Question must have at least one correct choice.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ public class MultipleChoiceSingleResponseQuestionValidator : AbstractValidator<M
public MultipleChoiceSingleResponseQuestionValidator()
{
Include(new MultipleChoiceQuestionValidator());
RuleFor(x => x.CorrectChoices)
.Must(x => x.Count == 1)
.WithMessage("Question must have exactly one correct choice.");
// Validation for single response is solved by the radio group in the UI.
}
}
8 changes: 4 additions & 4 deletions IntegrationTest/CachingMapperIt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -242,10 +242,10 @@ private static PresentationLogic CreateTestablePresentationLogic(
adaptivityActionCommandFactory ??= Substitute.For<IAdaptivityActionCommandFactory>();
fileSystem ??= new MockFileSystem();

return new PresentationLogic(configuration, businessLogic, mapper,
cachingMapper, selectedViewModelsProvider, serviceProvider, logger, hybridSupportWrapper,
shellWrapper, questionCommandFactory, taskCommandFactory, conditionCommandFactory, elementCommandFactory, layoutCommandFactory,
pathwayCommandFactory, spaceCommandFactory, topicCommandFactory, worldCommandFactory, batchCommandFactory,
return new PresentationLogic(configuration, businessLogic, mapper, cachingMapper, selectedViewModelsProvider,
serviceProvider, logger, hybridSupportWrapper, shellWrapper, questionCommandFactory, taskCommandFactory,
conditionCommandFactory, elementCommandFactory, layoutCommandFactory, pathwayCommandFactory,
spaceCommandFactory, topicCommandFactory, worldCommandFactory, batchCommandFactory,
adaptivityRuleCommandFactory, adaptivityActionCommandFactory,
fileSystem);
}
Expand Down
154 changes: 154 additions & 0 deletions IntegrationTest/Components/Adaptivity/AdaptivityQuestionPreviewUt.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
using System.Collections.Generic;
using Bunit;
using NSubstitute;
using NUnit.Framework;
using Presentation.Components.Adaptivity;
using Presentation.PresentationLogic.LearningContent.AdaptivityContent;
using Presentation.PresentationLogic.LearningContent.AdaptivityContent.Question;
using Shared.Adaptivity;
using TestHelpers;

namespace IntegrationTest.Components.Adaptivity;

[TestFixture]
public class AdaptivityQuestionPreviewUt : MudBlazorTestFixture<AdaptivityQuestionPreview>
{
[Test]
public void Render_InjectsDependenciesAndParameters()
{
var adaptivityQuestion = Substitute.For<IAdaptivityQuestionViewModel>();

var sut = GetRenderedComponent(adaptivityQuestion);
Assert.Multiple(() =>
{
Assert.That(sut.Instance.Localizer, Is.Not.Null);
Assert.That(sut.Instance.AdaptivityQuestion, Is.EqualTo(adaptivityQuestion));
});
}

[Test]
public void Render_Difficulty_RenderPreviewHeader([Values] QuestionDifficulty expectedDifficulty)
{
var adaptivityQuestion = Substitute.For<IAdaptivityQuestionViewModel>();
adaptivityQuestion.Difficulty.Returns(expectedDifficulty);

var sut = GetRenderedComponent(adaptivityQuestion);

var header = sut.Find("h5");
Assert.That(header.InnerHtml, Is.EqualTo("AdaptivityQuestionPreview.Header.Question." + expectedDifficulty));
}

[Test]
public void Render_MultipleChoiceQuestions_RenderQuestionText()
{
const string expectedQuestionText = "expectedQuestionText";
var adaptivityQuestion = Substitute.For<IMultipleChoiceQuestionViewModel>();
adaptivityQuestion.Text = expectedQuestionText;

var sut = GetRenderedComponent(adaptivityQuestion);

var questionText = sut.Find("p.mud-typography-body1");
Assert.That(questionText.InnerHtml, Is.EqualTo(expectedQuestionText));
}

[Test]
public void Render_Choices_RenderChoicesText([Values] bool hideChoices)
{
var adaptivityQuestion = Substitute.For<IMultipleChoiceQuestionViewModel>();
var expectedChoice1Text = "choice1";
var expectedChoice2Text = "choice2";
var choice1 = ViewModelProvider.GetChoice(expectedChoice1Text);
var choice2 = ViewModelProvider.GetChoice(expectedChoice2Text);
adaptivityQuestion.Choices.Returns(new List<ChoiceViewModel> {choice1, choice2});
adaptivityQuestion.CorrectChoices.Returns(new List<ChoiceViewModel> {choice1});

var sut = GetRenderedComponent(adaptivityQuestion, hideChoices);

var choices = sut.FindAll("p.mud-typography-body2");
if (hideChoices)
{
Assert.That(choices, Is.Empty);
}
else
{
Assert.Multiple(() =>
{
Assert.That(choices[0].InnerHtml, Is.EqualTo(expectedChoice1Text));
Assert.That(choices[1].InnerHtml, Is.EqualTo(expectedChoice2Text));
});
}
}

[Test]
public void Render_HasCommentAction_RenderCommentActionWithHeader()
{
const string expectedComment = "expectedComment";
var commentAction = ViewModelProvider.GetCommentAction(expectedComment);
var rule = Substitute.For<IAdaptivityRuleViewModel>();
rule.Action.Returns(commentAction);
var adaptivityQuestion = Substitute.For<IAdaptivityQuestionViewModel>();
adaptivityQuestion.Rules.Returns(new List<IAdaptivityRuleViewModel> {rule});

var sut = GetRenderedComponent(adaptivityQuestion);

var commentHeader = sut.Find("h6");
var comment = sut.Find("p.mud-typography-body1");
Assert.Multiple(() =>
{
Assert.That(commentHeader.InnerHtml, Is.EqualTo("AdaptivityQuestionPreview.Header.Comment"));
Assert.That(comment.InnerHtml, Is.EqualTo(expectedComment));
});
}

[Test]
public void Render_HasContentReferenceAction_RenderContentReferenceActionWithHeader()
{
var contentReferenceAction = ViewModelProvider.GetContentReferenceAction();
var rule = Substitute.For<IAdaptivityRuleViewModel>();
rule.Action.Returns(contentReferenceAction);
var adaptivityQuestion = Substitute.For<IAdaptivityQuestionViewModel>();
adaptivityQuestion.Rules.Returns(new List<IAdaptivityRuleViewModel> {rule});

var sut = GetRenderedComponent(adaptivityQuestion);

var commentHeader = sut.Find("h6");
var contentReference = sut.Find("p.mud-typography-body1");
Assert.Multiple(() =>
{
Assert.That(commentHeader.InnerHtml, Is.EqualTo("AdaptivityQuestionPreview.Header.ContentReference"));
Assert.That(contentReference.InnerHtml, Is.Not.Empty);
});
}

[Test]
public void Render_HasElementReferenceAction_RenderElementReferenceActionWithHeader()
{
var elementReferenceAction = ViewModelProvider.GetElementReferenceAction();
var rule = Substitute.For<IAdaptivityRuleViewModel>();
rule.Action.Returns(elementReferenceAction);
var adaptivityQuestion = Substitute.For<IAdaptivityQuestionViewModel>();
adaptivityQuestion.Rules.Returns(new List<IAdaptivityRuleViewModel> {rule});

var sut = GetRenderedComponent(adaptivityQuestion);

var commentHeader = sut.Find("h6");
var elementReference = sut.Find("p.mud-typography-body1");
Assert.Multiple(() =>
{
Assert.That(commentHeader.InnerHtml, Is.EqualTo("AdaptivityQuestionPreview.Header.ElementReference"));
Assert.That(elementReference.InnerHtml, Is.Not.Empty);
});
}

private IRenderedComponent<AdaptivityQuestionPreview> GetRenderedComponent(
IAdaptivityQuestionViewModel? adaptivityQuestion, bool hideChoices = false)
{
adaptivityQuestion ??= Substitute.For<IAdaptivityQuestionViewModel>();

return Context.RenderComponent<AdaptivityQuestionPreview>(p =>
{
p.Add(c => c.AdaptivityQuestion, adaptivityQuestion);
p.Add(c => c.HideChoices, hideChoices);
});
}
}
Loading

0 comments on commit fb4624a

Please sign in to comment.