From 72c2dfa0d70a56f3da2a94afadfd778091a58d1a Mon Sep 17 00:00:00 2001 From: Software Antics <50978201+softwareantics@users.noreply.github.com> Date: Sun, 3 Dec 2023 00:48:08 +1100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Remove=20Entity=20Component=20from?= =?UTF-8?q?=20Entity=20(#286)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Initial * Added unit tests and fixed up issues. * Added documentation --- FinalEngine.Editor.Desktop/App.xaml | 1 + .../Controls/Common/IconButton.xaml | 7 + .../Controls/Common/IconButton.xaml.cs | 34 ++++ .../FinalEngine.Editor.Desktop.csproj | 7 + .../Resources/Images/Icons/Settings.png | Bin 0 -> 778 bytes .../Styles/Controls/IconButtonStyle.xaml | 27 ++++ .../Views/Inspectors/EntityComponentView.xaml | 17 +- .../FinalEngine.Editor.ViewModels.csproj | 1 - .../Inspectors/EntityComponentViewModel.cs | 94 +++++++++-- .../Inspectors/EntityInspectorViewModel.cs | 70 ++++++-- .../Inspectors/IEntityComponentViewModel.cs | 31 ++-- .../Inspectors/PropertiesToolViewModel.cs | 2 +- .../Entities/EntityModifiedMessage.cs | 36 +++++ .../Core/Input/Mouse/MouseTests.cs | 15 -- .../Services/Rendering/SceneRendererTests.cs | 43 ++++- .../EntityComponentViewModelTests.cs | 152 +++++++++++++++--- .../EntityInspectorViewModelTests.cs | 57 ++++++- .../Mocks/EntityComponentBoolean.cs | 2 +- .../PropertiesToolViewModelTests.cs | 8 +- FinalEngine.Tests/FinalEngine.Tests.csproj | 1 - .../OpenGL/OpenGLRenderContextTests.cs | 29 ---- FinalEngine.sln | 1 - test.txt | 1 - 23 files changed, 513 insertions(+), 123 deletions(-) create mode 100644 FinalEngine.Editor.Desktop/Controls/Common/IconButton.xaml create mode 100644 FinalEngine.Editor.Desktop/Controls/Common/IconButton.xaml.cs create mode 100644 FinalEngine.Editor.Desktop/Resources/Images/Icons/Settings.png create mode 100644 FinalEngine.Editor.Desktop/Styles/Controls/IconButtonStyle.xaml create mode 100644 FinalEngine.Editor.ViewModels/Messages/Entities/EntityModifiedMessage.cs delete mode 100644 test.txt diff --git a/FinalEngine.Editor.Desktop/App.xaml b/FinalEngine.Editor.Desktop/App.xaml index 9ae18f29..3c353475 100644 --- a/FinalEngine.Editor.Desktop/App.xaml +++ b/FinalEngine.Editor.Desktop/App.xaml @@ -19,6 +19,7 @@ + diff --git a/FinalEngine.Editor.Desktop/Controls/Common/IconButton.xaml b/FinalEngine.Editor.Desktop/Controls/Common/IconButton.xaml new file mode 100644 index 00000000..f850d18c --- /dev/null +++ b/FinalEngine.Editor.Desktop/Controls/Common/IconButton.xaml @@ -0,0 +1,7 @@ + diff --git a/FinalEngine.Editor.Desktop/Controls/Common/IconButton.xaml.cs b/FinalEngine.Editor.Desktop/Controls/Common/IconButton.xaml.cs new file mode 100644 index 00000000..70008906 --- /dev/null +++ b/FinalEngine.Editor.Desktop/Controls/Common/IconButton.xaml.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +namespace FinalEngine.Editor.Desktop.Controls.Common; + +using System.Windows.Controls; +using System.Windows.Media; + +/// +/// Interaction logic for IconButton.xaml. +/// +public partial class IconButton : Button +{ + /// + /// Initializes a new instance of the class. + /// + public IconButton() + { + this.InitializeComponent(); + } + + /// + /// Gets or sets the URI source for the icon. + /// + /// + /// The URI source for the icon. + /// + public ImageSource UriSource + { + get { return this.image.Source; } + set { this.image.Source = value; } + } +} diff --git a/FinalEngine.Editor.Desktop/FinalEngine.Editor.Desktop.csproj b/FinalEngine.Editor.Desktop/FinalEngine.Editor.Desktop.csproj index 68d17cee..a739a538 100644 --- a/FinalEngine.Editor.Desktop/FinalEngine.Editor.Desktop.csproj +++ b/FinalEngine.Editor.Desktop/FinalEngine.Editor.Desktop.csproj @@ -14,6 +14,7 @@ + @@ -48,6 +49,12 @@ + + + Never + + + diff --git a/FinalEngine.Editor.Desktop/Resources/Images/Icons/Settings.png b/FinalEngine.Editor.Desktop/Resources/Images/Icons/Settings.png new file mode 100644 index 0000000000000000000000000000000000000000..2f263a293a47c3b1b961a3906c039c72a6483173 GIT binary patch literal 778 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1quc4c9zf978PpmrnBaW(pK(n;W<>)jPU{OGwv9 zvuA-q(}w*I7Ww9hG&gM%b!^R5R{Eu|!FBd8#A3uIj?%WHhL68ieOJ*-%XKK^iDJqBTJ@Jtj@4Y`?xl>Dh{n&_5kbvS-y#oL zc)Z@V&%u#t$pPcZ^QX*LP-niB;KOxf4O`5N#zP)bd1kZ2H*h|8`6~Yj_6gsWEWMegZ(nFK z-SsuYn(nm5PwXe3AJhyoU+VVev-F%sPBoRxZQP6g*u-v#uG!$6H`h8*DlJNM#?^-V z#qv?ivU=0C8Ri?Wl;;bYlls@LM*XN~|MAV;_su5RFAC9G%M_gWbh$OtYr+3%X1A>y z{#xCeDX~-r80`$6u6{1-oD!OnICtnMi3;vy?5TPmKjUVd;7s9(o`Rap!fD@9($kmU z3tezCXPsF0(f0=uR$txqVve+*W-artkgewDKYw0Tb@0r*y!?FoU$fI^y{%YgT`lzQ zY8{j1ju)D9zXWdq+M-(G8c~vxSdwa$T$Bo=7>o>z40H|5b&V`T42-Od4Xg|dv<*Py Zw&(?~P&DM`r(~v8;?{7SO(G20BLIx3Jz4+& literal 0 HcmV?d00001 diff --git a/FinalEngine.Editor.Desktop/Styles/Controls/IconButtonStyle.xaml b/FinalEngine.Editor.Desktop/Styles/Controls/IconButtonStyle.xaml new file mode 100644 index 00000000..a4439bda --- /dev/null +++ b/FinalEngine.Editor.Desktop/Styles/Controls/IconButtonStyle.xaml @@ -0,0 +1,27 @@ + + + diff --git a/FinalEngine.Editor.Desktop/Views/Inspectors/EntityComponentView.xaml b/FinalEngine.Editor.Desktop/Views/Inspectors/EntityComponentView.xaml index 8f9c76e7..f1946f6e 100644 --- a/FinalEngine.Editor.Desktop/Views/Inspectors/EntityComponentView.xaml +++ b/FinalEngine.Editor.Desktop/Views/Inspectors/EntityComponentView.xaml @@ -7,11 +7,26 @@ xmlns:dtv="clr-namespace:FinalEngine.Editor.Desktop.Views.Editing.DataTypes" xmlns:dt="clr-namespace:FinalEngine.Editor.ViewModels.Editing.DataTypes;assembly=FinalEngine.Editor.ViewModels" xmlns:vm="clr-namespace:FinalEngine.Editor.ViewModels.Inspectors;assembly=FinalEngine.Editor.ViewModels" + xmlns:c="clr-namespace:FinalEngine.Editor.Desktop.Controls.Common" d:DataContext="{d:DesignInstance Type=vm:EntityComponentViewModel}" mc:Ignorable="d"> + + + + + - + + + + + + + + + + false true x64 - true diff --git a/FinalEngine.Editor.ViewModels/Inspectors/EntityComponentViewModel.cs b/FinalEngine.Editor.ViewModels/Inspectors/EntityComponentViewModel.cs index d25d080e..be5bf8b0 100644 --- a/FinalEngine.Editor.ViewModels/Inspectors/EntityComponentViewModel.cs +++ b/FinalEngine.Editor.ViewModels/Inspectors/EntityComponentViewModel.cs @@ -1,5 +1,5 @@ // -// Copyright (c) Software Antics. All rights reserved. +// Copyright (c) Software Antics. All rights reserved. // namespace FinalEngine.Editor.ViewModels.Inspectors; @@ -13,47 +13,75 @@ namespace FinalEngine.Editor.ViewModels.Inspectors; using System.Windows.Input; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using CommunityToolkit.Mvvm.Messaging; using FinalEngine.ECS; +using FinalEngine.ECS.Components.Core; using FinalEngine.Editor.ViewModels.Editing.DataTypes; using FinalEngine.Editor.ViewModels.Exceptions.Inspectors; +using FinalEngine.Editor.ViewModels.Messages.Entities; /// -/// Provides a standard implementation of an . +/// Provides a standard implementation of an . /// -/// -/// +/// +/// public sealed class EntityComponentViewModel : ObservableObject, IEntityComponentViewModel { /// - /// The property view models associated with this component model. + /// The component to model. + /// + private readonly IEntityComponent component; + + /// + /// The entity that contains the component to be modeled. + /// + private readonly Entity entity; + + /// + /// The messenger, used to notify other views when the entity has been modified. + /// + private readonly IMessenger messenger; + + /// + /// The property view models associated with this component model. /// private readonly ObservableCollection propertyViewModels; /// - /// Indicates whether the components properties are visible. + /// Indicates whether the components properties are visible. /// private bool isVisible; /// - /// The toggle command. + /// The remove command, used to remove the component from the entity. + /// + private ICommand? removeCommand; + + /// + /// The toggle command. /// private ICommand? toggleCommand; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// + /// + /// The messenger. + /// /// - /// The component to be modelled. + /// The component to be modeled. + /// + /// + /// The entity that contains the specified . /// /// - /// The specified parameter cannot be null. + /// The specified , or parameter cannot be null. /// - public EntityComponentViewModel(IEntityComponent component) + public EntityComponentViewModel(IMessenger messenger, Entity entity, IEntityComponent component) { - if (component == null) - { - throw new ArgumentNullException(nameof(component)); - } + this.messenger = messenger ?? throw new ArgumentNullException(nameof(messenger)); + this.entity = entity ?? throw new ArgumentNullException(nameof(entity)); + this.component = component ?? throw new ArgumentNullException(nameof(component)); this.propertyViewModels = new ObservableCollection(); @@ -138,6 +166,12 @@ public ICollection PropertyViewModels get { return this.propertyViewModels; } } + /// + public ICommand RemoveCommand + { + get { return this.removeCommand ??= new RelayCommand(this.Remove, this.CanRemove); } + } + /// public ICommand ToggleCommand { @@ -145,7 +179,35 @@ public ICommand ToggleCommand } /// - /// Toggles the visibility of the components properties. + /// Determines whether the component can be removed from the entity. + /// + /// + /// true if the component can be removed from the entity; otherwise, false. + /// + private bool CanRemove() + { + return this.component.GetType() != typeof(TagComponent); + } + + /// + /// Removes the component from the entity and notifies other views that the entity has been modified. + /// + /// + /// The provided to this instance does not contain an of the required type. + /// + private void Remove() + { + if (!this.entity.ContainsComponent(this.component)) + { + throw new InvalidOperationException($"The {nameof(Entity)} provided to this instance does not contain an {nameof(IEntityComponent)} of type: '{this.component.GetType()}'"); + } + + this.entity.RemoveComponent(this.component); + this.messenger.Send(new EntityModifiedMessage(this.entity)); + } + + /// + /// Toggles the visibility of the components properties. /// private void Toggle() { diff --git a/FinalEngine.Editor.ViewModels/Inspectors/EntityInspectorViewModel.cs b/FinalEngine.Editor.ViewModels/Inspectors/EntityInspectorViewModel.cs index 85adc5f9..65b88fb9 100644 --- a/FinalEngine.Editor.ViewModels/Inspectors/EntityInspectorViewModel.cs +++ b/FinalEngine.Editor.ViewModels/Inspectors/EntityInspectorViewModel.cs @@ -1,5 +1,5 @@ // -// Copyright (c) Software Antics. All rights reserved. +// Copyright (c) Software Antics. All rights reserved. // namespace FinalEngine.Editor.ViewModels.Inspectors; @@ -8,43 +8,53 @@ namespace FinalEngine.Editor.ViewModels.Inspectors; using System.Collections.Generic; using System.Collections.ObjectModel; using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Messaging; using FinalEngine.ECS; +using FinalEngine.Editor.ViewModels.Messages.Entities; /// -/// Provides a standard implementation of an . +/// Provides a standard implementation of an . /// -/// -/// +/// +/// public sealed class EntityInspectorViewModel : ObservableObject, IEntityInspectorViewModel { /// - /// The component view models. + /// The component view models. /// private readonly ObservableCollection componentViewModels; /// - /// The entity being inspected. + /// The entity being inspected. /// private readonly Entity entity; /// - /// Initializes a new instance of the class. + /// The messenger. /// + private readonly IMessenger messenger; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The messenger. + /// /// - /// The entity to be inspected. + /// The entity to be inspected. /// /// - /// The specified parameter cannot be null. + /// The specified parameter cannot be null. /// - public EntityInspectorViewModel(Entity entity) + public EntityInspectorViewModel(IMessenger messenger, Entity entity) { + this.messenger = messenger ?? throw new ArgumentNullException(nameof(messenger)); this.entity = entity ?? throw new ArgumentNullException(nameof(entity)); this.componentViewModels = new ObservableCollection(); - foreach (var component in this.entity.Components) - { - this.componentViewModels.Add(new EntityComponentViewModel(component)); - } + this.messenger.Register(this, this.HandleEntityModified); + + this.InitializeEntityComponents(); } /// @@ -52,4 +62,36 @@ public ICollection ComponentViewModels { get { return this.componentViewModels; } } + + /// + /// Handles the and initializes the entity component view models. + /// + /// + /// The recipient. + /// + /// + /// The message. + /// + private void HandleEntityModified(object recipient, EntityModifiedMessage message) + { + if (!ReferenceEquals(this.entity, message.Entity)) + { + return; + } + + this.InitializeEntityComponents(); + } + + /// + /// Initializes the entity component view models for the . + /// + private void InitializeEntityComponents() + { + this.componentViewModels.Clear(); + + foreach (var component in this.entity.Components) + { + this.componentViewModels.Add(new EntityComponentViewModel(this.messenger, this.entity, component)); + } + } } diff --git a/FinalEngine.Editor.ViewModels/Inspectors/IEntityComponentViewModel.cs b/FinalEngine.Editor.ViewModels/Inspectors/IEntityComponentViewModel.cs index e3b9d0c7..361566d2 100644 --- a/FinalEngine.Editor.ViewModels/Inspectors/IEntityComponentViewModel.cs +++ b/FinalEngine.Editor.ViewModels/Inspectors/IEntityComponentViewModel.cs @@ -1,5 +1,5 @@ // -// Copyright (c) Software Antics. All rights reserved. +// Copyright (c) Software Antics. All rights reserved. // namespace FinalEngine.Editor.ViewModels.Inspectors; @@ -10,12 +10,12 @@ namespace FinalEngine.Editor.ViewModels.Inspectors; using FinalEngine.ECS; /// -/// Defines an interface that represents a model of an entity component view. +/// Defines an interface that represents a model of an entity component view. /// public interface IEntityComponentViewModel { /// - /// Gets a value indicating whether the components properties are visible. + /// Gets a value indicating whether the components properties are visible. /// /// /// true if the components properties are visible; otherwise, false. @@ -23,29 +23,40 @@ public interface IEntityComponentViewModel bool IsVisible { get; } /// - /// Gets the name of the . + /// Gets the name of the . /// /// - /// The name of the . + /// The name of the . /// string Name { get; } /// - /// Gets the property view models. + /// Gets the property view models. /// /// - /// The property view models. + /// The property view models. /// ICollection PropertyViewModels { get; } /// - /// Gets the toggle command. + /// Gets the remove command. /// /// - /// The toggle command. + /// The remove command. /// /// - /// The is used to toggle the visibility of the components properties. + /// The is used to remove an from an . + /// + ICommand RemoveCommand { get; } + + /// + /// Gets the toggle command. + /// + /// + /// The toggle command. + /// + /// + /// The is used to toggle the visibility of the components properties. /// ICommand ToggleCommand { get; } } diff --git a/FinalEngine.Editor.ViewModels/Inspectors/PropertiesToolViewModel.cs b/FinalEngine.Editor.ViewModels/Inspectors/PropertiesToolViewModel.cs index 68d243d3..f81e5839 100644 --- a/FinalEngine.Editor.ViewModels/Inspectors/PropertiesToolViewModel.cs +++ b/FinalEngine.Editor.ViewModels/Inspectors/PropertiesToolViewModel.cs @@ -96,7 +96,7 @@ private void HandleEntitySelected(object recipient, EntitySelectedMessage messag this.logger.LogInformation($"Changing properties view to: '{nameof(EntityInspectorViewModel)}'."); this.Title = "Entity Inspector"; - this.CurrentViewModel = new EntityInspectorViewModel(message.Entity); + this.CurrentViewModel = new EntityInspectorViewModel(this.messenger, message.Entity); } /// diff --git a/FinalEngine.Editor.ViewModels/Messages/Entities/EntityModifiedMessage.cs b/FinalEngine.Editor.ViewModels/Messages/Entities/EntityModifiedMessage.cs new file mode 100644 index 00000000..21aa4cfe --- /dev/null +++ b/FinalEngine.Editor.ViewModels/Messages/Entities/EntityModifiedMessage.cs @@ -0,0 +1,36 @@ +// +// Copyright (c) Software Antics. All rights reserved. +// + +namespace FinalEngine.Editor.ViewModels.Messages.Entities; + +using System; +using FinalEngine.ECS; + +/// +/// Provides a message that should be published when an has been modified. +/// +public sealed class EntityModifiedMessage +{ + /// + /// Initializes a new instance of the class. + /// + /// + /// The entity that has been modified. + /// + /// + /// The specified parameter cannot be null. + /// + public EntityModifiedMessage(Entity entity) + { + this.Entity = entity ?? throw new ArgumentNullException(nameof(entity)); + } + + /// + /// Gets the entity that has been modified. + /// + /// + /// The entity that has been modified. + /// + public Entity Entity { get; } +} diff --git a/FinalEngine.Tests/Core/Input/Mouse/MouseTests.cs b/FinalEngine.Tests/Core/Input/Mouse/MouseTests.cs index da87de49..4eac2124 100644 --- a/FinalEngine.Tests/Core/Input/Mouse/MouseTests.cs +++ b/FinalEngine.Tests/Core/Input/Mouse/MouseTests.cs @@ -70,21 +70,6 @@ public void DeltaShouldReturnDeviceLocationDeltaWhenDeviceIsNotNull() this.mouseDevice.VerifyGet(x => x.LocationDelta, Times.Once); } - [Test] - public void DeltaShouldReturnPointEmptyWhenDeviceIsNull() - { - // Arrange - var mouse = new Mouse(null); - PointF expected = Point.Empty; - - // Act - var actual = mouse.Delta; - mouse.Dispose(); - - // Assert - Assert.AreEqual(expected, actual); - } - [Test] public void DeviceButtonDownShouldThrowArgumentNullExceptionWhenEventDataIsNull() { diff --git a/FinalEngine.Tests/Editor/Common/Services/Rendering/SceneRendererTests.cs b/FinalEngine.Tests/Editor/Common/Services/Rendering/SceneRendererTests.cs index 14352237..4d489957 100644 --- a/FinalEngine.Tests/Editor/Common/Services/Rendering/SceneRendererTests.cs +++ b/FinalEngine.Tests/Editor/Common/Services/Rendering/SceneRendererTests.cs @@ -1,5 +1,5 @@ // -// Copyright (c) Software Antics. All rights reserved. +// Copyright (c) Software Antics. All rights reserved. // namespace FinalEngine.Tests.Editor.Common.Services.Rendering; @@ -15,6 +15,8 @@ namespace FinalEngine.Tests.Editor.Common.Services.Rendering; [TestFixture] public sealed class SceneRendererTests { + private Mock pipeline; + private Mock renderDevice; private Mock scene; @@ -23,13 +25,23 @@ public sealed class SceneRendererTests private SceneRenderer sceneRenderer; + [Test] + public void ConstructorShouldThrowArgumentNullExceptionWhenPipelineIsNull() + { + // Act and assert + Assert.Throws(() => + { + new SceneRenderer(null, this.renderDevice.Object, this.sceneManager.Object); + }); + } + [Test] public void ConstructorShouldThrowArgumentNullExceptionWhenRenderDeviceIsNull() { // Act and assert Assert.Throws(() => { - new SceneRenderer(null, this.sceneManager.Object); + new SceneRenderer(this.pipeline.Object, null, this.sceneManager.Object); }); } @@ -39,7 +51,7 @@ public void ConstructorShouldThrowArgumentNullExceptionWhenSceneManagerIsNull() // Act and assert Assert.Throws(() => { - new SceneRenderer(this.renderDevice.Object, null); + new SceneRenderer(this.pipeline.Object, this.renderDevice.Object, null); }); } @@ -53,6 +65,27 @@ public void RenderShouldInvokeActiveSceneRenderWhenInvoked() this.scene.Verify(x => x.Render(), Times.Once); } + [Test] + public void RenderShouldInvokePipelineInitializedOnceWhenInvokedMoreThanOnce() + { + // Act + this.sceneRenderer.Render(); + this.sceneRenderer.Render(); + + // Assert + this.pipeline.Verify(x => x.Initialize(), Times.Once); + } + + [Test] + public void RenderShouldInvokePipelineInitializedWhenInvoked() + { + // Act + this.sceneRenderer.Render(); + + // Assert + this.pipeline.Verify(x => x.Initialize(), Times.Once); + } + [Test] public void RenderShouldInvokeRenderDeviceClearWhenInvoked() { @@ -68,10 +101,12 @@ public void Setup() { this.sceneManager = new Mock(); this.renderDevice = new Mock(); + this.pipeline = new Mock(); + this.scene = new Mock(); this.sceneManager.SetupGet(x => x.ActiveScene).Returns(this.scene.Object); - this.sceneRenderer = new SceneRenderer(this.renderDevice.Object, this.sceneManager.Object); + this.sceneRenderer = new SceneRenderer(this.pipeline.Object, this.renderDevice.Object, this.sceneManager.Object); } } diff --git a/FinalEngine.Tests/Editor/ViewModels/Inspectors/EntityComponentViewModelTests.cs b/FinalEngine.Tests/Editor/ViewModels/Inspectors/EntityComponentViewModelTests.cs index 7d53497a..67d6e7b9 100644 --- a/FinalEngine.Tests/Editor/ViewModels/Inspectors/EntityComponentViewModelTests.cs +++ b/FinalEngine.Tests/Editor/ViewModels/Inspectors/EntityComponentViewModelTests.cs @@ -1,29 +1,38 @@ // -// Copyright (c) Software Antics. All rights reserved. +// Copyright (c) Software Antics. All rights reserved. // namespace FinalEngine.Tests.Editor.ViewModels.Inspectors; using System; using System.Linq; +using CommunityToolkit.Mvvm.Messaging; +using FinalEngine.ECS; using FinalEngine.ECS.Components.Core; using FinalEngine.Editor.ViewModels.Editing.DataTypes; using FinalEngine.Editor.ViewModels.Exceptions.Inspectors; using FinalEngine.Editor.ViewModels.Inspectors; +using FinalEngine.Editor.ViewModels.Messages.Entities; using FinalEngine.Tests.Editor.ViewModels.Inspectors.Mocks; +using FinalEngine.Tests.Editor.ViewModels.Messages; +using Moq; using NUnit.Framework; [TestFixture] public sealed class EntityComponentViewModelTests { + private Entity entity; + + private Mock messenger; + [Test] public void ConstructorShouldAddBooleanPropertyViewModelWhenComponentHasBoolean() { // Arrange - var component = new EntityComponenBoolean(); + var component = new EntityComponentBoolean(); // Act - var viewModel = new EntityComponentViewModel(component); + var viewModel = new EntityComponentViewModel(this.messenger.Object, this.entity, component); // Assert Assert.That(viewModel.PropertyViewModels.FirstOrDefault(), Is.TypeOf()); @@ -36,7 +45,7 @@ public void ConstructorShouldAddDoublePropertyViewModelWhenComponentHasDouble() var component = new EntityComponentDouble(); // Act - var viewModel = new EntityComponentViewModel(component); + var viewModel = new EntityComponentViewModel(this.messenger.Object, this.entity, component); // Assert Assert.That(viewModel.PropertyViewModels.FirstOrDefault(), Is.TypeOf()); @@ -49,7 +58,7 @@ public void ConstructorShouldAddFloatPropertyViewModelWhenComponentHasFloat() var component = new EntityComponentFloat(); // Act - var viewModel = new EntityComponentViewModel(component); + var viewModel = new EntityComponentViewModel(this.messenger.Object, this.entity, component); // Assert Assert.That(viewModel.PropertyViewModels.FirstOrDefault(), Is.TypeOf()); @@ -62,7 +71,7 @@ public void ConstructorShouldAddIntPropertyViewModelWhenComponentHasInteger() var component = new EntityComponentInteger(); // Act - var viewModel = new EntityComponentViewModel(component); + var viewModel = new EntityComponentViewModel(this.messenger.Object, this.entity, component); // Assert Assert.That(viewModel.PropertyViewModels.FirstOrDefault(), Is.TypeOf()); @@ -75,7 +84,7 @@ public void ConstructorShouldAddPropertyViewModelWhenComponentDoesNotHaveBrowsab var component = new TagComponent(); // Act - var viewModel = new EntityComponentViewModel(component); + var viewModel = new EntityComponentViewModel(this.messenger.Object, this.entity, component); // Assert Assert.That(viewModel.PropertyViewModels, Has.Count.EqualTo(1)); @@ -88,7 +97,7 @@ public void ConstructorShouldAddPropertyViewModelWhenComponentIsBrowseable() var component = new EntityComponentBrowsable(); // Act - var viewModel = new EntityComponentViewModel(component); + var viewModel = new EntityComponentViewModel(this.messenger.Object, this.entity, component); // Assert Assert.That(viewModel.PropertyViewModels, Has.Count.EqualTo(1)); @@ -101,7 +110,7 @@ public void ConstructorShouldAddStringPropertyViewModelWhenComponentHasString() var component = new EntityComponentString(); // Act - var viewModel = new EntityComponentViewModel(component); + var viewModel = new EntityComponentViewModel(this.messenger.Object, this.entity, component); // Assert Assert.That(viewModel.PropertyViewModels.FirstOrDefault(), Is.TypeOf()); @@ -114,7 +123,7 @@ public void ConstructorShouldAddVector2PropertyViewModelWhenComponentHasVector2( var component = new EntityComponentVector2(); // Act - var viewModel = new EntityComponentViewModel(component); + var viewModel = new EntityComponentViewModel(this.messenger.Object, this.entity, component); // Assert Assert.That(viewModel.PropertyViewModels.FirstOrDefault(), Is.TypeOf()); @@ -127,7 +136,7 @@ public void ConstructorShouldAddVector3PropertyViewModelWhenComponentHasVector3( var component = new EntityComponentVector3(); // Act - var viewModel = new EntityComponentViewModel(component); + var viewModel = new EntityComponentViewModel(this.messenger.Object, this.entity, component); // Assert Assert.That(viewModel.PropertyViewModels.FirstOrDefault(), Is.TypeOf()); @@ -140,7 +149,7 @@ public void ConstructorShouldAddVector4PropertyViewModelWhenComponentHasVector4( var component = new EntityComponentVector4(); // Act - var viewModel = new EntityComponentViewModel(component); + var viewModel = new EntityComponentViewModel(this.messenger.Object, this.entity, component); // Assert Assert.That(viewModel.PropertyViewModels.FirstOrDefault(), Is.TypeOf()); @@ -153,7 +162,7 @@ public void ConstructorShouldNotAddPropertyViewModelWhenComponentNotBrowseable() var component = new EntityComponentNotBrowsable(); // Act - var viewModel = new EntityComponentViewModel(component); + var viewModel = new EntityComponentViewModel(this.messenger.Object, this.entity, component); // Assert Assert.That(viewModel.PropertyViewModels, Is.Empty); @@ -166,7 +175,7 @@ public void ConstructorShouldNotAddPropertyViewModelWhenPropertyHasNonPublicGett var component = new EntityComponentPrivateGetter(); // Act - var viewModel = new EntityComponentViewModel(component); + var viewModel = new EntityComponentViewModel(this.messenger.Object, this.entity, component); // Assert Assert.That(viewModel.PropertyViewModels.Count, Is.Zero); @@ -179,7 +188,7 @@ public void ConstructorShouldNotAddPropertyViewModelWhenPropertyHasNonPublicSett var component = new EntityComponentPrivateSetter(); // Act - var viewModel = new EntityComponentViewModel(component); + var viewModel = new EntityComponentViewModel(this.messenger.Object, this.entity, component); // Assert Assert.That(viewModel.PropertyViewModels.Count, Is.Zero); @@ -192,7 +201,7 @@ public void ConstructorShouldSetIsVisibleToTrueWhenInvoked() var component = new TagComponent(); // Act - var viewModel = new EntityComponentViewModel(component); + var viewModel = new EntityComponentViewModel(this.messenger.Object, this.entity, component); // Assert Assert.That(viewModel.IsVisible, Is.True); @@ -204,7 +213,27 @@ public void ConstructorShouldThrowArgumentNullExceptionWhenComponentIsNull() // Act and assert Assert.Throws(() => { - new EntityComponentViewModel(null); + new EntityComponentViewModel(this.messenger.Object, this.entity, null); + }); + } + + [Test] + public void ConstructorShouldThrowArgumentNullExceptionWhenEntityIsNull() + { + // Act and assert + Assert.Throws(() => + { + new EntityComponentViewModel(this.messenger.Object, null, new TagComponent()); + }); + } + + [Test] + public void ConstructorShouldThrowArgumentNullExceptionWhenMessengerIsNull() + { + // Act and assert + Assert.Throws(() => + { + new EntityComponentViewModel(null, this.entity, new TagComponent()); }); } @@ -214,7 +243,7 @@ public void ConstructorShouldThrowPropertyTypeNotFoundExceptionWhenPropertyTypeI // Act and assert Assert.Throws(() => { - new EntityComponentViewModel(new EntityComponentUnsupportedPropertyType()); + new EntityComponentViewModel(this.messenger.Object, this.entity, new EntityComponentUnsupportedPropertyType()); }); } @@ -228,7 +257,7 @@ public void NameShouldReturnTagComponentWhenInvoked() }; string expected = component.GetType().Name; - var viewModel = new EntityComponentViewModel(component); + var viewModel = new EntityComponentViewModel(this.messenger.Object, this.entity, component); // Act string actual = viewModel.Name; @@ -237,12 +266,93 @@ public void NameShouldReturnTagComponentWhenInvoked() Assert.That(actual, Is.EqualTo(expected)); } + [Test] + public void RemoveCommandCanExecuteShouldReturnFalseWhenComponentIsTagComponent() + { + // Arrange + var component = new TagComponent(); + var viewModel = new EntityComponentViewModel(this.messenger.Object, this.entity, component); + + // Act + bool actual = viewModel.RemoveCommand.CanExecute(null); + + // Assert + Assert.That(actual, Is.False); + } + + [Test] + public void RemoveCommandCanExecuteShouldReturnTrueWhenComponentIsNotTagComponent() + { + // Arrange + var component = new EntityComponentVector3(); + var viewModel = new EntityComponentViewModel(this.messenger.Object, this.entity, component); + + // Act + bool actual = viewModel.RemoveCommand.CanExecute(null); + + // Assert + Assert.That(actual, Is.True); + } + + [Test] + public void RemoveCommandExecuteShouldRemoveComponentWhenComponentIsAddedToEntity() + { + // Arrange + var component = new EntityComponentBoolean(); + this.entity.AddComponent(component); + + var viewModel = new EntityComponentViewModel(this.messenger.Object, this.entity, component); + + // Act + viewModel.RemoveCommand.Execute(null); + + // Assert + Assert.That(this.entity.ContainsComponent(component), Is.False); + } + + [Test] + public void RemoveCommandExecuteShouldSendEntityModifiedMessageWhenComponentIsAddedToEntity() + { + // Arrange + var component = new EntityComponentBoolean(); + this.entity.AddComponent(component); + + var viewModel = new EntityComponentViewModel(this.messenger.Object, this.entity, component); + + // Act + viewModel.RemoveCommand.Execute(null); + + // Assert + this.messenger.Verify(x => x.Send(It.IsAny(), It.IsAny()), Times.Once); + } + + [Test] + public void RemoveCommandExecuteShouldThrowInvalidOperationExceptionWhenComponentIsNotAddedToEntity() + { + // Arrange + var component = new EntityComponentBoolean(); + var viewModel = new EntityComponentViewModel(this.messenger.Object, this.entity, component); + + // Act and assert + Assert.Throws(() => + { + viewModel.RemoveCommand.Execute(null); + }); + } + + [SetUp] + public void Setup() + { + this.entity = new Entity(); + this.messenger = new Mock(); + } + [Test] public void ToggleCommandShouldSetIsVisibleToFalseWhenIsVisibleIsTrue() { // Arrange var component = new TagComponent(); - var viewModel = new EntityComponentViewModel(component); + var viewModel = new EntityComponentViewModel(this.messenger.Object, this.entity, component); // Act viewModel.ToggleCommand.Execute(null); @@ -256,7 +366,7 @@ public void ToggleCommandShouldSetIsVisibleToTrueWhenIsVisibleIsFalse() { // Arrange var component = new TagComponent(); - var viewModel = new EntityComponentViewModel(component); + var viewModel = new EntityComponentViewModel(this.messenger.Object, this.entity, component); viewModel.ToggleCommand.Execute(null); diff --git a/FinalEngine.Tests/Editor/ViewModels/Inspectors/EntityInspectorViewModelTests.cs b/FinalEngine.Tests/Editor/ViewModels/Inspectors/EntityInspectorViewModelTests.cs index 58b93549..c2a297c7 100644 --- a/FinalEngine.Tests/Editor/ViewModels/Inspectors/EntityInspectorViewModelTests.cs +++ b/FinalEngine.Tests/Editor/ViewModels/Inspectors/EntityInspectorViewModelTests.cs @@ -1,14 +1,17 @@ // -// Copyright (c) Software Antics. All rights reserved. +// Copyright (c) Software Antics. All rights reserved. // namespace FinalEngine.Tests.Editor.ViewModels.Inspectors; using System; using System.Linq; +using CommunityToolkit.Mvvm.Messaging; using FinalEngine.ECS; using FinalEngine.ECS.Components.Core; using FinalEngine.Editor.ViewModels.Inspectors; +using FinalEngine.Editor.ViewModels.Messages.Entities; +using FinalEngine.Tests.Editor.ViewModels.Inspectors.Mocks; using NUnit.Framework; [TestFixture] @@ -16,6 +19,8 @@ public sealed class EntityInspectorViewModelTests { private Entity entity; + private IMessenger messenger; + private EntityInspectorViewModel viewModel; [Test] @@ -38,16 +43,60 @@ public void ComponentViewModelsShouldReturnEntityComponentViewModels() Assert.That(actual, Is.AssignableFrom()); } + [Test] + public void ConstructorShouldRegisterEntityModifiedMessageWhenInvoked() + { + // Assert + this.messenger.IsRegistered(this.viewModel); + } + [Test] public void ConstructorShouldThrowArgumentNullExceptionWhenEntityIsNull() { // Act and assert Assert.Throws(() => { - new EntityInspectorViewModel(null); + new EntityInspectorViewModel(this.messenger, null); + }); + } + + [Test] + public void ConstructorShouldThrowArgumentNullExceptionWhenMessengerIsNull() + { + // Act and assert + Assert.Throws(() => + { + new EntityInspectorViewModel(null, this.entity); }); } + [Test] + public void HandleEntityModifiedShouldNotRefreshComponentsWhenEntityIsNotSameEntity() + { + // Arrange + var entity = new Entity(); + entity.AddComponent(new EntityComponentBoolean()); + + // Act + this.messenger.Send(new EntityModifiedMessage(entity)); + + // Assert + Assert.That(this.viewModel.ComponentViewModels.Count, Is.EqualTo(1)); + } + + [Test] + public void HandleEntityModifiedShouldRefreshComponentsWhenEntityIsNotSameEntity() + { + // Arrange + this.entity.AddComponent(new EntityComponentBoolean()); + + // Act + this.messenger.Send(new EntityModifiedMessage(this.entity)); + + // Assert + Assert.That(this.viewModel.ComponentViewModels.Count, Is.EqualTo(2)); + } + [SetUp] public void Setup() { @@ -58,6 +107,8 @@ public void Setup() Tag = "Tag", }); - this.viewModel = new EntityInspectorViewModel(this.entity); + this.messenger = WeakReferenceMessenger.Default; + + this.viewModel = new EntityInspectorViewModel(this.messenger, this.entity); } } diff --git a/FinalEngine.Tests/Editor/ViewModels/Inspectors/Mocks/EntityComponentBoolean.cs b/FinalEngine.Tests/Editor/ViewModels/Inspectors/Mocks/EntityComponentBoolean.cs index db9707df..f1ec56a6 100644 --- a/FinalEngine.Tests/Editor/ViewModels/Inspectors/Mocks/EntityComponentBoolean.cs +++ b/FinalEngine.Tests/Editor/ViewModels/Inspectors/Mocks/EntityComponentBoolean.cs @@ -6,7 +6,7 @@ namespace FinalEngine.Tests.Editor.ViewModels.Inspectors.Mocks; using FinalEngine.ECS; -public sealed class EntityComponenBoolean : IEntityComponent +public sealed class EntityComponentBoolean : IEntityComponent { public bool Test { get; set; } } diff --git a/FinalEngine.Tests/Editor/ViewModels/Inspectors/PropertiesToolViewModelTests.cs b/FinalEngine.Tests/Editor/ViewModels/Inspectors/PropertiesToolViewModelTests.cs index 840dbbdc..739f54f0 100644 --- a/FinalEngine.Tests/Editor/ViewModels/Inspectors/PropertiesToolViewModelTests.cs +++ b/FinalEngine.Tests/Editor/ViewModels/Inspectors/PropertiesToolViewModelTests.cs @@ -1,5 +1,5 @@ // -// Copyright (c) Software Antics. All rights reserved. +// Copyright (c) Software Antics. All rights reserved. // namespace FinalEngine.Tests.Editor.ViewModels.Inspectors; @@ -22,7 +22,7 @@ public sealed class PropertiesToolViewModelTests private PropertiesToolViewModel viewModel; [Test] - public void COnstructorShouldRegisterEntityDeletedMessageWhenInvoked() + public void ConstructorShouldRegisterEntityDeletedMessageWhenInvoked() { // Assert Assert.That(WeakReferenceMessenger.Default.IsRegistered(this.viewModel), Is.True); @@ -82,7 +82,7 @@ public void ConstructorShouldThrowArgumentNullExceptionWhenMessengerIsNull() } [Test] - public void MessengerPubilshShouldSetCurrentViewToEntityInspectorViewModelWhenInvoked() + public void MessengerPublishShouldSetCurrentViewToEntityInspectorViewModelWhenInvoked() { // Arrange var entity = new Entity(); @@ -96,7 +96,7 @@ public void MessengerPubilshShouldSetCurrentViewToEntityInspectorViewModelWhenIn } [Test] - public void MessengerPubilshShouldSetTitleToEntityInspectorWhenInvoked() + public void MessengerPublishShouldSetTitleToEntityInspectorWhenInvoked() { // Arrange var entity = new Entity(); diff --git a/FinalEngine.Tests/FinalEngine.Tests.csproj b/FinalEngine.Tests/FinalEngine.Tests.csproj index 58a75698..46633ca7 100644 --- a/FinalEngine.Tests/FinalEngine.Tests.csproj +++ b/FinalEngine.Tests/FinalEngine.Tests.csproj @@ -7,7 +7,6 @@ All false x64 - true diff --git a/FinalEngine.Tests/Rendering/OpenGL/OpenGLRenderContextTests.cs b/FinalEngine.Tests/Rendering/OpenGL/OpenGLRenderContextTests.cs index 495c8d62..e057a52d 100644 --- a/FinalEngine.Tests/Rendering/OpenGL/OpenGLRenderContextTests.cs +++ b/FinalEngine.Tests/Rendering/OpenGL/OpenGLRenderContextTests.cs @@ -69,19 +69,6 @@ public void ConstructorShouldThrowArgumentNullExceptionWhenInvokerIsNull() }); } - [Test] - public void DisposeShouldNotExecuteWhenAlreadyDisposed() - { - // Arrange - this.renderContext.Dispose(); - - // Act - this.renderContext.Dispose(); - - // Assert - this.invoker.Verify(x => x.DeleteVertexArray(VertexArrayID), Times.Once); - } - [SetUp] public void Setup() { @@ -109,16 +96,6 @@ public void SwapBuffersShouldInvokeSwapBuffersWhenContextIsCurrent() this.graphicsContext.Verify(x => x.SwapBuffers(), Times.Once); } - [Test] - public void SwapBuffersShouldThrowObjectDisposedExceptionWhenDisposed() - { - // Arrange - this.renderContext.Dispose(); - - // Act and assert - Assert.Throws(this.renderContext.SwapBuffers); - } - [Test] public void SwapBuffersShouldThrowRenderContextExceptionWhenContextIsNotCurrent() { @@ -128,10 +105,4 @@ public void SwapBuffersShouldThrowRenderContextExceptionWhenContextIsNotCurrent( // Act and assert Assert.Throws(this.renderContext.SwapBuffers); } - - [TearDown] - public void TearDown() - { - this.renderContext.Dispose(); - } } diff --git a/FinalEngine.sln b/FinalEngine.sln index 9619029a..d9a112ee 100644 --- a/FinalEngine.sln +++ b/FinalEngine.sln @@ -6,7 +6,6 @@ MinimumVisualStudioVersion = 15.0.26124.0 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{14909D16-2584-4EB6-8656-7DA0C1DD540F}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig - CodeReview.md = CodeReview.md SharedAssemblyInfo.cs = SharedAssemblyInfo.cs EndProjectSection EndProject diff --git a/test.txt b/test.txt deleted file mode 100644 index 8b137891..00000000 --- a/test.txt +++ /dev/null @@ -1 +0,0 @@ -