Skip to content

Commit

Permalink
Implemented cascading objectsInPathWay (#382)
Browse files Browse the repository at this point in the history
  • Loading branch information
MarvinHo64 committed Oct 30, 2023
1 parent b710d78 commit f0f3603
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 40 deletions.
10 changes: 5 additions & 5 deletions IntegrationTest/Forms/Space/CreateSpaceFormIt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ public async Task SubmitButtonClicked_SubmitsIfFormValid()
var submitButton = systemUnderTest.FindComponent<DefaultSubmitButton>();
submitButton.Find("button").Click();
WorldPresenter.DidNotReceive().CreateLearningSpace(Expected, Arg.Any<string>(), Arg.Any<string>(),
Arg.Any<int>(), Arg.Any<Theme>(), Arg.Any<double>(), Arg.Any<double>());
Arg.Any<int>(), Arg.Any<Theme>());

var mudInput = systemUnderTest.FindComponent<MudTextField<string>>();
var input = mudInput.Find("input");
Expand All @@ -132,17 +132,17 @@ public async Task SubmitButtonClicked_SubmitsIfFormValid()

submitButton.Find("button").Click();
WorldPresenter.Received().CreateLearningSpace(Expected, Arg.Any<string>(), Arg.Any<string>(),
Arg.Any<int>(), Arg.Any<Theme>(), Arg.Any<double>(), Arg.Any<double>());
Arg.Any<int>(), Arg.Any<Theme>());
}

private void ConfigureValidatorNameIsTest()
{
Validator.ValidateAsync(Entity, Arg.Any<string>()).Returns(ci =>
{
if (ci.Arg<string>() != nameof(FormModel.Name)) return Enumerable.Empty<string>();
return (string) FormModel.GetType().GetProperty(ci.Arg<string>()).GetValue(FormModel) == Expected
return (string)FormModel.GetType().GetProperty(ci.Arg<string>()).GetValue(FormModel) == Expected
? Enumerable.Empty<string>()
: new[] {"Must be test"};
: new[] { "Must be test" };
}
);
}
Expand All @@ -160,7 +160,7 @@ private void ConfigureValidatorAllMembersTestOr123OrCampus()
Theme t => t == Theme.Campus,
_ => throw new ArgumentOutOfRangeException()
};
return valid ? Enumerable.Empty<string>() : new[] {"Must be test or 123"};
return valid ? Enumerable.Empty<string>() : new[] { "Must be test or 123" };
}
);
}
Expand Down
12 changes: 6 additions & 6 deletions Presentation/Components/Forms/Space/CreateSpaceForm.razor
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
FormDataContainer="FormDataContainer">
<Fields>
<Collapsable Title=@Localizer["CreateSpaceForm.Fields.Collapsable.General.Title"]
@bind-Collapsed="_collapsedGeneral">
@bind-Collapsed="_collapsedGeneral">
<div class="rounded-lg w-4/4 px-4 pb-2">
<MudTextField @bind-Value="FormDataContainer.FormModel.Name"
For="@(() => FormModel.Name)"
Expand All @@ -27,7 +27,7 @@
</Collapsable>

<Collapsable Title=@Localizer["CreateSpaceForm.Fields.Collapsable.CompletionConditions.Title"]
@bind-Collapsed="_collapsedConditions">
@bind-Collapsed="_collapsedConditions">
<div class="rounded-lg w-4/4 px-4 pb-2">
<MudText Typo="Typo.caption">
@Localizer["CreateSpaceForm.Fields.Collapsable.CompletionConditions.Field.RequiredPoints.Text"]
Expand All @@ -42,7 +42,7 @@
</Collapsable>

<Collapsable Title=@Localizer["CreateSpaceForm.Fields.Collapsable.Theme.Title"]
@bind-Collapsed="_collapsedTheme">
@bind-Collapsed="_collapsedTheme">
<div class="rounded-lg w-4/4 px-4 pb-2">
<MudSelect @bind-Value="FormDataContainer.FormModel.Theme"
AnchorOrigin="Origin.BottomCenter"
Expand All @@ -58,7 +58,7 @@
</Collapsable>

<Collapsable Title=@Localizer["CreateSpaceForm.Fields.Collapsable.Goals.Title"]
@bind-Collapsed="_collapsedGoals">
@bind-Collapsed="_collapsedGoals">
<div class="rounded-lg w-4/4 px-4">
<MudText Typo="Typo.caption">
@Localizer["CreateSpaceForm.Fields.Collapsable.Goals.Field.Description.Text"]
Expand Down Expand Up @@ -111,13 +111,13 @@
private bool _collapsedGoals = true;
private bool _collapsedConditions = true;
private bool _collapsedTheme = true;
private Theme[] _themes = (Theme[]) Enum.GetValues(typeof(Theme));
private Theme[] _themes = (Theme[])Enum.GetValues(typeof(Theme));


private void OnValidSubmit(LearningSpaceFormModel model)
{
LearningWorldPresenter.CreateLearningSpace(model.Name, model.Description,
model.Goals, (int) model.RequiredPoints, model.Theme, positionX: 0, positionY: 0);
model.Goals, (int)model.RequiredPoints, model.Theme);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ namespace Presentation.PresentationLogic.LearningPathway;

public class PathWayConditionViewModel : IObjectInPathWayViewModel
{
public const int InputConnectionXOffset = 38;
public const int InputConnectionYOffset = -10;
public const int OutputConnectionXOffset = 38;
public const int OutputConnectionYOffset = 45;

private double _positionX;
private double _positionY;

/// <summary>
/// Private Constructor for AutoMapper
/// </summary>
Expand All @@ -20,10 +28,10 @@ private PathWayConditionViewModel()
Condition = ConditionEnum.Or;
UnsavedChanges = false;
}

public PathWayConditionViewModel(ConditionEnum condition, bool unsavedChanges,
double positionX = 0, double positionY = 0,
ICollection<IObjectInPathWayViewModel>? inBoundObjects = null,
ICollection<IObjectInPathWayViewModel>? inBoundObjects = null,
ICollection<IObjectInPathWayViewModel>? outBoundObjects = null)
{
Id = Guid.NewGuid();
Expand All @@ -34,46 +42,42 @@ public PathWayConditionViewModel(ConditionEnum condition, bool unsavedChanges,
PositionX = positionX;
PositionY = positionY;
}


public ConditionEnum Condition { get; set; }

public Guid Id { get; private set; }
public double PositionX {

public double PositionX
{
get => _positionX;
set
{
_positionX = value switch
{
< 0 => 0,
> 268 => 268,
_ => value
};
}
}
}

public double PositionY {

public double PositionY
{
get => _positionY;
set
{
_positionY = value switch
{
< 0 => 0,
> 711 => 711,
_ => value
};
}
}
}
public const int InputConnectionXOffset = 38;
public const int InputConnectionYOffset = -10;
public const int OutputConnectionXOffset = 38;
public const int OutputConnectionYOffset = 45;

public double InputConnectionX => PositionX + InputConnectionXOffset;
public double InputConnectionY => PositionY + InputConnectionYOffset;
public double OutputConnectionX => PositionX + OutputConnectionXOffset;
public double OutputConnectionY => PositionY + OutputConnectionYOffset;
public bool UnsavedChanges { get; }
public ICollection<IObjectInPathWayViewModel> InBoundObjects { get; set; }
public ICollection<IObjectInPathWayViewModel> OutBoundObjects { get; set; }
public ConditionEnum Condition { get; set; }

private double _positionX;
private double _positionY;
}
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,6 @@ public double PositionX
_positionX = value switch
{
< 0 => 0,
> 437 => 437,
_ => value
};
}
Expand All @@ -147,7 +146,6 @@ public double PositionY
_positionY = value switch
{
< 0 => 0,
> 681 => 681,
_ => value
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public interface ILearningWorldPresenter : INotifyPropertyChanged, INotifyProper
/// <param name="positionY">The Y position of the learning space (default is 0).</param>
/// <param name="topic">The topic of the learning space (optional).</param>
void CreateLearningSpace(string name, string description, string goals,
int requiredPoints, Theme theme, double positionX = 0, double positionY = 0, TopicViewModel? topic = null);
int requiredPoints, Theme theme, TopicViewModel? topic = null);

/// <summary>
/// Asynchronously loads the learning space associated with the currently selected learning world.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,17 +261,16 @@ public void SetSelectedLearningSpace(IObjectInPathWayViewModel objectInPathWayVi

/// <inheritdoc cref="ILearningWorldPresenter.CreateLearningSpace"/>
public void CreateLearningSpace(string name, string description, string goals, int requiredPoints,
Theme theme,
double positionX = 0D,
double positionY = 0D,
TopicViewModel? topic = null)
Theme theme, TopicViewModel? topic = null)
{
if (!CheckLearningWorldNotNull("CreateLearningSpace"))
return;

var positionY = GetNextAvailableYPosition(25);

//Nullability of LearningWorldVm is checked in CheckLearningWorldNotNull
_presentationLogic.CreateLearningSpace(LearningWorldVm!, name, description, goals,
requiredPoints, theme, positionX, positionY, topic);
requiredPoints, theme, 0, positionY, topic);
}

/// <inheritdoc cref="ILearningWorldPresenter.AddNewLearningSpace"/>
Expand Down Expand Up @@ -365,7 +364,8 @@ public void CreatePathWayCondition(ConditionEnum condition = ConditionEnum.Or)
return;
try
{
_presentationLogic.CreatePathWayCondition(LearningWorldVm!, condition, 0, 0);
var positionY = GetNextAvailableYPosition(25);
_presentationLogic.CreatePathWayCondition(LearningWorldVm!, condition, 0, positionY);
}
catch (ApplicationException e)
{
Expand Down Expand Up @@ -420,14 +420,47 @@ public void SetOnHoveredObjectInPathWay(IObjectInPathWayViewModel sourceObject,
{
//LearningWorldVm can not be null because it is checked before call. -m.ho
var objectAtPosition = LearningWorldVm?.LearningSpaces.FirstOrDefault(ls =>
ls.PositionX <= x && ls.PositionX + 84 >= x && ls.PositionY <= y &&
ls.PositionY + 84 >= y) ??
ls.PositionX <= x && ls.PositionX + 66 >= x && ls.PositionY <= y &&
ls.PositionY + 64 >= y) ??
(IObjectInPathWayViewModel?)LearningWorldVm?.PathWayConditions.FirstOrDefault(lc =>
lc.PositionX <= x && lc.PositionX + 76 >= x && lc.PositionY <= y &&
lc.PositionY + 43 >= y);
return objectAtPosition;
}

/// <summary>
/// Determines the next available Y position, taking into account objects already existing at the current position and the position shifted by an offset.
/// </summary>
/// <param name="xOffset">The horizontal offset to consider when identifying colliding objects.</param>
/// <param name="startY">The Y starting point from which to begin the search. Defaults to 0.</param>
/// <returns>Returns the next available Y position. If the determined Y position exceeds the maximum value, the maximum Y value is returned.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when an unknown object is found at the current position.</exception>
private int GetNextAvailableYPosition(int xOffset, int startY = 0)
{
var positionY = startY;
const int maxPositionY = 405;

while (true)
{
var objAtPosition = GetObjectAtPosition(0, positionY);
var objAtPositionWithOffset = GetObjectAtPosition(0 + xOffset, positionY + xOffset);

if (objAtPosition == null && objAtPositionWithOffset == null)
{
return positionY <= maxPositionY ? positionY : maxPositionY;
}

var currentOffset = objAtPosition switch
{
ILearningSpaceViewModel => 70,
PathWayConditionViewModel => 55,
_ => throw new ArgumentOutOfRangeException()
};

positionY += currentOffset;
}
}

/// <inheritdoc cref="IPositioningService.CreateLearningPathWay"/>
public void CreateLearningPathWay(IObjectInPathWayViewModel sourceObject, double x, double y)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Presentation.PresentationLogic;
using Presentation.PresentationLogic.API;
using Presentation.PresentationLogic.AuthoringToolWorkspace;
using Presentation.PresentationLogic.LearningPathway;
using Presentation.PresentationLogic.LearningSpace;
using Presentation.PresentationLogic.LearningWorld;
using Presentation.PresentationLogic.Mediator;
Expand Down Expand Up @@ -355,6 +356,84 @@ public void CreateNewLearningSpace_SelectedLearningWorldIsNull_CallsErrorService
errorService.Received().SetError("Operation failed", "No learning world selected");
}

[Test]
public void CreateMultipleLearningObjects_PositionIsCorrect()
{
var world = ViewModelProvider.GetLearningWorld();
var presentationLogic = Substitute.For<IPresentationLogic>();

var systemUnderTest = CreatePresenterForTesting(presentationLogic: presentationLogic);
systemUnderTest.LearningWorldVm = world;

systemUnderTest.CreateLearningSpace("foo", "bar", "foo", 5, Theme.Campus);
systemUnderTest.LearningWorldVm.LearningSpaces.Add(new LearningSpaceViewModel("aa", "bb", "cc", Theme.Campus));

systemUnderTest.CreateLearningSpace("foo", "bar", "foo", 5, Theme.Campus);
systemUnderTest.LearningWorldVm.LearningSpaces.Add(new LearningSpaceViewModel("aa", "bb", "cc", Theme.Campus, 0,
null, 0, 70));

systemUnderTest.CreateLearningSpace("foo", "bar", "foo", 5, Theme.Campus);
systemUnderTest.LearningWorldVm.LearningSpaces.Add(new LearningSpaceViewModel("aa", "bb", "cc", Theme.Campus, 0,
null, 0, 140));

systemUnderTest.CreatePathWayCondition(ConditionEnum.And);
systemUnderTest.LearningWorldVm.PathWayConditions.Add(
new PathWayConditionViewModel(ConditionEnum.And, false, 0, 210));

systemUnderTest.CreateLearningSpace("foo", "bar", "foo", 5, Theme.Campus);
systemUnderTest.LearningWorldVm.LearningSpaces.Add(new LearningSpaceViewModel("aa", "bb", "cc", Theme.Campus, 0,
null, 0, 265));

systemUnderTest.CreatePathWayCondition(ConditionEnum.And);
systemUnderTest.LearningWorldVm.PathWayConditions.Add(
new PathWayConditionViewModel(ConditionEnum.And, false, 0, 335));

systemUnderTest.CreatePathWayCondition(ConditionEnum.And);
systemUnderTest.LearningWorldVm.PathWayConditions.Add(
new PathWayConditionViewModel(ConditionEnum.And, false, 0, 390));

systemUnderTest.CreateLearningSpace("foo", "bar", "foo", 5, Theme.Campus);
systemUnderTest.LearningWorldVm.LearningSpaces.Add(new LearningSpaceViewModel("aa", "bb", "cc", Theme.Campus, 0,
null, 0, 405));

systemUnderTest.CreatePathWayCondition(ConditionEnum.And);
systemUnderTest.LearningWorldVm.PathWayConditions.Add(
new PathWayConditionViewModel(ConditionEnum.And, false, 0, 405));

Received.InOrder(() =>
{
presentationLogic.Received().CreateLearningSpace(world, Arg.Any<string>(), Arg.Any<string>(),
Arg.Any<string>(),
Arg.Any<int>(), Arg.Any<Theme>(), 0, 0, Arg.Any<TopicViewModel>());

presentationLogic.Received().CreateLearningSpace(world, Arg.Any<string>(), Arg.Any<string>(),
Arg.Any<string>(),
Arg.Any<int>(), Arg.Any<Theme>(), 0, 70, Arg.Any<TopicViewModel>());

presentationLogic.Received().CreateLearningSpace(world, Arg.Any<string>(), Arg.Any<string>(),
Arg.Any<string>(),
Arg.Any<int>(), Arg.Any<Theme>(), 0, 140, Arg.Any<TopicViewModel>());

presentationLogic.Received().CreatePathWayCondition(world, ConditionEnum.And, 0, 210);

presentationLogic.Received().CreateLearningSpace(world, Arg.Any<string>(), Arg.Any<string>(),
Arg.Any<string>(),
Arg.Any<int>(), Arg.Any<Theme>(), 0, 265, Arg.Any<TopicViewModel>());

presentationLogic.Received().CreatePathWayCondition(world, ConditionEnum.And, 0, 335);

presentationLogic.Received().CreatePathWayCondition(world, ConditionEnum.And, 0, 390);

//max value for positionY is 405
presentationLogic.Received().CreateLearningSpace(world, Arg.Any<string>(), Arg.Any<string>(),
Arg.Any<string>(),
Arg.Any<int>(), Arg.Any<Theme>(), 0, 405, Arg.Any<TopicViewModel>());

//max value for positionY is 405
presentationLogic.Received().CreatePathWayCondition(world, ConditionEnum.And, 0, 405);
});
}

[Test]
public void DeleteSelectedLearningObject_SelectedLearningWorldIsNull_CallsErrorService()
{
Expand Down

0 comments on commit f0f3603

Please sign in to comment.