fix(vt): list column fix round 5/11 — every column throw names its column

BuildListColumn's per-attribute binder calls used the generic "column
items"/"column values"/"column onchange"/"column onclick" context
strings from the initial slice — indistinguishable when a list has
several columns of the same type. Route every column-attribute binder
call (text items/colors/onclick, check values/onchange, icon
values/onclick) through the ColumnContext helper item 2 introduced, so
every throw message reads column[N] type="..." attr. ValidateIconKind
gains an optional context parameter (default "iconkind" for the
existing non-column call sites — <icon>, <button icon>, <list icons>,
none of which changed message-wise beyond wording) so the column
iconkind check can identify its own column too.

Updated all twelve column-throw tests in MarkupListColumnsTests to
assert Assert.Contains on the distinguishing phrase: the ten
attribute-specific ones now check column[0] type="..." attr; the
unknown-<column type> and combined-with-legacy-attribute tests keep
(and, for the type one, add to) their existing distinguishing
assertions. Confirmed each attribute-specific assertion is meaningful
by first running the malformed-colors case against the pre-fix
"column colors must be..." message (mismatch), then landing the
production change alongside the rest.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-09-06 21:03:43 +02:00
parent b58668c6cd
commit 5d760331f6
2 changed files with 38 additions and 19 deletions

View file

@ -616,13 +616,21 @@ public static class MarkupDocument
/// with no <see cref="IMarkupIconResolver"/> wired at all. A malformed
/// attribute is a Build-time author error regardless of what the host
/// happens to support.
///
/// <para>
/// Fix round item 5: <paramref name="context"/> (default <c>"iconkind"</c>
/// for the non-column call sites — <c>&lt;icon&gt;</c>, <c>&lt;button
/// icon&gt;</c>, <c>&lt;list icons&gt;</c>) prefixes the throw message so
/// <c>&lt;column type="icon"&gt;</c>'s own call site can identify which
/// column failed (<c>column[2] type="icon" iconkind</c>).
/// </para>
/// </summary>
private static string ValidateIconKind(string? iconKind) =>
private static string ValidateIconKind(string? iconKind, string context = "iconkind") =>
(iconKind ?? "did") switch
{
"did" or "spell" or "item" => iconKind ?? "did",
var other => throw new FormatException(
$"unknown iconkind \"{other}\" (expected did, spell, or item)"),
$"{context} must be did, spell, or item (got \"{other}\")"),
};
/// <summary>
@ -1005,11 +1013,11 @@ public static class MarkupDocument
case "text":
{
var textSource = BindStringList(
(string?)columnEl.Attribute("items"), binding, "column items");
(string?)columnEl.Attribute("items"), binding, ColumnContext(index, "text", "items"));
string? colorsAttr = (string?)columnEl.Attribute("colors");
Func<IReadOnlyList<uint>>? colorsSource = colorsAttr is null
? null
: BindUintList(colorsAttr, binding, "column colors");
: BindUintList(colorsAttr, binding, ColumnContext(index, "text", "colors"));
// Fix round finding 1: optional onclick — a text cell that
// declares one fires it with the row index instead of
// selecting; one that doesn't keeps the original
@ -1029,19 +1037,19 @@ public static class MarkupDocument
case "check":
{
var checkSource = BindBoolList(
(string?)columnEl.Attribute("values"), binding, "column values");
(string?)columnEl.Attribute("values"), binding, ColumnContext(index, "check", "values"));
var onChange = BindRequiredIntAction(
(string?)columnEl.Attribute("onchange"), binding, "column onchange");
(string?)columnEl.Attribute("onchange"), binding, ColumnContext(index, "check", "onchange"));
return UiMarkupListColumn.Check(width, checkSource, onChange, isAutoWidth);
}
case "icon":
{
var valuesSource = BindRequiredUintList(
(string?)columnEl.Attribute("values"), binding, "column values");
(string?)columnEl.Attribute("values"), binding, ColumnContext(index, "icon", "values"));
string? iconKind = (string?)columnEl.Attribute("iconkind");
ValidateIconKind(iconKind);
ValidateIconKind(iconKind, ColumnContext(index, "icon", "iconkind"));
var onClick = BindRequiredIntAction(
(string?)columnEl.Attribute("onclick"), binding, "column onclick");
(string?)columnEl.Attribute("onclick"), binding, ColumnContext(index, "icon", "onclick"));
Func<uint, (uint, int, int)>? resolve = icons is not null
? BuildRowIconResolve(iconKind, icons)
: null;