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.
HandleFileDrop now resolves each opened LoadedAssembly to its
AssemblyTreeNode (via AssemblyListTreeNode.FindAssemblyNode) after
any Move and writes the set into model.SelectedItems. Lookup happens
post-Move because Move re-creates the node wrappers — a pre-Move
reference would dangle. Selecting drives the decompiler view to
render the new assembly, which is what makes the LoadAsync task
observable to the user.
Assisted-by: Claude:claude-opus-4-7:Claude Code
ProDataGrid's row-drag pipeline only sees in-grid drags, so external
file drops bypass it entirely. Wires Avalonia's standard DragDrop
pipeline alongside it: DragDrop.AllowDrop on the tree DataGrid plus
DragOver / Drop handlers that read DataFormat.File from the
IDataTransfer, resolve each IStorageItem to a local path, and route
through AssemblyListPane.HandleFileDrop.
Assisted-by: Claude:claude-opus-4-7:Claude Code