From 0b7ca7bf4468ea4f686bc73f2931e3896678edf3 Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 4 Jan 2024 14:13:31 +0000 Subject: [PATCH] Fixes #3097. Now that View is IDisposable, it should expose a Disposing event. (#3104) --- Terminal.Gui/Input/Responder.cs | 6 ++ UnitTests/Input/ResponderTests.cs | 134 ++++++++++++++++++++++++++++-- 2 files changed, 131 insertions(+), 9 deletions(-) diff --git a/Terminal.Gui/Input/Responder.cs b/Terminal.Gui/Input/Responder.cs index bdf8ec1ea..e42f6e764 100644 --- a/Terminal.Gui/Input/Responder.cs +++ b/Terminal.Gui/Input/Responder.cs @@ -47,6 +47,11 @@ public class Responder : IDisposable { } #endif + /// + /// Event raised when has been called to signal that this object is being disposed. + /// + public event EventHandler Disposing; + /// /// Gets or sets a value indicating whether this can focus. /// @@ -186,6 +191,7 @@ public class Responder : IDisposable { public void Dispose () { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Disposing?.Invoke (this, EventArgs.Empty); Dispose (disposing: true); GC.SuppressFinalize (this); #if DEBUG_IDISPOSABLE diff --git a/UnitTests/Input/ResponderTests.cs b/UnitTests/Input/ResponderTests.cs index 0942be6d2..2b01c2cca 100644 --- a/UnitTests/Input/ResponderTests.cs +++ b/UnitTests/Input/ResponderTests.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Xunit; // Alias Console to MockConsole so we don't accidentally use Console @@ -6,7 +7,8 @@ using Console = Terminal.Gui.FakeConsole; namespace Terminal.Gui.InputTests; public class ResponderTests { - [Fact] [TestRespondersDisposed] + [Fact] + [TestRespondersDisposed] public void New_Initializes () { var r = new Responder (); @@ -19,7 +21,8 @@ public class ResponderTests { r.Dispose (); } - [Fact] [TestRespondersDisposed] + [Fact] + [TestRespondersDisposed] public void New_Methods_Return_False () { var r = new View (); @@ -59,7 +62,8 @@ public class ResponderTests { } // Generic lifetime (IDisposable) tests - [Fact] [TestRespondersDisposed] + [Fact] + [TestRespondersDisposed] public void Dispose_Works () { @@ -83,7 +87,8 @@ public class ResponderTests { } } - [Fact] [TestRespondersDisposed] + [Fact] + [TestRespondersDisposed] public void IsOverridden_False_IfNotOverridden () { // MouseEvent IS defined on Responder but NOT overridden @@ -106,17 +111,18 @@ public class ResponderTests { #endif } - [Fact] [TestRespondersDisposed] + [Fact] + [TestRespondersDisposed] public void IsOverridden_True_IfOverridden () { // MouseEvent is defined on Responder IS overriden on ScrollBarView (but not View) Assert.True (Responder.IsOverridden (new ScrollBarView () { Text = "ScrollBarView overrides MouseEvent" }, "MouseEvent")); - //// OnKeyDown is defined on View - //Assert.True (Responder.IsOverridden (new View () { Text = "View overrides OnKeyDown" }, "OnKeyDown")); + // OnKeyDown is defined on View + Assert.False (Responder.IsOverridden (new View () { Text = "View overrides OnKeyDown" }, "OnKeyDown")); - //// OnKeyDown is defined on DerivedView - //Assert.True (Responder.IsOverridden (new DerivedView () { Text = "DerivedView overrides OnKeyDown" }, "OnKeyDown")); + // OnKeyDown is defined on DerivedView + Assert.True (Responder.IsOverridden (new DerivedView () { Text = "DerivedView overrides OnKeyDown" }, "OnKeyDown")); // ScrollBarView overrides both MouseEvent (from Responder) and Redraw (from View) Assert.True (Responder.IsOverridden (new ScrollBarView () { Text = "ScrollBarView overrides MouseEvent" }, "MouseEvent")); @@ -129,4 +135,114 @@ public class ResponderTests { Assert.Empty (Responder.Instances); #endif } + + [Fact] + public void Responder_Not_Notifying_Dispose () + { + var container1 = new View () { Id = "Container1" }; + + var view = new View () { Id = "View" }; + container1.Add (view); + Assert.Equal (container1, view.SuperView); + + Assert.Single (container1.Subviews); + + var container2 = new View () { Id = "Container2" }; + + container2.Add (view); + Assert.Equal (container2, view.SuperView); + Assert.Equal (container1.Subviews.Count, container2.Subviews.Count); + container1.Dispose (); + + Assert.Empty (container1.Subviews); + Assert.NotEmpty (container2.Subviews); + Assert.Single (container2.Subviews); + Assert.Null (view.SuperView); + + // Trying access disposed properties + Assert.True (container2.Subviews [0].WasDisposed); + Assert.False (container2.Subviews [0].CanFocus); + Assert.Null (container2.Subviews [0].Margin); + Assert.Null (container2.Subviews [0].Border); + Assert.Null (container2.Subviews [0].Padding); + Assert.Null (view.SuperView); + + container2.Dispose (); + + Assert.Empty (Responder.Instances); + } + + [Fact] + public void Disposing_Event_Notify_All_Subscribers_On_The_Second_Container () + { + var container1 = new View () { Id = "Container1" }; + + var view = new View () { Id = "View" }; + container1.Add (view); + Assert.Equal (container1, view.SuperView); + Assert.Single (container1.Subviews); + + var container2 = new View () { Id = "Container2" }; + var count = 0; + + view.Disposing += View_Disposing; + container2.Add (view); + Assert.Equal (container2, view.SuperView); + + void View_Disposing (object sender, System.EventArgs e) + { + count++; + Assert.Equal (view, sender); + container2.Remove ((View)sender); + } + + Assert.Equal (container1.Subviews.Count, container2.Subviews.Count); + container1.Dispose (); + + Assert.Empty (container1.Subviews); + Assert.Empty (container2.Subviews); + Assert.Equal (1, count); + Assert.Null (view.SuperView); + + container2.Dispose (); + + Assert.Empty (Responder.Instances); + } + + [Fact] + public void Disposing_Event_Notify_All_Subscribers_On_The_First_Container () + { + var container1 = new View () { Id = "Container1" }; + var count = 0; + + var view = new View () { Id = "View" }; + view.Disposing += View_Disposing; + container1.Add (view); + Assert.Equal (container1, view.SuperView); + + void View_Disposing (object sender, System.EventArgs e) + { + count++; + Assert.Equal (view, sender); + container1.Remove ((View)sender); + } + + Assert.Single (container1.Subviews); + + var container2 = new View () { Id = "Container2" }; + + container2.Add (view); + Assert.Equal (container2, view.SuperView); + Assert.Equal (container1.Subviews.Count, container2.Subviews.Count); + container2.Dispose (); + + Assert.Empty (container1.Subviews); + Assert.Empty (container2.Subviews); + Assert.Equal (1, count); + Assert.Null (view.SuperView); + + container1.Dispose (); + + Assert.Empty (Responder.Instances); + } } \ No newline at end of file