ConfigurationMangerTests.cs 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017
  1. using System.Collections.Frozen;
  2. using System.Diagnostics;
  3. using System.Reflection;
  4. using System.Text;
  5. using System.Text.Json;
  6. using ColorHelper;
  7. using Xunit.Abstractions;
  8. using static Terminal.Gui.Configuration.ConfigurationManager;
  9. using File = System.IO.File;
  10. #pragma warning disable IDE1006
  11. namespace Terminal.Gui.ConfigurationTests;
  12. public class ConfigurationManagerTests (ITestOutputHelper output)
  13. {
  14. [Fact]
  15. public void ModuleInitializer_Was_Called ()
  16. {
  17. Assert.False (IsEnabled);
  18. Assert.True (IsInitialized ());
  19. }
  20. [Fact]
  21. public void Initialize_Throws_If_Called_Explicitly ()
  22. {
  23. Assert.False (IsEnabled);
  24. Assert.Throws<InvalidOperationException> (Initialize);
  25. }
  26. [Fact]
  27. public void HardCodedDefaultCache_Properties_Are_Copies ()
  28. {
  29. Assert.False (IsEnabled);
  30. Enable (ConfigLocations.HardCoded);
  31. Assert.Equal (Key.Esc, Application.QuitKey);
  32. ConfigProperty fromSettings = Settings! ["Application.QuitKey"];
  33. FrozenDictionary<string, ConfigProperty> initialCache = GetHardCodedConfigPropertyCache ();
  34. Assert.NotNull (initialCache);
  35. ConfigProperty fromCache = initialCache ["Application.QuitKey"];
  36. // Assert
  37. Assert.NotEqual (fromCache, fromSettings);
  38. Disable (true);
  39. }
  40. [Fact]
  41. public void HardCodedDefaultCache_Properties_Are_Immutable ()
  42. {
  43. Assert.False (IsEnabled);
  44. try
  45. {
  46. Enable (ConfigLocations.HardCoded);
  47. Assert.Equal (Key.Esc, Application.QuitKey);
  48. FrozenDictionary<string, ConfigProperty> initialCache = GetHardCodedConfigPropertyCache ();
  49. Assert.NotNull (initialCache);
  50. Assert.Equal (Key.Esc, (Key)initialCache ["Application.QuitKey"].PropertyValue);
  51. // Act
  52. Settings! ["Application.QuitKey"].PropertyValue = Key.Q;
  53. Assert.Equal (Key.Q, (Key)Settings ["Application.QuitKey"].PropertyValue);
  54. Settings ["Application.QuitKey"].Apply ();
  55. Assert.Equal (Key.Q, Application.QuitKey);
  56. Application.QuitKey = Key.K;
  57. // Assert
  58. FrozenDictionary<string, ConfigProperty> cache = GetHardCodedConfigPropertyCache ();
  59. Assert.Equal (initialCache, cache);
  60. Assert.True (initialCache ["Application.QuitKey"].Immutable);
  61. Assert.Equal (Key.Esc, (Key)initialCache ["Application.QuitKey"].PropertyValue);
  62. }
  63. finally
  64. {
  65. Disable (true);
  66. Application.ResetState (true);
  67. }
  68. }
  69. [Fact]
  70. public void Disable_Settings_Is_NotNull ()
  71. {
  72. Assert.False (IsEnabled);
  73. Disable ();
  74. Assert.NotNull (Settings);
  75. }
  76. [Fact]
  77. public void Disable_With_ResetToHardCodedDefaults_True_Works_When_Disabled ()
  78. {
  79. Assert.False (ConfigurationManager.IsEnabled);
  80. ConfigurationManager.Disable (true);
  81. }
  82. [Fact]
  83. public void Enable_Settings_Is_Valid ()
  84. {
  85. Assert.False (IsEnabled);
  86. Enable (ConfigLocations.HardCoded);
  87. Assert.NotNull (Settings);
  88. Disable ();
  89. }
  90. [Fact]
  91. public void Apply_Applies_Theme ()
  92. {
  93. Assert.False (IsEnabled);
  94. Enable (ConfigLocations.HardCoded);
  95. var theme = new ThemeScope ();
  96. theme.LoadHardCodedDefaults ();
  97. Assert.NotEmpty (theme);
  98. Assert.True (ThemeManager.Themes!.TryAdd ("testTheme", theme));
  99. Assert.Equal (2, ThemeManager.Themes.Count);
  100. Assert.Equal (LineStyle.Rounded, FrameView.DefaultBorderStyle);
  101. theme ["FrameView.DefaultBorderStyle"].PropertyValue = LineStyle.Double;
  102. ThemeManager.Theme = "testTheme";
  103. ConfigurationManager.Apply ();
  104. Assert.Equal (LineStyle.Double, FrameView.DefaultBorderStyle);
  105. Disable (resetToHardCodedDefaults: true);
  106. }
  107. [Fact]
  108. public void Apply_Raises_Applied ()
  109. {
  110. Assert.False (IsEnabled);
  111. Enable (ConfigLocations.HardCoded);
  112. Applied += ConfigurationManagerApplied;
  113. var fired = false;
  114. void ConfigurationManagerApplied (object sender, ConfigurationManagerEventArgs obj)
  115. {
  116. fired = true;
  117. // assert
  118. Assert.Equal (KeyCode.Q, Application.QuitKey.KeyCode);
  119. Assert.Equal (KeyCode.F, Application.NextTabGroupKey.KeyCode);
  120. Assert.Equal (KeyCode.B, Application.PrevTabGroupKey.KeyCode);
  121. }
  122. // act
  123. Settings! ["Application.QuitKey"].PropertyValue = Key.Q;
  124. Settings ["Application.NextTabGroupKey"].PropertyValue = Key.F;
  125. Settings ["Application.PrevTabGroupKey"].PropertyValue = Key.B;
  126. Apply ();
  127. // assert
  128. Assert.True (fired);
  129. Applied -= ConfigurationManagerApplied;
  130. Disable (resetToHardCodedDefaults: true);
  131. Application.ResetState (true);
  132. }
  133. [Fact]
  134. public void Load_Raises_Updated ()
  135. {
  136. Assert.False (IsEnabled);
  137. var fired = false;
  138. Enable (ConfigLocations.HardCoded);
  139. ThrowOnJsonErrors = true;
  140. Assert.Equal (Key.Esc, ((Key)Settings! ["Application.QuitKey"].PropertyValue)!.KeyCode);
  141. Updated += ConfigurationManagerUpdated;
  142. // Act
  143. // Only select locations under test control
  144. Load (ConfigLocations.LibraryResources | ConfigLocations.AppResources | ConfigLocations.Runtime);
  145. // assert
  146. Assert.True (fired);
  147. // clean up
  148. Updated -= ConfigurationManagerUpdated;
  149. Disable (true);
  150. Application.ResetState (true);
  151. return;
  152. void ConfigurationManagerUpdated (object sender, ConfigurationManagerEventArgs obj) { fired = true; }
  153. }
  154. [Fact]
  155. public void Load_And_Apply_Performance_Check ()
  156. {
  157. Assert.False (IsEnabled);
  158. Enable (ConfigLocations.HardCoded);
  159. try
  160. {
  161. // Start stopwatch
  162. var stopwatch = new Stopwatch ();
  163. stopwatch.Start ();
  164. // Act
  165. Load (ConfigLocations.All);
  166. Apply ();
  167. // Stop stopwatch
  168. stopwatch.Stop ();
  169. // Assert
  170. output.WriteLine ($"Load took {stopwatch.ElapsedMilliseconds} ms");
  171. // Ensure load time is reasonable (adjust threshold as needed)
  172. Assert.True (
  173. stopwatch.ElapsedMilliseconds < 1000,
  174. $"Loading configuration took {stopwatch.ElapsedMilliseconds}ms, which exceeds reasonable threshold");
  175. }
  176. finally
  177. {
  178. Disable (true);
  179. Application.ResetState (true);
  180. }
  181. }
  182. [Fact]
  183. public void Load_Loads_Custom_Json ()
  184. {
  185. Assert.False (IsEnabled);
  186. try
  187. {
  188. Enable (ConfigLocations.HardCoded);
  189. ThrowOnJsonErrors = true;
  190. Assert.Equal (Key.Esc, (Key)Settings! ["Application.QuitKey"].PropertyValue);
  191. // act
  192. RuntimeConfig = """
  193. {
  194. "Application.QuitKey": "Ctrl-Q"
  195. }
  196. """;
  197. Load (ConfigLocations.Runtime);
  198. // assert
  199. Assert.Equal (Key.Q.WithCtrl, (Key)Settings ["Application.QuitKey"].PropertyValue);
  200. }
  201. finally
  202. {
  203. // clean up
  204. Disable (true);
  205. Application.ResetState (true);
  206. }
  207. }
  208. [Fact (Skip = "Events disabled")]
  209. public void ResetToCurrentValues_Raises_Updated ()
  210. {
  211. Assert.False (IsEnabled);
  212. var fired = false;
  213. try
  214. {
  215. Enable (ConfigLocations.HardCoded);
  216. Settings! ["Application.QuitKey"].PropertyValue = Key.Q;
  217. Updated += ConfigurationManagerUpdated;
  218. // Act
  219. ResetToCurrentValues ();
  220. // assert
  221. Assert.True (fired);
  222. }
  223. finally
  224. {
  225. Updated -= ConfigurationManagerUpdated;
  226. Disable (true);
  227. Application.ResetState (true);
  228. }
  229. return;
  230. void ConfigurationManagerUpdated (object sender, ConfigurationManagerEventArgs obj) { fired = true; }
  231. }
  232. [Fact]
  233. public void ResetToHardCodedDefaults_and_Load_LibraryResourcesOnly_are_same ()
  234. {
  235. Assert.False (IsEnabled);
  236. try
  237. {
  238. // arrange
  239. Enable (ConfigLocations.HardCoded);
  240. Settings! ["Application.QuitKey"].PropertyValue = Key.Q;
  241. Settings ["Application.NextTabGroupKey"].PropertyValue = Key.F;
  242. Settings ["Application.PrevTabGroupKey"].PropertyValue = Key.B;
  243. Settings.Apply ();
  244. // assert apply worked
  245. Assert.Equal (KeyCode.Q, Application.QuitKey.KeyCode);
  246. Assert.Equal (KeyCode.F, Application.NextTabGroupKey.KeyCode);
  247. Assert.Equal (KeyCode.B, Application.PrevTabGroupKey.KeyCode);
  248. //act
  249. ResetToHardCodedDefaults ();
  250. // assert
  251. Assert.NotEmpty (ThemeManager.Themes!);
  252. Assert.Equal ("Default", ThemeManager.Theme);
  253. Assert.Equal (Key.Esc, Application.QuitKey);
  254. Assert.Equal (Key.F6, Application.NextTabGroupKey);
  255. Assert.Equal (Key.F6.WithShift, Application.PrevTabGroupKey);
  256. // arrange
  257. Settings ["Application.QuitKey"].PropertyValue = Key.Q;
  258. Settings ["Application.NextTabGroupKey"].PropertyValue = Key.F;
  259. Settings ["Application.PrevTabGroupKey"].PropertyValue = Key.B;
  260. Settings.Apply ();
  261. // act
  262. Load (ConfigLocations.LibraryResources);
  263. Apply ();
  264. // assert
  265. Assert.NotEmpty (ThemeManager.Themes);
  266. Assert.Equal ("Default", ThemeManager.Theme);
  267. Assert.Equal (KeyCode.Esc, Application.QuitKey.KeyCode);
  268. Assert.Equal (Key.F6, Application.NextTabGroupKey);
  269. Assert.Equal (Key.F6.WithShift, Application.PrevTabGroupKey);
  270. }
  271. finally
  272. {
  273. Disable (true);
  274. Application.ResetState (true);
  275. }
  276. }
  277. [Fact]
  278. public void ResetToCurrentValues_Enabled_Resets ()
  279. {
  280. Assert.False (IsEnabled);
  281. // Act
  282. Enable (ConfigLocations.HardCoded);
  283. Application.QuitKey = Key.A;
  284. ResetToCurrentValues ();
  285. Assert.Equal (Key.A, (Key)Settings! ["Application.QuitKey"].PropertyValue);
  286. Assert.NotNull (Settings);
  287. Assert.NotNull (AppSettings);
  288. Assert.NotNull (ThemeManager.Themes);
  289. // Default Theme should be "Default"
  290. Assert.Single (ThemeManager.Themes);
  291. Assert.Equal (ThemeManager.DEFAULT_THEME_NAME, ThemeManager.Theme);
  292. ResetToHardCodedDefaults ();
  293. Assert.Equal (Key.Esc, (Key)Settings! ["Application.QuitKey"].PropertyValue);
  294. Disable ();
  295. Application.ResetState (true);
  296. }
  297. [Fact]
  298. public void ConfigurationManager_DefaultPrecedence_IsRespected ()
  299. {
  300. Assert.False (IsEnabled);
  301. try
  302. {
  303. // arrange
  304. Enable (ConfigLocations.HardCoded);
  305. ThrowOnJsonErrors = true;
  306. // Setup multiple configurations with the same setting
  307. // with different precedence levels
  308. RuntimeConfig = """
  309. {
  310. "Application.QuitKey": "Alt+Q"
  311. }
  312. """;
  313. var defaultConfig = """
  314. {
  315. "Application.QuitKey": "Ctrl+X"
  316. }
  317. """;
  318. // Update default config first (lower precedence)
  319. ConfigurationManager.SourcesManager?.Load (Settings, defaultConfig, "default-test", ConfigLocations.LibraryResources);
  320. // Then load runtime config, which should override default
  321. Load (ConfigLocations.Runtime);
  322. // Assert - the runtime config should win due to precedence
  323. Assert.Equal (Key.Q.WithAlt, (Key)Settings! ["Application.QuitKey"].PropertyValue);
  324. }
  325. finally
  326. {
  327. Disable (true);
  328. }
  329. }
  330. /// <summary>Save the `config.json` file; this can be used to update the file in `Terminal.Gui.Resources.config.json'.</summary>
  331. /// <remarks>
  332. /// IMPORTANT: For the file generated to be valid, this must be the ONLY test run. Config Properties are all
  333. /// static and thus can be overwritten by other tests.
  334. /// </remarks>
  335. [Fact]
  336. public void Save_HardCodedDefaults_To_config_json ()
  337. {
  338. Assert.False (IsEnabled);
  339. try
  340. {
  341. Enable (ConfigLocations.HardCoded);
  342. // Get the hard coded settings
  343. ResetToHardCodedDefaults ();
  344. // Serialize to a JSON string
  345. string json = ConfigurationManager.SourcesManager?.ToJson (Settings);
  346. // Write the JSON string to the file
  347. File.WriteAllText ("hard_coded_defaults_config.json", json);
  348. // Verify the file was created
  349. Assert.True (File.Exists ("hard_coded_defaults_config.json"), "Failed to create config.json file");
  350. }
  351. finally
  352. {
  353. Disable (true);
  354. }
  355. }
  356. /// <summary>Save the `config.json` file; this can be used to update the file in `Terminal.Gui.Resources.config.json'.</summary>
  357. /// <remarks>
  358. /// IMPORTANT: For the file generated to be valid, this must be the ONLY test run. Config Properties are all
  359. /// static and thus can be overwritten by other tests.
  360. /// </remarks>
  361. [Fact]
  362. public void Save_Library_Defaults_To_config_json ()
  363. {
  364. Assert.False (IsEnabled);
  365. try
  366. {
  367. Enable (ConfigLocations.LibraryResources);
  368. // Serialize to a JSON string
  369. string json = ConfigurationManager.SourcesManager?.ToJson (Settings);
  370. // Write the JSON string to the file
  371. File.WriteAllText ("library_defaults_config.json", json);
  372. // Verify the file was created
  373. Assert.True (File.Exists ("library_defaults_config.json"), "Failed to create config.json file");
  374. }
  375. finally
  376. {
  377. Disable (true);
  378. }
  379. }
  380. [Fact]
  381. public void TestConfigProperties ()
  382. {
  383. Assert.False (IsEnabled);
  384. try
  385. {
  386. Enable (ConfigLocations.HardCoded);
  387. Assert.NotEmpty (Settings!);
  388. // test that all ConfigProperties have our attribute
  389. Assert.All (
  390. Settings,
  391. item => Assert.Contains (item.Value.PropertyInfo!.CustomAttributes, a => a.AttributeType
  392. == typeof (ConfigurationPropertyAttribute)
  393. ));
  394. #pragma warning disable xUnit2030
  395. Assert.DoesNotContain (Settings, cp => cp.Value.PropertyInfo!.GetCustomAttribute (
  396. typeof (ConfigurationPropertyAttribute)
  397. )
  398. == null
  399. );
  400. #pragma warning restore xUnit2030
  401. // Application is a static class
  402. PropertyInfo pi = typeof (Application).GetProperty ("QuitKey");
  403. Assert.Equal (pi, Settings ["Application.QuitKey"].PropertyInfo);
  404. // FrameView is not a static class and DefaultBorderStyle is Scope.Scheme
  405. pi = typeof (FrameView).GetProperty ("DefaultBorderStyle");
  406. Assert.False (Settings.ContainsKey ("FrameView.DefaultBorderStyle"));
  407. Assert.True (ThemeManager.GetCurrentTheme ().ContainsKey ("FrameView.DefaultBorderStyle"));
  408. Assert.Equal (pi, ThemeManager.GetCurrentTheme () ["FrameView.DefaultBorderStyle"].PropertyInfo);
  409. }
  410. finally
  411. {
  412. Disable (true);
  413. }
  414. }
  415. [Fact]
  416. public void Load_And_Apply_HardCoded ()
  417. {
  418. Assert.False (IsEnabled);
  419. try
  420. {
  421. // Spot check by setting some of the config properties
  422. Application.QuitKey = Key.X.WithCtrl;
  423. FileDialog.MaxSearchResults = 1;
  424. Enable (ConfigLocations.HardCoded);
  425. Load (ConfigLocations.HardCoded);
  426. // Spot check
  427. Assert.Equal (Key.Esc, Settings ["Application.QuitKey"].PropertyValue as Key);
  428. Assert.Equal (10000, (int)Settings ["FileDialog.MaxSearchResults"].PropertyValue!);
  429. Assert.Single (ThemeManager.Themes!);
  430. Assert.Equal ("Default", ThemeManager.Theme);
  431. Assert.NotEmpty (ThemeManager.Themes [ThemeManager.Theme]);
  432. // Verify schemes are properly initialized
  433. Assert.NotNull (SchemeManager.GetSchemes ());
  434. Assert.NotEmpty (SchemeManager.GetSchemes ());
  435. // Verify "Base" has correct values
  436. //Assert.Equal (Color.White, SchemeManager.GetSchemes () ["Base"]!.Normal.Foreground);
  437. //Assert.Equal (Color.Blue, SchemeManager.GetSchemes () ["Base"].Normal.Background);
  438. Apply ();
  439. Assert.Equal (Key.Esc, Application.QuitKey);
  440. Assert.Equal (10000, FileDialog.MaxSearchResults);
  441. }
  442. finally
  443. {
  444. Disable (true);
  445. }
  446. }
  447. [Fact]
  448. public void Load_And_Apply_LibraryResources ()
  449. {
  450. Assert.False (IsEnabled);
  451. try
  452. {
  453. // Spot check by setting some of the config properties
  454. Application.QuitKey = Key.X.WithCtrl;
  455. FileDialog.MaxSearchResults = 1;
  456. Enable (ConfigLocations.HardCoded);
  457. Load (ConfigLocations.LibraryResources);
  458. // Spot check
  459. Assert.Equal (Key.Esc, Settings! ["Application.QuitKey"].PropertyValue as Key);
  460. Assert.Equal (10000, (int)Settings ["FileDialog.MaxSearchResults"].PropertyValue!);
  461. Assert.NotEmpty (ThemeManager.Themes!);
  462. Assert.Equal ("Default", ThemeManager.Theme);
  463. Assert.NotEmpty (ThemeManager.Themes [ThemeManager.Theme]);
  464. // Verify schemes are properly initialized
  465. Assert.NotNull (SchemeManager.GetSchemes ());
  466. Assert.NotEmpty (SchemeManager.GetSchemes ());
  467. // This is too fragile as the default scheme may change
  468. // Verify "Base" has correct values
  469. //Assert.Equal (Color.White, SchemeManager.Schemes ["Base"]!.Normal.Foreground);
  470. //Assert.Equal (Color.Blue, SchemeManager.Schemes ["Base"].Normal.Background);
  471. Apply ();
  472. Assert.Equal (Key.Esc, Application.QuitKey);
  473. Assert.Equal (10000, FileDialog.MaxSearchResults);
  474. }
  475. finally
  476. {
  477. Disable (true);
  478. }
  479. }
  480. [Fact]
  481. public void Load_And_Apply_RuntimeConfig ()
  482. {
  483. Assert.False (IsEnabled);
  484. try
  485. {
  486. // Spot check by setting some of the config properties
  487. Application.QuitKey = Key.X.WithCtrl;
  488. FileDialog.MaxSearchResults = 1;
  489. Glyphs.Apple = new ('z');
  490. ThrowOnJsonErrors = true;
  491. Enable (ConfigLocations.HardCoded);
  492. RuntimeConfig = """
  493. {
  494. "Application.QuitKey": "Alt-Q",
  495. "FileDialog.MaxSearchResults":9,
  496. "Themes" : [
  497. {
  498. "Default" : {
  499. "Glyphs.Apple": "a"
  500. }
  501. }
  502. ]
  503. }
  504. """;
  505. Load (ConfigLocations.Runtime);
  506. Assert.Equal (Key.Q.WithAlt, Settings! ["Application.QuitKey"].PropertyValue as Key);
  507. Assert.Equal (9, (int)Settings ["FileDialog.MaxSearchResults"].PropertyValue!);
  508. Assert.Equal (new Rune ('a'), ThemeManager.GetCurrentTheme () ["Glyphs.Apple"].PropertyValue);
  509. Apply ();
  510. Assert.Equal (Key.Q.WithAlt, Application.QuitKey);
  511. Assert.Equal (9, FileDialog.MaxSearchResults);
  512. Assert.Equal (new ('a'), Glyphs.Apple);
  513. }
  514. finally
  515. {
  516. Disable (true);
  517. }
  518. }
  519. [Fact]
  520. public void InvalidJsonLogs ()
  521. {
  522. Assert.False (IsEnabled);
  523. Enable (ConfigLocations.HardCoded);
  524. ThrowOnJsonErrors = false;
  525. // "brown" is not a color
  526. var json = @"
  527. {
  528. ""Themes"" : [
  529. {
  530. ""Default"" : {
  531. ""Schemes"": [
  532. {
  533. ""UserDefined"": {
  534. ""hotNormal"": {
  535. ""foreground"": ""brown"",
  536. ""background"": ""1234""
  537. }
  538. }
  539. }
  540. ]
  541. }
  542. }
  543. }
  544. }";
  545. ConfigurationManager.SourcesManager?.Load (Settings, json, "test", ConfigLocations.Runtime);
  546. // AbNormal is not a Scheme attribute
  547. json = @"
  548. {
  549. ""Themes"" : [
  550. {
  551. ""Default"" : {
  552. ""Schemes"": [
  553. {
  554. ""UserDefined"": {
  555. ""AbNormal"": {
  556. ""foreground"": ""green"",
  557. ""background"": ""black""
  558. }
  559. }
  560. }
  561. ]
  562. }
  563. }
  564. }
  565. }";
  566. ConfigurationManager.SourcesManager?.Load (Settings, json, "test", ConfigLocations.Runtime);
  567. // Modify hotNormal background only
  568. json = @"
  569. {
  570. ""Themes"" : [
  571. {
  572. ""Default"" : {
  573. ""Schemes"": [
  574. {
  575. ""UserDefined"": {
  576. ""hotNormal"": {
  577. ""background"": ""cyan""
  578. }
  579. }
  580. }
  581. ]
  582. }
  583. }
  584. }
  585. }";
  586. ConfigurationManager.SourcesManager?.Load (Settings, json, "test", ConfigLocations.Runtime);
  587. ConfigurationManager.SourcesManager?.Load (Settings, "{}}", "test", ConfigLocations.Runtime);
  588. Assert.NotEqual (0, _jsonErrors.Length);
  589. ThrowOnJsonErrors = false;
  590. Disable (true);
  591. }
  592. [Fact]
  593. public void InvalidJsonThrows ()
  594. {
  595. Assert.False (IsEnabled);
  596. Enable (ConfigLocations.HardCoded);
  597. ThrowOnJsonErrors = true;
  598. // "yellow" is not a color
  599. var json = @"
  600. {
  601. ""Themes"" : [
  602. {
  603. ""Default"" : {
  604. ""Schemes"": [
  605. {
  606. ""UserDefined"": {
  607. ""hotNormal"": {
  608. ""foreground"": ""brownish"",
  609. ""background"": ""1234""
  610. }
  611. }
  612. }
  613. ]
  614. }
  615. }
  616. ]
  617. }";
  618. var jsonException = Assert.Throws<JsonException> (() => ConfigurationManager.SourcesManager?.Load (Settings, json, "test", ConfigLocations.Runtime));
  619. Assert.StartsWith ("foreground: \"\"brownish\"\"", jsonException.Message);
  620. // AbNormal is not a Scheme attribute
  621. json = @"
  622. {
  623. ""Themes"" : [
  624. {
  625. ""Default"" : {
  626. ""Schemes"": [
  627. {
  628. ""UserDefined"": {
  629. ""AbNormal"": {
  630. ""foreground"": ""green"",
  631. ""background"": ""black""
  632. }
  633. }
  634. }
  635. ]
  636. }
  637. }
  638. ]
  639. }";
  640. jsonException = Assert.Throws<JsonException> (() => ConfigurationManager.SourcesManager?.Load (Settings, json, "test", ConfigLocations.Runtime));
  641. Assert.StartsWith ("AbNormal:", jsonException.Message);
  642. // Modify hotNormal background only
  643. json = @"
  644. {
  645. ""Themes"" : [
  646. {
  647. ""Default"" : {
  648. ""Schemes"": [
  649. {
  650. ""UserDefined"": {
  651. ""hotNormal"": {
  652. ""background"": ""cyan""
  653. }
  654. }
  655. }
  656. ]
  657. }
  658. }
  659. ]
  660. }";
  661. jsonException = Assert.Throws<JsonException> (() => ConfigurationManager.SourcesManager?.Load (Settings, json, "test", ConfigLocations.Runtime));
  662. Assert.StartsWith ("background:", jsonException.Message);
  663. // Unknown property
  664. json = @"
  665. {
  666. ""Unknown"" : ""Not known""
  667. }";
  668. jsonException = Assert.Throws<JsonException> (() => ConfigurationManager.SourcesManager?.Load (Settings, json, "test", ConfigLocations.Runtime));
  669. Assert.StartsWith ("Unknown:", jsonException.Message);
  670. Assert.Equal (0, _jsonErrors.Length);
  671. ThrowOnJsonErrors = false;
  672. Disable (true);
  673. }
  674. [Fact]
  675. public void UpdateFromJson ()
  676. {
  677. Assert.False (IsEnabled);
  678. try
  679. {
  680. Enable (ConfigLocations.HardCoded);
  681. // Arrange
  682. var json = @"
  683. {
  684. ""$schema"": ""https://gui-cs.github.io/Terminal.Gui/schemas/tui-config-schema.json"",
  685. ""Application.QuitKey"": ""Alt-Z"",
  686. ""Theme"": ""Default"",
  687. ""Themes"": [
  688. {
  689. ""Default"": {
  690. ""MessageBox.DefaultButtonAlignment"": ""End"",
  691. ""Schemes"": [
  692. {
  693. ""TopLevel"": {
  694. ""Normal"": {
  695. ""Foreground"": ""BrightGreen"",
  696. ""Background"": ""Black""
  697. },
  698. ""Focus"": {
  699. ""Foreground"": ""White"",
  700. ""Background"": ""Cyan""
  701. },
  702. ""HotNormal"": {
  703. ""Foreground"": ""Yellow"",
  704. ""Background"": ""Black""
  705. },
  706. ""HotFocus"": {
  707. ""Foreground"": ""Blue"",
  708. ""Background"": ""Cyan""
  709. },
  710. ""Disabled"": {
  711. ""Foreground"": ""DarkGray"",
  712. ""Background"": ""Black""
  713. }
  714. }
  715. },
  716. {
  717. ""Base"": {
  718. ""Normal"": {
  719. ""Foreground"": ""White"",
  720. ""Background"": ""Blue""
  721. },
  722. ""Focus"": {
  723. ""Foreground"": ""Black"",
  724. ""Background"": ""Gray""
  725. },
  726. ""HotNormal"": {
  727. ""Foreground"": ""BrightCyan"",
  728. ""Background"": ""Blue""
  729. },
  730. ""HotFocus"": {
  731. ""Foreground"": ""BrightBlue"",
  732. ""Background"": ""Gray""
  733. },
  734. ""Disabled"": {
  735. ""Foreground"": ""DarkGray"",
  736. ""Background"": ""Blue""
  737. }
  738. }
  739. },
  740. {
  741. ""Dialog"": {
  742. ""Normal"": {
  743. ""Foreground"": ""Black"",
  744. ""Background"": ""Gray""
  745. },
  746. ""Focus"": {
  747. ""Foreground"": ""White"",
  748. ""Background"": ""DarkGray""
  749. },
  750. ""HotNormal"": {
  751. ""Foreground"": ""Blue"",
  752. ""Background"": ""Gray""
  753. },
  754. ""HotFocus"": {
  755. ""Foreground"": ""BrightYellow"",
  756. ""Background"": ""DarkGray""
  757. },
  758. ""Disabled"": {
  759. ""Foreground"": ""Gray"",
  760. ""Background"": ""DarkGray""
  761. }
  762. }
  763. },
  764. {
  765. ""Menu"": {
  766. ""Normal"": {
  767. ""Foreground"": ""White"",
  768. ""Background"": ""DarkGray""
  769. },
  770. ""Focus"": {
  771. ""Foreground"": ""White"",
  772. ""Background"": ""Black""
  773. },
  774. ""HotNormal"": {
  775. ""Foreground"": ""BrightYellow"",
  776. ""Background"": ""DarkGray""
  777. },
  778. ""HotFocus"": {
  779. ""Foreground"": ""BrightYellow"",
  780. ""Background"": ""Black""
  781. },
  782. ""Disabled"": {
  783. ""Foreground"": ""Gray"",
  784. ""Background"": ""DarkGray""
  785. }
  786. }
  787. },
  788. {
  789. ""Error"": {
  790. ""Normal"": {
  791. ""Foreground"": ""Red"",
  792. ""Background"": ""White""
  793. },
  794. ""Focus"": {
  795. ""Foreground"": ""Black"",
  796. ""Background"": ""BrightRed""
  797. },
  798. ""HotNormal"": {
  799. ""Foreground"": ""Black"",
  800. ""Background"": ""White""
  801. },
  802. ""HotFocus"": {
  803. ""Foreground"": ""White"",
  804. ""Background"": ""BrightRed""
  805. },
  806. ""Disabled"": {
  807. ""Foreground"": ""DarkGray"",
  808. ""Background"": ""White""
  809. }
  810. }
  811. }
  812. ]
  813. }
  814. }
  815. ]
  816. }
  817. ";
  818. ResetToCurrentValues ();
  819. ThrowOnJsonErrors = true;
  820. ConfigurationManager.SourcesManager?.Load (Settings, json, "UpdateFromJson", ConfigLocations.Runtime);
  821. Assert.Equal ("Default", ThemeManager.Theme);
  822. Assert.Equal (KeyCode.Esc, Application.QuitKey.KeyCode);
  823. Assert.Equal (KeyCode.Z | KeyCode.AltMask, ((Key)Settings! ["Application.QuitKey"].PropertyValue)!.KeyCode);
  824. Assert.Equal (Alignment.Center, MessageBox.DefaultButtonAlignment);
  825. // Now re-apply
  826. Apply ();
  827. Assert.Equal ("Default", ThemeManager.Theme);
  828. Assert.Equal (KeyCode.Z | KeyCode.AltMask, Application.QuitKey.KeyCode);
  829. Assert.Equal (Alignment.End, MessageBox.DefaultButtonAlignment);
  830. Assert.Equal (Color.White, SchemeManager.GetSchemes ()! ["Base"].Normal.Foreground);
  831. Assert.Equal (Color.Blue, SchemeManager.GetSchemes ()! ["Base"].Normal.Background);
  832. }
  833. finally
  834. {
  835. Disable (resetToHardCodedDefaults: true);
  836. }
  837. }
  838. }