mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 15:57:56 +01:00
* Initial plan * Add SuperViewChanging event infrastructure and tests Co-authored-by: tig <585482+tig@users.noreply.github.com> * Refactor SuperViewChanging to follow Cancellable Work Pattern with cancellation support Co-authored-by: tig <585482+tig@users.noreply.github.com> * Remove SuperViewChangingEventArgs class and use CancelEventArgs<View?> directly Co-authored-by: tig <585482+tig@users.noreply.github.com> * Simplified SuperViewChanged * Refactor SuperView change to use CWP and event args Refactored the handling of SuperView changes in the View hierarchy to use the Cancellable Work Pattern (CWP) and standardized event argument types. The SuperView property is now settable only via an internal SetSuperView method, which leverages CWP for property changes and cancellation. - Updated OnSuperViewChanging to accept ValueChangingEventArgs<View?>. - SuperViewChanging event now uses EventHandler<ValueChangingEventArgs<View?>>; cancellation is via e.Handled = true. - OnSuperViewChanged and SuperViewChanged now use ValueChangedEventArgs<View?>. - All SuperView assignments in Add, Remove, and RemoveAll now use SetSuperView, which returns a bool for cancellation. - CWPPropertyHelper.ChangeProperty now accepts a sender parameter, passed to event handlers. - All property changes in View now pass this as sender to ChangeProperty. - Updated event handler signatures and overrides in MenuBar, StatusBar, TextField, and TextView. - Updated unit tests to use new event args and cancellation pattern. - Minor code cleanups and improved comments. These changes modernize and standardize property change handling, improving API consistency, extensibility, and testability. * Refactor subview removal and event arg types Removed redundant index check when removing subviews in View.cs, simplifying the cleanup loop. Updated TextField.OnSuperViewChanged to use a non-nullable ValueChangedEventArgs<View> parameter, enforcing stricter type safety. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: tig <585482+tig@users.noreply.github.com> Co-authored-by: Tig <tig@users.noreply.github.com>
This commit is contained in:
@@ -18,7 +18,7 @@ public class SubViewTests
|
||||
};
|
||||
sub.SuperViewChanged += (s, e) =>
|
||||
{
|
||||
if (e.SuperView is { })
|
||||
if (sub.SuperView is { })
|
||||
{
|
||||
subRaisedCount++;
|
||||
}
|
||||
@@ -46,7 +46,7 @@ public class SubViewTests
|
||||
};
|
||||
sub.SuperViewChanged += (s, e) =>
|
||||
{
|
||||
if (e.SuperView is null)
|
||||
if (sub.SuperView is null)
|
||||
{
|
||||
subRaisedCount++;
|
||||
}
|
||||
@@ -393,23 +393,23 @@ public class SubViewTests
|
||||
var superView = new View ();
|
||||
|
||||
int superViewChangedCount = 0;
|
||||
//int superViewChangingCount = 0;
|
||||
int superViewChangingCount = 0;
|
||||
|
||||
view.SuperViewChanged += (s, e) =>
|
||||
{
|
||||
superViewChangedCount++;
|
||||
};
|
||||
|
||||
//view.SuperViewChanging += (s, e) =>
|
||||
//{
|
||||
// superViewChangingCount++;
|
||||
//};
|
||||
view.SuperViewChanging += (s, e) =>
|
||||
{
|
||||
superViewChangingCount++;
|
||||
};
|
||||
|
||||
// Act
|
||||
superView.Add (view);
|
||||
|
||||
// Assert
|
||||
//Assert.Equal (1, superViewChangingCount);
|
||||
Assert.Equal (1, superViewChangingCount);
|
||||
Assert.Equal (1, superViewChangedCount);
|
||||
|
||||
}
|
||||
@@ -692,4 +692,295 @@ public class SubViewTests
|
||||
Assert.Single (removedViews);
|
||||
Assert.Contains (subView2, removedViews);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SuperViewChanging_Raised_Before_SuperViewChanged ()
|
||||
{
|
||||
// Arrange
|
||||
var superView = new View ();
|
||||
var subView = new View ();
|
||||
|
||||
var events = new List<string> ();
|
||||
|
||||
subView.SuperViewChanging += (s, e) => { events.Add ("SuperViewChanging"); };
|
||||
|
||||
subView.SuperViewChanged += (s, e) => { events.Add ("SuperViewChanged"); };
|
||||
|
||||
// Act
|
||||
superView.Add (subView);
|
||||
|
||||
// Assert
|
||||
Assert.Equal (2, events.Count);
|
||||
Assert.Equal ("SuperViewChanging", events [0]);
|
||||
Assert.Equal ("SuperViewChanged", events [1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SuperViewChanging_Provides_OldSuperView_On_Add ()
|
||||
{
|
||||
// Arrange
|
||||
var superView = new View ();
|
||||
var subView = new View ();
|
||||
|
||||
View? currentValueInEvent = new View (); // Set to non-null to ensure it gets updated
|
||||
View? newValueInEvent = null;
|
||||
|
||||
subView.SuperViewChanging += (s, e) =>
|
||||
{
|
||||
currentValueInEvent = e.CurrentValue;
|
||||
newValueInEvent = e.NewValue;
|
||||
};
|
||||
|
||||
// Act
|
||||
superView.Add (subView);
|
||||
|
||||
// Assert
|
||||
Assert.Null (currentValueInEvent); // Was null before add
|
||||
Assert.Equal (superView, newValueInEvent); // Will be superView after add
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SuperViewChanging_Provides_OldSuperView_On_Remove ()
|
||||
{
|
||||
// Arrange
|
||||
var superView = new View ();
|
||||
var subView = new View ();
|
||||
|
||||
superView.Add (subView);
|
||||
|
||||
View? currentValueInEvent = null;
|
||||
View? newValueInEvent = new View (); // Set to non-null to ensure it gets updated
|
||||
|
||||
subView.SuperViewChanging += (s, e) =>
|
||||
{
|
||||
currentValueInEvent = e.CurrentValue;
|
||||
newValueInEvent = e.NewValue;
|
||||
};
|
||||
|
||||
// Act
|
||||
superView.Remove (subView);
|
||||
|
||||
// Assert
|
||||
Assert.Equal (superView, currentValueInEvent); // Was superView before remove
|
||||
Assert.Null (newValueInEvent); // Will be null after remove
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SuperViewChanging_Allows_Access_To_App_Before_Remove ()
|
||||
{
|
||||
// Arrange
|
||||
using IApplication app = Application.Create ();
|
||||
var runnable = new Runnable<bool> ();
|
||||
var subView = new View ();
|
||||
|
||||
runnable.Add (subView);
|
||||
SessionToken? token = app.Begin (runnable);
|
||||
|
||||
IApplication? appInEvent = null;
|
||||
|
||||
subView.SuperViewChanging += (s, e) =>
|
||||
{
|
||||
Assert.NotNull (s);
|
||||
// At this point, SuperView is still set, so App should be accessible
|
||||
appInEvent = (s as View)?.App;
|
||||
};
|
||||
|
||||
|
||||
Assert.NotNull (runnable.App);
|
||||
|
||||
// Act
|
||||
runnable.Remove (subView);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull (appInEvent);
|
||||
Assert.Equal (app, appInEvent);
|
||||
|
||||
app.End (token!);
|
||||
runnable.Dispose ();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnSuperViewChanging_Called_Before_OnSuperViewChanged ()
|
||||
{
|
||||
// Arrange
|
||||
var superView = new View ();
|
||||
var events = new List<string> ();
|
||||
|
||||
var subView = new TestViewWithSuperViewEvents (events);
|
||||
|
||||
// Act
|
||||
superView.Add (subView);
|
||||
|
||||
// Assert
|
||||
Assert.Equal (2, events.Count);
|
||||
Assert.Equal ("OnSuperViewChanging", events [0]);
|
||||
Assert.Equal ("OnSuperViewChanged", events [1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SuperViewChanging_Raised_When_Changing_Between_SuperViews ()
|
||||
{
|
||||
// Arrange
|
||||
var superView1 = new View ();
|
||||
var superView2 = new View ();
|
||||
var subView = new View ();
|
||||
|
||||
superView1.Add (subView);
|
||||
|
||||
View? currentValueInEvent = null;
|
||||
View? newValueInEvent = null;
|
||||
|
||||
subView.SuperViewChanging += (s, e) =>
|
||||
{
|
||||
currentValueInEvent = e.CurrentValue;
|
||||
newValueInEvent = e.NewValue;
|
||||
};
|
||||
|
||||
// Act
|
||||
superView2.Add (subView);
|
||||
|
||||
// Assert
|
||||
Assert.Equal (superView1, currentValueInEvent);
|
||||
Assert.Equal (superView2, newValueInEvent);
|
||||
}
|
||||
|
||||
// Helper class for testing virtual method calls
|
||||
private class TestViewWithSuperViewEvents : View
|
||||
{
|
||||
private readonly List<string> _events;
|
||||
|
||||
public TestViewWithSuperViewEvents (List<string> events) { _events = events; }
|
||||
|
||||
protected override bool OnSuperViewChanging (ValueChangingEventArgs<View?> args)
|
||||
{
|
||||
_events.Add ("OnSuperViewChanging");
|
||||
return base.OnSuperViewChanging (args);
|
||||
}
|
||||
|
||||
protected override void OnSuperViewChanged (ValueChangedEventArgs<View?> args)
|
||||
{
|
||||
_events.Add ("OnSuperViewChanged");
|
||||
base.OnSuperViewChanged (args);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SuperViewChanging_Can_Be_Cancelled_Via_Event ()
|
||||
{
|
||||
// Arrange
|
||||
var superView = new View ();
|
||||
var subView = new View ();
|
||||
|
||||
subView.SuperViewChanging += (s, e) =>
|
||||
{
|
||||
e.Handled = true; // Cancel the change
|
||||
};
|
||||
|
||||
// Act
|
||||
superView.Add (subView);
|
||||
|
||||
// Assert - SuperView should not be set because the change was cancelled
|
||||
Assert.Null (subView.SuperView);
|
||||
Assert.Empty (superView.SubViews);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SuperViewChanging_Can_Be_Cancelled_Via_Virtual_Method ()
|
||||
{
|
||||
// Arrange
|
||||
var superView = new View ();
|
||||
var subView = new TestViewThatCancelsChange ();
|
||||
|
||||
// Act
|
||||
superView.Add (subView);
|
||||
|
||||
// Assert - SuperView should not be set because the change was cancelled
|
||||
Assert.Null (subView.SuperView);
|
||||
Assert.Empty (superView.SubViews);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SuperViewChanging_Virtual_Method_Cancellation_Prevents_Event ()
|
||||
{
|
||||
// Arrange
|
||||
var superView = new View ();
|
||||
var subView = new TestViewThatCancelsChange ();
|
||||
|
||||
var eventRaised = false;
|
||||
subView.SuperViewChanging += (s, e) =>
|
||||
{
|
||||
eventRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
superView.Add (subView);
|
||||
|
||||
// Assert - Event should not be raised because virtual method cancelled first
|
||||
Assert.False (eventRaised);
|
||||
Assert.Null (subView.SuperView);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SuperViewChanging_Cancellation_On_Remove ()
|
||||
{
|
||||
// Arrange
|
||||
var superView = new View ();
|
||||
var subView = new View ();
|
||||
|
||||
superView.Add (subView);
|
||||
Assert.Equal (superView, subView.SuperView);
|
||||
|
||||
subView.SuperViewChanging += (s, e) =>
|
||||
{
|
||||
// Cancel removal if trying to set to null
|
||||
if (e.NewValue is null)
|
||||
{
|
||||
e.Handled = true;
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
superView.Remove (subView);
|
||||
|
||||
// Assert - SuperView should still be set because removal was cancelled
|
||||
Assert.Equal (superView, subView.SuperView);
|
||||
Assert.Single (superView.SubViews);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SuperViewChanging_Cancellation_When_Changing_Between_SuperViews ()
|
||||
{
|
||||
// Arrange
|
||||
var superView1 = new View ();
|
||||
var superView2 = new View ();
|
||||
var subView = new View ();
|
||||
|
||||
superView1.Add (subView);
|
||||
|
||||
subView.SuperViewChanging += (s, e) =>
|
||||
{
|
||||
// Cancel if trying to move to superView2
|
||||
if (e.NewValue == superView2)
|
||||
{
|
||||
e.Handled = true;
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
superView2.Add (subView);
|
||||
|
||||
// Assert - Should still be in superView1 because change was cancelled
|
||||
Assert.Equal (superView1, subView.SuperView);
|
||||
Assert.Single (superView1.SubViews);
|
||||
Assert.Empty (superView2.SubViews);
|
||||
}
|
||||
|
||||
// Helper class for testing cancellation
|
||||
private class TestViewThatCancelsChange : View
|
||||
{
|
||||
protected override bool OnSuperViewChanging (ValueChangingEventArgs<View?> args)
|
||||
{
|
||||
return true; // Always cancel the change
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user