diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Models/JSModelMapper.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Models/JSModelMapper.cs index 7a6f1555e7..40599bcb1d 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Models/JSModelMapper.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Models/JSModelMapper.cs @@ -634,9 +634,10 @@ internal static class JSModelMapper private static byte ReadByte(JsonElement element, string camel, string pascal, byte defaultValue = 0) { - if (TryGetAnyCase(element, camel, pascal, out var prop) && prop.ValueKind == JsonValueKind.Number) + if (TryGetAnyCase(element, camel, pascal, out var prop) && prop.ValueKind == JsonValueKind.Number && + prop.TryGetByte(out var value)) { - return prop.GetByte(); + return value; } return defaultValue; @@ -645,9 +646,10 @@ internal static class JSModelMapper private static int ReadInt(JsonElement element, string name, int defaultValue) { if (element.ValueKind == JsonValueKind.Object && - element.TryGetProperty(name, out var prop) && prop.ValueKind == JsonValueKind.Number) + element.TryGetProperty(name, out var prop) && prop.ValueKind == JsonValueKind.Number && + prop.TryGetInt32(out var value)) { - return prop.GetInt32(); + return value; } return defaultValue; diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.UI.ViewModels.UnitTests/JSAdapterRemediationTests.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.UI.ViewModels.UnitTests/JSAdapterRemediationTests.cs index a1beb3990a..ba6069d2c2 100644 --- a/src/modules/cmdpal/Tests/Microsoft.CmdPal.UI.ViewModels.UnitTests/JSAdapterRemediationTests.cs +++ b/src/modules/cmdpal/Tests/Microsoft.CmdPal.UI.ViewModels.UnitTests/JSAdapterRemediationTests.cs @@ -391,6 +391,53 @@ public partial class JSAdapterRemediationTests Assert.IsFalse(withoutAccent.AccentColor.HasValue); } + // p3-12: a tag whose color components are out of byte range or fractional must + // not throw out of the WinRT-visible Tags getter. Every numeric component that + // does not fit is dropped to its default, and the surrounding item metadata + // (title, subtitle, other tags) is preserved rather than collapsing to Error. + [TestMethod] + public void Tags_OutOfRangeOrFractionalColorComponentsDefaultInsteadOfThrowing() + { + using var fake = new JSFakeExtension(); + var element = ParseElement(new JsonObject + { + ["title"] = "Item With Bad Tag Color", + ["subtitle"] = "Still Here", + ["tags"] = new JsonArray + { + new JsonObject + { + ["text"] = "over", + ["foreground"] = new JsonObject + { + // 256 overflows a byte and 1.5 is fractional; both would throw + // from JsonElement.GetByte, so they must fall back to defaults. + ["r"] = 256, + ["g"] = 1.5, + ["b"] = 12, + }, + }, + new JsonObject { ["text"] = "clean" }, + }, + }); + + var adapter = new JSListItemAdapter(element, fake.Connection); + + var tags = adapter.Tags; + Assert.AreEqual(2, tags.Length); + Assert.AreEqual("over", tags[0].Text); + Assert.AreEqual("clean", tags[1].Text); + + var foreground = tags[0].Foreground; + Assert.IsTrue(foreground.HasValue); + Assert.AreEqual(0, foreground.Color.R); + Assert.AreEqual(0, foreground.Color.G); + Assert.AreEqual(12, foreground.Color.B); + + Assert.AreEqual("Item With Bad Tag Color", adapter.Title); + Assert.AreEqual("Still Here", adapter.Subtitle); + } + private static async Task SubmitAndReadFormId(JSFormContentProxy form, TaskCompletionSource captured) { await Task.Run(() => form.SubmitForm("{}", "{}"));