SchemeTests.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  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 GetAttributeForRole_SubView_DefersToSuperView_WhenNoExplicitScheme ()
  201. {
  202. var parentView = new View { SchemeName = "Base" };
  203. var childView = new View ();
  204. parentView.Add (childView);
  205. // Parent customizes attribute resolution
  206. var customAttribute = new Attribute (Color.BrightMagenta, Color.BrightGreen);
  207. parentView.GettingAttributeForRole += (sender, args) =>
  208. {
  209. if (args.Role == VisualRole.Normal)
  210. {
  211. args.Result = customAttribute;
  212. args.Handled = true;
  213. }
  214. };
  215. // Child without explicit scheme should get customized attribute from parent
  216. Assert.Equal (customAttribute, childView.GetAttributeForRole (VisualRole.Normal));
  217. childView.Dispose ();
  218. parentView.Dispose ();
  219. }
  220. [Fact]
  221. public void GetAttributeForRole_SubView_UsesOwnScheme_WhenExplicitlySet ()
  222. {
  223. var parentView = new View { SchemeName = "Base" };
  224. var childView = new View ();
  225. parentView.Add (childView);
  226. // Set explicit scheme on child
  227. var childScheme = SchemeManager.GetHardCodedSchemes ()? ["Dialog"];
  228. childView.SetScheme (childScheme);
  229. // Parent customizes attribute resolution
  230. var customAttribute = new Attribute (Color.BrightMagenta, Color.BrightGreen);
  231. parentView.GettingAttributeForRole += (sender, args) =>
  232. {
  233. if (args.Role == VisualRole.Normal)
  234. {
  235. args.Result = customAttribute;
  236. args.Handled = true;
  237. }
  238. };
  239. // Child with explicit scheme should NOT get customized attribute from parent
  240. Assert.NotEqual (customAttribute, childView.GetAttributeForRole (VisualRole.Normal));
  241. Assert.Equal (childScheme!.Normal, childView.GetAttributeForRole (VisualRole.Normal));
  242. childView.Dispose ();
  243. parentView.Dispose ();
  244. }
  245. [Fact]
  246. public void GetAttributeForRole_Adornment_UsesParentScheme ()
  247. {
  248. // Border (an Adornment) doesn't have a SuperView but should use its Parent's scheme
  249. var view = new View { SchemeName = "Dialog" };
  250. var border = view.Border!;
  251. Assert.NotNull (border);
  252. Assert.Null (border.SuperView); // Adornments don't have SuperView
  253. Assert.NotNull (border.Parent);
  254. var dialogScheme = SchemeManager.GetHardCodedSchemes ()? ["Dialog"];
  255. // Border should use its Parent's scheme, not Base
  256. Assert.Equal (dialogScheme!.Normal, border.GetAttributeForRole (VisualRole.Normal));
  257. view.Dispose ();
  258. }
  259. [Fact]
  260. public void GetAttributeForRole_SubView_UsesSchemeName_WhenSet ()
  261. {
  262. var parentView = new View { SchemeName = "Base" };
  263. var childView = new View ();
  264. parentView.Add (childView);
  265. // Set SchemeName on child (not explicit scheme)
  266. childView.SchemeName = "Dialog";
  267. // Parent customizes attribute resolution
  268. var customAttribute = new Attribute (Color.BrightMagenta, Color.BrightGreen);
  269. parentView.GettingAttributeForRole += (sender, args) =>
  270. {
  271. if (args.Role == VisualRole.Normal)
  272. {
  273. args.Result = customAttribute;
  274. args.Handled = true;
  275. }
  276. };
  277. // Child with SchemeName should NOT get customized attribute from parent
  278. // It should use the Dialog scheme instead
  279. var dialogScheme = SchemeManager.GetHardCodedSchemes ()? ["Dialog"];
  280. Assert.NotEqual (customAttribute, childView.GetAttributeForRole (VisualRole.Normal));
  281. Assert.Equal (dialogScheme!.Normal, childView.GetAttributeForRole (VisualRole.Normal));
  282. childView.Dispose ();
  283. parentView.Dispose ();
  284. }
  285. [Fact]
  286. public void GetAttributeForRole_NestedHierarchy_DefersCorrectly ()
  287. {
  288. // Test: grandchild without explicit scheme defers through parent to grandparent
  289. // Would fail without the SuperView deferral fix (commit 154ac15)
  290. var grandparentView = new View { SchemeName = "Base" };
  291. var parentView = new View (); // No scheme or SchemeName
  292. var childView = new View (); // No scheme or SchemeName
  293. grandparentView.Add (parentView);
  294. parentView.Add (childView);
  295. // Grandparent customizes attributes
  296. var customAttribute = new Attribute (Color.BrightYellow, Color.BrightBlue);
  297. grandparentView.GettingAttributeForRole += (sender, args) =>
  298. {
  299. if (args.Role == VisualRole.Normal)
  300. {
  301. args.Result = customAttribute;
  302. args.Handled = true;
  303. }
  304. };
  305. // Child should get attribute from grandparent through parent
  306. Assert.Equal (customAttribute, childView.GetAttributeForRole (VisualRole.Normal));
  307. // Parent should also get attribute from grandparent
  308. Assert.Equal (customAttribute, parentView.GetAttributeForRole (VisualRole.Normal));
  309. childView.Dispose ();
  310. parentView.Dispose ();
  311. grandparentView.Dispose ();
  312. }
  313. [Fact]
  314. public void GetAttributeForRole_ParentWithSchemeNameBreaksChain ()
  315. {
  316. // Test: parent with SchemeName stops deferral chain
  317. // Would fail without the SchemeName check (commit 866e002)
  318. var grandparentView = new View { SchemeName = "Base" };
  319. var parentView = new View { SchemeName = "Dialog" }; // Sets SchemeName
  320. var childView = new View (); // No scheme or SchemeName
  321. grandparentView.Add (parentView);
  322. parentView.Add (childView);
  323. // Grandparent customizes attributes
  324. var customAttribute = new Attribute (Color.BrightYellow, Color.BrightBlue);
  325. grandparentView.GettingAttributeForRole += (sender, args) =>
  326. {
  327. if (args.Role == VisualRole.Normal)
  328. {
  329. args.Result = customAttribute;
  330. args.Handled = true;
  331. }
  332. };
  333. // Parent should NOT get grandparent's customization (it has SchemeName)
  334. var dialogScheme = SchemeManager.GetHardCodedSchemes ()? ["Dialog"];
  335. Assert.NotEqual (customAttribute, parentView.GetAttributeForRole (VisualRole.Normal));
  336. Assert.Equal (dialogScheme!.Normal, parentView.GetAttributeForRole (VisualRole.Normal));
  337. // Child should get parent's Dialog scheme (defers to parent, parent uses Dialog scheme)
  338. Assert.Equal (dialogScheme!.Normal, childView.GetAttributeForRole (VisualRole.Normal));
  339. childView.Dispose ();
  340. parentView.Dispose ();
  341. grandparentView.Dispose ();
  342. }
  343. [Fact]
  344. public void GetAttributeForRole_OnGettingAttributeForRole_TakesPrecedence ()
  345. {
  346. // Test: view's own OnGettingAttributeForRole takes precedence over parent
  347. // This should work with or without the fix, but validates precedence
  348. var parentView = new View { SchemeName = "Base" };
  349. var childView = new TestViewWithAttributeOverride ();
  350. parentView.Add (childView);
  351. // Parent customizes attributes
  352. var parentAttribute = new Attribute (Color.BrightYellow, Color.BrightBlue);
  353. parentView.GettingAttributeForRole += (sender, args) =>
  354. {
  355. if (args.Role == VisualRole.Normal)
  356. {
  357. args.Result = parentAttribute;
  358. args.Handled = true;
  359. }
  360. };
  361. // Child's own override should take precedence
  362. var childOverrideAttribute = new Attribute (Color.BrightRed, Color.BrightCyan);
  363. childView.OverrideAttribute = childOverrideAttribute;
  364. Assert.Equal (childOverrideAttribute, childView.GetAttributeForRole (VisualRole.Normal));
  365. childView.Dispose ();
  366. parentView.Dispose ();
  367. }
  368. [Fact]
  369. public void GetAttributeForRole_MultipleRoles_DeferCorrectly ()
  370. {
  371. // Test: multiple VisualRoles all defer correctly
  372. // Would fail without the SuperView deferral fix for any role
  373. var parentView = new View { SchemeName = "Base" };
  374. var childView = new View ();
  375. parentView.Add (childView);
  376. var normalAttr = new Attribute (Color.Red, Color.Blue);
  377. var focusAttr = new Attribute (Color.Green, Color.Yellow);
  378. var hotNormalAttr = new Attribute (Color.Magenta, Color.Cyan);
  379. parentView.GettingAttributeForRole += (sender, args) =>
  380. {
  381. switch (args.Role)
  382. {
  383. case VisualRole.Normal:
  384. args.Result = normalAttr;
  385. args.Handled = true;
  386. break;
  387. case VisualRole.Focus:
  388. args.Result = focusAttr;
  389. args.Handled = true;
  390. break;
  391. case VisualRole.HotNormal:
  392. args.Result = hotNormalAttr;
  393. args.Handled = true;
  394. break;
  395. }
  396. };
  397. // All roles should defer to parent
  398. Assert.Equal (normalAttr, childView.GetAttributeForRole (VisualRole.Normal));
  399. Assert.Equal (focusAttr, childView.GetAttributeForRole (VisualRole.Focus));
  400. Assert.Equal (hotNormalAttr, childView.GetAttributeForRole (VisualRole.HotNormal));
  401. childView.Dispose ();
  402. parentView.Dispose ();
  403. }
  404. private class TestViewWithAttributeOverride : View
  405. {
  406. public Attribute? OverrideAttribute { get; set; }
  407. protected override bool OnGettingAttributeForRole (in VisualRole role, ref Attribute currentAttribute)
  408. {
  409. if (OverrideAttribute.HasValue && role == VisualRole.Normal)
  410. {
  411. currentAttribute = OverrideAttribute.Value;
  412. return true;
  413. }
  414. return base.OnGettingAttributeForRole (role, ref currentAttribute);
  415. }
  416. }
  417. }