mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 15:57:56 +01:00
Fixes #4177: View.GetAttributeForRole now defers to SuperView for proper attribute hierarchy (#4292)
* Initial plan * Fix GetAttributeForRole to defer to SuperView when no explicit scheme Co-authored-by: tig <585482+tig@users.noreply.github.com> * Add test for Adornment attribute resolution Co-authored-by: tig <585482+tig@users.noreply.github.com> * Fix: Also check SchemeName when deferring to SuperView in GetAttributeForRole Co-authored-by: tig <585482+tig@users.noreply.github.com> * Add test for StatusBar/Bar not deferring when SchemeName is set Co-authored-by: tig <585482+tig@users.noreply.github.com> * Add comprehensive low-level tests for GetAttributeForRole hierarchy Co-authored-by: tig <585482+tig@users.noreply.github.com> * Update AGENTS.md with PR branch pull instructions Co-authored-by: tig <585482+tig@users.noreply.github.com> --------- 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:
@@ -269,4 +269,269 @@ public class SchemeTests
|
||||
view.Dispose ();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAttributeForRole_SubView_DefersToSuperView_WhenNoExplicitScheme ()
|
||||
{
|
||||
var parentView = new View { SchemeName = "Base" };
|
||||
var childView = new View ();
|
||||
parentView.Add (childView);
|
||||
|
||||
// Parent customizes attribute resolution
|
||||
var customAttribute = new Attribute (Color.BrightMagenta, Color.BrightGreen);
|
||||
parentView.GettingAttributeForRole += (sender, args) =>
|
||||
{
|
||||
if (args.Role == VisualRole.Normal)
|
||||
{
|
||||
args.Result = customAttribute;
|
||||
args.Handled = true;
|
||||
}
|
||||
};
|
||||
|
||||
// Child without explicit scheme should get customized attribute from parent
|
||||
Assert.Equal (customAttribute, childView.GetAttributeForRole (VisualRole.Normal));
|
||||
|
||||
childView.Dispose ();
|
||||
parentView.Dispose ();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAttributeForRole_SubView_UsesOwnScheme_WhenExplicitlySet ()
|
||||
{
|
||||
var parentView = new View { SchemeName = "Base" };
|
||||
var childView = new View ();
|
||||
parentView.Add (childView);
|
||||
|
||||
// Set explicit scheme on child
|
||||
var childScheme = SchemeManager.GetHardCodedSchemes ()? ["Dialog"];
|
||||
childView.SetScheme (childScheme);
|
||||
|
||||
// Parent customizes attribute resolution
|
||||
var customAttribute = new Attribute (Color.BrightMagenta, Color.BrightGreen);
|
||||
parentView.GettingAttributeForRole += (sender, args) =>
|
||||
{
|
||||
if (args.Role == VisualRole.Normal)
|
||||
{
|
||||
args.Result = customAttribute;
|
||||
args.Handled = true;
|
||||
}
|
||||
};
|
||||
|
||||
// Child with explicit scheme should NOT get customized attribute from parent
|
||||
Assert.NotEqual (customAttribute, childView.GetAttributeForRole (VisualRole.Normal));
|
||||
Assert.Equal (childScheme!.Normal, childView.GetAttributeForRole (VisualRole.Normal));
|
||||
|
||||
childView.Dispose ();
|
||||
parentView.Dispose ();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAttributeForRole_Adornment_UsesParentScheme ()
|
||||
{
|
||||
// Border (an Adornment) doesn't have a SuperView but should use its Parent's scheme
|
||||
var view = new View { SchemeName = "Dialog" };
|
||||
var border = view.Border!;
|
||||
|
||||
Assert.NotNull (border);
|
||||
Assert.Null (border.SuperView); // Adornments don't have SuperView
|
||||
Assert.NotNull (border.Parent);
|
||||
|
||||
var dialogScheme = SchemeManager.GetHardCodedSchemes ()? ["Dialog"];
|
||||
|
||||
// Border should use its Parent's scheme, not Base
|
||||
Assert.Equal (dialogScheme!.Normal, border.GetAttributeForRole (VisualRole.Normal));
|
||||
|
||||
view.Dispose ();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAttributeForRole_SubView_UsesSchemeName_WhenSet ()
|
||||
{
|
||||
var parentView = new View { SchemeName = "Base" };
|
||||
var childView = new View ();
|
||||
parentView.Add (childView);
|
||||
|
||||
// Set SchemeName on child (not explicit scheme)
|
||||
childView.SchemeName = "Dialog";
|
||||
|
||||
// Parent customizes attribute resolution
|
||||
var customAttribute = new Attribute (Color.BrightMagenta, Color.BrightGreen);
|
||||
parentView.GettingAttributeForRole += (sender, args) =>
|
||||
{
|
||||
if (args.Role == VisualRole.Normal)
|
||||
{
|
||||
args.Result = customAttribute;
|
||||
args.Handled = true;
|
||||
}
|
||||
};
|
||||
|
||||
// Child with SchemeName should NOT get customized attribute from parent
|
||||
// It should use the Dialog scheme instead
|
||||
var dialogScheme = SchemeManager.GetHardCodedSchemes ()? ["Dialog"];
|
||||
Assert.NotEqual (customAttribute, childView.GetAttributeForRole (VisualRole.Normal));
|
||||
Assert.Equal (dialogScheme!.Normal, childView.GetAttributeForRole (VisualRole.Normal));
|
||||
|
||||
childView.Dispose ();
|
||||
parentView.Dispose ();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAttributeForRole_NestedHierarchy_DefersCorrectly ()
|
||||
{
|
||||
// Test: grandchild without explicit scheme defers through parent to grandparent
|
||||
// Would fail without the SuperView deferral fix (commit 154ac15)
|
||||
|
||||
var grandparentView = new View { SchemeName = "Base" };
|
||||
var parentView = new View (); // No scheme or SchemeName
|
||||
var childView = new View (); // No scheme or SchemeName
|
||||
|
||||
grandparentView.Add (parentView);
|
||||
parentView.Add (childView);
|
||||
|
||||
// Grandparent customizes attributes
|
||||
var customAttribute = new Attribute (Color.BrightYellow, Color.BrightBlue);
|
||||
grandparentView.GettingAttributeForRole += (sender, args) =>
|
||||
{
|
||||
if (args.Role == VisualRole.Normal)
|
||||
{
|
||||
args.Result = customAttribute;
|
||||
args.Handled = true;
|
||||
}
|
||||
};
|
||||
|
||||
// Child should get attribute from grandparent through parent
|
||||
Assert.Equal (customAttribute, childView.GetAttributeForRole (VisualRole.Normal));
|
||||
|
||||
// Parent should also get attribute from grandparent
|
||||
Assert.Equal (customAttribute, parentView.GetAttributeForRole (VisualRole.Normal));
|
||||
|
||||
childView.Dispose ();
|
||||
parentView.Dispose ();
|
||||
grandparentView.Dispose ();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAttributeForRole_ParentWithSchemeNameBreaksChain ()
|
||||
{
|
||||
// Test: parent with SchemeName stops deferral chain
|
||||
// Would fail without the SchemeName check (commit 866e002)
|
||||
|
||||
var grandparentView = new View { SchemeName = "Base" };
|
||||
var parentView = new View { SchemeName = "Dialog" }; // Sets SchemeName
|
||||
var childView = new View (); // No scheme or SchemeName
|
||||
|
||||
grandparentView.Add (parentView);
|
||||
parentView.Add (childView);
|
||||
|
||||
// Grandparent customizes attributes
|
||||
var customAttribute = new Attribute (Color.BrightYellow, Color.BrightBlue);
|
||||
grandparentView.GettingAttributeForRole += (sender, args) =>
|
||||
{
|
||||
if (args.Role == VisualRole.Normal)
|
||||
{
|
||||
args.Result = customAttribute;
|
||||
args.Handled = true;
|
||||
}
|
||||
};
|
||||
|
||||
// Parent should NOT get grandparent's customization (it has SchemeName)
|
||||
var dialogScheme = SchemeManager.GetHardCodedSchemes ()? ["Dialog"];
|
||||
Assert.NotEqual (customAttribute, parentView.GetAttributeForRole (VisualRole.Normal));
|
||||
Assert.Equal (dialogScheme!.Normal, parentView.GetAttributeForRole (VisualRole.Normal));
|
||||
|
||||
// Child should get parent's Dialog scheme (defers to parent, parent uses Dialog scheme)
|
||||
Assert.Equal (dialogScheme!.Normal, childView.GetAttributeForRole (VisualRole.Normal));
|
||||
|
||||
childView.Dispose ();
|
||||
parentView.Dispose ();
|
||||
grandparentView.Dispose ();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAttributeForRole_OnGettingAttributeForRole_TakesPrecedence ()
|
||||
{
|
||||
// Test: view's own OnGettingAttributeForRole takes precedence over parent
|
||||
// This should work with or without the fix, but validates precedence
|
||||
|
||||
var parentView = new View { SchemeName = "Base" };
|
||||
var childView = new TestViewWithAttributeOverride ();
|
||||
parentView.Add (childView);
|
||||
|
||||
// Parent customizes attributes
|
||||
var parentAttribute = new Attribute (Color.BrightYellow, Color.BrightBlue);
|
||||
parentView.GettingAttributeForRole += (sender, args) =>
|
||||
{
|
||||
if (args.Role == VisualRole.Normal)
|
||||
{
|
||||
args.Result = parentAttribute;
|
||||
args.Handled = true;
|
||||
}
|
||||
};
|
||||
|
||||
// Child's own override should take precedence
|
||||
var childOverrideAttribute = new Attribute (Color.BrightRed, Color.BrightCyan);
|
||||
childView.OverrideAttribute = childOverrideAttribute;
|
||||
|
||||
Assert.Equal (childOverrideAttribute, childView.GetAttributeForRole (VisualRole.Normal));
|
||||
|
||||
childView.Dispose ();
|
||||
parentView.Dispose ();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAttributeForRole_MultipleRoles_DeferCorrectly ()
|
||||
{
|
||||
// Test: multiple VisualRoles all defer correctly
|
||||
// Would fail without the SuperView deferral fix for any role
|
||||
|
||||
var parentView = new View { SchemeName = "Base" };
|
||||
var childView = new View ();
|
||||
parentView.Add (childView);
|
||||
|
||||
var normalAttr = new Attribute (Color.Red, Color.Blue);
|
||||
var focusAttr = new Attribute (Color.Green, Color.Yellow);
|
||||
var hotNormalAttr = new Attribute (Color.Magenta, Color.Cyan);
|
||||
|
||||
parentView.GettingAttributeForRole += (sender, args) =>
|
||||
{
|
||||
switch (args.Role)
|
||||
{
|
||||
case VisualRole.Normal:
|
||||
args.Result = normalAttr;
|
||||
args.Handled = true;
|
||||
break;
|
||||
case VisualRole.Focus:
|
||||
args.Result = focusAttr;
|
||||
args.Handled = true;
|
||||
break;
|
||||
case VisualRole.HotNormal:
|
||||
args.Result = hotNormalAttr;
|
||||
args.Handled = true;
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// All roles should defer to parent
|
||||
Assert.Equal (normalAttr, childView.GetAttributeForRole (VisualRole.Normal));
|
||||
Assert.Equal (focusAttr, childView.GetAttributeForRole (VisualRole.Focus));
|
||||
Assert.Equal (hotNormalAttr, childView.GetAttributeForRole (VisualRole.HotNormal));
|
||||
|
||||
childView.Dispose ();
|
||||
parentView.Dispose ();
|
||||
}
|
||||
|
||||
private class TestViewWithAttributeOverride : View
|
||||
{
|
||||
public Attribute? OverrideAttribute { get; set; }
|
||||
|
||||
protected override bool OnGettingAttributeForRole (in VisualRole role, ref Attribute currentAttribute)
|
||||
{
|
||||
if (OverrideAttribute.HasValue && role == VisualRole.Normal)
|
||||
{
|
||||
currentAttribute = OverrideAttribute.Value;
|
||||
return true;
|
||||
}
|
||||
return base.OnGettingAttributeForRole (role, ref currentAttribute);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user