#nullable enable namespace UnitTests_Parallelizable.ViewsTests; /// /// Tests for functionality that applies to all selector implementations. /// These tests use as a concrete implementation to test the base class. /// public class SelectorBaseTests { #region Initialization Tests [Fact] public void Constructor_SetsDefaults () { var selector = new OptionSelector (); Assert.True (selector.CanFocus); Assert.Equal (Dim.Auto (DimAutoStyle.Content), selector.Width); Assert.Equal (Dim.Auto (DimAutoStyle.Content), selector.Height); Assert.Equal (Orientation.Vertical, selector.Orientation); Assert.Null (selector.Labels); Assert.Null (selector.Values); Assert.False (selector.AssignHotKeys); Assert.Empty (selector.UsedHotKeys); Assert.Equal (SelectorStyles.None, selector.Styles); Assert.True (selector.DoubleClickAccepts); Assert.Equal (2, selector.HorizontalSpace); } #endregion #region Value Property Tests [Fact] public void Value_Set_ValidValue_UpdatesValue () { var selector = new OptionSelector (); selector.Labels = ["Option1", "Option2"]; selector.Value = 1; Assert.Equal (1, selector.Value); } [Fact] public void Value_Set_InvalidValue_ThrowsArgumentOutOfRangeException () { var selector = new OptionSelector (); selector.Labels = ["Option1", "Option2"]; Assert.Throws (() => selector.Value = 5); Assert.Throws (() => selector.Value = -1); } [Fact] public void Value_Set_Null_Succeeds () { var selector = new OptionSelector (); selector.Labels = ["Option1", "Option2"]; selector.Value = null; Assert.Null (selector.Value); } [Fact] public void Value_Set_SameValue_DoesNotRaiseEvent () { var selector = new OptionSelector (); selector.Labels = ["Option1", "Option2"]; selector.Value = 1; var eventRaisedCount = 0; selector.ValueChanged += (s, e) => eventRaisedCount++; selector.Value = 1; // Set to same value Assert.Equal (0, eventRaisedCount); } [Fact] public void Value_Changed_RaisesValueChangedEvent () { var selector = new OptionSelector (); selector.Labels = ["Option1", "Option2"]; int? capturedValue = null; selector.ValueChanged += (s, e) => capturedValue = e.Value; selector.Value = 1; Assert.Equal (1, capturedValue); } #endregion #region Values Property Tests [Fact] public void Values_Get_WhenNull_ReturnsSequentialValues () { var selector = new OptionSelector (); selector.Labels = ["Option1", "Option2", "Option3"]; IReadOnlyList? values = selector.Values; Assert.NotNull (values); Assert.Equal (3, values.Count); Assert.Equal ([0, 1, 2], values); } [Fact] public void Values_Set_UpdatesValues () { var selector = new OptionSelector (); selector.Labels = ["Option1", "Option2"]; selector.Values = [10, 20]; Assert.Equal ([10, 20], selector.Values); } [Fact] public void Values_Set_SetsDefaultValue () { var selector = new OptionSelector (); selector.Value = null; selector.Labels = ["Option1", "Option2"]; selector.Values = [10, 20]; Assert.Equal (10, selector.Value); // Should default to first value } #endregion #region Labels Property Tests [Fact] public void Labels_Set_CreatesSubViews () { var selector = new OptionSelector (); selector.Labels = ["Option1", "Option2"]; Assert.Equal (2, selector.SubViews.OfType ().Count ()); } [Fact] public void Labels_Set_Null_RemovesSubViews () { var selector = new OptionSelector (); selector.Labels = ["Option1", "Option2"]; selector.Labels = null; Assert.Empty (selector.SubViews.OfType ()); } [Fact] public void Labels_Values_CountMismatch_DoesNotCreateSubViews () { var selector = new OptionSelector (); selector.Values = [0, 1, 2]; selector.Labels = ["Option1", "Option2"]; // Mismatch Assert.Empty (selector.SubViews.OfType ()); } #endregion #region SetValuesAndLabels Tests [Fact] public void SetValuesAndLabels_FromEnum_SetsValuesAndLabels () { var selector = new OptionSelector (); selector.SetValuesAndLabels (); Assert.NotNull (selector.Values); Assert.NotNull (selector.Labels); Assert.Equal (Enum.GetValues ().Length, selector.Values.Count); Assert.Equal (Enum.GetNames (), selector.Labels); } [Fact] public void SetValuesAndLabels_SetsCorrectIntegerValues () { var selector = new OptionSelector (); selector.SetValuesAndLabels (); // Verify values match enum integer values var expectedValues = Enum.GetValues ().Select (e => (int)e).ToList (); Assert.Equal (expectedValues, selector.Values); } #endregion #region Styles Property Tests [Fact] public void Styles_Set_None_NoExtraSubViews () { var selector = new OptionSelector (); selector.Labels = ["Option1", "Option2"]; selector.Styles = SelectorStyles.None; Assert.Equal (2, selector.SubViews.Count); Assert.Null (selector.SubViews.FirstOrDefault (v => v.Id == "valueField")); } [Fact] public void Styles_Set_ShowValue_AddsValueField () { var selector = new OptionSelector (); selector.Labels = ["Option1", "Option2"]; selector.Styles = SelectorStyles.ShowValue; View? valueField = selector.SubViews.FirstOrDefault (v => v.Id == "valueField"); Assert.NotNull (valueField); Assert.IsType (valueField); } [Fact] public void Styles_Set_ShowValue_ValueFieldDisplaysCurrentValue () { var selector = new OptionSelector (); selector.Labels = ["Option1", "Option2"]; selector.Value = 1; selector.Styles = SelectorStyles.ShowValue; var valueField = (TextField?)selector.SubViews.FirstOrDefault (v => v.Id == "valueField"); Assert.NotNull (valueField); Assert.Equal ("1", valueField.Text); } [Fact] public void Styles_Set_ShowValue_ValueFieldUpdatesOnValueChange () { var selector = new OptionSelector (); selector.Labels = ["Option1", "Option2"]; selector.Styles = SelectorStyles.ShowValue; selector.Value = 1; var valueField = (TextField?)selector.SubViews.FirstOrDefault (v => v.Id == "valueField"); Assert.NotNull (valueField); Assert.Equal ("1", valueField.Text); } [Fact] public void Styles_Set_SameValue_DoesNotRecreateSubViews () { var selector = new OptionSelector (); selector.Labels = ["Option1", "Option2"]; selector.Styles = SelectorStyles.ShowValue; CheckBox firstCheckBox = selector.SubViews.OfType ().First (); selector.Styles = SelectorStyles.ShowValue; // Set to same value // Should be the same instance Assert.Same (firstCheckBox, selector.SubViews.OfType ().First ()); } #endregion #region AssignHotKeys Tests [Fact] public void AssignHotKeys_True_AssignsHotKeysToCheckBoxes () { var selector = new OptionSelector { AssignHotKeys = true }; selector.Labels = ["Option1", "Option2"]; CheckBox [] checkBoxes = selector.SubViews.OfType ().ToArray (); Assert.NotEqual (Key.Empty, checkBoxes [0].HotKey); Assert.NotEqual (Key.Empty, checkBoxes [1].HotKey); } [Fact] public void AssignHotKeys_True_AddsHotKeySpecifierToTitles () { var selector = new OptionSelector { AssignHotKeys = true }; selector.Labels = ["Option1", "Option2"]; CheckBox [] checkBoxes = selector.SubViews.OfType ().ToArray (); Assert.Contains ('_', checkBoxes [0].Title); Assert.Contains ('_', checkBoxes [1].Title); } [Fact] public void AssignHotKeys_True_AssignsUniqueHotKeys () { var selector = new OptionSelector { AssignHotKeys = true }; selector.Labels = ["Option1", "Option2", "Option3"]; CheckBox [] checkBoxes = selector.SubViews.OfType ().ToArray (); var hotKeys = checkBoxes.Select (cb => cb.HotKey).ToList (); Assert.Equal (3, hotKeys.Distinct ().Count ()); // All unique } [Fact] public void AssignHotKeys_False_DoesNotAssignHotKeys () { var selector = new OptionSelector { AssignHotKeys = false }; selector.Labels = ["Option1", "Option2"]; CheckBox [] checkBoxes = selector.SubViews.OfType ().ToArray (); Assert.Equal (Key.Empty, checkBoxes [0].HotKey); Assert.Equal (Key.Empty, checkBoxes [1].HotKey); } [Fact] public void AssignHotKeys_PreservesExistingHotKeys () { var selector = new OptionSelector { AssignHotKeys = true }; selector.Labels = ["_Alt Option", "Option2"]; CheckBox [] checkBoxes = selector.SubViews.OfType ().ToArray (); // Should use 'A' from "_Alt" Assert.Equal (Key.A, checkBoxes [0].HotKey); } #endregion #region UsedHotKeys Tests [Fact] public void UsedHotKeys_SkipsMarkedKeys () { var selector = new OptionSelector { AssignHotKeys = true }; selector.UsedHotKeys.Add (Key.O); // Mark 'O' as used selector.Labels = ["Option1", "Option2"]; CheckBox [] checkBoxes = selector.SubViews.OfType ().ToArray (); // Should skip 'O' and use next available character Assert.NotEqual (Key.O, checkBoxes [0].HotKey); } [Fact] public void UsedHotKeys_PopulatedWhenHotKeysAssigned () { var selector = new OptionSelector { AssignHotKeys = true }; selector.Labels = ["Option1", "Option2"]; // UsedHotKeys should contain the assigned hotkeys Assert.NotEmpty (selector.UsedHotKeys); CheckBox [] checkBoxes = selector.SubViews.OfType ().ToArray (); foreach (CheckBox cb in checkBoxes) { Assert.Contains (cb.HotKey, selector.UsedHotKeys); } } #endregion #region Orientation Tests [Fact] public void Orientation_Vertical_CheckBoxesStackedVertically () { var selector = new OptionSelector (); selector.Labels = ["Option1", "Option2"]; selector.Orientation = Orientation.Vertical; selector.Layout (); CheckBox [] checkBoxes = selector.SubViews.OfType ().ToArray (); Assert.Equal (0, checkBoxes [0].Frame.Y); Assert.True (checkBoxes [1].Frame.Y > checkBoxes [0].Frame.Y); } [Fact] public void Orientation_Horizontal_CheckBoxesArrangedHorizontally () { var selector = new OptionSelector (); selector.Labels = ["Option1", "Option2"]; selector.Orientation = Orientation.Horizontal; selector.Layout (); CheckBox [] checkBoxes = selector.SubViews.OfType ().ToArray (); Assert.Equal (0, checkBoxes [0].Frame.Y); Assert.Equal (0, checkBoxes [1].Frame.Y); Assert.True (checkBoxes [1].Frame.X > checkBoxes [0].Frame.X); } [Fact] public void Orientation_Change_TriggersLayout () { var selector = new OptionSelector (); selector.Labels = ["Option1", "Option2"]; selector.Layout (); CheckBox [] checkBoxes = selector.SubViews.OfType ().ToArray (); int originalYDiff = checkBoxes [1].Frame.Y - checkBoxes [0].Frame.Y; selector.Orientation = Orientation.Horizontal; selector.Layout (); int newYDiff = checkBoxes [1].Frame.Y - checkBoxes [0].Frame.Y; Assert.NotEqual (originalYDiff, newYDiff); Assert.Equal (0, newYDiff); // Both should be at Y=0 now } #endregion #region HorizontalSpace Tests [Fact] public void HorizontalSpace_Default_Is2 () { var selector = new OptionSelector (); Assert.Equal (2, selector.HorizontalSpace); } [Fact] public void HorizontalSpace_Set_UpdatesSpacing () { var selector = new OptionSelector { Orientation = Orientation.Horizontal }; selector.Labels = ["Option1", "Option2"]; selector.HorizontalSpace = 2; selector.Layout (); CheckBox [] checkBoxes = selector.SubViews.OfType ().ToArray (); // HorizontalSpace is applied via Margin.Thickness.Right int spacing2 = checkBoxes [0].Margin!.Thickness.Right; selector.HorizontalSpace = 5; selector.Layout (); int spacing5 = checkBoxes [0].Margin!.Thickness.Right; Assert.True (spacing5 > spacing2); Assert.Equal (2, spacing2); Assert.Equal (5, spacing5); } [Fact] public void HorizontalSpace_OnlyAppliesToHorizontalOrientation () { var selector = new OptionSelector { Orientation = Orientation.Vertical }; selector.Labels = ["Option1", "Option2"]; selector.HorizontalSpace = 10; selector.Layout (); CheckBox [] checkBoxes = selector.SubViews.OfType ().ToArray (); // In vertical mode, checkboxes should be at same X Assert.Equal (checkBoxes [0].Frame.X, checkBoxes [1].Frame.X); } #endregion #region DoubleClickAccepts Tests [Fact] public void DoubleClickAccepts_Default_IsTrue () { var selector = new OptionSelector (); Assert.True (selector.DoubleClickAccepts); } [Fact] public void DoubleClickAccepts_True_AcceptOnDoubleClick () { var selector = new OptionSelector { DoubleClickAccepts = true }; selector.Labels = ["Option1", "Option2"]; selector.Layout (); var acceptCount = 0; selector.Accepting += (s, e) => acceptCount++; CheckBox checkBox = selector.SubViews.OfType ().First (); checkBox.NewMouseEvent (new () { Position = Point.Empty, Flags = MouseFlags.Button1Clicked }); checkBox.NewMouseEvent (new () { Position = Point.Empty, Flags = MouseFlags.Button1DoubleClicked }); Assert.Equal (1, acceptCount); } [Fact] public void DoubleClickAccepts_False_DoesNotAcceptOnDoubleClick () { var selector = new OptionSelector { DoubleClickAccepts = false }; selector.Labels = ["Option1", "Option2"]; selector.Layout (); var acceptCount = 0; selector.Accepting += (s, e) => acceptCount++; CheckBox checkBox = selector.SubViews.OfType ().First (); checkBox.NewMouseEvent (new () { Position = Point.Empty, Flags = MouseFlags.Button1Clicked }); checkBox.NewMouseEvent (new () { Position = Point.Empty, Flags = MouseFlags.Button1DoubleClicked }); Assert.Equal (0, acceptCount); } #endregion #region CreateSubViews Tests [Fact] public void CreateSubViews_RemovesOldSubViewsAndCreatesNew () { var selector = new OptionSelector (); selector.Labels = ["Option1", "Option2"]; int oldCount = selector.SubViews.Count; selector.Labels = ["New1", "New2", "New3"]; Assert.NotEqual (oldCount, selector.SubViews.Count); Assert.Equal (3, selector.SubViews.OfType ().Count ()); Assert.Contains (selector.SubViews.OfType (), cb => cb.Title == "New1"); } [Fact] public void CreateSubViews_SetsCheckBoxProperties () { var selector = new OptionSelector (); selector.Labels = ["Test Option"]; selector.Values = [42]; CheckBox checkBox = selector.SubViews.OfType ().First (); Assert.Equal ("Test Option", checkBox.Title); Assert.Equal ("Test Option", checkBox.Id); Assert.Equal (42, checkBox.Data); Assert.True (checkBox.CanFocus); } #endregion #region HotKey Command Tests [Fact] public void HotKey_Command_DoesNotFireAccept () { var selector = new OptionSelector (); selector.Labels = ["Option1", "Option2"]; var acceptCount = 0; selector.Accepting += (s, e) => acceptCount++; selector.InvokeCommand (Command.HotKey); Assert.Equal (0, acceptCount); } [Fact] public void Accept_Command_FiresAccept () { var selector = new OptionSelector (); selector.Labels = ["Option1", "Option2"]; var acceptCount = 0; selector.Accepting += (s, e) => acceptCount++; selector.InvokeCommand (Command.Accept); Assert.Equal (1, acceptCount); } #endregion #region Edge Cases [Fact] public void EmptyLabels_CreatesNoSubViews () { var selector = new OptionSelector (); selector.Labels = []; Assert.Empty (selector.SubViews); } [Fact] public void Value_WithNoLabels_CanBeSet () { var selector = new OptionSelector (); // This should work even without labels var exception = Record.Exception (() => selector.Value = null); Assert.Null (exception); Assert.Null (selector.Value); } #endregion }