From 439527c8ad4f66689ae55b62a95fa98cd8cbb60e Mon Sep 17 00:00:00 2001 From: mark-sil <83427558+mark-sil@users.noreply.github.com> Date: Thu, 4 Jun 2026 14:32:41 -0400 Subject: [PATCH 1/2] LT-21515: Implement GetToolForList in Pub/Sub system --- Src/LexText/LexTextDll/AreaListener.cs | 12 ++++++------ Src/xWorks/LinkListener.cs | 4 +--- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/Src/LexText/LexTextDll/AreaListener.cs b/Src/LexText/LexTextDll/AreaListener.cs index 61ecee5ef7..bd454aa5a4 100644 --- a/Src/LexText/LexTextDll/AreaListener.cs +++ b/Src/LexText/LexTextDll/AreaListener.cs @@ -130,6 +130,7 @@ protected virtual void Dispose(bool disposing) Subscriber.Unsubscribe(EventConstants.SetToolFromName, SetToolFromName); Subscriber.Unsubscribe(EventConstants.ReloadAreaTools, ReloadAreaTools); Subscriber.Unsubscribe(EventConstants.GetContentControlParameters, GetContentControlParameters); + Subscriber.Unsubscribe(EventConstants.GetToolForList, GetToolForList); Subscriber.Unsubscribe(EventConstants.SetInitialContentObject, SetInitialContentObject); // Dispose managed resources here. @@ -157,6 +158,7 @@ public void Init(Mediator mediator, PropertyTable propertyTable, XmlNode configu Subscriber.Subscribe(EventConstants.SetToolFromName, SetToolFromName); Subscriber.Subscribe(EventConstants.ReloadAreaTools, ReloadAreaTools); Subscriber.Subscribe(EventConstants.GetContentControlParameters, GetContentControlParameters); + Subscriber.Subscribe(EventConstants.GetToolForList, GetToolForList); } private DateTime m_lastToolChange = DateTime.MinValue; @@ -379,12 +381,11 @@ private bool FillListAreaList(UIListDisplayProperties display) } /// - /// This method is called BY REFLECTION through the mediator from LinkListener.FollowActiveLink, because the assembly dependencies + /// This method is called through the FwUtils Publisher/Subscriber from LinkListener.FollowActiveLink, because the assembly dependencies /// are in the wrong direction. It finds the name of the tool we need to invoke to edit a given list. + /// The result is returned via the second element of the object array payload. /// - /// - /// - public bool OnGetToolForList(object parameters) + private void GetToolForList(object parameters) { var realParams = (object[]) parameters; var list = (ICmPossibilityList)realParams[0]; @@ -409,12 +410,11 @@ public bool OnGetToolForList(object parameters) if (possibleList == list) { realParams[1] = toolName; - return true; + return; } } // If it's not a known list, try custom. realParams[1] = GetCustomListToolName(list); - return true; } #region Custom List Methods diff --git a/Src/xWorks/LinkListener.cs b/Src/xWorks/LinkListener.cs index 640761ab86..3af303d4ad 100644 --- a/Src/xWorks/LinkListener.cs +++ b/Src/xWorks/LinkListener.cs @@ -512,9 +512,7 @@ private bool FollowActiveLink(bool suspendLoadingRecord) // Thus we've created this method (on AreaListener) which we call awkwardly throught the mediator. var parameters = new object[2]; parameters[0] = majorObject; -#pragma warning disable 618 // suppress obsolete warning - m_mediator.SendMessage("GetToolForList", parameters); -#pragma warning restore 618 + Publisher.Publish(new PublisherParameterObject(EventConstants.GetToolForList, parameters)); realTool = (string)parameters[1]; break; case RnResearchNbkTags.kClassId: From 5fe81b2fca44b9289bffea287b7b023e9c3ce9b5 Mon Sep 17 00:00:00 2001 From: mark-sil <83427558+mark-sil@users.noreply.github.com> Date: Tue, 9 Jun 2026 10:10:05 -0400 Subject: [PATCH 2/2] Add tests --- .../LexTextDllTests/AreaListenerTests.cs | 87 +++++++++++++++++++ Src/xWorks/LinkListener.cs | 2 +- 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/Src/LexText/LexTextDll/LexTextDllTests/AreaListenerTests.cs b/Src/LexText/LexTextDll/LexTextDllTests/AreaListenerTests.cs index 665773ec92..0277589f58 100644 --- a/Src/LexText/LexTextDll/LexTextDllTests/AreaListenerTests.cs +++ b/Src/LexText/LexTextDll/LexTextDllTests/AreaListenerTests.cs @@ -12,8 +12,10 @@ using NUnit.Framework; using SIL.LCModel; using SIL.LCModel.DomainServices; +using SIL.FieldWorks.Common.FwUtils; using SIL.FieldWorks.XWorks.LexText; using XCore; +using static SIL.FieldWorks.Common.FwUtils.FwUtils; namespace LexTextDllTests { @@ -115,6 +117,35 @@ private static XmlNode SetupMinimalWindowConfig() return fakeWindowConfig.DocumentElement; } + /// + /// Builds a window configuration whose 'lists' area has a single tool wired, via its clerk's + /// recordList, to the possibility list identified by . + /// + private static XmlNode SetupWindowConfigWithListTool(string clerkId, string toolValue, string listGuid) + { + var fakeWindowConfig = new XmlDocument(); + fakeWindowConfig.LoadXml( + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + ""); + return fakeWindowConfig.DocumentElement; + } + #endregion ///-------------------------------------------------------------------------------------- @@ -161,5 +192,61 @@ public void AddListToXmlConfig() var ccontextNodesAfter = node.SelectNodes(contextXPath).Count; Assert.That(ccontextNodesAfter, Is.EqualTo(ccontextNodesBefore + 1), "Didn't add a context menu node."); } + + ///-------------------------------------------------------------------------------------- + /// + /// Publishing EventConstants.GetToolForList for a list that is wired into the window + /// configuration must return that tool's name via the second element of the payload array. + /// This exercises the full Pub/Sub path that LinkListener.FollowActiveLink relies on: + /// publish through the Publisher, the AreaListener subscriber handles it synchronously, + /// and the result comes back in parameters[1]. (LT-21515) + /// + ///-------------------------------------------------------------------------------------- + [Test] + public void GetToolForList_KnownList_ReturnsConfiguredToolName() + { + // Setup: a list wired to a configured tool via its clerk's recordList. + var ws = WritingSystemServices.kwsAnals; + var list = Cache.ServiceLocator.GetInstance().CreateUnowned("Some List", ws); + var windowConfig = SetupWindowConfigWithListTool("someListClerk", "myConfiguredListEdit", list.Guid.ToString()); + m_propertyTable.SetProperty("WindowConfiguration", windowConfig, true); + m_propertyTable.SetPropertyPersistence("WindowConfiguration", false); + + var parameters = new object[2]; + parameters[0] = list; + + // SUT: publish exactly as LinkListener.FollowActiveLink does. + Publisher.Publish(new PublisherParameterObject(EventConstants.GetToolForList, parameters)); + + // Verify: the configured tool name was returned via the payload. + Assert.That(parameters[1], Is.EqualTo("myConfiguredListEdit")); + } + + ///-------------------------------------------------------------------------------------- + /// + /// Publishing EventConstants.GetToolForList for a list that is NOT in the configuration + /// must fall back to the generated custom-list tool name (the list name with whitespace + /// removed, plus "Edit"), returned via parameters[1]. (LT-21515) + /// + ///-------------------------------------------------------------------------------------- + [Test] + public void GetToolForList_UnknownList_ReturnsCustomToolName() + { + // Setup: a window configuration whose 'tools' section has no matching tool. + m_propertyTable.SetProperty("WindowConfiguration", SetupMinimalWindowConfig(), true); + m_propertyTable.SetPropertyPersistence("WindowConfiguration", false); + + var ws = WritingSystemServices.kwsAnals; + var customList = Cache.ServiceLocator.GetInstance().CreateUnowned("My Custom List", ws); + + var parameters = new object[2]; + parameters[0] = customList; + + // SUT + Publisher.Publish(new PublisherParameterObject(EventConstants.GetToolForList, parameters)); + + // Verify: whitespace stripped from the name, with "Edit" appended. + Assert.That(parameters[1], Is.EqualTo("MyCustomListEdit")); + } } } diff --git a/Src/xWorks/LinkListener.cs b/Src/xWorks/LinkListener.cs index 3af303d4ad..c69a9065df 100644 --- a/Src/xWorks/LinkListener.cs +++ b/Src/xWorks/LinkListener.cs @@ -509,7 +509,7 @@ private bool FollowActiveLink(bool suspendLoadingRecord) // Unfortunately AreaListener is in an assembly we can't reference. // But there may be custom ones, so just listing them all here does not seem to be an option, // and anyway it would be hard to maintain. - // Thus we've created this method (on AreaListener) which we call awkwardly throught the mediator. + // Thus we've created this method (on AreaListener) which we call through the FwUtils Publisher/Subscriber. var parameters = new object[2]; parameters[0] = majorObject; Publisher.Publish(new PublisherParameterObject(EventConstants.GetToolForList, parameters));