-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Update ActiveLayout bar widget to a drop-down (#1036)
The `ActiveLayoutWidget` has changed from a button which toggles through options to a dropdown. This required the creation of a custom `DropDown` control, in order to control the width of the drop-down button. This PR also takes the opportunity to rip out some usages of the old Manager API. --------- Co-authored-by: Andrei Zhvaleuski <[email protected]> Co-authored-by: Isaac Daly <[email protected]>
- Loading branch information
1 parent
8206b19
commit 09f8cd3
Showing
12 changed files
with
564 additions
and
169 deletions.
There are no files selected for viewing
315 changes: 263 additions & 52 deletions
315
src/Whim.Bar.Tests/ActiveLayout/ActiveLayoutWidgetViewModelTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,77 +1,288 @@ | ||
using NSubstitute; | ||
using Whim.TestUtils; | ||
using Windows.Win32.Graphics.Gdi; | ||
using Xunit; | ||
|
||
namespace Whim.Bar.Tests; | ||
|
||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope")] | ||
public class ActiveLayoutWidgetViewModelTests | ||
{ | ||
private static ActiveLayoutWidgetViewModel CreateSut(IContext context, IMonitor monitor) => new(context, monitor); | ||
private static ActiveLayoutWidgetViewModel CreateSut(IContext ctx, IMonitor monitor) => new(ctx, monitor); | ||
|
||
[Theory, AutoSubstituteData] | ||
public void WorkspaceManager_ActiveLayoutEngineChanged(IContext context, IMonitor monitor, IWorkspace workspace) | ||
private static ILayoutEngine CreateLayoutEngine(string name) | ||
{ | ||
// Given | ||
ActiveLayoutWidgetViewModel viewModel = CreateSut(context, monitor); | ||
|
||
// When | ||
// Then | ||
Assert.PropertyChanged( | ||
viewModel, | ||
nameof(viewModel.ActiveLayoutEngine), | ||
() => | ||
context.WorkspaceManager.ActiveLayoutEngineChanged += Raise.Event< | ||
EventHandler<ActiveLayoutEngineChangedEventArgs> | ||
>( | ||
context.WorkspaceManager, | ||
new ActiveLayoutEngineChangedEventArgs() | ||
{ | ||
Workspace = workspace, | ||
PreviousLayoutEngine = workspace.ActiveLayoutEngine, | ||
CurrentLayoutEngine = workspace.ActiveLayoutEngine, | ||
} | ||
) | ||
); | ||
ILayoutEngine layoutEngine = Substitute.For<ILayoutEngine>(); | ||
layoutEngine.Name.Returns(name); | ||
return layoutEngine; | ||
} | ||
|
||
[Theory, AutoSubstituteData] | ||
public void WorkspaceManager_MonitorWorkspaceChanged(IContext context, IMonitor monitor, IWorkspace workspace) | ||
internal void Constructor_SetsEventListeners(IContext ctx) | ||
{ | ||
// Given | ||
ActiveLayoutWidgetViewModel viewModel = CreateSut(context, monitor); | ||
|
||
// When | ||
// Then | ||
Assert.PropertyChanged( | ||
viewModel, | ||
nameof(viewModel.ActiveLayoutEngine), | ||
() => | ||
context.Butler.MonitorWorkspaceChanged += Raise.Event<EventHandler<MonitorWorkspaceChangedEventArgs>>( | ||
context.Butler, | ||
new MonitorWorkspaceChangedEventArgs() { Monitor = monitor, CurrentWorkspace = workspace } | ||
) | ||
); | ||
// GIVEN a monitor and view model | ||
IMonitor monitor = StoreTestUtils.CreateMonitor((HMONITOR)1); | ||
ActiveLayoutWidgetViewModel sut = CreateSut(ctx, monitor); | ||
|
||
// THEN all required event listeners are registered with the context store | ||
Assert.NotNull(sut); | ||
ctx.Store.WorkspaceEvents.Received().ActiveLayoutEngineChanged += Arg.Any< | ||
EventHandler<ActiveLayoutEngineChangedEventArgs> | ||
>(); | ||
ctx.Store.MapEvents.Received().MonitorWorkspaceChanged += Arg.Any< | ||
EventHandler<MonitorWorkspaceChangedEventArgs> | ||
>(); | ||
ctx.Store.WindowEvents.Received().WindowFocused += Arg.Any<EventHandler<WindowFocusedEventArgs>>(); | ||
} | ||
|
||
[Theory, AutoSubstituteData] | ||
[System.Diagnostics.CodeAnalysis.SuppressMessage( | ||
"Usage", | ||
"NS5000:Received check.", | ||
Justification = "The analyzer is wrong" | ||
)] | ||
public void Dispose(IContext context, IMonitor monitor) | ||
internal void Dispose_RemovesEventListeners(IContext ctx) | ||
{ | ||
// Given | ||
ActiveLayoutWidgetViewModel viewModel = CreateSut(context, monitor); | ||
// GIVEN a monitor and initialized view model | ||
IMonitor monitor = StoreTestUtils.CreateMonitor((HMONITOR)1); | ||
ActiveLayoutWidgetViewModel sut = CreateSut(ctx, monitor); | ||
|
||
// When | ||
viewModel.Dispose(); | ||
// WHEN the view model is disposed | ||
sut.Dispose(); | ||
|
||
// Then | ||
context.WorkspaceManager.Received(1).ActiveLayoutEngineChanged -= Arg.Any< | ||
// THEN all event listeners are properly unregistered from the context store | ||
ctx.Store.WorkspaceEvents.Received().ActiveLayoutEngineChanged -= Arg.Any< | ||
EventHandler<ActiveLayoutEngineChangedEventArgs> | ||
>(); | ||
context.Butler.Received(1).MonitorWorkspaceChanged -= Arg.Any<EventHandler<MonitorWorkspaceChangedEventArgs>>(); | ||
ctx.Store.MapEvents.Received().MonitorWorkspaceChanged -= Arg.Any< | ||
EventHandler<MonitorWorkspaceChangedEventArgs> | ||
>(); | ||
ctx.Store.WindowEvents.Received().WindowFocused -= Arg.Any<EventHandler<WindowFocusedEventArgs>>(); | ||
} | ||
|
||
[Theory, AutoSubstituteData<StoreCustomization>] | ||
internal void Store_ActiveLayoutEngineChanged_UpdatesProperty( | ||
IContext ctx, | ||
MutableRootSector root, | ||
ILayoutEngine previousLayoutEngine, | ||
ILayoutEngine currentLayoutEngine | ||
) | ||
{ | ||
// GIVEN an initialized view model monitoring a specific workspace | ||
IMonitor monitor = StoreTestUtils.CreateMonitor((HMONITOR)1); | ||
Workspace workspace = StoreTestUtils.CreateWorkspace(ctx); | ||
StoreTestUtils.AddWorkspacesToManager(ctx, root, workspace); | ||
|
||
ActiveLayoutWidgetViewModel sut = CreateSut(ctx, monitor); | ||
bool propertyChanged = false; | ||
sut.PropertyChanged += (s, e) => | ||
{ | ||
if (e.PropertyName == nameof(ActiveLayoutWidgetViewModel.ActiveLayoutEngine)) | ||
{ | ||
propertyChanged = true; | ||
} | ||
}; | ||
|
||
// WHEN the active layout engine changes in the workspace | ||
root.WorkspaceSector.QueueEvent( | ||
new ActiveLayoutEngineChangedEventArgs() | ||
{ | ||
Workspace = workspace, | ||
PreviousLayoutEngine = previousLayoutEngine, | ||
CurrentLayoutEngine = currentLayoutEngine, | ||
} | ||
); | ||
root.WorkspaceSector.DispatchEvents(); | ||
|
||
// THEN the view model property is updated and notifies of the change | ||
Assert.True(propertyChanged); | ||
} | ||
|
||
[Theory, AutoSubstituteData<StoreCustomization>] | ||
internal void Store_MonitorWorkspaceChanged_SameMonitor_UpdatesProperties(IContext ctx, MutableRootSector root) | ||
{ | ||
// GIVEN | ||
IMonitor monitor = StoreTestUtils.CreateMonitor((HMONITOR)1); | ||
ILayoutEngine layoutEngine1 = CreateLayoutEngine("Layout1"); | ||
ILayoutEngine layoutEngine2 = CreateLayoutEngine("Layout2"); | ||
Workspace workspace = StoreTestUtils.CreateWorkspace(ctx) with | ||
{ | ||
LayoutEngines = [layoutEngine1, layoutEngine2], | ||
}; | ||
StoreTestUtils.PopulateMonitorWorkspaceMap(ctx, root, monitor, workspace); | ||
|
||
ActiveLayoutWidgetViewModel sut = CreateSut(ctx, monitor); | ||
List<string> propertyChangedNames = []; | ||
sut.PropertyChanged += (s, e) => propertyChangedNames.Add(e.PropertyName ?? string.Empty); | ||
|
||
// WHEN | ||
root.MapSector.QueueEvent( | ||
new MonitorWorkspaceChangedEventArgs | ||
{ | ||
Monitor = monitor, | ||
CurrentWorkspace = workspace, | ||
PreviousWorkspace = null, | ||
} | ||
); | ||
root.MapSector.DispatchEvents(); | ||
|
||
// THEN | ||
Assert.Contains(nameof(ActiveLayoutWidgetViewModel.LayoutEngines), propertyChangedNames); | ||
Assert.Contains(nameof(ActiveLayoutWidgetViewModel.ActiveLayoutEngine), propertyChangedNames); | ||
Assert.Collection( | ||
sut.LayoutEngines, | ||
item => Assert.Equal("Layout1", item), | ||
item => Assert.Equal("Layout2", item) | ||
); | ||
} | ||
|
||
[Theory, AutoSubstituteData<StoreCustomization>] | ||
internal void Store_MonitorWorkspaceChanged_DifferentMonitor_DoesNotUpdateProperties( | ||
IContext ctx, | ||
MutableRootSector root | ||
) | ||
{ | ||
// GIVEN | ||
IMonitor widgetMonitor = StoreTestUtils.CreateMonitor((HMONITOR)1); | ||
IMonitor differentMonitor = StoreTestUtils.CreateMonitor((HMONITOR)2); | ||
ILayoutEngine layoutEngine1 = CreateLayoutEngine("Layout1"); | ||
ILayoutEngine layoutEngine2 = CreateLayoutEngine("Layout2"); | ||
Workspace workspace = StoreTestUtils.CreateWorkspace(ctx) with | ||
{ | ||
LayoutEngines = [layoutEngine1, layoutEngine2], | ||
}; | ||
StoreTestUtils.PopulateMonitorWorkspaceMap(ctx, root, differentMonitor, workspace); | ||
|
||
ActiveLayoutWidgetViewModel sut = CreateSut(ctx, widgetMonitor); | ||
List<string> propertyChangedNames = []; | ||
sut.PropertyChanged += (s, e) => propertyChangedNames.Add(e.PropertyName ?? string.Empty); | ||
|
||
// WHEN | ||
root.MapSector.QueueEvent( | ||
new MonitorWorkspaceChangedEventArgs | ||
{ | ||
Monitor = differentMonitor, | ||
CurrentWorkspace = workspace, | ||
PreviousWorkspace = null, | ||
} | ||
); | ||
root.MapSector.DispatchEvents(); | ||
|
||
// THEN | ||
Assert.DoesNotContain(nameof(ActiveLayoutWidgetViewModel.LayoutEngines), propertyChangedNames); | ||
Assert.DoesNotContain(nameof(ActiveLayoutWidgetViewModel.ActiveLayoutEngine), propertyChangedNames); | ||
Assert.Empty(sut.LayoutEngines); | ||
} | ||
|
||
[Theory, AutoSubstituteData<StoreCustomization>] | ||
internal void WindowEvents_WindowFocused_ClosesDropDown(IContext ctx, MutableRootSector root, IWindow window) | ||
{ | ||
// GIVEN a view model with an open dropdown | ||
IMonitor monitor = StoreTestUtils.CreateMonitor((HMONITOR)1); | ||
ActiveLayoutWidgetViewModel sut = CreateSut(ctx, monitor); | ||
sut.IsDropDownOpen = true; | ||
|
||
// WHEN a window focus event occurs | ||
root.WindowSector.QueueEvent(new WindowFocusedEventArgs { Window = window }); | ||
root.WindowSector.DispatchEvents(); | ||
|
||
// THEN the dropdown is automatically closed | ||
Assert.False(sut.IsDropDownOpen); | ||
} | ||
|
||
[Theory, AutoSubstituteData<StoreCustomization>] | ||
internal void ActiveLayoutEngine_Get_ReturnsEmptyString_WhenNoLayoutEngine(IContext ctx) | ||
{ | ||
// GIVEN a view model with no associated workspace or layout engine | ||
IMonitor monitor = StoreTestUtils.CreateMonitor((HMONITOR)1); | ||
ActiveLayoutWidgetViewModel sut = CreateSut(ctx, monitor); | ||
// No layout engine or workspace is populated in the store | ||
|
||
// WHEN requesting the active layout engine name | ||
string result = sut.ActiveLayoutEngine; | ||
|
||
// THEN an empty string is returned | ||
Assert.Equal("", result); | ||
} | ||
|
||
[Theory, AutoSubstituteData<StoreCustomization>] | ||
internal void ActiveLayoutEngine_Get_ReturnsLayoutEngineName(IContext ctx, MutableRootSector root) | ||
{ | ||
// GIVEN a view model with a workspace containing a specific layout engine | ||
IMonitor monitor = StoreTestUtils.CreateMonitor((HMONITOR)1); | ||
ILayoutEngine layoutEngine = CreateLayoutEngine("TestLayout"); | ||
Workspace workspace = StoreTestUtils.CreateWorkspace(ctx) with { LayoutEngines = [layoutEngine] }; | ||
StoreTestUtils.PopulateMonitorWorkspaceMap(ctx, root, monitor, workspace); | ||
|
||
ActiveLayoutWidgetViewModel sut = CreateSut(ctx, monitor); | ||
|
||
// WHEN requesting the active layout engine name | ||
string result = sut.ActiveLayoutEngine; | ||
|
||
// THEN the correct layout engine name is returned | ||
Assert.Equal("TestLayout", result); | ||
} | ||
|
||
[Theory, AutoSubstituteData<StoreCustomization>] | ||
internal void ActiveLayoutEngine_Set_DoesNothing_WhenNoWorkspace(IContext ctx) | ||
{ | ||
// GIVEN a view model with no associated workspace | ||
IMonitor monitor = StoreTestUtils.CreateMonitor((HMONITOR)1); | ||
ActiveLayoutWidgetViewModel sut = CreateSut(ctx, monitor); | ||
// No workspace is populated in the store | ||
bool propertyChanged = false; | ||
sut.PropertyChanged += (s, e) => propertyChanged = true; | ||
|
||
// WHEN attempting to set a new layout engine | ||
sut.ActiveLayoutEngine = "NewLayout"; | ||
|
||
// THEN no property change occurs since there is no workspace to modify | ||
Assert.False(propertyChanged); | ||
} | ||
|
||
[Theory, AutoSubstituteData<StoreCustomization>] | ||
internal void ActiveLayoutEngine_Set_DoesNotDispatch_WhenSameLayoutEngine(IContext ctx, List<object> transforms) | ||
{ | ||
// GIVEN a view model with a workspace and active layout engine | ||
IMonitor monitor = StoreTestUtils.CreateMonitor((HMONITOR)1); | ||
ILayoutEngine layoutEngine = CreateLayoutEngine("TestLayout"); | ||
Workspace workspace = StoreTestUtils.CreateWorkspace(ctx) with { LayoutEngines = [layoutEngine] }; | ||
|
||
ActiveLayoutWidgetViewModel sut = CreateSut(ctx, monitor); | ||
bool propertyChanged = false; | ||
sut.PropertyChanged += (s, e) => propertyChanged = true; | ||
|
||
// WHEN setting the same layout engine name | ||
sut.ActiveLayoutEngine = "TestLayout"; | ||
|
||
// THEN no changes are dispatched and no property change is triggered | ||
Assert.Empty(transforms); | ||
Assert.False(propertyChanged); | ||
} | ||
|
||
[Theory, AutoSubstituteData<StoreCustomization>] | ||
internal void ActiveLayoutEngine_Set_Dispatches_WhenDifferentLayoutEngine( | ||
IContext ctx, | ||
MutableRootSector root, | ||
List<object> transforms | ||
) | ||
{ | ||
// GIVEN a view model with a workspace and specific layout engine | ||
IMonitor monitor = StoreTestUtils.CreateMonitor((HMONITOR)1); | ||
ILayoutEngine layoutEngine = CreateLayoutEngine("TestLayout"); | ||
Workspace workspace = StoreTestUtils.CreateWorkspace(ctx) with { LayoutEngines = [layoutEngine] }; | ||
StoreTestUtils.PopulateMonitorWorkspaceMap(ctx, root, monitor, workspace); | ||
|
||
ActiveLayoutWidgetViewModel sut = CreateSut(ctx, monitor); | ||
bool propertyChanged = false; | ||
sut.PropertyChanged += (s, e) => | ||
{ | ||
if (e.PropertyName == nameof(ActiveLayoutWidgetViewModel.ActiveLayoutEngine)) | ||
{ | ||
propertyChanged = true; | ||
} | ||
}; | ||
|
||
// WHEN setting a different layout engine name | ||
sut.ActiveLayoutEngine = "NewLayout"; | ||
|
||
// THEN a transform is dispatched and the property change is notified | ||
Assert.Contains(transforms, t => t.Equals(new SetLayoutEngineFromNameTransform(workspace.Id, "NewLayout"))); | ||
Assert.True(propertyChanged); | ||
} | ||
} |
55 changes: 0 additions & 55 deletions
55
src/Whim.Bar.Tests/ActiveLayout/NextLayoutEngineCommandTests.cs
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.