SchemeTests.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537
  1. #nullable enable
  2. using Xunit;
  3. namespace Terminal.Gui.ViewTests;
  4. [Trait ("Category", "View.Scheme")]
  5. public class SchemeTests
  6. {
  7. [Fact]
  8. public void GetScheme_Default_ReturnsBaseScheme ()
  9. {
  10. var view = new View ();
  11. var baseScheme = SchemeManager.GetHardCodedSchemes ()? ["Base"];
  12. Assert.Equal (baseScheme, view.GetScheme ());
  13. view.Dispose ();
  14. }
  15. [Fact]
  16. public void SetScheme_Explicitly_SetsSchemeCorrectly ()
  17. {
  18. var view = new View ();
  19. var dialogScheme = SchemeManager.GetHardCodedSchemes ()? ["Dialog"];
  20. view.SetScheme (dialogScheme);
  21. Assert.True (view.HasScheme);
  22. Assert.Equal (dialogScheme, view.GetScheme ());
  23. view.Dispose ();
  24. }
  25. [Fact]
  26. public void GetScheme_InheritsFromSuperView_WhenNotExplicitlySet ()
  27. {
  28. var superView = new View ();
  29. var subView = new View ();
  30. superView.Add (subView);
  31. var dialogScheme = SchemeManager.GetHardCodedSchemes ()? ["Dialog"];
  32. superView.SetScheme (dialogScheme);
  33. Assert.Equal (dialogScheme, subView.GetScheme ());
  34. Assert.False (subView.HasScheme);
  35. subView.Dispose ();
  36. superView.Dispose ();
  37. }
  38. [Fact]
  39. public void SetSchemeName_OverridesInheritedScheme ()
  40. {
  41. var view = new View ();
  42. view.SchemeName = "Dialog";
  43. var dialogScheme = SchemeManager.GetHardCodedSchemes ()? ["Dialog"];
  44. Assert.Equal (dialogScheme, view.GetScheme ());
  45. view.Dispose ();
  46. }
  47. [Fact]
  48. public void GetAttribute_ReturnsCorrectAttribute_Via_Mock ()
  49. {
  50. var view = new View { SchemeName = "Base" };
  51. view.Driver = new MockConsoleDriver ();
  52. view.Driver.SetAttribute (new Attribute (Color.Red, Color.Green));
  53. // Act
  54. var attribute = view.GetCurrentAttribute ();
  55. // Assert
  56. Assert.Equal (new Attribute (Color.Red, Color.Green), attribute);
  57. }
  58. [Fact]
  59. public void GetAttributeForRole_ReturnsCorrectAttribute ()
  60. {
  61. var view = new View { SchemeName = "Base" };
  62. Assert.Equal (view.GetScheme ().Normal, view.GetAttributeForRole (VisualRole.Normal));
  63. Assert.Equal (view.GetScheme ().HotNormal, view.GetAttributeForRole (VisualRole.HotNormal));
  64. Assert.Equal (view.GetScheme ().Focus, view.GetAttributeForRole (VisualRole.Focus));
  65. Assert.Equal (view.GetScheme ().HotFocus, view.GetAttributeForRole (VisualRole.HotFocus));
  66. Assert.Equal (view.GetScheme ().Disabled, view.GetAttributeForRole (VisualRole.Disabled));
  67. view.Dispose ();
  68. }
  69. [Fact]
  70. public void GetAttributeForRole_DisabledView_ReturnsCorrectAttribute ()
  71. {
  72. var view = new View { SchemeName = "Base" };
  73. view.Enabled = false;
  74. Assert.Equal (view.GetScheme ().Disabled, view.GetAttributeForRole (VisualRole.Normal));
  75. Assert.Equal (view.GetScheme ().Disabled, view.GetAttributeForRole (VisualRole.HotNormal));
  76. view.Dispose ();
  77. }
  78. [Fact]
  79. public void SetAttributeForRole_SetsCorrectAttribute ()
  80. {
  81. var view = new View { SchemeName = "Base" };
  82. view.Driver = new MockConsoleDriver ();
  83. view.Driver.SetAttribute (new Attribute (Color.Red, Color.Green));
  84. var previousAttribute = view.SetAttributeForRole (VisualRole.Focus);
  85. Assert.Equal (view.GetScheme ().Focus, view.GetCurrentAttribute ());
  86. Assert.NotEqual (previousAttribute, view.GetCurrentAttribute ());
  87. view.Dispose ();
  88. }
  89. [Fact]
  90. public void OnGettingScheme_Override_StopsDefaultBehavior ()
  91. {
  92. var view = new CustomView ();
  93. var customScheme = SchemeManager.GetHardCodedSchemes ()? ["Error"];
  94. Assert.Equal (customScheme, view.GetScheme ());
  95. view.Dispose ();
  96. }
  97. [Fact]
  98. public void OnSettingScheme_Override_PreventsSettingScheme ()
  99. {
  100. var view = new CustomView ();
  101. var dialogScheme = SchemeManager.GetHardCodedSchemes ()? ["Dialog"];
  102. view.SetScheme (dialogScheme);
  103. Assert.NotEqual (dialogScheme, view.GetScheme ());
  104. view.Dispose ();
  105. }
  106. [Fact]
  107. public void GettingScheme_Event_CanOverrideScheme ()
  108. {
  109. var view = new View ();
  110. var customScheme = SchemeManager.GetHardCodedSchemes ()? ["Error"]! with { Normal = Attribute.Default };
  111. Assert.NotEqual (Attribute.Default, view.GetScheme ().Normal);
  112. view.GettingScheme += (sender, args) =>
  113. {
  114. args.Result = customScheme;
  115. args.Handled = true;
  116. };
  117. Assert.Equal (customScheme, view.GetScheme ());
  118. Assert.Equal (Attribute.Default, view.GetScheme ().Normal);
  119. view.Dispose ();
  120. }
  121. [Fact]
  122. public void SettingScheme_Event_CanCancelSchemeChange ()
  123. {
  124. var view = new View ();
  125. var dialogScheme = SchemeManager.GetHardCodedSchemes ()? ["Dialog"];
  126. view.SchemeChanging += (sender, args) => args.Handled = true;
  127. view.SetScheme (dialogScheme);
  128. Assert.NotEqual (dialogScheme, view.GetScheme ());
  129. view.Dispose ();
  130. }
  131. [Fact]
  132. public void GetAttributeForRole_Event_CanOverrideAttribute ()
  133. {
  134. var view = new View { SchemeName = "Base" };
  135. var customAttribute = new Attribute (Color.BrightRed, Color.BrightYellow);
  136. view.GettingAttributeForRole += (sender, args) =>
  137. {
  138. if (args.Role == VisualRole.Focus)
  139. {
  140. args.Result = customAttribute;
  141. args.Handled = true;
  142. }
  143. };
  144. Assert.Equal (customAttribute, view.GetAttributeForRole (VisualRole.Focus));
  145. view.Dispose ();
  146. }
  147. [Fact]
  148. public void GetHardCodedSchemes_ReturnsExpectedSchemes ()
  149. {
  150. var schemes = Scheme.GetHardCodedSchemes ();
  151. Assert.NotNull (schemes);
  152. Assert.Contains ("Base", schemes.Keys);
  153. Assert.Contains ("Dialog", schemes.Keys);
  154. Assert.Contains ("Error", schemes.Keys);
  155. Assert.Contains ("Menu", schemes.Keys);
  156. Assert.Contains ("Toplevel", schemes.Keys);
  157. }
  158. [Fact]
  159. public void SchemeName_OverridesSuperViewScheme ()
  160. {
  161. var superView = new View ();
  162. var subView = new View ();
  163. superView.Add (subView);
  164. subView.SchemeName = "Error";
  165. var errorScheme = SchemeManager.GetHardCodedSchemes ()? ["Error"];
  166. Assert.Equal (errorScheme, subView.GetScheme ());
  167. subView.Dispose ();
  168. superView.Dispose ();
  169. }
  170. [Fact]
  171. public void Scheme_DefaultsToBase_WhenNotSet ()
  172. {
  173. var view = new View ();
  174. var baseScheme = SchemeManager.GetHardCodedSchemes ()? ["Base"];
  175. Assert.Equal (baseScheme, view.GetScheme ());
  176. view.Dispose ();
  177. }
  178. [Fact]
  179. public void Scheme_HandlesNullSuperViewGracefully ()
  180. {
  181. var view = new View ();
  182. view.SchemeName = "Dialog";
  183. var dialogScheme = SchemeManager.GetHardCodedSchemes ()? ["Dialog"];
  184. Assert.Equal (dialogScheme, view.GetScheme ());
  185. view.Dispose ();
  186. }
  187. private class CustomView : View
  188. {
  189. protected override bool OnGettingScheme (out Scheme? scheme)
  190. {
  191. scheme = SchemeManager.GetHardCodedSchemes ()? ["Error"];
  192. return true;
  193. }
  194. protected override bool OnSettingScheme (ValueChangingEventArgs<Scheme?> args)
  195. {
  196. return true; // Prevent setting the scheme
  197. }
  198. }
  199. [Fact]
  200. public void View_Resolves_Attributes_From_Scheme ()
  201. {
  202. View view = new Label { SchemeName = "Base" };
  203. foreach (VisualRole role in Enum.GetValues<VisualRole> ())
  204. {
  205. Attribute attr = view.GetAttributeForRole (role);
  206. Assert.NotEqual (default, attr.Foreground); // Defensive: avoid all-defaults
  207. }
  208. view.Dispose ();
  209. }
  210. [Fact]
  211. public void GetAttributeForRole_SubView_DefersToSuperView_WhenNoExplicitScheme ()
  212. {
  213. var parentView = new View { SchemeName = "Base" };
  214. var childView = new View ();
  215. parentView.Add (childView);
  216. // Parent customizes attribute resolution
  217. var customAttribute = new Attribute (Color.BrightMagenta, Color.BrightGreen);
  218. parentView.GettingAttributeForRole += (sender, args) =>
  219. {
  220. if (args.Role == VisualRole.Normal)
  221. {
  222. args.Result = customAttribute;
  223. args.Handled = true;
  224. }
  225. };
  226. // Child without explicit scheme should get customized attribute from parent
  227. Assert.Equal (customAttribute, childView.GetAttributeForRole (VisualRole.Normal));
  228. childView.Dispose ();
  229. parentView.Dispose ();
  230. }
  231. [Fact]
  232. public void GetAttributeForRole_SubView_UsesOwnScheme_WhenExplicitlySet ()
  233. {
  234. var parentView = new View { SchemeName = "Base" };
  235. var childView = new View ();
  236. parentView.Add (childView);
  237. // Set explicit scheme on child
  238. var childScheme = SchemeManager.GetHardCodedSchemes ()? ["Dialog"];
  239. childView.SetScheme (childScheme);
  240. // Parent customizes attribute resolution
  241. var customAttribute = new Attribute (Color.BrightMagenta, Color.BrightGreen);
  242. parentView.GettingAttributeForRole += (sender, args) =>
  243. {
  244. if (args.Role == VisualRole.Normal)
  245. {
  246. args.Result = customAttribute;
  247. args.Handled = true;
  248. }
  249. };
  250. // Child with explicit scheme should NOT get customized attribute from parent
  251. Assert.NotEqual (customAttribute, childView.GetAttributeForRole (VisualRole.Normal));
  252. Assert.Equal (childScheme!.Normal, childView.GetAttributeForRole (VisualRole.Normal));
  253. childView.Dispose ();
  254. parentView.Dispose ();
  255. }
  256. [Fact]
  257. public void GetAttributeForRole_Adornment_UsesParentScheme ()
  258. {
  259. // Border (an Adornment) doesn't have a SuperView but should use its Parent's scheme
  260. var view = new View { SchemeName = "Dialog" };
  261. var border = view.Border!;
  262. Assert.NotNull (border);
  263. Assert.Null (border.SuperView); // Adornments don't have SuperView
  264. Assert.NotNull (border.Parent);
  265. var dialogScheme = SchemeManager.GetHardCodedSchemes ()? ["Dialog"];
  266. // Border should use its Parent's scheme, not Base
  267. Assert.Equal (dialogScheme!.Normal, border.GetAttributeForRole (VisualRole.Normal));
  268. view.Dispose ();
  269. }
  270. [Fact]
  271. public void GetAttributeForRole_SubView_UsesSchemeName_WhenSet ()
  272. {
  273. var parentView = new View { SchemeName = "Base" };
  274. var childView = new View ();
  275. parentView.Add (childView);
  276. // Set SchemeName on child (not explicit scheme)
  277. childView.SchemeName = "Dialog";
  278. // Parent customizes attribute resolution
  279. var customAttribute = new Attribute (Color.BrightMagenta, Color.BrightGreen);
  280. parentView.GettingAttributeForRole += (sender, args) =>
  281. {
  282. if (args.Role == VisualRole.Normal)
  283. {
  284. args.Result = customAttribute;
  285. args.Handled = true;
  286. }
  287. };
  288. // Child with SchemeName should NOT get customized attribute from parent
  289. // It should use the Dialog scheme instead
  290. var dialogScheme = SchemeManager.GetHardCodedSchemes ()? ["Dialog"];
  291. Assert.NotEqual (customAttribute, childView.GetAttributeForRole (VisualRole.Normal));
  292. Assert.Equal (dialogScheme!.Normal, childView.GetAttributeForRole (VisualRole.Normal));
  293. childView.Dispose ();
  294. parentView.Dispose ();
  295. }
  296. [Fact]
  297. public void GetAttributeForRole_NestedHierarchy_DefersCorrectly ()
  298. {
  299. // Test: grandchild without explicit scheme defers through parent to grandparent
  300. // Would fail without the SuperView deferral fix (commit 154ac15)
  301. var grandparentView = new View { SchemeName = "Base" };
  302. var parentView = new View (); // No scheme or SchemeName
  303. var childView = new View (); // No scheme or SchemeName
  304. grandparentView.Add (parentView);
  305. parentView.Add (childView);
  306. // Grandparent customizes attributes
  307. var customAttribute = new Attribute (Color.BrightYellow, Color.BrightBlue);
  308. grandparentView.GettingAttributeForRole += (sender, args) =>
  309. {
  310. if (args.Role == VisualRole.Normal)
  311. {
  312. args.Result = customAttribute;
  313. args.Handled = true;
  314. }
  315. };
  316. // Child should get attribute from grandparent through parent
  317. Assert.Equal (customAttribute, childView.GetAttributeForRole (VisualRole.Normal));
  318. // Parent should also get attribute from grandparent
  319. Assert.Equal (customAttribute, parentView.GetAttributeForRole (VisualRole.Normal));
  320. childView.Dispose ();
  321. parentView.Dispose ();
  322. grandparentView.Dispose ();
  323. }
  324. [Fact]
  325. public void GetAttributeForRole_ParentWithSchemeNameBreaksChain ()
  326. {
  327. // Test: parent with SchemeName stops deferral chain
  328. // Would fail without the SchemeName check (commit 866e002)
  329. var grandparentView = new View { SchemeName = "Base" };
  330. var parentView = new View { SchemeName = "Dialog" }; // Sets SchemeName
  331. var childView = new View (); // No scheme or SchemeName
  332. grandparentView.Add (parentView);
  333. parentView.Add (childView);
  334. // Grandparent customizes attributes
  335. var customAttribute = new Attribute (Color.BrightYellow, Color.BrightBlue);
  336. grandparentView.GettingAttributeForRole += (sender, args) =>
  337. {
  338. if (args.Role == VisualRole.Normal)
  339. {
  340. args.Result = customAttribute;
  341. args.Handled = true;
  342. }
  343. };
  344. // Parent should NOT get grandparent's customization (it has SchemeName)
  345. var dialogScheme = SchemeManager.GetHardCodedSchemes ()? ["Dialog"];
  346. Assert.NotEqual (customAttribute, parentView.GetAttributeForRole (VisualRole.Normal));
  347. Assert.Equal (dialogScheme!.Normal, parentView.GetAttributeForRole (VisualRole.Normal));
  348. // Child should get parent's Dialog scheme (defers to parent, parent uses Dialog scheme)
  349. Assert.Equal (dialogScheme!.Normal, childView.GetAttributeForRole (VisualRole.Normal));
  350. childView.Dispose ();
  351. parentView.Dispose ();
  352. grandparentView.Dispose ();
  353. }
  354. [Fact]
  355. public void GetAttributeForRole_OnGettingAttributeForRole_TakesPrecedence ()
  356. {
  357. // Test: view's own OnGettingAttributeForRole takes precedence over parent
  358. // This should work with or without the fix, but validates precedence
  359. var parentView = new View { SchemeName = "Base" };
  360. var childView = new TestViewWithAttributeOverride ();
  361. parentView.Add (childView);
  362. // Parent customizes attributes
  363. var parentAttribute = new Attribute (Color.BrightYellow, Color.BrightBlue);
  364. parentView.GettingAttributeForRole += (sender, args) =>
  365. {
  366. if (args.Role == VisualRole.Normal)
  367. {
  368. args.Result = parentAttribute;
  369. args.Handled = true;
  370. }
  371. };
  372. // Child's own override should take precedence
  373. var childOverrideAttribute = new Attribute (Color.BrightRed, Color.BrightCyan);
  374. childView.OverrideAttribute = childOverrideAttribute;
  375. Assert.Equal (childOverrideAttribute, childView.GetAttributeForRole (VisualRole.Normal));
  376. childView.Dispose ();
  377. parentView.Dispose ();
  378. }
  379. [Fact]
  380. public void GetAttributeForRole_MultipleRoles_DeferCorrectly ()
  381. {
  382. // Test: multiple VisualRoles all defer correctly
  383. // Would fail without the SuperView deferral fix for any role
  384. var parentView = new View { SchemeName = "Base" };
  385. var childView = new View ();
  386. parentView.Add (childView);
  387. var normalAttr = new Attribute (Color.Red, Color.Blue);
  388. var focusAttr = new Attribute (Color.Green, Color.Yellow);
  389. var hotNormalAttr = new Attribute (Color.Magenta, Color.Cyan);
  390. parentView.GettingAttributeForRole += (sender, args) =>
  391. {
  392. switch (args.Role)
  393. {
  394. case VisualRole.Normal:
  395. args.Result = normalAttr;
  396. args.Handled = true;
  397. break;
  398. case VisualRole.Focus:
  399. args.Result = focusAttr;
  400. args.Handled = true;
  401. break;
  402. case VisualRole.HotNormal:
  403. args.Result = hotNormalAttr;
  404. args.Handled = true;
  405. break;
  406. }
  407. };
  408. // All roles should defer to parent
  409. Assert.Equal (normalAttr, childView.GetAttributeForRole (VisualRole.Normal));
  410. Assert.Equal (focusAttr, childView.GetAttributeForRole (VisualRole.Focus));
  411. Assert.Equal (hotNormalAttr, childView.GetAttributeForRole (VisualRole.HotNormal));
  412. childView.Dispose ();
  413. parentView.Dispose ();
  414. }
  415. private class TestViewWithAttributeOverride : View
  416. {
  417. public Attribute? OverrideAttribute { get; set; }
  418. protected override bool OnGettingAttributeForRole (in VisualRole role, ref Attribute currentAttribute)
  419. {
  420. if (OverrideAttribute.HasValue && role == VisualRole.Normal)
  421. {
  422. currentAttribute = OverrideAttribute.Value;
  423. return true;
  424. }
  425. return base.OnGettingAttributeForRole (role, ref currentAttribute);
  426. }
  427. }
  428. }