瀏覽代碼

Fixes #3097. Now that View is IDisposable, it should expose a Disposing event. (#3104)

BDisp 1 年之前
父節點
當前提交
0b7ca7bf44
共有 2 個文件被更改,包括 131 次插入9 次删除
  1. 6 0
      Terminal.Gui/Input/Responder.cs
  2. 125 9
      UnitTests/Input/ResponderTests.cs

+ 6 - 0
Terminal.Gui/Input/Responder.cs

@@ -47,6 +47,11 @@ public class Responder : IDisposable {
 	}
 #endif
 
+	/// <summary>
+	/// Event raised when <see cref="Dispose()"/> has been called to signal that this object is being disposed.
+	/// </summary>
+	public event EventHandler Disposing;
+
 	/// <summary>
 	/// Gets or sets a value indicating whether this <see cref="Responder"/> can focus.
 	/// </summary>
@@ -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

+ 125 - 9
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);
+	}
 }