Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 43 additions & 2 deletions src/CommunityToolkit.Maui.UnitTests/Layouts/StateContainerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -514,11 +514,30 @@ public void StateContainer_CreatesControllerWithLayout()
}

[Fact]
public void Controller_ReturnsErrorLabelOnInvalidState()
public void Controller_ThrowsStateContainerExceptionInvalidStateKey()
{
Assert.Throws<StateContainerException>(() => controller.SwitchToState("InvalidStateKey"));
}

[Fact]
public void Controller_ThrowsStateContainerExceptionOnDuplicateStateKey()
{
// Arrange
var stackLayout = new StackLayout();
var label = new Label();
var button = new Button();

StateView.SetStateKey(label, StateKey.Anything);
StateView.SetStateKey(button, StateKey.Anything);
StateContainer.SetStateViews(stackLayout, [label, button]);

// Assert
var exception = Assert.Throws<StateContainerException>(() => StateContainer.SetCurrentState(stackLayout, StateKey.Anything));
Assert.IsType<InvalidOperationException>(exception.InnerException);
exception.Message.Should().Contain("multiple");
exception.InnerException.Message.Should().Contain("Sequence contains more than one matching element");
}

[Fact]
public void Controller_SwitchesToStateFromContentSuccess()
{
Expand Down Expand Up @@ -594,12 +613,34 @@ public void EnsureDefaults()
var stackLayout = new StackLayout();

// Act Assert
Assert.Equal(StateContainerDefaults.StateViews, StateContainer.GetStateViews(stackLayout));
Assert.Equal([], StateContainer.GetStateViews(stackLayout));
Assert.Empty(StateContainer.GetStateViews(stackLayout));
Assert.Equal(StateContainerDefaults.CurrentState, StateContainer.GetCurrentState(stackLayout));
Assert.Equal(StateContainerDefaults.CanStateChange, StateContainer.GetCanStateChange(stackLayout));
Assert.Equal(StateViewDefaults.StateKey, StateView.GetStateKey(stackLayout));
}

[Fact]
public void EnsureLayoutControllerIsUniquePerLayout()
{
// Arrange
var grid1 = new Grid();
var grid2 = new Grid();

// Act
var grid1StateViews = StateContainer.GetStateViews(grid1);
var grid2StateViews = StateContainer.GetStateViews(grid2);
grid1StateViews.Add(new Label
{
Text = "Test",
});

// Assert
Assert.NotSame(grid1StateViews, grid2StateViews);
Assert.Single(grid1StateViews);
Assert.Empty(grid2StateViews);
}

sealed class ViewModel : INotifyPropertyChanged
{
public bool CanChangeState
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ static void ValidateCanStateChange(in BindableObject bindable)
}
}

static IList<View> CreateDefaultStateViewsProperty(BindableObject bindable) => StateContainerDefaults.StateViews;
static IList<View> CreateDefaultStateViewsProperty(BindableObject bindable) => [];
}

/// <summary>
Expand All @@ -201,5 +201,6 @@ static void ValidateCanStateChange(in BindableObject bindable)
/// <remarks>
/// Constructor for <see cref="StateContainerException"/>
/// </remarks>
/// <param name="message"><see cref="Exception.Message"/></param>
public sealed class StateContainerException(string message) : InvalidOperationException(message);
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="innerException">The exception that is the cause of the current exception. If the <paramref name="innerException" /> parameter is not a null reference (<see langword="Nothing" /> in Visual Basic), the current exception is raised in a <see langword="catch" /> block that handles the inner exception.</param>
public sealed class StateContainerException(string message, Exception? innerException = null) : InvalidOperationException(message, innerException);
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,21 @@
/// <summary>
/// StateContainer Controller
/// </summary>
sealed class StateContainerController
/// <remarks>
/// Initialize <see cref="StateContainerController"/> with a <see cref="Layout"/>
/// </remarks>
/// <param name="layout">The layout that this controller manages.</param>
sealed class StateContainerController(Layout layout)
{
readonly WeakReference<Layout> layoutWeakReference;
readonly WeakReference<Layout> layoutWeakReference = new(layout);

string? previousState;
List<IView> originalContent = Enumerable.Empty<IView>().ToList();

/// <summary>
/// Initialize <see cref="StateContainerController"/> with a <see cref="Layout"/>
/// </summary>
/// <param name="layout"></param>
public StateContainerController(Layout layout) => layoutWeakReference = new WeakReference<Layout>(layout);

/// <summary>
/// The StateViews defined in the StateContainer.
/// </summary>
public required IList<View> StateViews { get; set; }
public required IList<View> StateViews { get; init; }

/// <summary>
/// Display the default content.
Expand Down Expand Up @@ -84,7 +82,14 @@ internal Layout GetLayout()

View GetViewForState(string state)
{
var view = StateViews.FirstOrDefault(x => StateView.GetStateKey(x) == state);
return view ?? throw new StateContainerException($"View for {state} not defined.");
try
{
var view = StateViews.SingleOrDefault(x => StateView.GetStateKey(x) == state);
return view ?? throw new StateContainerException($"{nameof(StateView)} for {state} not defined.");
}
catch (InvalidOperationException e)
{
throw new StateContainerException($"Unable to determine {nameof(StateView)} for State: {state}. This State has been assigned to multiple {nameof(StateView)}s. Ensure each {nameof(StateView)} has a unique StateKey", e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,4 @@ static class StateContainerDefaults
{
public const string? CurrentState = null;
public const bool CanStateChange = true;
public static IList<View> StateViews { get; } = [];
}
Loading