|
| 1 | +// Copyright (c) 2026 SIL International |
| 2 | +// This software is licensed under the LGPL, version 2.1 or later |
| 3 | +// (http://www.gnu.org/licenses/lgpl-2.1.html) |
| 4 | + |
| 5 | +using System.Collections.Generic; |
| 6 | +using System.Linq; |
| 7 | +using System.Threading; |
| 8 | +using System.Windows.Forms; |
| 9 | +using System.Xml; |
| 10 | +using NUnit.Framework; |
| 11 | +using SIL.SilSidePane; |
| 12 | + |
| 13 | +namespace XCore |
| 14 | +{ |
| 15 | + /// <summary> |
| 16 | + /// How <see cref="SidebarAdapter"/> reflects areaChoice and ToolForAreaNamed_ changes in |
| 17 | + /// the side pane without setting those properties again. |
| 18 | + /// </summary> |
| 19 | + [TestFixture] |
| 20 | + [Apartment(ApartmentState.STA)] |
| 21 | + public class SidebarAdapterSelectionTests |
| 22 | + { |
| 23 | + /// <summary> |
| 24 | + /// Two areas with two tools each, in the shape of the sidebar section of Main.xml. |
| 25 | + /// </summary> |
| 26 | + private const string SidebarXml = |
| 27 | + "<root>" |
| 28 | + + "<lists>" |
| 29 | + + "<list id=\"AreasList\">" |
| 30 | + + "<item label=\"Lexicon\" value=\"lexicon\"><parameters><panels>" |
| 31 | + + "<listPanel label=\"Lexical Tools\" listId=\"LexicalToolsList\"/></panels></parameters></item>" |
| 32 | + + "<item label=\"Grammar\" value=\"grammar\"><parameters><panels>" |
| 33 | + + "<listPanel label=\"Grammar Tools\" listId=\"GrammarToolsList\"/></panels></parameters></item>" |
| 34 | + + "</list>" |
| 35 | + + "<list id=\"LexicalToolsList\">" |
| 36 | + + "<item label=\"Lexicon Edit\" value=\"lexiconEdit\"><control/></item>" |
| 37 | + + "<item label=\"Browse\" value=\"lexiconBrowse\"><control/></item>" |
| 38 | + + "</list>" |
| 39 | + + "<list id=\"GrammarToolsList\">" |
| 40 | + + "<item label=\"Category Edit\" value=\"posEdit\"><control/></item>" |
| 41 | + + "<item label=\"Category Browse\" value=\"posBrowse\"><control/></item>" |
| 42 | + + "</list>" |
| 43 | + + "</lists>" |
| 44 | + + "<sidebar>" |
| 45 | + + "<tab label=\"Areas\" list=\"AreasList\" behavior=\"singlePropertyAtomicValue\" property=\"areaChoice\"/>" |
| 46 | + + "<tab label=\"Lexical Tools\" list=\"LexicalToolsList\" behavior=\"singlePropertyAtomicValue\"" |
| 47 | + + " property=\"currentContentControl\"/>" |
| 48 | + + "<tab label=\"Grammar Tools\" list=\"GrammarToolsList\" behavior=\"singlePropertyAtomicValue\"" |
| 49 | + + " property=\"currentContentControl\"/>" |
| 50 | + + "</sidebar>" |
| 51 | + + "</root>"; |
| 52 | + |
| 53 | + private Form m_window; |
| 54 | + private Mediator m_mediator; |
| 55 | + private PropertyTable m_propertyTable; |
| 56 | + private ImageCollection m_smallImages; |
| 57 | + private ImageCollection m_largeImages; |
| 58 | + private SidebarAdapter m_adapter; |
| 59 | + private Control m_panel; |
| 60 | + private ChoiceGroupCollection m_groups; |
| 61 | + |
| 62 | + [SetUp] |
| 63 | + public void SetUp() |
| 64 | + { |
| 65 | + m_window = new Form(); |
| 66 | + m_mediator = new Mediator(); |
| 67 | + m_propertyTable = new PropertyTable(m_mediator); |
| 68 | + m_smallImages = new ImageCollection(false); |
| 69 | + m_largeImages = new ImageCollection(true); |
| 70 | + m_adapter = new SidebarAdapter(); |
| 71 | + m_panel = m_adapter.Init(m_window, m_smallImages, m_largeImages, m_mediator, m_propertyTable); |
| 72 | + m_window.Controls.Add(m_panel); |
| 73 | + |
| 74 | + var doc = new XmlDocument(); |
| 75 | + doc.LoadXml(SidebarXml); |
| 76 | + m_groups = new ChoiceGroupCollection(m_mediator, m_propertyTable, m_adapter, |
| 77 | + doc.DocumentElement.SelectSingleNode("sidebar")); |
| 78 | + m_groups.Init(); |
| 79 | + m_adapter.FinishInit(); |
| 80 | + CreateHandles(m_window); |
| 81 | + } |
| 82 | + |
| 83 | + [TearDown] |
| 84 | + public void TearDown() |
| 85 | + { |
| 86 | + m_adapter.Dispose(); |
| 87 | + m_window.Dispose(); |
| 88 | + m_smallImages.Dispose(); |
| 89 | + m_largeImages.Dispose(); |
| 90 | + m_propertyTable.Dispose(); |
| 91 | + m_mediator.Dispose(); |
| 92 | + } |
| 93 | + |
| 94 | + /// <summary> |
| 95 | + /// The side pane's list views raise their selection events only once they have a |
| 96 | + /// window handle, which a hidden child of an unshown form never gets on its own. |
| 97 | + /// </summary> |
| 98 | + private static void CreateHandles(Control control) |
| 99 | + { |
| 100 | + var unused = control.Handle; |
| 101 | + foreach (Control child in control.Controls) |
| 102 | + CreateHandles(child); |
| 103 | + } |
| 104 | + |
| 105 | + private static IEnumerable<Control> AllControls(Control root) |
| 106 | + { |
| 107 | + yield return root; |
| 108 | + foreach (Control child in root.Controls) |
| 109 | + foreach (var descendant in AllControls(child)) |
| 110 | + yield return descendant; |
| 111 | + } |
| 112 | + |
| 113 | + /// <summary> |
| 114 | + /// Every side-pane item widget, across all the tabs' item lists. The form is never |
| 115 | + /// shown, so a list's Visible flag cannot tell which tab is current. |
| 116 | + /// </summary> |
| 117 | + private IEnumerable<ListViewItem> AllItemWidgets() |
| 118 | + { |
| 119 | + return AllControls(m_panel).OfType<ListView>().SelectMany(list => list.Items.Cast<ListViewItem>()); |
| 120 | + } |
| 121 | + |
| 122 | + /// <summary> |
| 123 | + /// The tool selected in the item list that holds the named tool, or null when none is. |
| 124 | + /// Each tab's list keeps its own selection, so the list is named by one of its tools. |
| 125 | + /// </summary> |
| 126 | + private string SelectedToolInTheListHolding(string tool) |
| 127 | + { |
| 128 | + var list = AllItemWidgets().First(widget => ((Item)widget.Tag).Name == tool).ListView; |
| 129 | + if (list.SelectedItems.Count == 0) |
| 130 | + return null; |
| 131 | + return ((ListPropertyChoice)((Item)list.SelectedItems[0].Tag).Tag).Value; |
| 132 | + } |
| 133 | + |
| 134 | + private void ShowArea(string area, string rememberedTool) |
| 135 | + { |
| 136 | + m_propertyTable.SetProperty("areaChoice", area, false); |
| 137 | + m_propertyTable.SetProperty("ToolForAreaNamed_" + area, rememberedTool, false); |
| 138 | + m_adapter.OnPropertyChanged("areaChoice"); |
| 139 | + } |
| 140 | + |
| 141 | + [Test] |
| 142 | + public void AreaChoiceChange_SelectsTheRememberedToolWithoutSettingAnyProperty() |
| 143 | + { |
| 144 | + ShowArea("grammar", "posBrowse"); |
| 145 | + |
| 146 | + Assert.That(SelectedToolInTheListHolding("posBrowse"), Is.EqualTo("posBrowse")); |
| 147 | + Assert.That(m_mediator.JobItems, Is.EqualTo(0), "the selection must not set a property"); |
| 148 | + Assert.That(m_propertyTable.PropertyExists("currentContentControl"), Is.False); |
| 149 | + Assert.That(m_propertyTable.PropertyExists("areaChoiceParameters"), Is.False); |
| 150 | + } |
| 151 | + |
| 152 | + [Test] |
| 153 | + public void ReturnToAnArea_SelectsTheToolRememberedSinceTheLastVisit() |
| 154 | + { |
| 155 | + ShowArea("lexicon", "lexiconEdit"); |
| 156 | + ShowArea("grammar", "posEdit"); |
| 157 | + |
| 158 | + ShowArea("lexicon", "lexiconBrowse"); |
| 159 | + |
| 160 | + Assert.That(SelectedToolInTheListHolding("lexiconBrowse"), Is.EqualTo("lexiconBrowse")); |
| 161 | + Assert.That(m_mediator.JobItems, Is.EqualTo(0), "the selection must not set a property"); |
| 162 | + Assert.That(m_propertyTable.PropertyExists("currentContentControl"), Is.False); |
| 163 | + } |
| 164 | + |
| 165 | + [Test] |
| 166 | + public void ToolForAreaChange_SelectsThatToolWithoutSettingAnyProperty() |
| 167 | + { |
| 168 | + ShowArea("lexicon", "lexiconEdit"); |
| 169 | + m_propertyTable.SetProperty("ToolForAreaNamed_lexicon", "lexiconBrowse", false); |
| 170 | + |
| 171 | + m_adapter.OnPropertyChanged("ToolForAreaNamed_lexicon"); |
| 172 | + |
| 173 | + Assert.That(SelectedToolInTheListHolding("lexiconBrowse"), Is.EqualTo("lexiconBrowse")); |
| 174 | + Assert.That(m_mediator.JobItems, Is.EqualTo(0), "the selection must not set a property"); |
| 175 | + Assert.That(m_propertyTable.PropertyExists("currentContentControl"), Is.False); |
| 176 | + } |
| 177 | + |
| 178 | + [Test] |
| 179 | + public void ItemSelectedOutsideTheAdapter_StillSetsTheProperty() |
| 180 | + { |
| 181 | + ShowArea("lexicon", "lexiconEdit"); |
| 182 | + |
| 183 | + AllItemWidgets().First(widget => ((Item)widget.Tag).Name == "lexiconBrowse").Selected = true; |
| 184 | + |
| 185 | + Assert.That(m_propertyTable.GetStringProperty("currentContentControl", null), Is.EqualTo("lexiconBrowse")); |
| 186 | + Assert.That(m_mediator.JobItems, Is.GreaterThan(0), "a selection the adapter did not make is a click"); |
| 187 | + } |
| 188 | + } |
| 189 | +} |
0 commit comments