Parse nested moreCommands on context items in JS host mapper

ParseContextItem only read title/command/icon and dropped any nested moreCommands array, so JS/TS extensions could not surface nested context-menu commands even when the wire carried them. Recursively parse the moreCommands array (reusing ParseContextItems) and assign the children onto the existing CommandContextItem.MoreCommands, leaving the default empty array when the field is absent.

Pairs with the Phase 1 SDK serialization change (8f123a8616).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 8e96286c-f6eb-42d5-bcd3-68fc6a76622f
This commit is contained in:
Michael Jolley
2026-07-15 16:30:55 -05:00
parent 0302d0efbf
commit 47c7b60586
2 changed files with 58 additions and 0 deletions

View File

@@ -281,6 +281,15 @@ internal static class JSModelMapper
item.Subtitle = subtitle;
}
// Context items can carry their own nested context menu via "moreCommands".
// The wire omits the field entirely when empty, so only assign when the
// recursive parse yields children and otherwise leave the default.
var moreCommands = ParseContextItems(element, "moreCommands", "MoreCommands", connection);
if (moreCommands.Length > 0)
{
item.MoreCommands = moreCommands;
}
return item;
}

View File

@@ -139,6 +139,55 @@ public class JSAdapterProxyTests
Assert.AreEqual("Item B", items[2].Title);
}
[TestMethod]
public void ContextItems_ParseNestedMoreCommandsRecursively()
{
using var fake = new JSFakeExtension();
fake.OnResult("provider/getCommand", """{ "id": "nested-list", "pageType": "listPage", "name": "Nested" }""");
var itemsJson =
"""
{
"items": [
{
"title": "Root Item",
"moreCommands": [
{
"command": { "id": "level1", "name": "Level 1" },
"title": "Level 1",
"moreCommands": [
{ "command": { "id": "level2", "name": "Level 2" }, "title": "Level 2" }
]
}
]
},
{ "title": "Leaf Item" }
]
}
""";
fake.OnResult("listPage/getItems", itemsJson);
var provider = CreateProvider(fake);
var page = (IListPage)provider.GetCommand("nested-list")!;
var items = page.GetItems();
Assert.AreEqual(2, items.Length);
// The root item carries a first-level nested command.
var firstLevel = items[0].MoreCommands;
Assert.AreEqual(1, firstLevel.Length);
var firstLevelCommand = (ICommandContextItem)firstLevel[0];
Assert.AreEqual("Level 1", firstLevelCommand.Title);
// That first-level command carries its own second-level nested command.
Assert.AreEqual(1, firstLevelCommand.MoreCommands.Length);
var secondLevelCommand = (ICommandContextItem)firstLevelCommand.MoreCommands[0];
Assert.AreEqual("Level 2", secondLevelCommand.Title);
Assert.AreEqual(0, secondLevelCommand.MoreCommands.Length);
// The leaf item with no moreCommands yields no children.
Assert.AreEqual(0, items[1].MoreCommands.Length);
}
[TestMethod]
public async Task DynamicListPage_ForwardsSearchTextAndRaisesItemsChanged()
{