Tests for XamlNodeDiff.ComputeDiff(), the build-time diff engine that compares two XAML node trees and produces a change set of property changes (set/clear) and child list mutations (add/remove/reorder).
All XAML snippets are wrapped in <ContentPage xmlns="..." x:Class="Test.MyPage">…</ContentPage>. The Diff block shows the exact ToDebugString() output where available.
<Label Text="Hello" TextColor="Blue" />→ same
Diff: empty
<Label Text="Hello" TextColor="Blue" />→ same
Diff: empty
<!-- no content -->→ same
Diff: empty
Old: <ContentPage Title="Hello">…
New: <ContentPage Title="World">…
Diff: 1 node(s) with property changes
[root] Title = "World"
| NodeChanges | "" (root): Title = "World" (Set) |
|---|---|
| ChildListChanges | 0 |
Old
<Label Text="Hello" />New
<Label Text="World" />Diff: 1 node(s) with property changes
[Label_0] Text = "World"
Old: <ContentPage Title="Hello">
New: <ContentPage>
| NodeChanges | "" (root): Title cleared |
|---|---|
| ChildListChanges | 0 |
Old: <ContentPage>
New: <ContentPage Title="New">
| NodeChanges | "" (root): Title = "New" (Set) |
|---|---|
| ChildListChanges | 0 |
Old
<Label Text="Hello" FontSize="14" />New
<Label Text="World" FontSize="18" />Diff: 1 node(s) with property changes
[Label_0] Text = "World", FontSize = "18"
Old
<VerticalStackLayout>
<Label Text="A" />
<Label Text="B" />
</VerticalStackLayout>New
<VerticalStackLayout>
<Label Text="X" />
<Label Text="Y" />
</VerticalStackLayout>Diff: 2 node(s) with property changes
[VerticalStackLayout_0/Label_0] Text = "X"
[VerticalStackLayout_0/Label_1] Text = "Y"
Old
<VerticalStackLayout>
<Grid>
<Label Text="Deep" />
</Grid>
</VerticalStackLayout>New: Label Text → "Changed"
Diff: 1 node(s) with property changes
[VerticalStackLayout_0/Grid_0/Label_0] Text = "Changed"
Old
<Label Text="Hello" />New
<Label Text="Hello" FontSize="20" />Diff: 1 node(s) with property changes
[Label_0] FontSize = "20"
Old
<Label Text="Hello" FontSize="14" />New
<Label Text="Hello" />Diff: 1 node(s) with property changes
[Label_0] FontSize cleared
Old: <ContentPage …>
New: <ContentView …>
Diff: null — full reload required
Old
<Label x:Name="oldLabel" Text="Hello" />New
<Label x:Name="newLabel" Text="Hello" />Diff: null — x:Name generates a field in code-behind
Old: <ContentPage x:DataType="MyViewModel">
New: <ContentPage x:DataType="OtherViewModel">
Diff: null — x:DataType drives compiled bindings
<Label x:Name="myLabel" Text="Hello" />→ same
Diff: empty (unchanged x:Name is fine)
Old
<VerticalStackLayout>
<Label Text="A" />
<Button Text="B" />
</VerticalStackLayout>New
<VerticalStackLayout>
<Button Text="B" />
<Label Text="A" />
</VerticalStackLayout>Diff: 0 node(s) with property changes, 1 child list change(s)
children [VerticalStackLayout_0] VerticalStackLayout_0/Button_1 → VerticalStackLayout_0/Button_0, VerticalStackLayout_0/Label_0 → VerticalStackLayout_0/Label_1
Old
<VerticalStackLayout>
<Label Text="A" />
<Label Text="B" />
</VerticalStackLayout>New: Labels swapped (Text="B", Text="A")
Two Labels can't be distinguished by type — treated as positional property changes:
| NodeChanges | 2 — Label_0: Text="B", Label_1: Text="A" |
|---|---|
| ChildListChanges | 0 |
Old: Label(A), Button(B) → New: Button(B2), Label(A)
| NodeChanges | 1 — Button: Text="B2" |
|---|---|
| ChildListChanges | 1 — reorder |
Old: Label(A), Button(B) → New: Label(A2), Button(B2)
| NodeChanges | 2 — both Text changed |
|---|---|
| ChildListChanges | 0 |
Old: Label, Button, Entry → New: Entry, Label, Button
| ChildListChanges | 1 — 3 entries: Entry(2→0), Label(0→1), Button(1→2) |
|---|
Old
<VerticalStackLayout>
<Label Text="A" />
</VerticalStackLayout>New
<VerticalStackLayout>
<Label Text="A" />
<Button Text="B" />
</VerticalStackLayout>Diff: 0 node(s) with property changes, 1 child list change(s)
children [VerticalStackLayout_0] VerticalStackLayout_0/Label_0 (unchanged), +VerticalStackLayout_0/Button_1
Old: Label(A), Button(B) → New: Label(A)
Diff: 0 node(s) with property changes, 1 child list change(s)
children [VerticalStackLayout_0] VerticalStackLayout_0/Label_0 (unchanged); removed: -VerticalStackLayout_0/Button_1
Old: Label, Button → New: Label, Entry, Button
| ChildListChanges | Label retained(0→0), Entry added(1), Button retained(1→2) |
|---|
Old: Label, Button, Entry → New: Label, Switch, Entry
Diff: 0 node(s) with property changes, 1 child list change(s)
children [VerticalStackLayout_0] VerticalStackLayout_0/Label_0 (unchanged), +VerticalStackLayout_0/Switch_1; removed: -VerticalStackLayout_0/Button_1
Old: Label(A) → New: Label(A), Label({Binding Name})
| ChildListChanges | 1 — Label retained, Label(binding) added |
|---|
Diff succeeds — codegen decides how to handle the binding.
Old: Label(A) → New: Label(A), Button(B), Entry(C)
| ChildListChanges | Label retained + Button added + Entry added |
|---|
Old: <Label Text="Hello" /> → New: <Entry Text="Hello" />
| ChildListChanges | Label_0 removed, Entry_0 added |
|---|
Old: <Label Text="Hello" /> → New: <Label Text="Hello" /><Label Text="World" />
| ChildListChanges | 1 — second Label added |
|---|
Old: <Label Text="Hello" /><Label Text="World" /> → New: <Label Text="Hello" />
| ChildListChanges | 1 — second Label removed |
|---|
Old
<Label Text="Hello" />New
<Label Text="{Binding Name}" />Diff: 1 node(s) with property changes
[Label_0] Text = {MarkupNode}
| Property | Text | Set | NewValue=null, NewNode=MarkupNode |
|---|
Old: <Label Text="{Binding Name}" />
New: <Label Text="Hello" />
| Property | Text | Set | NewValue="Hello", NewNode=null |
|---|
Old: <Label />
New: <Label Text="{Binding Name}" />
| Property | Text | Set | NewNode=MarkupNode |
|---|
Old
<Button>
<Button.Shadow>
<Shadow Color="Red" />
</Button.Shadow>
</Button>New: Shadow Color → "Blue"
| NodeChanges | Button_0: Shadow = {ElementNode} |
|---|
Same <Shadow Color="Red" /> on both sides.
Diff: empty (recursive ElementNodeEquals confirms equality)
Old: <Button Text="Click" />
New: <Button Text="Click"><Button.Shadow><Shadow Color="Red" /></Button.Shadow></Button>
| NodeChanges | Button_0: Shadow = {ElementNode} (Set) |
|---|
Same <Label.GestureRecognizers> with TapGestureRecognizer + SwipeGestureRecognizer on both sides.
Diff: empty (recursive ListNodeEquals confirms equality)
Old: GestureRecognizers = [TapGestureRecognizer]
New: GestureRecognizers = [TapGestureRecognizer, SwipeGestureRecognizer]
| NodeChanges | Label_0: GestureRecognizers = {ListNode} |
|---|
Old: <Label>Hello</Label>
New: <Label>World</Label>
Diff: 1 node(s) with property changes
[root] _Content = "World"
<Label>Hello</Label> → same
Diff: empty
Old: Title="Page1", <Label Text="Hello" />
New: Title="Page2", <Label Text="World" />
| NodeChanges | 2 — root: Title="Page2", Label_0: Text="World" |
|---|---|
| ChildListChanges | 0 |
Old: VSL > Label(A)
New: VSL > Label(B), Button(New)
| NodeChanges | 1 — Label: Text="B" |
|---|---|
| ChildListChanges | 1 — Button added |
Old: VSL > Label(A), Button(B)
New: VSL > Label(Changed)
| NodeChanges | 1 — Label: Text="Changed" |
|---|---|
| ChildListChanges | 1 — Button removed |
Old: Label(A, FontSize=14), Button(B)
New: Button(B2), Label(A2, FontSize=20)
| NodeChanges | 2 — Label: Text+FontSize, Button: Text |
|---|---|
| ChildListChanges | 1 — reorder |
Old
<VerticalStackLayout>
<HorizontalStackLayout>
<Label Text="Deep" />
</HorizontalStackLayout>
<Entry Text="Remove" />
</VerticalStackLayout>New
<VerticalStackLayout>
<HorizontalStackLayout>
<Label Text="Changed" />
</HorizontalStackLayout>
<Switch />
</VerticalStackLayout>| NodeChanges | 1 — VSL_0/HSL_0/Label_0: Text="Changed" |
|---|---|
| ChildListChanges | 1 — Entry removed, Switch added |
Old: Root Title="Old", VSL Spacing="10", Label Text="A", Button Text="B"
New: Root Title="New", VSL Spacing="20", Label Text="A2", Button Text="B2"
| NodeChanges | 4 — root, VSL_0, VSL_0/Label_0, VSL_0/HSL_1/Button_0 |
|---|---|
| ChildListChanges | 0 |
Old: VSL > Label(Text="Static")
New: VSL > Label(Text="{Binding Name}"), Entry(Placeholder="New")
| NodeChanges | 1 — Label: Text = {MarkupNode} |
|---|---|
| ChildListChanges | 1 — Entry added |
Old: Title="Old", VSL > Label(A), Button(B)
New: Title="New", VSL > Label(A2), Switch
Debug string contains:
[root] Title = "New"Text = "A2"+(Switch added)removed:(Button removed)
xaml1: <Label Text="Draft" TextColor="Gray" />
xaml2: <Label Text="Hello" TextColor="Gray" />
xaml3: <Label Text="Hello" TextColor="Blue" />
xaml4: <Label Text="Hello, World!" TextColor="Blue" />
| Transition | Diff |
|---|---|
| v1→v2 | Text = "Hello" |
| v2→v3 | TextColor = "Blue" |
| v3→v4 | Text = "Hello, World!" |
xaml1: VSL > Label("Title")
xaml2: VSL > Label("Title"), Entry("Name")
xaml3: VSL > Label("Title"), Entry("Name"), Button("Submit")
| Transition | Diff |
|---|---|
| v1→v2 | +Entry |
| v2→v3 | +Button |
xaml1: VSL > Label("Hello")
xaml2: VSL > Label("Hello"), Button("Click")
xaml3: VSL > Label("Hello") ← undo
xaml4: VSL > Label("Goodbye")
| Transition | Diff |
|---|---|
| v1→v2 | +Button |
| v2→v3 | −Button |
| v3→v4 | Text = "Goodbye" (property only) |
xaml1: VSL > Label(A), Button(B), Entry(C)
xaml2: VSL > Entry(C), Label(A), Button(B)
xaml3: VSL > Entry("Search..."), Label(A), Button(B)
| Transition | Diff |
|---|---|
| v1→v2 | Reorder (child list change, no property changes) |
| v2→v3 | Placeholder = "Search..." (property only, no reorder) |
xaml1: <Label Text="Static" />
xaml2: <Label Text="{Binding Name}" />
xaml3: <Label Text="Back to static" />
| Transition | Diff |
|---|---|
| v1→v2 | Text = {MarkupNode} (NewNode set, NewValue null) |
| v2→v3 | Text = "Back to static" (NewNode null, NewValue set) |
xaml1: VSL > Entry("Name"), Button("Submit")
xaml2: VSL > Editor("Name"), Button("Submit")
xaml3: VSL > Editor("Bio"), ImageButton
| Transition | Diff |
|---|---|
| v1→v2 | −Entry, +Editor |
| v2→v3 | −Button, +ImageButton, Editor Placeholder="Bio" |
Root property change → NodeId is "" (displayed as [root] in debug string).
<Label Text="A" /> → NodeId = Label_0
<VerticalStackLayout><Label Text="A" /></VerticalStackLayout> → NodeId = VerticalStackLayout_0/Label_0
No content on either side.
Diff: empty
Old: <ContentPage BackgroundColor="White">
New: <ContentPage BackgroundColor="Black">
| NodeChanges | root: BackgroundColor = "Black" |
|---|