To audit what each UI test actually exercises, every step now snapshots the
live window to <TestFixtureName>/<TestName>_<NN>_<ShortDescription>.png: a
booted frame (emitted automatically by TestHarness.BootAsync), one after each
state-changing action, and one before each assertion. Flip ILSPY_TESTS_VISIBLE=1
to render the filmstrip; it lands under %TEMP%/ilspy-test-captures (overridable
via ILSPY_TEST_CAPTURES).
The step number and fixture/test name are derived automatically so inserting a
breakpoint never renumbers the rest. The identity is recorded up front from the
real ITest in an ITestAction hook rather than read live: NUnit's
TestContext.CurrentContext does not flow onto async continuations, so a capture
after an await would otherwise collide under the ad-hoc context. And when
rendering is off the whole call is a true no-op -- not even a dispatcher pump --
so instrumenting a test can never perturb the navigation/tab timing it asserts
on. Full headless suite stays green.
Nearly every headless UI test opened with the same four-line prologue
(resolve the shared MainWindow, show it, cast its DataContext, wait for the
assembly list), then repeated the corelib lookup, the EnsureLazyChildren +
Children.OfType<T>().Single() drill, the registry single-by-header lookups,
and the open-an-assembly-and-wait dance. The duplication made the intent of
each test hard to see and every signature tweak a suite-wide edit.
Collapse those into TestHarness (BootAsync, OpenAssemblyAsync, GetCommand,
GetEntry) and TreeNavigation extensions (FindCoreLib, GetChild<T>, Expand<T>),
then apply them across the suite. Net ~865 lines of boilerplate removed with
no change in behaviour; the full headless suite stays green.
Matches the WPF behaviour at master:ILSpy/AssemblyTree/AssemblyTreeModel.cs:440
where DEBUG builds get "ILSpy {DecompilerVersionInfo.FullVersion}" so the
developer can tell from a glance which build the running window came from
(especially handy when the avalonia-preview tag floats forward and several
windows are open at once). Release builds keep the bare "ILSpy" title.
The title is bound from MainWindowViewModel.Title (axaml: Title="{Binding
Title}"), so threading the version through the VM is the natural seam --
no axaml change and the bound value can later be made notifying if we
want to mix in the active assembly-list name.
Assisted-by: Claude:claude-opus-4-7:Claude Code
GrayscaleAwareImage : Image bakes a luma-transformed Bitmap on the disabled
edge (BT.601 weights) and swaps Source. Replaces the blanket Opacity=0.35
setter that was the previous cue. Applied to the static toolbar buttons in
MainToolBar.axaml plus the MEF-built buttons constructed in BuildButton.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Replaces the brittle root.GetVisualDescendants().OfType<T>().Single() / .First()
pattern with a new WaitForComponent<T>() extension that polls until the requested
control is in the visual tree, then returns it. Avalonia.Headless tests routinely
queried the visual tree before lazily-templated panes (DataGrid, dock content)
had materialised, surfacing as intermittent 'Sequence contains no elements'
failures across the suite.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Each tree-expansion, selection change, command execution, async wait and
key/mouse injection inside an [AvaloniaTest] now carries a short imperative
comment on the line above (e.g. "// expand typeNode", "// select methodA",
"// wait for assemblies to load", "// execute aboutCmd"). The comments are
the same scaffolding the manual debug-with-screenshot workflow uses to
follow what's happening at each breakpoint, surfaced into the committed
source so the tests are readable without the breakpoint markers attached.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Adds a leading paragraph describing what each test verifies and Arrange /
Act / Assert markers (split into numbered phases for multi-step tests) so
the intent of each fixture is readable from comments alone.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Added two new headless tests in MainWindowTests:
- Back_Navigation_Restores_Previously_Selected_Node — selects two methods on
Enumerable, executes DockWorkspace.NavigateBackCommand, asserts the original
node is reselected, decompiled text reverts, and the row is centred.
- Opening_Assembly_Adds_It_To_The_Tree — drives the production OpenCommand via
MainMenuCommandRegistry (matched by Resources._Open header), confirms the
new LoadedAssembly appears, the AssemblyTreeNode is reachable by short name,
and SharpTreeNode.IsSelected is set on the freshly opened node.
Assisted-by: Claude:claude-opus-4-7:Claude Code
DecompileAsEnumerableTest exercises the full path: open mscorlib, navigate
to Enumerable.AsEnumerable, render the decompilation, verify text and
verify the row centred in the assembly tree. Writing it surfaced two
pre-existing bugs the manual smoke-test missed:
New SessionSettings (ObservableObject + ISettingsSection) saved under
<SessionSettings> in ILSpy.xml: ActiveAssemblyList, WindowState, and
WindowBounds (Left/Top/Width/Height attributes).
Assisted-by: Claude:claude-opus-4-7:Claude Code
Adds the assembly tree pane (DataGrid-based, ProDataGrid hierarchical
rows), the SharpTreeNode-derived node type skeletons it consumes, the
empty MainToolBar placeholder, the ILSpySettings file-path provider
wiring in App.axaml.cs, and the AssemblyTreeModel.Initialize() hook in
the MainWindow code-behind.
Assisted-by: Claude:claude-opus-4-7:Claude Code