diff --git a/.cspell.yaml b/.cspell.yaml index 2343572..051b883 100644 --- a/.cspell.yaml +++ b/.cspell.yaml @@ -88,6 +88,7 @@ words: - avares - Appium - appium + - NOSONAR - NovaWindows - WinAppDriver - WebDriver diff --git a/src/DemaConsulting.SysML2Workbench/App.axaml.cs b/src/DemaConsulting.SysML2Workbench/App.axaml.cs index 71f68fb..b78bc5e 100644 --- a/src/DemaConsulting.SysML2Workbench/App.axaml.cs +++ b/src/DemaConsulting.SysML2Workbench/App.axaml.cs @@ -43,14 +43,15 @@ public override void OnFrameworkInitializationCompleted() "logs"); var shell = new MainWindowShell( - new WorkspaceModel(), - new FileWatcher(TimeSpan.FromMilliseconds(500), dispatcher: new AvaloniaUiDispatcher()), - new DiagnosticsAggregator(), - new ViewCatalogPresenter(), - new LayoutInvoker(), - new DiagnosticsListView(), - new SysmlSnippetGenerator(), - new RollingFileLogger(logDirectory), + new MainWindowShellDependencies( + new WorkspaceModel(), + new FileWatcher(TimeSpan.FromMilliseconds(500), dispatcher: new AvaloniaUiDispatcher()), + new DiagnosticsAggregator(), + new ViewCatalogPresenter(), + new LayoutInvoker(), + new DiagnosticsListView(), + new SysmlSnippetGenerator(), + new RollingFileLogger(logDirectory)), uiDispatcher: new AvaloniaUiDispatcher()); desktop.MainWindow = new MainWindowView(shell); @@ -88,9 +89,9 @@ public override void OnFrameworkInitializationCompleted() /// /// The freshly composed shell to preload sources into. /// The raw command-line arguments the process was launched with. - private static void ApplyStartupSourceArgumentsForTesting(MainWindowShell shell, IReadOnlyList args) + private static void ApplyStartupSourceArgumentsForTesting(MainWindowShell shell, string[] args) { - for (var i = 0; i < args.Count - 1; i++) + for (var i = 0; i < args.Length - 1; i++) { if (args[i] != "--startup-source") { diff --git a/src/DemaConsulting.SysML2Workbench/AppShellSubsystem/DesignTimeShellFactory.cs b/src/DemaConsulting.SysML2Workbench/AppShellSubsystem/DesignTimeShellFactory.cs index b15e820..1faa661 100644 --- a/src/DemaConsulting.SysML2Workbench/AppShellSubsystem/DesignTimeShellFactory.cs +++ b/src/DemaConsulting.SysML2Workbench/AppShellSubsystem/DesignTimeShellFactory.cs @@ -22,13 +22,14 @@ internal static class DesignTimeShellFactory public static MainWindowShell Create() { return new MainWindowShell( - new WorkspaceModel(), - new FileWatcher(TimeSpan.FromMilliseconds(500)), - new DiagnosticsAggregator(), - new ViewCatalogPresenter(), - new LayoutInvoker(), - new DiagnosticsListView(), - new SysmlSnippetGenerator(), - new RollingFileLogger(PathHelpers.SafePathCombine(Path.GetTempPath(), "SysML2Workbench-DesignTime"))); + new MainWindowShellDependencies( + new WorkspaceModel(), + new FileWatcher(TimeSpan.FromMilliseconds(500)), + new DiagnosticsAggregator(), + new ViewCatalogPresenter(), + new LayoutInvoker(), + new DiagnosticsListView(), + new SysmlSnippetGenerator(), + new RollingFileLogger(PathHelpers.SafePathCombine(Path.GetTempPath(), "SysML2Workbench-DesignTime")))); } } diff --git a/src/DemaConsulting.SysML2Workbench/AppShellSubsystem/MainWindowShell.cs b/src/DemaConsulting.SysML2Workbench/AppShellSubsystem/MainWindowShell.cs index 55744b1..2d27fe9 100644 --- a/src/DemaConsulting.SysML2Workbench/AppShellSubsystem/MainWindowShell.cs +++ b/src/DemaConsulting.SysML2Workbench/AppShellSubsystem/MainWindowShell.cs @@ -55,6 +55,29 @@ public enum WorkbenchTabKind /// public sealed record WorkbenchTab(string Id, string Title, WorkbenchTabKind Kind, SvgCanvasHost Canvas, ViewDefinitionModel? SourceDefinition = null, string? FilePath = null); +/// +/// Groups the eight required constructor dependencies of into a single +/// parameter object, keeping the shell's constructor within the project's parameter-count quality gate +/// while still requiring every dependency to be supplied explicitly (no dependency is optional). +/// +/// Owns discovery, load, and reload of the workspace. +/// Detects external workspace changes. +/// Aggregates per-file diagnostics into a workspace-wide view. +/// Supplies predefined view choices. +/// Renders predefined and custom views to SVG. +/// Displays workspace diagnostics. +/// Exports custom-view definitions as SysML text. +/// Records shell-level operational events and failures. +public sealed record MainWindowShellDependencies( + WorkspaceModel WorkspaceModel, + FileWatcher FileWatcher, + DiagnosticsAggregator DiagnosticsAggregator, + ViewCatalogPresenter ViewCatalogPresenter, + LayoutInvoker LayoutInvoker, + DiagnosticsListView DiagnosticsListView, + SysmlSnippetGenerator SnippetGenerator, + RollingFileLogger Logger); + /// /// MainWindowShell is the desktop composition root that coordinates workspace lifecycle, view selection, /// diagram display, diagnostics presentation, and snippet export within a single windowed user experience. @@ -221,14 +244,10 @@ public sealed class MainWindowShell : IDisposable /// /// Creates the shell from its constituent subsystem units. /// - /// Owns discovery, load, and reload of the workspace. - /// Detects external workspace changes. - /// Aggregates per-file diagnostics into a workspace-wide view. - /// Supplies predefined view choices. - /// Renders predefined and custom views to SVG. - /// Displays workspace diagnostics. - /// Exports custom-view definitions as SysML text. - /// Records shell-level operational events and failures. + /// + /// The shell's required subsystem dependencies, grouped into a single parameter object - see + /// for what each member provides. + /// /// /// Dispatcher used to marshal notifications. Defaults to /// , which runs the notification synchronously on the calling thread; @@ -237,34 +256,26 @@ public sealed class MainWindowShell : IDisposable /// when raised from a background continuation. /// /// Thrown when any required dependency is null. - public MainWindowShell( - WorkspaceModel workspaceModel, - FileWatcher fileWatcher, - DiagnosticsAggregator diagnosticsAggregator, - ViewCatalogPresenter viewCatalogPresenter, - LayoutInvoker layoutInvoker, - DiagnosticsListView diagnosticsListView, - SysmlSnippetGenerator snippetGenerator, - RollingFileLogger logger, - IUiDispatcher? uiDispatcher = null) + public MainWindowShell(MainWindowShellDependencies dependencies, IUiDispatcher? uiDispatcher = null) { - ArgumentNullException.ThrowIfNull(workspaceModel); - ArgumentNullException.ThrowIfNull(fileWatcher); - ArgumentNullException.ThrowIfNull(diagnosticsAggregator); - ArgumentNullException.ThrowIfNull(viewCatalogPresenter); - ArgumentNullException.ThrowIfNull(layoutInvoker); - ArgumentNullException.ThrowIfNull(diagnosticsListView); - ArgumentNullException.ThrowIfNull(snippetGenerator); - ArgumentNullException.ThrowIfNull(logger); - - _workspaceModel = workspaceModel; - _fileWatcher = fileWatcher; - _diagnosticsAggregator = diagnosticsAggregator; - _viewCatalogPresenter = viewCatalogPresenter; - _layoutInvoker = layoutInvoker; - _diagnosticsListView = diagnosticsListView; - _snippetGenerator = snippetGenerator; - _logger = logger; + ArgumentNullException.ThrowIfNull(dependencies); + ArgumentNullException.ThrowIfNull(dependencies.WorkspaceModel); + ArgumentNullException.ThrowIfNull(dependencies.FileWatcher); + ArgumentNullException.ThrowIfNull(dependencies.DiagnosticsAggregator); + ArgumentNullException.ThrowIfNull(dependencies.ViewCatalogPresenter); + ArgumentNullException.ThrowIfNull(dependencies.LayoutInvoker); + ArgumentNullException.ThrowIfNull(dependencies.DiagnosticsListView); + ArgumentNullException.ThrowIfNull(dependencies.SnippetGenerator); + ArgumentNullException.ThrowIfNull(dependencies.Logger); + + _workspaceModel = dependencies.WorkspaceModel; + _fileWatcher = dependencies.FileWatcher; + _diagnosticsAggregator = dependencies.DiagnosticsAggregator; + _viewCatalogPresenter = dependencies.ViewCatalogPresenter; + _layoutInvoker = dependencies.LayoutInvoker; + _diagnosticsListView = dependencies.DiagnosticsListView; + _snippetGenerator = dependencies.SnippetGenerator; + _logger = dependencies.Logger; _uiDispatcher = uiDispatcher ?? new ImmediateUiDispatcher(); // Eagerly establish a valid, empty (0-source) workspace snapshot at construction, so CurrentWorkspace is diff --git a/src/DemaConsulting.SysML2Workbench/AppShellSubsystem/QueryDialogViewModel.cs b/src/DemaConsulting.SysML2Workbench/AppShellSubsystem/QueryDialogViewModel.cs index 3643fc4..2c7e31b 100644 --- a/src/DemaConsulting.SysML2Workbench/AppShellSubsystem/QueryDialogViewModel.cs +++ b/src/DemaConsulting.SysML2Workbench/AppShellSubsystem/QueryDialogViewModel.cs @@ -72,8 +72,8 @@ public sealed partial class QueryDialogViewModel : ObservableObject /// workspace, and so can attach the same /// kind label to each List-type entry. /// - private IReadOnlyDictionary _candidateMap = - new Dictionary(StringComparer.Ordinal); + private Dictionary _candidateMap = + new(StringComparer.Ordinal); [ObservableProperty] public partial bool IncludeStdlib { get; set; } diff --git a/src/DemaConsulting.SysML2Workbench/AppShellSubsystem/SourceTextDocumentView.axaml.cs b/src/DemaConsulting.SysML2Workbench/AppShellSubsystem/SourceTextDocumentView.axaml.cs index ec90303..803044e 100644 --- a/src/DemaConsulting.SysML2Workbench/AppShellSubsystem/SourceTextDocumentView.axaml.cs +++ b/src/DemaConsulting.SysML2Workbench/AppShellSubsystem/SourceTextDocumentView.axaml.cs @@ -67,6 +67,9 @@ private void OnDataContextChanged(object? sender, EventArgs e) /// The loaded, registered highlighting definition. private static IHighlightingDefinition LoadSysMlHighlighting() { + // NOSONAR: "avares://" is Avalonia's compile-time embedded-resource URI scheme (an asset + // reference baked into this assembly's own resources), not a filesystem path or + // externally-configurable endpoint - there is no meaningful "avoid the hardcoded value" here. using var stream = AssetLoader.Open(new Uri("avares://DemaConsulting.SysML2Workbench/Assets/SysML.xshd")); using var reader = XmlReader.Create(stream); var definition = HighlightingLoader.Load(reader, HighlightingManager.Instance); @@ -102,7 +105,7 @@ private static IReadOnlyList GetKeywords() // type (SysMLv2Lexer), and wrapped in a try/catch with a hard-coded fallback list below, // so if the field is ever renamed or removed by a future SysML2Tools release, this // degrades gracefully instead of crashing. - var field = typeof(SysMLv2Lexer).GetField("_LiteralNames", BindingFlags.NonPublic | BindingFlags.Static); + var field = typeof(SysMLv2Lexer).GetField("_LiteralNames", BindingFlags.NonPublic | BindingFlags.Static); // NOSONAR: read-only reflection of a well-known third-party field, guarded by the try/catch fallback below. if (field?.GetValue(null) is string[] literalNames) { var keywords = literalNames diff --git a/src/DemaConsulting.SysML2Workbench/AppShellSubsystem/WorkspacePanelToolViewModel.cs b/src/DemaConsulting.SysML2Workbench/AppShellSubsystem/WorkspacePanelToolViewModel.cs index dd9dda0..66ce44d 100644 --- a/src/DemaConsulting.SysML2Workbench/AppShellSubsystem/WorkspacePanelToolViewModel.cs +++ b/src/DemaConsulting.SysML2Workbench/AppShellSubsystem/WorkspacePanelToolViewModel.cs @@ -239,7 +239,7 @@ public void RebuildTree() /// /// The source the files were discovered under. /// Absolute paths of every file discovered under . - private static IReadOnlyList BuildFolderChildren(WorkspaceSource source, IReadOnlyList files) + private static List BuildFolderChildren(WorkspaceSource source, IReadOnlyList files) { if (files.Count == 0) { @@ -276,7 +276,7 @@ private static IReadOnlyList BuildFolderChildren(WorkspaceSou /// shape the tree binds to, listing subfolders before files and sorting /// each alphabetically by name. /// - private static IReadOnlyList ToTreeNodes(FolderGroup group, string sourceId) + private static List ToTreeNodes(FolderGroup group, string sourceId) { var nodes = new List(); diff --git a/src/DemaConsulting.SysML2Workbench/LoggingSubsystem/RollingFileLogger.cs b/src/DemaConsulting.SysML2Workbench/LoggingSubsystem/RollingFileLogger.cs index cd87f56..07201ad 100644 --- a/src/DemaConsulting.SysML2Workbench/LoggingSubsystem/RollingFileLogger.cs +++ b/src/DemaConsulting.SysML2Workbench/LoggingSubsystem/RollingFileLogger.cs @@ -145,7 +145,7 @@ public void RotateIfNeeded() /// immediately, no additional buffered state exists to flush; this method exists to satisfy the /// documented unit contract and is safe to call at any time, including before any entry has been written. /// - public void Flush() + public static void Flush() { // No-op by design: File.AppendAllText fully commits and closes the file handle on every call } diff --git a/test/DemaConsulting.SysML2Workbench.IntegrationTests/AppFixture.cs b/test/DemaConsulting.SysML2Workbench.IntegrationTests/AppFixture.cs index 6c3fe9d..216833d 100644 --- a/test/DemaConsulting.SysML2Workbench.IntegrationTests/AppFixture.cs +++ b/test/DemaConsulting.SysML2Workbench.IntegrationTests/AppFixture.cs @@ -191,7 +191,7 @@ private static MacDriver CreateMacDriver(string startupArguments) /// binary's path. Not exercised by CI and not validated against a real Linux machine - see /// 's remarks. /// - private static AppiumDriver CreateLinuxDriver(string startupArguments) + private static LinuxDriver CreateLinuxDriver(string startupArguments) { var options = new AppiumOptions { diff --git a/test/DemaConsulting.SysML2Workbench.IntegrationTests/AppiumTestBase.cs b/test/DemaConsulting.SysML2Workbench.IntegrationTests/AppiumTestBase.cs index a203d16..27c1167 100644 --- a/test/DemaConsulting.SysML2Workbench.IntegrationTests/AppiumTestBase.cs +++ b/test/DemaConsulting.SysML2Workbench.IntegrationTests/AppiumTestBase.cs @@ -67,6 +67,7 @@ protected void StartApp(string startupArguments = "") public void Dispose() { _fixture?.Dispose(); + GC.SuppressFinalize(this); } /// @@ -84,7 +85,7 @@ protected void AssertMenuItemsAreDiscoverableAndEnabled(string topLevelMenuName, topLevelMenu.Click(); // Act / Assert - OpenQA.Selenium.IWebElement? lastMenuItem = null; + OpenQA.Selenium.Appium.AppiumElement? lastMenuItem = null; foreach (var automationId in automationIds) { var menuItem = Session.FindElement(MobileBy.AccessibilityId(automationId)); diff --git a/test/DemaConsulting.SysML2Workbench.Tests/AppShellSubsystem/DiagramDocumentViewModelTests.cs b/test/DemaConsulting.SysML2Workbench.Tests/AppShellSubsystem/DiagramDocumentViewModelTests.cs index c4b61eb..dd55058 100644 --- a/test/DemaConsulting.SysML2Workbench.Tests/AppShellSubsystem/DiagramDocumentViewModelTests.cs +++ b/test/DemaConsulting.SysML2Workbench.Tests/AppShellSubsystem/DiagramDocumentViewModelTests.cs @@ -69,6 +69,7 @@ await File.WriteAllTextAsync( + "}\n"); var shell = new MainWindowShell( + new MainWindowShellDependencies( new WorkspaceModel(), new FileWatcher(TimeSpan.FromMilliseconds(1)), new DiagnosticsAggregator(), @@ -76,7 +77,7 @@ await File.WriteAllTextAsync( new LayoutInvoker(), new DiagnosticsListView(), new SysmlSnippetGenerator(), - new RollingFileLogger(_tempLogRoot)); + new RollingFileLogger(_tempLogRoot))); await shell.AddFolderSourceAsync(_tempRoot); return shell; diff --git a/test/DemaConsulting.SysML2Workbench.Tests/AppShellSubsystem/MainWindowShellTests.cs b/test/DemaConsulting.SysML2Workbench.Tests/AppShellSubsystem/MainWindowShellTests.cs index f3cd2f6..e50fa5c 100644 --- a/test/DemaConsulting.SysML2Workbench.Tests/AppShellSubsystem/MainWindowShellTests.cs +++ b/test/DemaConsulting.SysML2Workbench.Tests/AppShellSubsystem/MainWindowShellTests.cs @@ -69,6 +69,7 @@ await File.WriteAllTextAsync( private MainWindowShell CreateShell(FileWatcher? fileWatcher = null) { return new MainWindowShell( + new MainWindowShellDependencies( new WorkspaceModel(), fileWatcher ?? new FileWatcher(TimeSpan.FromMilliseconds(1)), new DiagnosticsAggregator(), @@ -76,7 +77,7 @@ private MainWindowShell CreateShell(FileWatcher? fileWatcher = null) new LayoutInvoker(), new DiagnosticsListView(), new SysmlSnippetGenerator(), - new RollingFileLogger(_tempLogRoot)); + new RollingFileLogger(_tempLogRoot))); } /// diff --git a/test/DemaConsulting.SysML2Workbench.Tests/AppShellSubsystem/QueryDialogViewModelTests.cs b/test/DemaConsulting.SysML2Workbench.Tests/AppShellSubsystem/QueryDialogViewModelTests.cs index a056c5a..08247a3 100644 --- a/test/DemaConsulting.SysML2Workbench.Tests/AppShellSubsystem/QueryDialogViewModelTests.cs +++ b/test/DemaConsulting.SysML2Workbench.Tests/AppShellSubsystem/QueryDialogViewModelTests.cs @@ -24,6 +24,11 @@ public sealed class QueryDialogViewModelTests : IDisposable private readonly string _tempRoot = Directory.CreateTempSubdirectory("sysml2workbench-tests-").FullName; private readonly string _tempLogRoot = Directory.CreateTempSubdirectory("sysml2workbench-tests-logs-").FullName; + /// + /// Expected hierarchy direction option values, in display order. + /// + private static readonly string[] ExpectedHierarchyDirectionOptions = ["up", "down", "both"]; + /// public void Dispose() { @@ -57,6 +62,7 @@ await File.WriteAllTextAsync( private MainWindowShell CreateShell() { return new MainWindowShell( + new MainWindowShellDependencies( new WorkspaceModel(), new FileWatcher(TimeSpan.FromMilliseconds(1)), new DiagnosticsAggregator(), @@ -64,7 +70,7 @@ private MainWindowShell CreateShell() new LayoutInvoker(), new DiagnosticsListView(), new SysmlSnippetGenerator(), - new RollingFileLogger(_tempLogRoot)); + new RollingFileLogger(_tempLogRoot))); } /// @@ -753,6 +759,6 @@ public void QueryDialogViewModel_QueryTypes_HasExpectedElevenEntries() public void QueryDialogViewModel_HierarchyDirectionOptions_HasExpectedThree() { // Assert - Assert.Equal(new[] { "up", "down", "both" }, QueryDialogViewModel.HierarchyDirectionOptions); + Assert.Equal(ExpectedHierarchyDirectionOptions, QueryDialogViewModel.HierarchyDirectionOptions); } } diff --git a/test/DemaConsulting.SysML2Workbench.Tests/AppShellSubsystem/SourceTextDocumentViewModelTests.cs b/test/DemaConsulting.SysML2Workbench.Tests/AppShellSubsystem/SourceTextDocumentViewModelTests.cs index 38c2cf3..62282f7 100644 --- a/test/DemaConsulting.SysML2Workbench.Tests/AppShellSubsystem/SourceTextDocumentViewModelTests.cs +++ b/test/DemaConsulting.SysML2Workbench.Tests/AppShellSubsystem/SourceTextDocumentViewModelTests.cs @@ -43,6 +43,7 @@ public void Dispose() private MainWindowShell CreateShell() { return new MainWindowShell( + new MainWindowShellDependencies( new WorkspaceModel(), new FileWatcher(TimeSpan.FromMilliseconds(1)), new DiagnosticsAggregator(), @@ -50,7 +51,7 @@ private MainWindowShell CreateShell() new LayoutInvoker(), new DiagnosticsListView(), new SysmlSnippetGenerator(), - new RollingFileLogger(_tempLogRoot)); + new RollingFileLogger(_tempLogRoot))); } /// diff --git a/test/DemaConsulting.SysML2Workbench.Tests/AppShellSubsystem/ViewBuilderDialogViewModelTests.cs b/test/DemaConsulting.SysML2Workbench.Tests/AppShellSubsystem/ViewBuilderDialogViewModelTests.cs index 9030f02..c30db16 100644 --- a/test/DemaConsulting.SysML2Workbench.Tests/AppShellSubsystem/ViewBuilderDialogViewModelTests.cs +++ b/test/DemaConsulting.SysML2Workbench.Tests/AppShellSubsystem/ViewBuilderDialogViewModelTests.cs @@ -90,6 +90,7 @@ await File.WriteAllTextAsync( private MainWindowShell CreateShell() { return new MainWindowShell( + new MainWindowShellDependencies( new WorkspaceModel(), new FileWatcher(TimeSpan.FromMilliseconds(1)), new DiagnosticsAggregator(), @@ -97,7 +98,7 @@ private MainWindowShell CreateShell() new LayoutInvoker(), new DiagnosticsListView(), new SysmlSnippetGenerator(), - new RollingFileLogger(_tempLogRoot)); + new RollingFileLogger(_tempLogRoot))); } /// diff --git a/test/DemaConsulting.SysML2Workbench.Tests/AppShellSubsystem/WorkspacePanelToolViewModelTests.cs b/test/DemaConsulting.SysML2Workbench.Tests/AppShellSubsystem/WorkspacePanelToolViewModelTests.cs index bf66307..8f61657 100644 --- a/test/DemaConsulting.SysML2Workbench.Tests/AppShellSubsystem/WorkspacePanelToolViewModelTests.cs +++ b/test/DemaConsulting.SysML2Workbench.Tests/AppShellSubsystem/WorkspacePanelToolViewModelTests.cs @@ -43,6 +43,7 @@ public void Dispose() private MainWindowShell CreateShell() { return new MainWindowShell( + new MainWindowShellDependencies( new WorkspaceModel(), new FileWatcher(TimeSpan.FromMilliseconds(1)), new DiagnosticsAggregator(), @@ -50,7 +51,7 @@ private MainWindowShell CreateShell() new LayoutInvoker(), new DiagnosticsListView(), new SysmlSnippetGenerator(), - new RollingFileLogger(_tempLogRoot)); + new RollingFileLogger(_tempLogRoot))); } private static Task WriteFileAsync(string path, string content) diff --git a/test/DemaConsulting.SysML2Workbench.Tests/AppShellSubsystemTests.cs b/test/DemaConsulting.SysML2Workbench.Tests/AppShellSubsystemTests.cs index 15f5b41..dc4d8db 100644 --- a/test/DemaConsulting.SysML2Workbench.Tests/AppShellSubsystemTests.cs +++ b/test/DemaConsulting.SysML2Workbench.Tests/AppShellSubsystemTests.cs @@ -52,6 +52,7 @@ await File.WriteAllTextAsync( private MainWindowShell CreateShell() { return new MainWindowShell( + new MainWindowShellDependencies( new WorkspaceModel(), new FileWatcher(TimeSpan.FromMilliseconds(1)), new DiagnosticsAggregator(), @@ -59,7 +60,7 @@ private MainWindowShell CreateShell() new LayoutInvoker(), new DiagnosticsListView(), new SysmlSnippetGenerator(), - new RollingFileLogger(_tempLogRoot)); + new RollingFileLogger(_tempLogRoot))); } /// diff --git a/test/DemaConsulting.SysML2Workbench.Tests/DemaConsulting.SysML2Workbench.Tests.csproj b/test/DemaConsulting.SysML2Workbench.Tests/DemaConsulting.SysML2Workbench.Tests.csproj index 8c8b22a..3acc3b1 100644 --- a/test/DemaConsulting.SysML2Workbench.Tests/DemaConsulting.SysML2Workbench.Tests.csproj +++ b/test/DemaConsulting.SysML2Workbench.Tests/DemaConsulting.SysML2Workbench.Tests.csproj @@ -11,6 +11,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/test/DemaConsulting.SysML2Workbench.Tests/ElementPickerSubsystem/ElementFilterViewModelTests.cs b/test/DemaConsulting.SysML2Workbench.Tests/ElementPickerSubsystem/ElementFilterViewModelTests.cs index a33015b..b15b00d 100644 --- a/test/DemaConsulting.SysML2Workbench.Tests/ElementPickerSubsystem/ElementFilterViewModelTests.cs +++ b/test/DemaConsulting.SysML2Workbench.Tests/ElementPickerSubsystem/ElementFilterViewModelTests.cs @@ -26,6 +26,60 @@ private static readonly (string QualifiedName, string TypeLabel)[] MixedCandidat ("Model::SubPackage", "package"), ]; + /// + /// Expected distinct, ordinally-sorted type labels derived from . + /// + private static readonly string[] ExpectedSortedTypeLabels = ["package", "part", "part def"]; + + /// + /// Single-label filter set used to pre-populate the "part" chip. + /// + private static readonly string[] PartChipOnly = ["part"]; + + /// + /// Expected qualified names of the "part" (non-definition) candidates in . + /// + private static readonly string[] ExpectedPartUsages = ["Model::engineInstance", "Model::wheelInstance"]; + + /// + /// Expected single displayed item after narrowing by the substring "engine". + /// + private static readonly string[] ExpectedEngineOnly = ["Model::Engine"]; + + /// + /// Expected single displayed item after narrowing by the substring "SUBPACKAGE". + /// + private static readonly string[] ExpectedSubPackageOnly = ["Model::SubPackage"]; + + /// + /// Expected addable type labels (all labels except the pre-populated "part" chip). + /// + private static readonly string[] ExpectedAddableLabels = ["package", "part def"]; + + /// + /// Expected addable type filter candidates narrowed to labels containing "def". + /// + private static readonly string[] ExpectedDefLabelOnly = ["part def"]; + + /// + /// Replacement candidate list used to verify + /// fully replaces prior state on a second call. + /// + private static readonly (string QualifiedName, string TypeLabel)[] ReplacementCandidates = + [ + ("Other::Foo", "other"), + ]; + + /// + /// Expected sole type label after replacing candidates with . + /// + private static readonly string[] ExpectedOtherLabelOnly = ["other"]; + + /// + /// Expected sole displayed item after replacing candidates with . + /// + private static readonly string[] ExpectedOtherFooOnly = ["Other::Foo"]; + /// /// Validates that a freshly-constructed filter has no candidates, no active filters, /// an empty (non-null) search text, and an empty displayed list. @@ -72,7 +126,7 @@ public void ElementFilterViewModel_SetCandidates_AvailableTypeLabels_IsDistinctA filter.SetCandidates(MixedCandidates); // Assert - Assert.Equal(new[] { "package", "part", "part def" }, filter.AvailableTypeLabels); + Assert.Equal(ExpectedSortedTypeLabels, filter.AvailableTypeLabels); } /// @@ -90,7 +144,7 @@ public void ElementFilterViewModel_SetCandidates_DefaultLabelPresent_Prepopulate filter.SetCandidates(MixedCandidates, defaultTypeFilterLabel: "part"); // Assert - Assert.Equal(new[] { "part" }, filter.ActiveTypeFilters); + Assert.Equal(PartChipOnly, filter.ActiveTypeFilters); } /// @@ -126,7 +180,7 @@ public void ElementFilterViewModel_DisplayedItems_DefaultPartChip_ShowsOnlyPartU // Assert Assert.Equal( - new[] { "Model::engineInstance", "Model::wheelInstance" }, + ExpectedPartUsages, filter.DisplayedItems); } @@ -189,7 +243,7 @@ public void ElementFilterViewModel_DisplayedItems_SearchText_AppliesAndSemantics // Assert - "part def" chip matches Engine + Wheel; "package" chip matches SubPackage. // After substring "engine", only Engine remains. - Assert.Equal(new[] { "Model::Engine" }, filter.DisplayedItems); + Assert.Equal(ExpectedEngineOnly, filter.DisplayedItems); } /// @@ -206,7 +260,7 @@ public void ElementFilterViewModel_DisplayedItems_SearchText_IsCaseInsensitive() filter.SearchText = "SUBPACKAGE"; // Assert - Assert.Equal(new[] { "Model::SubPackage" }, filter.DisplayedItems); + Assert.Equal(ExpectedSubPackageOnly, filter.DisplayedItems); } /// @@ -267,7 +321,7 @@ public void ElementFilterViewModel_GetAddableTypeLabels_ExcludesActiveChips() var addable = filter.GetAddableTypeLabels(); // Assert - Assert.Equal(new[] { "package", "part def" }, addable); + Assert.Equal(ExpectedAddableLabels, addable); } /// @@ -290,7 +344,7 @@ public void ElementFilterViewModel_BeginAddableTypeFilterSearch_ResetsSearchAndP // Assert Assert.Equal("", filter.AddableTypeFilterSearchText); - Assert.Equal(new[] { "package", "part def" }, filter.AddableTypeFilterCandidates); + Assert.Equal(ExpectedAddableLabels, filter.AddableTypeFilterCandidates); } /// @@ -311,7 +365,7 @@ public void ElementFilterViewModel_AddableTypeFilterSearchText_NarrowsCandidates filter.AddableTypeFilterSearchText = "DEF"; // Assert - Assert.Equal(new[] { "part def" }, filter.AddableTypeFilterCandidates); + Assert.Equal(ExpectedDefLabelOnly, filter.AddableTypeFilterCandidates); } /// @@ -373,11 +427,11 @@ public void ElementFilterViewModel_SetCandidates_SecondCall_ReplacesState() // Act filter.SearchText = null; - filter.SetCandidates([("Other::Foo", "other")], defaultTypeFilterLabel: null); + filter.SetCandidates(ReplacementCandidates, defaultTypeFilterLabel: null); // Assert - Assert.Equal(new[] { "other" }, filter.AvailableTypeLabels); + Assert.Equal(ExpectedOtherLabelOnly, filter.AvailableTypeLabels); Assert.Empty(filter.ActiveTypeFilters); - Assert.Equal(new[] { "Other::Foo" }, filter.DisplayedItems); + Assert.Equal(ExpectedOtherFooOnly, filter.DisplayedItems); } } diff --git a/test/DemaConsulting.SysML2Workbench.Tests/ElementPickerSubsystem/ElementPickerViewModelTests.cs b/test/DemaConsulting.SysML2Workbench.Tests/ElementPickerSubsystem/ElementPickerViewModelTests.cs index 84f8b42..3fc0ed0 100644 --- a/test/DemaConsulting.SysML2Workbench.Tests/ElementPickerSubsystem/ElementPickerViewModelTests.cs +++ b/test/DemaConsulting.SysML2Workbench.Tests/ElementPickerSubsystem/ElementPickerViewModelTests.cs @@ -24,6 +24,55 @@ private static readonly (string QualifiedName, string TypeLabel)[] MixedCandidat ("Model::SubPackage", "package"), ]; + /// + /// Expected distinct, ordinally-sorted type labels derived from . + /// + private static readonly string[] ExpectedSortedTypeLabels = ["package", "part", "part def"]; + + /// + /// Single-label filter set used to pre-populate the "part" chip. + /// + private static readonly string[] PartChipOnly = ["part"]; + + /// + /// Expected qualified names of the "part" (non-definition) candidates in . + /// + private static readonly string[] ExpectedPartUsages = ["Model::engineInstance", "Model::wheelInstance"]; + + /// + /// Expected single displayed item after narrowing by the substring "engine". + /// + private static readonly string[] ExpectedEngineOnly = ["Model::Engine"]; + + /// + /// Expected single displayed item after narrowing by the substring "SUBPACKAGE". + /// + private static readonly string[] ExpectedSubPackageOnly = ["Model::SubPackage"]; + + /// + /// Expected addable type labels (all labels except the pre-populated "part" chip). + /// + private static readonly string[] ExpectedAddableLabels = ["package", "part def"]; + + /// + /// Replacement candidate list used to verify + /// fully replaces prior state on a second call. + /// + private static readonly (string QualifiedName, string TypeLabel)[] ReplacementCandidates = + [ + ("Other::Foo", "other"), + ]; + + /// + /// Expected sole type label after replacing candidates with . + /// + private static readonly string[] ExpectedOtherLabelOnly = ["other"]; + + /// + /// Expected sole displayed item after replacing candidates with . + /// + private static readonly string[] ExpectedOtherFooOnly = ["Other::Foo"]; + /// /// Validates that a freshly-constructed picker has no candidates, no active filters, /// an empty (non-null) search text, and an empty displayed list. @@ -71,7 +120,7 @@ public void ElementPickerViewModel_SetCandidates_AvailableTypeLabels_IsDistinctA picker.SetCandidates(MixedCandidates); // Assert - Assert.Equal(new[] { "package", "part", "part def" }, picker.AvailableTypeLabels); + Assert.Equal(ExpectedSortedTypeLabels, picker.AvailableTypeLabels); } /// @@ -89,7 +138,7 @@ public void ElementPickerViewModel_SetCandidates_DefaultLabelPresent_Prepopulate picker.SetCandidates(MixedCandidates, defaultTypeFilterLabel: "part"); // Assert - Assert.Equal(new[] { "part" }, picker.ActiveTypeFilters); + Assert.Equal(PartChipOnly, picker.ActiveTypeFilters); } /// @@ -125,7 +174,7 @@ public void ElementPickerViewModel_DisplayedItems_DefaultPartChip_ShowsOnlyPartU // Assert Assert.Equal( - new[] { "Model::engineInstance", "Model::wheelInstance" }, + ExpectedPartUsages, picker.DisplayedItems); } @@ -188,7 +237,7 @@ public void ElementPickerViewModel_DisplayedItems_SearchText_AppliesAndSemantics // Assert - "part def" chip matches Engine + Wheel; "package" chip matches SubPackage. // After substring "engine", only Engine remains. - Assert.Equal(new[] { "Model::Engine" }, picker.DisplayedItems); + Assert.Equal(ExpectedEngineOnly, picker.DisplayedItems); } /// @@ -205,7 +254,7 @@ public void ElementPickerViewModel_DisplayedItems_SearchText_IsCaseInsensitive() picker.SearchText = "SUBPACKAGE"; // Assert - Assert.Equal(new[] { "Model::SubPackage" }, picker.DisplayedItems); + Assert.Equal(ExpectedSubPackageOnly, picker.DisplayedItems); } /// @@ -266,7 +315,7 @@ public void ElementPickerViewModel_GetAddableTypeLabels_ExcludesActiveChips() var addable = picker.GetAddableTypeLabels(); // Assert - Assert.Equal(new[] { "package", "part def" }, addable); + Assert.Equal(ExpectedAddableLabels, addable); } /// @@ -285,13 +334,13 @@ public void ElementPickerViewModel_SetCandidates_SecondCall_ReplacesState() // Act picker.SearchText = null; - picker.SetCandidates([("Other::Foo", "other")], defaultTypeFilterLabel: null); + picker.SetCandidates(ReplacementCandidates, defaultTypeFilterLabel: null); // Assert - Assert.Equal(new[] { "other" }, picker.AvailableTypeLabels); + Assert.Equal(ExpectedOtherLabelOnly, picker.AvailableTypeLabels); Assert.Empty(picker.ActiveTypeFilters); Assert.Null(picker.SelectedQualifiedName); - Assert.Equal(new[] { "Other::Foo" }, picker.DisplayedItems); + Assert.Equal(ExpectedOtherFooOnly, picker.DisplayedItems); } /// diff --git a/test/DemaConsulting.SysML2Workbench.Tests/ElementPickerSubsystemTests.cs b/test/DemaConsulting.SysML2Workbench.Tests/ElementPickerSubsystemTests.cs index a677048..da32e7e 100644 --- a/test/DemaConsulting.SysML2Workbench.Tests/ElementPickerSubsystemTests.cs +++ b/test/DemaConsulting.SysML2Workbench.Tests/ElementPickerSubsystemTests.cs @@ -11,6 +11,21 @@ namespace DemaConsulting.SysML2Workbench.Tests; /// public sealed class ElementPickerSubsystemTests { + /// + /// Expected distinct, ordinally-sorted type labels for the candidates constructed below. + /// + private static readonly string[] ExpectedSortedTypeLabels = ["package", "part", "part def"]; + + /// + /// Single-label filter set used to pre-populate the "part" chip. + /// + private static readonly string[] PartChipOnly = ["part"]; + + /// + /// Expected sole displayed item once the "part" chip narrows the candidate list. + /// + private static readonly string[] ExpectedPartUsageOnly = ["Sample::engine"]; + /// /// Validates that the labeler and the view model compose end-to-end: labels computed by /// flow through @@ -39,8 +54,8 @@ public void SubsystemComposition_LabelerFeedsViewModel_DefaultChipFiltersDisplay // Assert: all three labels surface in AvailableTypeLabels (distinct, sorted), the // default chip is active, and only the "part" candidate is displayed. - Assert.Equal(new[] { "package", "part", "part def" }, vm.AvailableTypeLabels); - Assert.Equal(new[] { "part" }, vm.ActiveTypeFilters); - Assert.Equal(new[] { "Sample::engine" }, vm.DisplayedItems); + Assert.Equal(ExpectedSortedTypeLabels, vm.AvailableTypeLabels); + Assert.Equal(PartChipOnly, vm.ActiveTypeFilters); + Assert.Equal(ExpectedPartUsageOnly, vm.DisplayedItems); } } diff --git a/test/DemaConsulting.SysML2Workbench.Tests/LoggingSubsystem/RollingFileLoggerTests.cs b/test/DemaConsulting.SysML2Workbench.Tests/LoggingSubsystem/RollingFileLoggerTests.cs index 67b497b..f1b1d66 100644 --- a/test/DemaConsulting.SysML2Workbench.Tests/LoggingSubsystem/RollingFileLoggerTests.cs +++ b/test/DemaConsulting.SysML2Workbench.Tests/LoggingSubsystem/RollingFileLoggerTests.cs @@ -92,10 +92,10 @@ public void RetentionLimit_RotatesLogFiles() public void Flush_BeforeAnyLogEntry_DoesNotThrow() { // Arrange - var logger = new RollingFileLogger(_tempRoot); + _ = new RollingFileLogger(_tempRoot); // Act / Assert - var exception = Record.Exception(logger.Flush); + var exception = Record.Exception(RollingFileLogger.Flush); Assert.Null(exception); } diff --git a/test/DemaConsulting.SysML2Workbench.Tests/SysML2WorkbenchTests.cs b/test/DemaConsulting.SysML2Workbench.Tests/SysML2WorkbenchTests.cs index ccc1a0d..a0beebb 100644 --- a/test/DemaConsulting.SysML2Workbench.Tests/SysML2WorkbenchTests.cs +++ b/test/DemaConsulting.SysML2Workbench.Tests/SysML2WorkbenchTests.cs @@ -49,6 +49,7 @@ await File.WriteAllTextAsync( private MainWindowShell CreateShell() { return new MainWindowShell( + new MainWindowShellDependencies( new WorkspaceModel(), new FileWatcher(TimeSpan.FromMilliseconds(1)), new DiagnosticsAggregator(), @@ -56,7 +57,7 @@ private MainWindowShell CreateShell() new LayoutInvoker(), new DiagnosticsListView(), new SysmlSnippetGenerator(), - new RollingFileLogger(_tempLogRoot)); + new RollingFileLogger(_tempLogRoot))); } /// @@ -168,6 +169,7 @@ public async Task StartSession_OpensShellAndWritesOperationalLogs() await WriteSampleWorkspaceAsync(); var logger = new RollingFileLogger(_tempLogRoot); using var shell = new MainWindowShell( + new MainWindowShellDependencies( new WorkspaceModel(), new FileWatcher(TimeSpan.FromMilliseconds(1)), new DiagnosticsAggregator(), @@ -175,7 +177,7 @@ public async Task StartSession_OpensShellAndWritesOperationalLogs() new LayoutInvoker(), new DiagnosticsListView(), new SysmlSnippetGenerator(), - logger); + logger)); // Act await shell.AddFolderSourceAsync(_tempRoot); diff --git a/test/DemaConsulting.SysML2Workbench.UiTests/AppShellSubsystem/MainWindowShellUiTests.cs b/test/DemaConsulting.SysML2Workbench.UiTests/AppShellSubsystem/MainWindowShellUiTests.cs index 9f0b737..02f1cb9 100644 --- a/test/DemaConsulting.SysML2Workbench.UiTests/AppShellSubsystem/MainWindowShellUiTests.cs +++ b/test/DemaConsulting.SysML2Workbench.UiTests/AppShellSubsystem/MainWindowShellUiTests.cs @@ -37,6 +37,7 @@ public void Dispose() private MainWindowShell CreateShell() { return new MainWindowShell( + new MainWindowShellDependencies( new WorkspaceModel(), new FileWatcher(TimeSpan.FromMilliseconds(1)), new DiagnosticsAggregator(), @@ -44,7 +45,7 @@ private MainWindowShell CreateShell() new LayoutInvoker(), new DiagnosticsListView(), new SysmlSnippetGenerator(), - new RollingFileLogger(_tempLogRoot)); + new RollingFileLogger(_tempLogRoot))); } /// @@ -200,7 +201,7 @@ await File.WriteAllTextAsync( shell.SelectPredefinedView(shell.ViewCatalog.AvailableViews[0].QualifiedName); shell.OpenSourceTextTab(filePath); - var factory = (WorkbenchDockFactory)((IDock)window.WorkbenchDockControl.Layout!).Factory!; + var factory = (WorkbenchDockFactory)window.WorkbenchDockControl.Layout!.Factory!; var documents = factory.DiagramDock.VisibleDockables!; var diagramDocument = documents.OfType().Single(); var sourceTextDocument = documents.OfType().Single(); diff --git a/test/DemaConsulting.SysML2Workbench.UiTests/AppShellSubsystem/QueryDialogUiTests.cs b/test/DemaConsulting.SysML2Workbench.UiTests/AppShellSubsystem/QueryDialogUiTests.cs index 15fc63e..68a4b6e 100644 --- a/test/DemaConsulting.SysML2Workbench.UiTests/AppShellSubsystem/QueryDialogUiTests.cs +++ b/test/DemaConsulting.SysML2Workbench.UiTests/AppShellSubsystem/QueryDialogUiTests.cs @@ -32,6 +32,7 @@ public void Dispose() private MainWindowShell CreateShell() { return new MainWindowShell( + new MainWindowShellDependencies( new WorkspaceModel(), new FileWatcher(TimeSpan.FromMilliseconds(1)), new DiagnosticsAggregator(), @@ -39,7 +40,7 @@ private MainWindowShell CreateShell() new LayoutInvoker(), new DiagnosticsListView(), new SysmlSnippetGenerator(), - new RollingFileLogger(_tempLogRoot)); + new RollingFileLogger(_tempLogRoot))); } /// diff --git a/test/DemaConsulting.SysML2Workbench.UiTests/AppShellSubsystem/WorkspacePanelUiTests.cs b/test/DemaConsulting.SysML2Workbench.UiTests/AppShellSubsystem/WorkspacePanelUiTests.cs index 2183a14..f6f4614 100644 --- a/test/DemaConsulting.SysML2Workbench.UiTests/AppShellSubsystem/WorkspacePanelUiTests.cs +++ b/test/DemaConsulting.SysML2Workbench.UiTests/AppShellSubsystem/WorkspacePanelUiTests.cs @@ -32,6 +32,7 @@ public void Dispose() private MainWindowShell CreateShell() { return new MainWindowShell( + new MainWindowShellDependencies( new WorkspaceModel(), new FileWatcher(TimeSpan.FromMilliseconds(1)), new DiagnosticsAggregator(), @@ -39,7 +40,7 @@ private MainWindowShell CreateShell() new LayoutInvoker(), new DiagnosticsListView(), new SysmlSnippetGenerator(), - new RollingFileLogger(_tempLogRoot)); + new RollingFileLogger(_tempLogRoot))); } /// diff --git a/test/DemaConsulting.SysML2Workbench.UiTests/DemaConsulting.SysML2Workbench.UiTests.csproj b/test/DemaConsulting.SysML2Workbench.UiTests/DemaConsulting.SysML2Workbench.UiTests.csproj index 2a38d3f..6a2b30a 100644 --- a/test/DemaConsulting.SysML2Workbench.UiTests/DemaConsulting.SysML2Workbench.UiTests.csproj +++ b/test/DemaConsulting.SysML2Workbench.UiTests/DemaConsulting.SysML2Workbench.UiTests.csproj @@ -12,6 +12,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/test/OtsSoftwareTests/AvaloniaEditTests.cs b/test/OtsSoftwareTests/AvaloniaEditTests.cs index cf97223..abba249 100644 --- a/test/OtsSoftwareTests/AvaloniaEditTests.cs +++ b/test/OtsSoftwareTests/AvaloniaEditTests.cs @@ -116,7 +116,7 @@ public void TextEditor_HostedInRealWindow_GetsAControlThemeAndVisualTemplate() /// SourceTextDocumentView.LoadSysMlHighlighting does, so this test verifies the real reflected /// keyword set rather than a hand-copied duplicate. /// - private static IReadOnlyList GetSysmlKeywords() + private static List GetSysmlKeywords() { var field = typeof(DemaConsulting.SysML2Tools.Parser.Antlr.SysMLv2Lexer).GetField( "_LiteralNames", diff --git a/test/OtsSoftwareTests/AvaloniaTests.cs b/test/OtsSoftwareTests/AvaloniaTests.cs index b3c2793..bed76e9 100644 --- a/test/OtsSoftwareTests/AvaloniaTests.cs +++ b/test/OtsSoftwareTests/AvaloniaTests.cs @@ -52,6 +52,7 @@ public void Dispose() private MainWindowShell CreateShell() { return new MainWindowShell( + new MainWindowShellDependencies( new WorkspaceModel(), new FileWatcher(TimeSpan.FromMilliseconds(1)), new DiagnosticsAggregator(), @@ -59,7 +60,7 @@ private MainWindowShell CreateShell() new LayoutInvoker(), new DiagnosticsListView(), new SysmlSnippetGenerator(), - new RollingFileLogger(_tempLogRoot), + new RollingFileLogger(_tempLogRoot)), uiDispatcher: new AvaloniaUiDispatcher()); } diff --git a/test/OtsSoftwareTests/DockTests.cs b/test/OtsSoftwareTests/DockTests.cs index 2c4d74c..16a30d8 100644 --- a/test/OtsSoftwareTests/DockTests.cs +++ b/test/OtsSoftwareTests/DockTests.cs @@ -42,6 +42,7 @@ public void Dispose() private MainWindowShell CreateShell(IUiDispatcher? dispatcher = null) { return new MainWindowShell( + new MainWindowShellDependencies( new WorkspaceModel(), new FileWatcher(TimeSpan.FromMilliseconds(1)), new DiagnosticsAggregator(), @@ -49,7 +50,7 @@ private MainWindowShell CreateShell(IUiDispatcher? dispatcher = null) new LayoutInvoker(), new DiagnosticsListView(), new SysmlSnippetGenerator(), - new RollingFileLogger(_tempLogRoot), + new RollingFileLogger(_tempLogRoot)), uiDispatcher: dispatcher); } diff --git a/test/OtsSoftwareTests/OtsSoftwareTests.csproj b/test/OtsSoftwareTests/OtsSoftwareTests.csproj index f012ff0..cb18976 100644 --- a/test/OtsSoftwareTests/OtsSoftwareTests.csproj +++ b/test/OtsSoftwareTests/OtsSoftwareTests.csproj @@ -19,6 +19,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive +