@ -50,22 +50,19 @@ public class DecompilerViewTests
// resulting C# (signature + body) must land in the document tab. Also verifies the
// resulting C# (signature + body) must land in the document tab. Also verifies the
// row scrolls into view (CenteredInView assertion) so the user sees the selection.
// row scrolls into view (CenteredInView assertion) so the user sees the selection.
// Arrange — boot the window, wait for assemblies, expand Enumerable, locate AsEnumerable.
// Arrange — boot, expand Enumerable, locate AsEnumerable.
var window = AppComposition . Current . GetExport < MainWindow > ( ) ;
var ( _ , vm ) = await TestHarness . BootAsync ( 3 ) ;
window . Show ( ) ;
var vm = ( MainWindowViewModel ) window . DataContext ! ;
await vm . AssemblyTreeModel . WaitForAssembliesAsync ( minimumCount : 3 ) ;
var typeNode = vm . AssemblyTreeModel . FindNode < TypeTreeNode > (
var typeNode = vm . AssemblyTreeModel . FindNode < TypeTreeNode > (
"System.Linq" , "System.Linq" , "System.Linq.Enumerable" ) ;
"System.Linq" , "System.Linq" , "System.Linq.Enumerable" ) . Expand ( ) ;
typeNode . IsExpanded = true ;
var methodNode = typeNode . GetChild < MethodTreeNode > ( m = > m . MethodDefinition . Name = = "AsEnumerable" ) ;
var methodNode = typeNode . Children . OfType < MethodTreeNode > ( )
. Single ( m = > m . MethodDefinition . Name = = "AsEnumerable" ) ;
methodNode . MethodDefinition . IsExtensionMethod . Should ( ) . BeTrue ( ) ;
methodNode . MethodDefinition . IsExtensionMethod . Should ( ) . BeTrue ( ) ;
// Act — select the method node and wait for the decompile.
// Act — select the method node and wait for the decompile.
vm . AssemblyTreeModel . SelectNode ( methodNode ) ;
vm . AssemblyTreeModel . SelectNode ( methodNode ) ;
TestCapture . Step ( "selected-asenumerable" ) ;
var tab = await vm . DockWorkspace . WaitForDecompiledTextAsync ( ) ;
var tab = await vm . DockWorkspace . WaitForDecompiledTextAsync ( ) ;
TestCapture . Step ( "asenumerable-decompiled" ) ;
// Assert — signature + body fragments are present and the row centred in the tree.
// Assert — signature + body fragments are present and the row centred in the tree.
tab . Text . Should ( ) . Contain ( "AsEnumerable" ) ;
tab . Text . Should ( ) . Contain ( "AsEnumerable" ) ;
@ -82,19 +79,18 @@ public class DecompilerViewTests
// summary listing (target framework, runtime pack, referenced assemblies, load log)
// summary listing (target framework, runtime pack, referenced assemblies, load log)
// rather than decompiled C#.
// rather than decompiled C#.
// Arrange — boot, wait for assemblies, expand the System.Linq assembly node.
// Arrange — boot, expand the System.Linq assembly node, grab its References folder.
var window = AppComposition . Current . GetExport < MainWindow > ( ) ;
var ( _ , vm ) = await TestHarness . BootAsync ( 3 ) ;
window . Show ( ) ;
var vm = ( MainWindowViewModel ) window . DataContext ! ;
await vm . AssemblyTreeModel . WaitForAssembliesAsync ( minimumCount : 3 ) ;
var assemblyNode = vm . AssemblyTreeModel . FindNode < AssemblyTreeNode > ( "System.Linq" ) ;
var assemblyNode = vm . AssemblyTreeModel . FindNode < AssemblyTreeNode > ( "System.Linq" ) . Expand ( ) ;
assemblyNode . IsExpanded = true ;
TestCapture . Step ( "expanded-system-linq" ) ;
var refFolder = assemblyNode . Children . OfType < ReferenceFolderTreeNode > ( ) . Single ( ) ;
var refFolder = assemblyNode . Get Child< ReferenceFolderTreeNode > ( ) ;
// Act — select the References folder and wait for its decompile output.
// Act — select the References folder and wait for its decompile output.
vm . AssemblyTreeModel . SelectNode ( refFolder ) ;
vm . AssemblyTreeModel . SelectNode ( refFolder ) ;
TestCapture . Step ( "selected-references" ) ;
var tab = await vm . DockWorkspace . WaitForDecompiledTextAsync ( ) ;
var tab = await vm . DockWorkspace . WaitForDecompiledTextAsync ( ) ;
TestCapture . Step ( "references-listing" ) ;
// Assert — every header and a representative referenced assembly land in the listing.
// Assert — every header and a representative referenced assembly land in the listing.
tab . Text . Should ( ) . Contain ( "Detected TargetFramework-Id:" ) ;
tab . Text . Should ( ) . Contain ( "Detected TargetFramework-Id:" ) ;
@ -110,19 +106,17 @@ public class DecompilerViewTests
// Selecting a TypeTreeNode (System.Version) must decompile the full class declaration
// Selecting a TypeTreeNode (System.Version) must decompile the full class declaration
// including representative members.
// including representative members.
// Arrange — boot, wait for assemblies, find System.Version.
// Arrange — boot, find System.Version.
var window = AppComposition . Current . GetExport < MainWindow > ( ) ;
var ( _ , vm ) = await TestHarness . BootAsync ( 3 ) ;
window . Show ( ) ;
var vm = ( MainWindowViewModel ) window . DataContext ! ;
await vm . AssemblyTreeModel . WaitForAssembliesAsync ( minimumCount : 3 ) ;
var coreLib = typeof ( object ) . Assembly . GetName ( ) . Name ! ;
var typeNode = vm . AssemblyTreeModel . FindNode < TypeTreeNode > (
var typeNode = vm . AssemblyTreeModel . FindNode < TypeTreeNode > (
c oreLib, "System" , "System.Version" ) ;
TreeNavigation . CoreLibName , "System" , "System.Version" ) ;
// Act — select the type node, wait for the decompile.
// Act — select the type node, wait for the decompile.
vm . AssemblyTreeModel . SelectNode ( typeNode ) ;
vm . AssemblyTreeModel . SelectNode ( typeNode ) ;
TestCapture . Step ( "selected-version" ) ;
var tab = await vm . DockWorkspace . WaitForDecompiledTextAsync ( ) ;
var tab = await vm . DockWorkspace . WaitForDecompiledTextAsync ( ) ;
TestCapture . Step ( "version-class" ) ;
// Assert — class declaration + representative property names land in the output.
// Assert — class declaration + representative property names land in the output.
tab . Text . Should ( ) . Contain ( "class Version" ) ;
tab . Text . Should ( ) . Contain ( "class Version" ) ;
@ -136,22 +130,18 @@ public class DecompilerViewTests
// PropertyTreeNode → just the single property's declaration (with type prefix), not
// PropertyTreeNode → just the single property's declaration (with type prefix), not
// the full enclosing class.
// the full enclosing class.
// Arrange — boot, wait for assemblies, expand System.Version, grab Major.
// Arrange — boot, expand System.Version, grab Major.
var window = AppComposition . Current . GetExport < MainWindow > ( ) ;
var ( _ , vm ) = await TestHarness . BootAsync ( 3 ) ;
window . Show ( ) ;
var vm = ( MainWindowViewModel ) window . DataContext ! ;
await vm . AssemblyTreeModel . WaitForAssembliesAsync ( minimumCount : 3 ) ;
var coreLib = typeof ( object ) . Assembly . GetName ( ) . Name ! ;
var typeNode = vm . AssemblyTreeModel . FindNode < TypeTreeNode > (
var typeNode = vm . AssemblyTreeModel . FindNode < TypeTreeNode > (
coreLib , "System" , "System.Version" ) ;
TreeNavigation . CoreLibName , "System" , "System.Version" ) . Expand ( ) ;
typeNode . IsExpanded = true ;
var propertyNode = typeNode . GetChild < PropertyTreeNode > ( p = > p . PropertyDefinition . Name = = "Major" ) ;
var propertyNode = typeNode . Children . OfType < PropertyTreeNode > ( )
. Single ( p = > p . PropertyDefinition . Name = = "Major" ) ;
// Act — select the property, wait for the decompile.
// Act — select the property, wait for the decompile.
vm . AssemblyTreeModel . SelectNode ( propertyNode ) ;
vm . AssemblyTreeModel . SelectNode ( propertyNode ) ;
TestCapture . Step ( "selected-major" ) ;
var tab = await vm . DockWorkspace . WaitForDecompiledTextAsync ( ) ;
var tab = await vm . DockWorkspace . WaitForDecompiledTextAsync ( ) ;
TestCapture . Step ( "major-property" ) ;
// Assert — property declaration lands in the output.
// Assert — property declaration lands in the output.
tab . Text . Should ( ) . Contain ( "public int Major" ) ;
tab . Text . Should ( ) . Contain ( "public int Major" ) ;
@ -163,22 +153,18 @@ public class DecompilerViewTests
// FieldTreeNode (System.Math.PI) → decompiled field declaration with its constant
// FieldTreeNode (System.Math.PI) → decompiled field declaration with its constant
// initializer.
// initializer.
// Arrange — boot, wait for assemblies, expand System.Math, grab PI.
// Arrange — boot, expand System.Math, grab PI.
var window = AppComposition . Current . GetExport < MainWindow > ( ) ;
var ( _ , vm ) = await TestHarness . BootAsync ( 3 ) ;
window . Show ( ) ;
var vm = ( MainWindowViewModel ) window . DataContext ! ;
await vm . AssemblyTreeModel . WaitForAssembliesAsync ( minimumCount : 3 ) ;
var coreLib = typeof ( object ) . Assembly . GetName ( ) . Name ! ;
var typeNode = vm . AssemblyTreeModel . FindNode < TypeTreeNode > (
var typeNode = vm . AssemblyTreeModel . FindNode < TypeTreeNode > (
coreLib , "System" , "System.Math" ) ;
TreeNavigation . CoreLibName , "System" , "System.Math" ) . Expand ( ) ;
typeNode . IsExpanded = true ;
var fieldNode = typeNode . GetChild < FieldTreeNode > ( f = > f . FieldDefinition . Name = = "PI" ) ;
var fieldNode = typeNode . Children . OfType < FieldTreeNode > ( )
. Single ( f = > f . FieldDefinition . Name = = "PI" ) ;
// Act — select the field, wait for the decompile.
// Act — select the field, wait for the decompile.
vm . AssemblyTreeModel . SelectNode ( fieldNode ) ;
vm . AssemblyTreeModel . SelectNode ( fieldNode ) ;
TestCapture . Step ( "selected-pi" ) ;
var tab = await vm . DockWorkspace . WaitForDecompiledTextAsync ( ) ;
var tab = await vm . DockWorkspace . WaitForDecompiledTextAsync ( ) ;
TestCapture . Step ( "pi-field" ) ;
// Assert — declaration and constant value both land.
// Assert — declaration and constant value both land.
tab . Text . Should ( ) . Contain ( "public const double PI" ) ;
tab . Text . Should ( ) . Contain ( "public const double PI" ) ;
@ -190,22 +176,18 @@ public class DecompilerViewTests
{
{
// EventTreeNode (AppDomain.ProcessExit) → decompiled event declaration.
// EventTreeNode (AppDomain.ProcessExit) → decompiled event declaration.
// Arrange — boot, wait for assemblies, expand AppDomain, grab ProcessExit.
// Arrange — boot, expand AppDomain, grab ProcessExit.
var window = AppComposition . Current . GetExport < MainWindow > ( ) ;
var ( _ , vm ) = await TestHarness . BootAsync ( 3 ) ;
window . Show ( ) ;
var vm = ( MainWindowViewModel ) window . DataContext ! ;
await vm . AssemblyTreeModel . WaitForAssembliesAsync ( minimumCount : 3 ) ;
var coreLib = typeof ( object ) . Assembly . GetName ( ) . Name ! ;
var typeNode = vm . AssemblyTreeModel . FindNode < TypeTreeNode > (
var typeNode = vm . AssemblyTreeModel . FindNode < TypeTreeNode > (
coreLib , "System" , "System.AppDomain" ) ;
TreeNavigation . CoreLibName , "System" , "System.AppDomain" ) . Expand ( ) ;
typeNode . IsExpanded = true ;
var eventNode = typeNode . GetChild < EventTreeNode > ( e = > e . EventDefinition . Name = = "ProcessExit" ) ;
var eventNode = typeNode . Children . OfType < EventTreeNode > ( )
. Single ( e = > e . EventDefinition . Name = = "ProcessExit" ) ;
// Act — select the event, wait for the decompile.
// Act — select the event, wait for the decompile.
vm . AssemblyTreeModel . SelectNode ( eventNode ) ;
vm . AssemblyTreeModel . SelectNode ( eventNode ) ;
TestCapture . Step ( "selected-processexit" ) ;
var tab = await vm . DockWorkspace . WaitForDecompiledTextAsync ( ) ;
var tab = await vm . DockWorkspace . WaitForDecompiledTextAsync ( ) ;
TestCapture . Step ( "processexit-event" ) ;
// Assert — event keyword + name land in the output.
// Assert — event keyword + name land in the output.
tab . Text . Should ( ) . Contain ( "event EventHandler" ) ;
tab . Text . Should ( ) . Contain ( "event EventHandler" ) ;
@ -217,26 +199,27 @@ public class DecompilerViewTests
{
{
// Under the IL disassembler, selecting a NamespaceTreeNode lists the namespace's
// Under the IL disassembler, selecting a NamespaceTreeNode lists the namespace's
// types — ILLanguage overrides DecompileNamespace to disassemble them, matching the
// types — ILLanguage overrides DecompileNamespace to disassemble them, matching the
// WPF app where IL mode disassembles th e namespace.
// app's IL mode that disassembles the whol e namespace.
// Arrange — boot, wait for assemblies, switch to IL, locate System.Runtime.Versioning.
// Arrange — boot, switch to IL, locate System.Runtime.Versioning.
var window = AppComposition . Current . GetExport < MainWindow > ( ) ;
var ( _ , vm ) = await TestHarness . BootAsync ( 3 ) ;
window . Show ( ) ;
var vm = ( MainWindowViewModel ) window . DataContext ! ;
await vm . AssemblyTreeModel . WaitForAssembliesAsync ( minimumCount : 3 ) ;
var languageService = AppComposition . Current . GetExport < LanguageService > ( ) ;
var languageService = AppComposition . Current . GetExport < LanguageService > ( ) ;
languageService . CurrentLanguage = languageService . GetLanguage ( "IL" ) ;
languageService . CurrentLanguage = languageService . GetLanguage ( "IL" ) ;
TestCapture . Step ( "select-il-language" ) ;
var coreLib = typeof ( object ) . Assembly . GetName ( ) . Name ! ;
var namespaceNode = vm . AssemblyTreeModel . FindNode < NamespaceTreeNode > (
var namespaceNode = vm . AssemblyTreeModel . FindNode < NamespaceTreeNode > (
c oreLib, "System.Runtime.Versioning" ) ;
TreeNavigation . C oreLibName , "System.Runtime.Versioning" ) ;
// Act — select the namespace, wait for the decompile.
// Act — select the namespace, wait for the decompile.
vm . AssemblyTreeModel . SelectNode ( namespaceNode ) ;
vm . AssemblyTreeModel . SelectNode ( namespaceNode ) ;
TestCapture . Step ( "selected-namespace" ) ;
var tab = await vm . DockWorkspace . WaitForDecompiledTextAsync ( ) ;
var tab = await vm . DockWorkspace . WaitForDecompiledTextAsync ( ) ;
TestCapture . Step ( "namespace-il-listing" ) ;
// Assert — representative type names land in the IL output.
// Assert — the document was produced by the IL language, and representative type
// names land in the IL output.
tab . Language . Name . Should ( ) . Be ( "IL" ) ;
tab . Text . Should ( ) . Contain ( "TargetFrameworkAttribute" ) ;
tab . Text . Should ( ) . Contain ( "TargetFrameworkAttribute" ) ;
tab . Text . Should ( ) . Contain ( "SupportedOSPlatformAttribute" ) ;
tab . Text . Should ( ) . Contain ( "SupportedOSPlatformAttribute" ) ;
}
}
@ -246,25 +229,24 @@ public class DecompilerViewTests
{
{
// The C# language does NOT override DecompileNamespace, so selecting a namespace node
// The C# language does NOT override DecompileNamespace, so selecting a namespace node
// falls through to the base Language.DecompileNamespace, which writes just a
// falls through to the base Language.DecompileNamespace, which writes just a
// "// <namespace>" comment — not the contained types. Matches WPF, and keeps a single
// "// <namespace>" comment — not the contained types. Keeps a single click from
// click from triggering a multi-second decompile of the whole namespace.
// triggering a multi-second decompile of the whole namespace.
// Arrange — boot, wait for assemblies, ensure C#, locate System.Runtime.Versioning.
// Arrange — boot, ensure C#, locate System.Runtime.Versioning.
var window = AppComposition . Current . GetExport < MainWindow > ( ) ;
var ( _ , vm ) = await TestHarness . BootAsync ( 3 ) ;
window . Show ( ) ;
var vm = ( MainWindowViewModel ) window . DataContext ! ;
await vm . AssemblyTreeModel . WaitForAssembliesAsync ( minimumCount : 3 ) ;
var languageService = AppComposition . Current . GetExport < LanguageService > ( ) ;
var languageService = AppComposition . Current . GetExport < LanguageService > ( ) ;
languageService . CurrentLanguage = languageService . GetLanguage ( "C#" ) ;
languageService . CurrentLanguage = languageService . GetLanguage ( "C#" ) ;
TestCapture . Step ( "select-csharp-language" ) ;
var coreLib = typeof ( object ) . Assembly . GetName ( ) . Name ! ;
var namespaceNode = vm . AssemblyTreeModel . FindNode < NamespaceTreeNode > (
var namespaceNode = vm . AssemblyTreeModel . FindNode < NamespaceTreeNode > (
c oreLib, "System.Runtime.Versioning" ) ;
TreeNavigation . C oreLibName , "System.Runtime.Versioning" ) ;
// Act — select the namespace, wait for the decompile.
// Act — select the namespace, wait for the decompile.
vm . AssemblyTreeModel . SelectNode ( namespaceNode ) ;
vm . AssemblyTreeModel . SelectNode ( namespaceNode ) ;
TestCapture . Step ( "selected-namespace" ) ;
var tab = await vm . DockWorkspace . WaitForDecompiledTextAsync ( ) ;
var tab = await vm . DockWorkspace . WaitForDecompiledTextAsync ( ) ;
TestCapture . Step ( "namespace-csharp-comment" ) ;
// Assert — only the namespace comment, none of the contained types.
// Assert — only the namespace comment, none of the contained types.
tab . Text . Should ( ) . Contain ( "System.Runtime.Versioning" ) ;
tab . Text . Should ( ) . Contain ( "System.Runtime.Versioning" ) ;
@ -279,29 +261,27 @@ public class DecompilerViewTests
// the full "(version, tfm)" suffix wired up. The tab title used to lock in the bare
// the full "(version, tfm)" suffix wired up. The tab title used to lock in the bare
// ShortName at that moment; it must instead update once Text becomes available.
// ShortName at that moment; it must instead update once Text becomes available.
// Arrange — boot, wait for assemblies.
// Arrange — boot.
var window = AppComposition . Current . GetExport < MainWindow > ( ) ;
var ( _ , vm ) = await TestHarness . BootAsync ( 3 ) ;
window . Show ( ) ;
var vm = ( MainWindowViewModel ) window . DataContext ! ;
await vm . AssemblyTreeModel . WaitForAssembliesAsync ( minimumCount : 3 ) ;
// Act 1 — fire OpenCommand on a new path. OpenCommand selects the new node before its
// Act 1 — fire OpenCommand on a new path. OpenCommand selects the new node before its
// rich Text (with version + tfm) settles, exercising the late-update path.
// rich Text (with version + tfm) settles, exercising the late-update path. We can't use
// OpenAssemblyAsync here: it awaits the load, which would skip the pre-settle window.
var newAsmPath = typeof ( System . Net . Http . HttpClient ) . Assembly . Location ;
var newAsmPath = typeof ( System . Net . Http . HttpClient ) . Assembly . Location ;
var registry = AppComposition . Current . GetExport < MainMenuCommandRegistry > ( ) ;
var openCommand = AppComposition . Current . GetExport < MainMenuCommandRegistry > ( )
var openCommand = registry . Commands
. GetCommand ( nameof ( Resources . _ Open ) ) ;
. Single ( c = > c . Metadata . Header = = nameof ( Resources . _ Open ) )
. CreateExport ( ) . Value ;
openCommand . Execute ( newAsmPath ) ;
openCommand . Execute ( newAsmPath ) ;
await Waiters . WaitForAsync ( ( ) = >
await Waiters . WaitForAsync ( ( ) = >
vm . AssemblyTreeModel . SelectedItem is AssemblyTreeNode n
vm . AssemblyTreeModel . SelectedItem is AssemblyTreeNode n
& & string . Equals ( n . LoadedAssembly . FileName , newAsmPath , System . StringComparison . OrdinalIgnoreCase ) ) ;
& & string . Equals ( n . LoadedAssembly . FileName , newAsmPath , System . StringComparison . OrdinalIgnoreCase ) ) ;
TestCapture . Step ( "assembly-selected" ) ;
// Act 2 — wait for the rich form of LoadedAssembly.Text to materialise.
// Act 2 — wait for the rich form of LoadedAssembly.Text to materialise.
var node = ( AssemblyTreeNode ) vm . AssemblyTreeModel . SelectedItem ! ;
var node = ( AssemblyTreeNode ) vm . AssemblyTreeModel . SelectedItem ! ;
await node . LoadedAssembly . GetLoadResultAsync ( ) ;
await node . LoadedAssembly . GetLoadResultAsync ( ) ;
var tab = await vm . DockWorkspace . WaitForDecompiledTextAsync ( ) ;
var tab = await vm . DockWorkspace . WaitForDecompiledTextAsync ( ) ;
TestCapture . Step ( "opened-assembly" ) ;
// Assert — once Text is rich, the tab title catches up. Sanity-check the form is rich
// Assert — once Text is rich, the tab title catches up. Sanity-check the form is rich
// (otherwise the test isn't exercising the late-update path).
// (otherwise the test isn't exercising the late-update path).
@ -318,28 +298,29 @@ public class DecompilerViewTests
// spinner glyphs so the user sees activity. The editor's Text must NOT receive the
// spinner glyphs so the user sees activity. The editor's Text must NOT receive the
// glyph (only the title bar). Once decompilation finishes, the glyph clears.
// glyph (only the title bar). Once decompilation finishes, the glyph clears.
// Arrange — boot, wait for assemblies, run an initial decompile so the second selection
// Arrange — boot, run an initial decompile so the second selection produces a real
// produces a real "in flight" period (not the cold-start one).
// "in flight" period (not the cold-start one).
var window = AppComposition . Current . GetExport < MainWindow > ( ) ;
var ( _ , vm ) = await TestHarness . BootAsync ( 3 ) ;
window . Show ( ) ;
var vm = ( MainWindowViewModel ) window . DataContext ! ;
await vm . AssemblyTreeModel . WaitForAssembliesAsync ( minimumCount : 3 ) ;
var firstNode = vm . AssemblyTreeModel . FindNode < AssemblyTreeNode > ( "System.Linq" ) ;
var firstNode = vm . AssemblyTreeModel . FindNode < AssemblyTreeNode > ( "System.Linq" ) ;
vm . AssemblyTreeModel . SelectNode ( firstNode ) ;
vm . AssemblyTreeModel . SelectNode ( firstNode ) ;
var tab = await vm . DockWorkspace . WaitForDecompiledTextAsync ( ) ;
var tab = await vm . DockWorkspace . WaitForDecompiledTextAsync ( ) ;
TestCapture . Step ( "first-decompile" ) ;
var titles = new List < string > ( ) ;
var titles = new List < string > ( ) ;
var editorTexts = new List < string > ( ) ;
var editorTexts = new List < string > ( ) ;
tab . PropertyChanged + = OnTabPropertyChanged ;
tab . PropertyChanged + = OnTabPropertyChanged ;
// Act — kick off a second decompile and observe Title/Text changes throughout.
// Act — kick off a second decompile and observe Title/Text changes throughout. No
var coreLib = typeof ( object ) . Assembly . GetName ( ) . Name ! ;
// breakpoint inside the observation window: a capture's RunJobs could perturb the
var second = vm . AssemblyTreeModel . FindNode < TypeTreeNode > ( coreLib , "System" , "System.Version" ) ;
// in-flight title/text transitions this test is asserting on.
var second = vm . AssemblyTreeModel . FindNode < TypeTreeNode > (
TreeNavigation . CoreLibName , "System" , "System.Version" ) ;
vm . AssemblyTreeModel . SelectNode ( second ) ;
vm . AssemblyTreeModel . SelectNode ( second ) ;
await vm . DockWorkspace . WaitForDecompiledTextAsync ( ) ;
await vm . DockWorkspace . WaitForDecompiledTextAsync ( ) ;
tab . PropertyChanged - = OnTabPropertyChanged ;
tab . PropertyChanged - = OnTabPropertyChanged ;
TestCapture . Step ( "after-second-decompile" ) ;
// Assert — at least one captured title contained a spinner glyph; no editor Text ever
// Assert — at least one captured title contained a spinner glyph; no editor Text ever
// did; the final title is glyph-free.
// did; the final title is glyph-free.
@ -366,15 +347,11 @@ public class DecompilerViewTests
// Selecting several nodes via Ctrl+Click feeds them all into one decompile. The tab
// Selecting several nodes via Ctrl+Click feeds them all into one decompile. The tab
// title must list every selected node's display text comma-joined.
// title must list every selected node's display text comma-joined.
// Arrange — boot, wait for assemblies, expand Enumerable, grab two methods.
// Arrange — boot, expand Enumerable, grab two methods.
var window = AppComposition . Current . GetExport < MainWindow > ( ) ;
var ( _ , vm ) = await TestHarness . BootAsync ( 3 ) ;
window . Show ( ) ;
var vm = ( MainWindowViewModel ) window . DataContext ! ;
await vm . AssemblyTreeModel . WaitForAssembliesAsync ( minimumCount : 3 ) ;
var typeNode = vm . AssemblyTreeModel . FindNode < TypeTreeNode > (
var typeNode = vm . AssemblyTreeModel . FindNode < TypeTreeNode > (
"System.Linq" , "System.Linq" , "System.Linq.Enumerable" ) ;
"System.Linq" , "System.Linq" , "System.Linq.Enumerable" ) . Expand ( ) ;
typeNode . IsExpanded = true ;
var asEnumerable = typeNode . Children . OfType < MethodTreeNode > ( )
var asEnumerable = typeNode . Children . OfType < MethodTreeNode > ( )
. First ( m = > m . MethodDefinition . Name = = "AsEnumerable" ) ;
. First ( m = > m . MethodDefinition . Name = = "AsEnumerable" ) ;
var empty = typeNode . Children . OfType < MethodTreeNode > ( )
var empty = typeNode . Children . OfType < MethodTreeNode > ( )
@ -384,8 +361,10 @@ public class DecompilerViewTests
vm . AssemblyTreeModel . SelectedItems . Clear ( ) ;
vm . AssemblyTreeModel . SelectedItems . Clear ( ) ;
vm . AssemblyTreeModel . SelectedItems . Add ( asEnumerable ) ;
vm . AssemblyTreeModel . SelectedItems . Add ( asEnumerable ) ;
vm . AssemblyTreeModel . SelectedItems . Add ( empty ) ;
vm . AssemblyTreeModel . SelectedItems . Add ( empty ) ;
TestCapture . Step ( "multi-selected" ) ;
var tab = await vm . DockWorkspace . WaitForDecompiledTextAsync ( ) ;
var tab = await vm . DockWorkspace . WaitForDecompiledTextAsync ( ) ;
TestCapture . Step ( "multi-selection-title" ) ;
// Assert — title is the two display texts joined with ", ".
// Assert — title is the two display texts joined with ", ".
tab . Title . Should ( ) . Be ( $"{asEnumerable.Text}, {empty.Text}" ) ;
tab . Title . Should ( ) . Be ( $"{asEnumerable.Text}, {empty.Text}" ) ;
@ -397,15 +376,11 @@ public class DecompilerViewTests
// Multi-selection isn't just a title concern — both methods' bodies must actually be
// Multi-selection isn't just a title concern — both methods' bodies must actually be
// decompiled into one combined document.
// decompiled into one combined document.
// Arrange — boot, wait for assemblies, expand Enumerable, grab two methods.
// Arrange — boot, expand Enumerable, grab two methods.
var window = AppComposition . Current . GetExport < MainWindow > ( ) ;
var ( _ , vm ) = await TestHarness . BootAsync ( 3 ) ;
window . Show ( ) ;
var vm = ( MainWindowViewModel ) window . DataContext ! ;
await vm . AssemblyTreeModel . WaitForAssembliesAsync ( minimumCount : 3 ) ;
var typeNode = vm . AssemblyTreeModel . FindNode < TypeTreeNode > (
var typeNode = vm . AssemblyTreeModel . FindNode < TypeTreeNode > (
"System.Linq" , "System.Linq" , "System.Linq.Enumerable" ) ;
"System.Linq" , "System.Linq" , "System.Linq.Enumerable" ) . Expand ( ) ;
typeNode . IsExpanded = true ;
var asEnumerable = typeNode . Children . OfType < MethodTreeNode > ( )
var asEnumerable = typeNode . Children . OfType < MethodTreeNode > ( )
. First ( m = > m . MethodDefinition . Name = = "AsEnumerable" ) ;
. First ( m = > m . MethodDefinition . Name = = "AsEnumerable" ) ;
var empty = typeNode . Children . OfType < MethodTreeNode > ( )
var empty = typeNode . Children . OfType < MethodTreeNode > ( )
@ -415,8 +390,10 @@ public class DecompilerViewTests
vm . AssemblyTreeModel . SelectedItems . Clear ( ) ;
vm . AssemblyTreeModel . SelectedItems . Clear ( ) ;
vm . AssemblyTreeModel . SelectedItems . Add ( asEnumerable ) ;
vm . AssemblyTreeModel . SelectedItems . Add ( asEnumerable ) ;
vm . AssemblyTreeModel . SelectedItems . Add ( empty ) ;
vm . AssemblyTreeModel . SelectedItems . Add ( empty ) ;
TestCapture . Step ( "multi-selected" ) ;
var tab = await vm . DockWorkspace . WaitForDecompiledTextAsync ( ) ;
var tab = await vm . DockWorkspace . WaitForDecompiledTextAsync ( ) ;
TestCapture . Step ( "multi-selection-decompiled" ) ;
// Assert — both names AND a body-fragment from one of them confirm both bodies
// Assert — both names AND a body-fragment from one of them confirm both bodies
// actually rendered (not just signatures).
// actually rendered (not just signatures).
@ -431,18 +408,16 @@ public class DecompilerViewTests
// AssemblyTreeNode → emit a "manifest"-style summary header (file, identity, runtime,
// AssemblyTreeNode → emit a "manifest"-style summary header (file, identity, runtime,
// architecture, global type) plus the assembly-level attributes block.
// architecture, global type) plus the assembly-level attributes block.
// Arrange — boot, wait for assemblies, find System.Linq.
// Arrange — boot, find System.Linq.
var window = AppComposition . Current . GetExport < MainWindow > ( ) ;
var ( _ , vm ) = await TestHarness . BootAsync ( 3 ) ;
window . Show ( ) ;
var vm = ( MainWindowViewModel ) window . DataContext ! ;
await vm . AssemblyTreeModel . WaitForAssembliesAsync ( minimumCount : 3 ) ;
var assemblyNode = vm . AssemblyTreeModel . FindNode < AssemblyTreeNode > ( "System.Linq" ) ;
var assemblyNode = vm . AssemblyTreeModel . FindNode < AssemblyTreeNode > ( "System.Linq" ) ;
// Act — select the assembly node, wait for the decompile.
// Act — select the assembly node, wait for the decompile.
vm . AssemblyTreeModel . SelectNode ( assemblyNode ) ;
vm . AssemblyTreeModel . SelectNode ( assemblyNode ) ;
TestCapture . Step ( "selected-assembly" ) ;
var tab = await vm . DockWorkspace . WaitForDecompiledTextAsync ( ) ;
var tab = await vm . DockWorkspace . WaitForDecompiledTextAsync ( ) ;
TestCapture . Step ( "assembly-manifest" ) ;
// Assert — every documented header line and at least one assembly attribute land.
// Assert — every documented header line and at least one assembly attribute land.
tab . Text . Should ( ) . Contain ( "// " + assemblyNode . LoadedAssembly . FileName ) ;
tab . Text . Should ( ) . Contain ( "// " + assemblyNode . LoadedAssembly . FileName ) ;
@ -460,12 +435,9 @@ public class DecompilerViewTests
// DecompilerTextView.ApplyDocument must wire AvaloniaEdit's XmlFoldingStrategy so
// DecompilerTextView.ApplyDocument must wire AvaloniaEdit's XmlFoldingStrategy so
// multi-line elements become collapsible.
// multi-line elements become collapsible.
// Arrange — boot, wait for assemblies, seed the decompiler tab with an assembly view
// Arrange — boot, seed the decompiler tab with an assembly view so the document/editor
// so the document/editor wiring is realised.
// wiring is realised.
var window = AppComposition . Current . GetExport < MainWindow > ( ) ;
var ( _ , vm ) = await TestHarness . BootAsync ( 3 ) ;
window . Show ( ) ;
var vm = ( MainWindowViewModel ) window . DataContext ! ;
await vm . AssemblyTreeModel . WaitForAssembliesAsync ( minimumCount : 3 ) ;
var assemblyNode = vm . AssemblyTreeModel . FindNode < AssemblyTreeNode > ( "System.Linq" ) ;
var assemblyNode = vm . AssemblyTreeModel . FindNode < AssemblyTreeNode > ( "System.Linq" ) ;
vm . AssemblyTreeModel . SelectNode ( assemblyNode ) ;
vm . AssemblyTreeModel . SelectNode ( assemblyNode ) ;
@ -477,6 +449,7 @@ public class DecompilerViewTests
var view = new DecompilerTextView { DataContext = tab } ;
var view = new DecompilerTextView { DataContext = tab } ;
var host = new global :: Avalonia . Controls . Window { Content = view , Width = 6 0 0 , Height = 4 0 0 } ;
var host = new global :: Avalonia . Controls . Window { Content = view , Width = 6 0 0 , Height = 4 0 0 } ;
host . Show ( ) ;
host . Show ( ) ;
host . Capture ( "assembly-in-host" ) ;
// Act — drop a synthetic XML resource into the tab. XmlResourceEntryNode triggers
// Act — drop a synthetic XML resource into the tab. XmlResourceEntryNode triggers
// SyntaxExtension=".xml" and the XmlFoldingStrategy install.
// SyntaxExtension=".xml" and the XmlFoldingStrategy install.
@ -491,6 +464,7 @@ public class DecompilerViewTests
global :: Avalonia . Threading . Dispatcher . UIThread . RunJobs ( ) ;
global :: Avalonia . Threading . Dispatcher . UIThread . RunJobs ( ) ;
await Task . Delay ( 2 0 ) ;
await Task . Delay ( 2 0 ) ;
}
}
host . Capture ( "xml-folding" ) ;
// Assert — FoldingManager is installed (private field, reflected) and produced fold
// Assert — FoldingManager is installed (private field, reflected) and produced fold
// ranges for the multi-line elements.
// ranges for the multi-line elements.
@ -524,20 +498,16 @@ public class DecompilerViewTests
System . Threading . Tasks . TaskScheduler . UnobservedTaskException + = handler ;
System . Threading . Tasks . TaskScheduler . UnobservedTaskException + = handler ;
try
try
{
{
var window = AppComposition . Current . GetExport < MainWindow > ( ) ;
var ( _ , vm ) = await TestHarness . BootAsync ( 3 ) ;
window . Show ( ) ;
var vm = ( MainWindowViewModel ) window . DataContext ! ;
await vm . AssemblyTreeModel . WaitForAssembliesAsync ( minimumCount : 3 ) ;
var typeNode = vm . AssemblyTreeModel . FindNode < TypeTreeNode > (
var method = vm . AssemblyTreeModel . FindNode < TypeTreeNode > (
"System.Linq" , "System.Linq" , "System.Linq.Enumerable" ) ;
"System.Linq" , "System.Linq" , "System.Linq.Enumerable" )
typeNode . IsExpanded = true ;
. Expand ( ) . GetChild < MethodTreeNode > ( m = > m . MethodDefinition . Name = = "AsEnumerable" ) ;
var method = typeNode . Children . OfType < MethodTreeNode > ( )
. First ( m = > m . MethodDefinition . Name = = "AsEnumerable" ) ;
vm . AssemblyTreeModel . SelectNode ( method ) ;
vm . AssemblyTreeModel . SelectNode ( method ) ;
var tab = await vm . DockWorkspace . WaitForDecompiledTextAsync ( ) ;
var tab = await vm . DockWorkspace . WaitForDecompiledTextAsync ( ) ;
tab . Foldings . Should ( ) . NotBeNull (
tab . Foldings . Should ( ) . NotBeNull (
"C# decompile of a method body should produce at least one fold" ) ;
"C# decompile of a method body should produce at least one fold" ) ;
TestCapture . Step ( "csharp-method" ) ;
// Switch the language — the buggy path was here.
// Switch the language — the buggy path was here.
var languageService = AppComposition . Current . GetExport < LanguageService > ( ) ;
var languageService = AppComposition . Current . GetExport < LanguageService > ( ) ;
@ -546,6 +516,7 @@ public class DecompilerViewTests
languageService . CurrentLanguage = blockIL ;
languageService . CurrentLanguage = blockIL ;
await vm . DockWorkspace . WaitForDecompiledTextAsync ( ) ;
await vm . DockWorkspace . WaitForDecompiledTextAsync ( ) ;
TestCapture . Step ( "ilast-method" ) ;
// Force GC to flush any unobserved Task faults that escaped via the dispatcher.
// Force GC to flush any unobserved Task faults that escaped via the dispatcher.
System . GC . Collect ( ) ;
System . GC . Collect ( ) ;