();
await cut.InvokeAsync(() => SetField(cut.Instance, "_session", session));
await cut.InvokeAsync(async () =>
await InvokeAsyncMethod(cut.Instance, "LoadPreviewAndAdvanceAsync"));
cut.Render();
// The code line-diff block is present and shows one add + one remove line.
Assert.NotNull(cut.Find("[data-testid='code-line-diff']"));
Assert.NotNull(cut.Find("[data-testid='line-diff']"));
Assert.Single(cut.FindAll("[data-testid='line-diff-add']"));
Assert.Single(cut.FindAll("[data-testid='line-diff-remove']"));
// The raw JSON fallback is NOT used for code-field diffs.
Assert.DoesNotContain("\"lineDiff\"", cut.Find("[data-testid='code-line-diff']").InnerHtml);
// No truncation marker for a complete diff.
Assert.Empty(cut.FindAll("[data-testid='line-diff-truncated']"));
}
// ─────────────────────────────────────────────────────────────────────
// Test 11 (M8 E2): truncation marker shows when the lineDiff is truncated.
// ─────────────────────────────────────────────────────────────────────
[Fact]
public async Task Modified_row_with_truncated_lineDiff_shows_truncation_marker()
{
const string fieldDiffJson = """
{
"changes": [
{
"field": "Code",
"oldValue": "x",
"newValue": "y",
"lineDiff": {
"hunks": [
{ "op": "remove", "text": "x", "oldLineNo": 1 },
{ "op": "add", "text": "y", "newLineNo": 1 }
],
"truncated": true,
"addedCount": 12,
"removedCount": 8
}
}
]
}
""";
var session = BuildEncryptedSession(sourceEnv: "prod-cluster");
var preview = new ImportPreview(session.SessionId, new List
{
new("Template", "Pump", 1, 2, ConflictKind.Modified, fieldDiffJson, null),
});
_importer.PreviewAsync(session.SessionId, Arg.Any()).Returns(preview);
var cut = Render();
await cut.InvokeAsync(() => SetField(cut.Instance, "_session", session));
await cut.InvokeAsync(async () =>
await InvokeAsyncMethod(cut.Instance, "LoadPreviewAndAdvanceAsync"));
cut.Render();
var marker = cut.Find("[data-testid='line-diff-truncated']");
Assert.Contains("truncated", marker.TextContent);
Assert.Contains("+12", marker.TextContent);
Assert.Contains("-8", marker.TextContent);
}
// ─────────────────────────────────────────────────────────────────────
// Reflection helpers — the wizard's per-instance state is private (the
// razor partial pattern). We poke at it via reflection rather than
// widening the surface of the production class with test-only accessors.
// ─────────────────────────────────────────────────────────────────────
private static void SetField(object obj, string name, object? value)
{
var field = obj.GetType().GetField(
name,
System.Reflection.BindingFlags.Instance
| System.Reflection.BindingFlags.NonPublic
| System.Reflection.BindingFlags.Public)
?? throw new InvalidOperationException($"Field '{name}' not found on {obj.GetType()}.");
field.SetValue(obj, value);
}
private static T GetField(object obj, string name)
{
var field = obj.GetType().GetField(
name,
System.Reflection.BindingFlags.Instance
| System.Reflection.BindingFlags.NonPublic
| System.Reflection.BindingFlags.Public)
?? throw new InvalidOperationException($"Field '{name}' not found on {obj.GetType()}.");
return (T)field.GetValue(obj)!;
}
private static async Task InvokeAsyncMethod(object obj, string name)
{
var method = obj.GetType().GetMethod(
name,
System.Reflection.BindingFlags.Instance
| System.Reflection.BindingFlags.NonPublic
| System.Reflection.BindingFlags.Public)
?? throw new InvalidOperationException($"Method '{name}' not found on {obj.GetType()}.");
var task = (Task)method.Invoke(obj, Array.Empty