ConfigPropertyTests.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. #nullable enable
  2. using System.Collections.Concurrent;
  3. using System.Reflection;
  4. using System.Text.Json;
  5. namespace UnitTests.ConfigurationTests;
  6. public class ConfigPropertyTests
  7. {
  8. [Fact]
  9. public void Apply_PropertyValueIsAppliedToStatic_String_Property ()
  10. {
  11. // Arrange
  12. TestConfiguration.Reset ();
  13. var propertyInfo = typeof (TestConfiguration).GetProperty (nameof (TestConfiguration.TestStringProperty));
  14. var configProperty = new ConfigProperty
  15. {
  16. PropertyInfo = propertyInfo,
  17. PropertyValue = "UpdatedValue"
  18. };
  19. // Act
  20. var result = configProperty.Apply ();
  21. // Assert
  22. Assert.Equal (1, TestConfiguration.TestStringPropertySetCount);
  23. Assert.True (result);
  24. Assert.Equal ("UpdatedValue", TestConfiguration.TestStringProperty);
  25. TestConfiguration.Reset ();
  26. }
  27. [Fact]
  28. public void Apply_PropertyValueIsAppliedToStatic_Key_Property ()
  29. {
  30. // Arrange
  31. TestConfiguration.Reset ();
  32. var propertyInfo = typeof (TestConfiguration).GetProperty (nameof (TestConfiguration.TestKeyProperty));
  33. var configProperty = new ConfigProperty
  34. {
  35. PropertyInfo = propertyInfo,
  36. PropertyValue = Key.Q.WithCtrl
  37. };
  38. // Act
  39. var result = configProperty.Apply ();
  40. // Assert
  41. Assert.Equal (1, TestConfiguration.TestKeyPropertySetCount);
  42. Assert.True (result);
  43. Assert.Equal (Key.Q.WithCtrl, TestConfiguration.TestKeyProperty);
  44. TestConfiguration.Reset ();
  45. }
  46. [Fact]
  47. public void RetrieveValue_GetsCurrentValueOfStaticProperty ()
  48. {
  49. // Arrange
  50. TestConfiguration.TestStringProperty = "CurrentValue";
  51. var propertyInfo = typeof (TestConfiguration).GetProperty (nameof (TestConfiguration.TestStringProperty));
  52. var configProperty = new ConfigProperty
  53. {
  54. PropertyInfo = propertyInfo
  55. };
  56. // Act
  57. var value = configProperty.UpdateToCurrentValue ();
  58. // Assert
  59. Assert.Equal ("CurrentValue", value);
  60. Assert.Equal ("CurrentValue", configProperty.PropertyValue);
  61. }
  62. [Fact]
  63. public void DeepCloneFrom_Updates_String_Property_Value ()
  64. {
  65. // Arrange
  66. TestConfiguration.Reset ();
  67. var propertyInfo = typeof (TestConfiguration).GetProperty (nameof (TestConfiguration.TestStringProperty));
  68. var configProperty = new ConfigProperty
  69. {
  70. PropertyInfo = propertyInfo,
  71. PropertyValue = "InitialValue"
  72. };
  73. // Act
  74. var updatedValue = configProperty.UpdateFrom ("NewValue");
  75. // Assert
  76. Assert.Equal (0, TestConfiguration.TestStringPropertySetCount);
  77. Assert.Equal ("NewValue", updatedValue);
  78. Assert.Equal ("NewValue", configProperty.PropertyValue);
  79. TestConfiguration.Reset ();
  80. }
  81. //[Fact]
  82. //public void UpdateValueFrom_InvalidType_ThrowsArgumentException()
  83. //{
  84. // // Arrange
  85. // var propertyInfo = typeof(TestConfiguration).GetProperty(nameof(TestConfiguration.TestStringProperty));
  86. // var configProperty = new ConfigProperty
  87. // {
  88. // PropertyInfo = propertyInfo
  89. // };
  90. // // Act & Assert
  91. // Assert.Throws<ArgumentException>(() => configProperty.UpdateValueFrom(123));
  92. //}
  93. [Fact]
  94. public void Apply_TargetInvocationException_ThrowsJsonException ()
  95. {
  96. // Arrange
  97. var propertyInfo = typeof (TestConfiguration).GetProperty (nameof (TestConfiguration.TestStringProperty));
  98. var configProperty = new ConfigProperty
  99. {
  100. PropertyInfo = propertyInfo,
  101. PropertyValue = null // This will cause ArgumentNullException in the set accessor
  102. };
  103. // Act & Assert
  104. var exception = Assert.Throws<JsonException> (() => configProperty.Apply ());
  105. }
  106. [Fact]
  107. public void GetJsonPropertyName_ReturnsJsonPropertyNameAttributeValue ()
  108. {
  109. // Arrange
  110. var propertyInfo = typeof (TestConfiguration).GetProperty (nameof (TestConfiguration.TestStringProperty));
  111. // Act
  112. var jsonPropertyName = ConfigProperty.GetJsonPropertyName (propertyInfo!);
  113. // Assert
  114. Assert.Equal ("TestStringProperty", jsonPropertyName);
  115. }
  116. [Fact]
  117. public void UpdateFrom_NullSource_ReturnsExistingValue()
  118. {
  119. // Arrange
  120. TestConfiguration.TestStringProperty = "CurrentValue";
  121. var propertyInfo = typeof(TestConfiguration).GetProperty(nameof(TestConfiguration.TestStringProperty));
  122. var configProperty = new ConfigProperty
  123. {
  124. PropertyInfo = propertyInfo,
  125. PropertyValue = "ExistingValue"
  126. };
  127. // Act
  128. var result = configProperty.UpdateFrom(null);
  129. // Assert
  130. Assert.Equal("ExistingValue", result);
  131. Assert.Equal("ExistingValue", configProperty.PropertyValue);
  132. }
  133. [Fact]
  134. public void UpdateFrom_InvalidType_ThrowsArgumentException()
  135. {
  136. // Arrange
  137. var propertyInfo = typeof(TestConfiguration).GetProperty(nameof(TestConfiguration.TestStringProperty));
  138. var configProperty = new ConfigProperty
  139. {
  140. PropertyInfo = propertyInfo,
  141. PropertyValue = "ExistingValue"
  142. };
  143. // Act & Assert
  144. Assert.Throws<ArgumentException>(() => configProperty.UpdateFrom(123));
  145. }
  146. [Fact]
  147. public void UpdateFrom_ConfigPropertySource_CopiesValue()
  148. {
  149. // Arrange
  150. var propertyInfo = typeof(TestConfiguration).GetProperty(nameof(TestConfiguration.TestStringProperty));
  151. var configProperty = new ConfigProperty
  152. {
  153. PropertyInfo = propertyInfo,
  154. PropertyValue = "ExistingValue"
  155. };
  156. var sourceConfigProperty = new ConfigProperty
  157. {
  158. PropertyValue = "SourceValue",
  159. HasValue = true
  160. };
  161. // Act
  162. var result = configProperty.UpdateFrom(sourceConfigProperty);
  163. // Assert
  164. Assert.Equal("SourceValue", result);
  165. Assert.Equal("SourceValue", configProperty.PropertyValue);
  166. }
  167. [Fact]
  168. public void UpdateFrom_ConfigPropertySource_WithoutValue_KeepsExistingValue()
  169. {
  170. // Arrange
  171. var propertyInfo = typeof(TestConfiguration).GetProperty(nameof(TestConfiguration.TestStringProperty));
  172. var configProperty = new ConfigProperty
  173. {
  174. PropertyInfo = propertyInfo,
  175. PropertyValue = "ExistingValue"
  176. };
  177. var sourceConfigProperty = new ConfigProperty
  178. {
  179. HasValue = false
  180. };
  181. // Act
  182. var result = configProperty.UpdateFrom(sourceConfigProperty);
  183. // Assert
  184. Assert.Equal("ExistingValue", result);
  185. Assert.Equal("ExistingValue", configProperty.PropertyValue);
  186. }
  187. [Fact]
  188. public void UpdateFrom_ConcurrentDictionaryOfThemeScopes_UpdatesValues()
  189. {
  190. // Arrange
  191. var propertyInfo = typeof(TestConfiguration).GetProperty(nameof(TestConfiguration.TestDictionaryProperty));
  192. // Create a destination dictionary
  193. var destinationDict = new ConcurrentDictionary<string, ThemeScope>(StringComparer.InvariantCultureIgnoreCase);
  194. destinationDict.TryAdd("theme1", new ThemeScope());
  195. var configProperty = new ConfigProperty
  196. {
  197. PropertyInfo = propertyInfo,
  198. PropertyValue = destinationDict
  199. };
  200. // Create a source dictionary with one matching key and one new key
  201. var sourceDict = new ConcurrentDictionary<string, ThemeScope>(StringComparer.InvariantCultureIgnoreCase);
  202. var sourceTheme1 = new ThemeScope();
  203. var sourceTheme2 = new ThemeScope();
  204. // Add a property to sourceTheme1 to verify it gets updated
  205. var keyProperty = typeof(TestConfiguration).GetProperty(nameof(TestConfiguration.TestKeyProperty));
  206. if (sourceTheme1.TryAdd("TestKey", new ConfigProperty { PropertyInfo = keyProperty, PropertyValue = Key.A.WithCtrl, HasValue = true }))
  207. {
  208. // Successfully added
  209. }
  210. sourceDict.TryAdd("theme1", sourceTheme1);
  211. sourceDict.TryAdd("theme2", sourceTheme2);
  212. // Act
  213. var result = configProperty.UpdateFrom(sourceDict);
  214. // Assert
  215. var resultDict = result as ConcurrentDictionary<string, ThemeScope>;
  216. Assert.NotNull(resultDict);
  217. Assert.Equal(2, resultDict.Count);
  218. Assert.True(resultDict.ContainsKey("theme1"));
  219. Assert.True(resultDict.ContainsKey("theme2"));
  220. // Verify the theme1 was updated with the property
  221. Assert.True(resultDict["theme1"].ContainsKey("TestKey"));
  222. Assert.Equal(Key.A.WithCtrl, resultDict["theme1"]["TestKey"].PropertyValue);
  223. }
  224. [Fact]
  225. public void UpdateFrom_ConcurrentDictionaryOfConfigProperties_UpdatesValues()
  226. {
  227. // Arrange
  228. var propertyInfo = typeof(TestConfiguration).GetProperty(nameof(TestConfiguration.TestConfigDictionaryProperty));
  229. // Create a destination dictionary
  230. var destinationDict = new ConcurrentDictionary<string, ConfigProperty>(StringComparer.InvariantCultureIgnoreCase);
  231. destinationDict.TryAdd("prop1", new ConfigProperty { PropertyValue = "Original", HasValue = true });
  232. var configProperty = new ConfigProperty
  233. {
  234. PropertyInfo = propertyInfo,
  235. PropertyValue = destinationDict
  236. };
  237. // Create a source dictionary with one matching key and one new key
  238. var sourceDict = new ConcurrentDictionary<string, ConfigProperty>(StringComparer.InvariantCultureIgnoreCase);
  239. sourceDict.TryAdd("prop1", new ConfigProperty { PropertyValue = "Updated", HasValue = true });
  240. sourceDict.TryAdd("prop2", new ConfigProperty { PropertyValue = "New", HasValue = true });
  241. // Act
  242. var result = configProperty.UpdateFrom(sourceDict);
  243. // Assert
  244. var resultDict = result as ConcurrentDictionary<string, ConfigProperty>;
  245. Assert.NotNull(resultDict);
  246. Assert.Equal(2, resultDict.Count);
  247. Assert.True(resultDict.ContainsKey("prop1"));
  248. Assert.True(resultDict.ContainsKey("prop2"));
  249. Assert.Equal("Updated", resultDict["prop1"].PropertyValue);
  250. Assert.Equal("New", resultDict["prop2"].PropertyValue);
  251. }
  252. [Fact]
  253. public void UpdateFrom_DictionaryOfConfigProperties_UpdatesValues()
  254. {
  255. // Arrange
  256. var propertyInfo = typeof(TestConfiguration).GetProperty(nameof(TestConfiguration.TestConfigDictionaryProperty));
  257. // Create a destination dictionary
  258. var destinationDict = new Dictionary<string, ConfigProperty>(StringComparer.InvariantCultureIgnoreCase);
  259. destinationDict.Add("prop1", new ConfigProperty { PropertyValue = "Original", HasValue = true });
  260. var configProperty = new ConfigProperty
  261. {
  262. PropertyInfo = propertyInfo,
  263. PropertyValue = destinationDict
  264. };
  265. // Create a source dictionary with one matching key and one new key
  266. var sourceDict = new Dictionary<string, ConfigProperty>(StringComparer.InvariantCultureIgnoreCase);
  267. sourceDict.Add("prop1", new ConfigProperty { PropertyValue = "Updated", HasValue = true });
  268. sourceDict.Add("prop2", new ConfigProperty { PropertyValue = "New", HasValue = true });
  269. // Act
  270. var result = configProperty.UpdateFrom(sourceDict);
  271. // Assert
  272. var resultDict = result as Dictionary<string, ConfigProperty>;
  273. Assert.NotNull(resultDict);
  274. Assert.Equal(2, resultDict.Count);
  275. Assert.True(resultDict.ContainsKey("prop1"));
  276. Assert.True(resultDict.ContainsKey("prop2"));
  277. Assert.Equal("Updated", resultDict["prop1"].PropertyValue);
  278. Assert.Equal("New", resultDict["prop2"].PropertyValue);
  279. }
  280. [Fact]
  281. public void PropertyValue_SetWhenImmutable_ThrowsException()
  282. {
  283. // Arrange
  284. var propertyInfo = typeof(TestConfiguration).GetProperty(nameof(TestConfiguration.TestStringProperty));
  285. var configProperty = new ConfigProperty
  286. {
  287. PropertyInfo = propertyInfo,
  288. Immutable = true
  289. };
  290. // Act & Assert
  291. var exception = Assert.Throws<InvalidOperationException>(() => configProperty.PropertyValue = "New Value");
  292. Assert.Contains("immutable", exception.Message);
  293. }
  294. [Fact]
  295. public void CreateWithAttributeInfo_ReturnsConfigPropertyWithCorrectValues()
  296. {
  297. // Arrange
  298. var propertyInfo = typeof(TestConfiguration).GetProperty(nameof(TestConfiguration.TestStringProperty));
  299. // Act
  300. var configProperty = ConfigProperty.CreateImmutableWithAttributeInfo(propertyInfo!);
  301. // Assert
  302. Assert.Equal(propertyInfo, configProperty.PropertyInfo);
  303. Assert.False(configProperty.OmitClassName);
  304. Assert.True(configProperty.Immutable);
  305. Assert.Null(configProperty.PropertyValue);
  306. Assert.False(configProperty.HasValue);
  307. }
  308. [Fact]
  309. public void HasConfigurationPropertyAttribute_ReturnsTrue_ForDecoratedProperty()
  310. {
  311. // Arrange
  312. var propertyInfo = typeof(TestConfiguration).GetProperty(nameof(TestConfiguration.TestStringProperty));
  313. // Act
  314. var result = ConfigProperty.HasConfigurationPropertyAttribute(propertyInfo!);
  315. // Assert
  316. Assert.True(result);
  317. }
  318. [Fact]
  319. public void HasConfigurationPropertyAttribute_ReturnsFalse_ForNonDecoratedProperty()
  320. {
  321. // Arrange
  322. var propertyInfo = typeof(TestConfiguration).GetProperty(nameof(TestConfiguration.TestStringPropertySetCount));
  323. // Act
  324. var result = ConfigProperty.HasConfigurationPropertyAttribute(propertyInfo!);
  325. // Assert
  326. Assert.False(result);
  327. }
  328. public class TestConfiguration
  329. {
  330. private static string _testStringProperty = "Default";
  331. public static int TestStringPropertySetCount { get; set; }
  332. [ConfigurationProperty]
  333. public static string TestStringProperty
  334. {
  335. get => _testStringProperty;
  336. set
  337. {
  338. TestStringPropertySetCount++;
  339. _testStringProperty = value ?? throw new ArgumentNullException (nameof (value));
  340. }
  341. }
  342. private static Key _testKeyProperty = Key.Esc;
  343. public static int TestKeyPropertySetCount { get; set; }
  344. [ConfigurationProperty]
  345. public static Key TestKeyProperty
  346. {
  347. get => _testKeyProperty;
  348. set
  349. {
  350. TestKeyPropertySetCount++;
  351. _testKeyProperty = value ?? throw new ArgumentNullException (nameof (value));
  352. }
  353. }
  354. // Add these new properties for testing dictionaries
  355. [ConfigurationProperty]
  356. public static ConcurrentDictionary<string, ThemeScope>? TestDictionaryProperty { get; set; }
  357. [ConfigurationProperty]
  358. public static Dictionary<string, ConfigProperty>? TestRegularDictionaryProperty { get; set; }
  359. [ConfigurationProperty]
  360. public static ConcurrentDictionary<string, ConfigProperty>? TestConfigDictionaryProperty { get; set; }
  361. [ConfigurationProperty]
  362. public static Scheme? TestSchemeProperty { get; set; }
  363. public static void Reset ()
  364. {
  365. TestStringPropertySetCount = 0;
  366. TestKeyPropertySetCount = 0;
  367. TestDictionaryProperty = null;
  368. TestRegularDictionaryProperty = null;
  369. TestConfigDictionaryProperty = null;
  370. TestSchemeProperty = null;
  371. }
  372. }
  373. [Fact]
  374. public void UpdateFrom_SchemeSource_UpdatesValue ()
  375. {
  376. // Arrange
  377. PropertyInfo? propertyInfo = typeof (TestConfiguration).GetProperty (nameof (TestConfiguration.TestSchemeProperty));
  378. Scheme sourceScheme = new (new Attribute (Color.Red, Color.Blue, TextStyle.Bold));
  379. var configProperty = new ConfigProperty
  380. {
  381. PropertyInfo = propertyInfo!,
  382. PropertyValue = new Scheme (new Attribute (Color.White, Color.Black, TextStyle.None))
  383. };
  384. // Act
  385. object? result = configProperty.UpdateFrom (sourceScheme);
  386. // Assert
  387. Assert.Equal (sourceScheme, result);
  388. Assert.Equal (sourceScheme, configProperty.PropertyValue);
  389. Assert.NotSame (sourceScheme, configProperty.PropertyValue); // Prove it's a clone, not a ref
  390. }
  391. }