From 419a9ae2760077421c3fc2ed7913db4151f3d310 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 30 Apr 2026 06:16:37 +0000 Subject: [PATCH 1/2] Bump Terminal.Gui from 2.0.0-develop.5264 to 2.0.1 --- updated-dependencies: - dependency-name: Terminal.Gui dependency-version: 2.0.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- src/App/DataMorph.App.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/App/DataMorph.App.csproj b/src/App/DataMorph.App.csproj index 6eb69af..40fd278 100644 --- a/src/App/DataMorph.App.csproj +++ b/src/App/DataMorph.App.csproj @@ -6,7 +6,7 @@ - + From e7be473bf28a3ad00cf5e1f8ccba793cc1be3103 Mon Sep 17 00:00:00 2001 From: Yuta Date: Fri, 1 May 2026 19:07:08 +0900 Subject: [PATCH 2/2] refactor: adapt to Terminal.Gui 2.0.1 and improve UI thread safety --- src/App/AppKeyHandler.cs | 4 +- src/App/FileDialogHandler.cs | 101 +++++++++++------- src/App/MainWindow.cs | 8 +- src/App/ViewManager.cs | 77 +++++++++---- src/App/Views/Dialogs/HelpDialog.cs | 2 +- src/App/Views/JsonLinesTreeView.cs | 16 ++- .../Views/JsonTreeNodes/JsonValueTreeNode.cs | 2 +- src/App/Views/LazyTransformer.cs | 11 ++ src/App/Views/MorphTableView.cs | 50 +++++++-- .../DataMorph.Tests/App/AppKeyHandlerTests.cs | 14 +-- .../App/FileDialogHandlerTests.cs | 14 ++- .../App/RecipeCommandHandlerTests.cs | 4 +- tests/DataMorph.Tests/App/ViewManagerTests.cs | 14 +-- .../App/Views/Dialogs/HelpDialogTests.cs | 15 +-- 14 files changed, 226 insertions(+), 106 deletions(-) diff --git a/src/App/AppKeyHandler.cs b/src/App/AppKeyHandler.cs index 21fa74f..d59b61d 100644 --- a/src/App/AppKeyHandler.cs +++ b/src/App/AppKeyHandler.cs @@ -166,13 +166,13 @@ internal bool HandleActionMenu() } if (mt.Table is null || mt.GetRawColumnName is null - || mt.OnMorphAction is null || mt.SelectedColumn < 0) + || mt.OnMorphAction is null || mt.Value is null) { return false; } var handler = new ColumnActionHandler( - _app, mt.Table, mt.SelectedColumn, + _app, mt.Table, mt.Value.Cursor.X, mt.GetRawColumnName, mt.OnMorphAction, mt.IsRowIndexComplete); using var dialog = new ActionMenuDialog(ColumnActionHandler.GetAvailableActions(), handler.ExecuteAction); diff --git a/src/App/FileDialogHandler.cs b/src/App/FileDialogHandler.cs index fed233f..647d646 100644 --- a/src/App/FileDialogHandler.cs +++ b/src/App/FileDialogHandler.cs @@ -68,64 +68,83 @@ internal async Task HandleFileSelectedAsync(string path) try { var schema = await schemaScanner.InitialScanAsync(); - if (schema.Columns.Count == 0) + _app.Invoke(() => { - _viewManager.ShowError("File contains no data"); - return; - } - - _state.Schema = schema; - _state.RowIndexer = indexer; - _state.CsvSchemaScanner = schemaScanner; - _state.CurrentMode = ViewMode.CsvTable; - - _viewManager.SwitchToCsvTable(indexer, schema); - - _ = schemaScanner - .StartBackgroundScanAsync(schema, _state.Cts.Token) - .ContinueWith( - t => - { - if (!t.IsCompletedSuccessfully) + if (schema.Columns.Count == 0) + { + _viewManager.ShowError("File contains no data"); + return; + } + + _state.Schema = schema; + _state.RowIndexer = indexer; + _state.CsvSchemaScanner = schemaScanner; + _state.CurrentMode = ViewMode.CsvTable; + + _viewManager.SwitchToCsvTable(indexer, schema); + + _ = schemaScanner + .StartBackgroundScanAsync(schema, _state.Cts.Token) + .ContinueWith( + t => { - return; - } - - _state.Schema = t.Result; - _state.OnSchemaRefined?.Invoke(t.Result); - }, - TaskScheduler.Default - ); - - _onIndexerStart(indexer); + if (!t.IsCompletedSuccessfully) + { + return; + } + + _app.Invoke(() => + { + _state.Schema = t.Result; + _state.OnSchemaRefined?.Invoke(t.Result); + }); + }, + TaskScheduler.Default + ); + + _onIndexerStart(indexer); + }); return; } #pragma warning disable CA1031 // UI top-level handler catch (Exception ex) #pragma warning restore CA1031 { - _viewManager.ShowError($"Error scanning CSV: {ex.Message}"); + _app.Invoke(() => _viewManager.ShowError($"Error scanning CSV: {ex.Message}")); return; } } if (format == DataFormat.JsonLines) { - _state.RowIndexer = indexer; - _state.JsonLinesSchemaScanner = null; - _state.Schema = null; - _state.OnSchemaRefined = null; + try + { + _state.RowIndexer = indexer; + _state.JsonLinesSchemaScanner = null; + _state.Schema = null; + _state.OnSchemaRefined = null; - var tcs = new TaskCompletionSource( - TaskCreationOptions.RunContinuationsAsynchronously); - indexer.FirstCheckpointReached += () => tcs.TrySetResult(); + var tcs = new TaskCompletionSource( + TaskCreationOptions.RunContinuationsAsynchronously); + indexer.FirstCheckpointReached += () => tcs.TrySetResult(); - _onIndexerStart(indexer); - await tcs.Task; + _onIndexerStart(indexer); + await tcs.Task; - _state.CurrentMode = ViewMode.JsonLinesTree; - _viewManager.SwitchToJsonLinesTree(indexer); - return; + _app.Invoke(() => + { + _state.CurrentMode = ViewMode.JsonLinesTree; + _viewManager.SwitchToJsonLinesTree(indexer); + }); + return; + } +#pragma warning disable CA1031 // UI top-level handler + catch (Exception ex) +#pragma warning restore CA1031 + { + _app.Invoke(() => _viewManager.ShowError($"Error loading JSON Lines: {ex.Message}")); + return; + } } _onIndexerStart(indexer); diff --git a/src/App/MainWindow.cs b/src/App/MainWindow.cs index 6f1526d..bc11971 100644 --- a/src/App/MainWindow.cs +++ b/src/App/MainWindow.cs @@ -40,6 +40,11 @@ internal sealed class MainWindow : Window )] private Label? _progressLabel; + [SuppressMessage( + "Reliability", + "CA2213:Disposable fields should be disposed", + Justification = "Child views added to the Window will be disposed automatically when the Window is disposed." + )] private StatusBar? _statusBar; public MainWindow(IApplication app, AppState state) @@ -53,7 +58,7 @@ public MainWindow(IApplication app, AppState state) Width = Dim.Fill(); Height = Dim.Fill(); - _viewManager = new ViewManager(this, state, _modeController); + _viewManager = new ViewManager(this, state, _modeController, app.Invoke); _fileDialogHandler = new FileDialogHandler(app, state, _viewManager, StartIndexing); _recipeCommandHandler = new RecipeCommandHandler(app, state, _viewManager); @@ -118,7 +123,6 @@ protected override void Dispose(bool disposing) _indexTaskManager.Dispose(); _state.Dispose(); _viewManager.Dispose(); - _statusBar?.Dispose(); } base.Dispose(disposing); } diff --git a/src/App/ViewManager.cs b/src/App/ViewManager.cs index 03f013c..20f12ab 100644 --- a/src/App/ViewManager.cs +++ b/src/App/ViewManager.cs @@ -23,17 +23,20 @@ internal sealed class ViewManager : IDisposable private readonly Window _container; private readonly AppState _state; private readonly ModeController _modeController; + private readonly Action _uiThreadInvoke; private View? _currentView; private bool _disposed; - internal ViewManager(Window container, AppState state, ModeController modeController) + internal ViewManager(Window container, AppState state, ModeController modeController, Action uiThreadInvoke) { ArgumentNullException.ThrowIfNull(container); ArgumentNullException.ThrowIfNull(state); ArgumentNullException.ThrowIfNull(modeController); + ArgumentNullException.ThrowIfNull(uiThreadInvoke); _container = container; _state = state; _modeController = modeController; + _uiThreadInvoke = uiThreadInvoke; } /// @@ -89,26 +92,29 @@ internal async Task ToggleJsonLinesModeAsync() { var result = await _modeController.ToggleJsonLinesModeAsync(); - if (result.IsFailure) + _uiThreadInvoke(() => { - ShowError(result.Error); - RefreshStatusBarHints(); - return; - } + if (result.IsFailure) + { + ShowError(result.Error); + RefreshStatusBarHints(); + return; + } - if (_state.CurrentMode == ViewMode.JsonLinesTree && _state.RowIndexer is not null) - { - SwitchToJsonLinesTree(_state.RowIndexer); - return; - } + if (_state.CurrentMode == ViewMode.JsonLinesTree && _state.RowIndexer is not null) + { + SwitchToJsonLinesTree(_state.RowIndexer); + return; + } - if ( - _state.CurrentMode == ViewMode.JsonLinesTable - && _state.RowIndexer is not null - && _state.Schema is not null) - { - SwitchToJsonLinesTableView(_state.RowIndexer, _state.Schema); - } + if ( + _state.CurrentMode == ViewMode.JsonLinesTable + && _state.RowIndexer is not null + && _state.Schema is not null) + { + SwitchToJsonLinesTableView(_state.RowIndexer, _state.Schema); + } + }); } /// @@ -170,7 +176,9 @@ internal void SwitchToCsvTable(IRowIndexer indexer, TableSchema schema) OnMorphAction = HandleMorphAction, GetRawColumnName = getRawColumnName, }; + SetInitialSelectionWhenReady(view, indexer); SwapView(view); + view.SetFocus(); RefreshStatusBarHints(); if (source is Views.LazyTransformer { FilterRowIndexer: { } filterIndexer }) @@ -258,7 +266,9 @@ [.. schema.Columns.Select(c => Encoding.UTF8.GetBytes(c.Name))], OnMorphAction = HandleMorphAction, GetRawColumnName = getRawColumnName, }; + SetInitialSelectionWhenReady(view, indexer); SwapView(view); + view.SetFocus(); RefreshStatusBarHints(); if (tableSource is Views.LazyTransformer { FilterRowIndexer: { } filterIndexer }) @@ -318,6 +328,37 @@ internal void ShowError(string message) SwapView(view); } + private void SetInitialSelectionWhenReady(MorphTableView view, IRowIndexer indexer) + { + if (indexer.TotalRows > 0) + { + view.SetSelection(0, 0, false); + view.Update(); + return; + } + + void onReady() + { + // Unsubscribe immediately to ensure initial selection logic runs only once + // and to release the captured view reference for garbage collection. + indexer.FirstCheckpointReached -= onReady; + + _uiThreadInvoke(() => + { + // Only set if this view is still active and the user hasn't moved the cursor yet + if (_currentView == view && view.Table is not null && view.Table.Rows > 0 + && (view.Value is null || view.Value.Cursor.Y <= 0)) + { + view.SetSelection(0, 0, false); + view.Update(); + view.SetNeedsDraw(); + } + }); + } + + indexer.FirstCheckpointReached += onReady; + } + private void SwapView(View newView) { if (_currentView is not null) diff --git a/src/App/Views/Dialogs/HelpDialog.cs b/src/App/Views/Dialogs/HelpDialog.cs index 7cee8ca..d1df794 100644 --- a/src/App/Views/Dialogs/HelpDialog.cs +++ b/src/App/Views/Dialogs/HelpDialog.cs @@ -87,6 +87,6 @@ protected override bool OnKeyDown(Key key) return true; } - return base.OnKeyDown(key); + return false; } } diff --git a/src/App/Views/JsonLinesTreeView.cs b/src/App/Views/JsonLinesTreeView.cs index 10a16a1..ca592ba 100644 --- a/src/App/Views/JsonLinesTreeView.cs +++ b/src/App/Views/JsonLinesTreeView.cs @@ -32,18 +32,24 @@ public JsonLinesTreeView(IRowIndexer indexer, Action onTableModeToggle) _onTableModeToggle = onTableModeToggle; LoadInitialRootNodes(); - ObjectActivated += OnObjectActivated; + Accepted += OnAccepted; } - private void OnObjectActivated(object? sender, ObjectActivatedEventArgs e) + private void OnAccepted(object? sender, CommandEventArgs e) { - if (IsExpanded(e.ActivatedObject)) + var node = SelectedObject; + if (node is null) { - Collapse(e.ActivatedObject); return; } - Expand(e.ActivatedObject); + if (IsExpanded(node)) + { + Collapse(node); + return; + } + + Expand(node); } private void LoadInitialRootNodes() diff --git a/src/App/Views/JsonTreeNodes/JsonValueTreeNode.cs b/src/App/Views/JsonTreeNodes/JsonValueTreeNode.cs index 84fe500..2ba24f2 100644 --- a/src/App/Views/JsonTreeNodes/JsonValueTreeNode.cs +++ b/src/App/Views/JsonTreeNodes/JsonValueTreeNode.cs @@ -14,8 +14,8 @@ internal sealed class JsonValueTreeNode : TreeNode /// /// The display text for this value node. public JsonValueTreeNode(string text) - : base(text) { + Text = text; Children = []; } diff --git a/src/App/Views/LazyTransformer.cs b/src/App/Views/LazyTransformer.cs index 853b542..59ce468 100644 --- a/src/App/Views/LazyTransformer.cs +++ b/src/App/Views/LazyTransformer.cs @@ -91,11 +91,15 @@ public LazyTransformer( /// internal string[] RawColumnNames => _rawColumnNames; + private bool _disposed; + /// public object this[int row, int col] { get { + ObjectDisposedException.ThrowIf(_disposed, this); + if (row < 0 || row >= Rows) { throw new ArgumentOutOfRangeException(nameof(row)); @@ -322,9 +326,16 @@ private sealed record WorkingColumn( public void Dispose() { + if (_disposed) + { + return; + } + if (_source is IDisposable d) { d.Dispose(); } + + _disposed = true; } } diff --git a/src/App/Views/MorphTableView.cs b/src/App/Views/MorphTableView.cs index 099f153..42da679 100644 --- a/src/App/Views/MorphTableView.cs +++ b/src/App/Views/MorphTableView.cs @@ -41,6 +41,20 @@ protected override bool OnKeyDown(Key key) var action = _vimKeys.Translate(key.KeyCode); + void moveToRow(int row) + { + // Cannot use Command.Start/End as they reset the column to 0 or rightmost. + // We need to preserve the current column while moving rows. + if (Value is null) + { + return; + } + + SetSelection(col: Value.Cursor.X, row: row, extendExistingSelection: false); + Update(); + SetNeedsDraw(); + } + static bool execute(Action a) { a(); @@ -49,14 +63,14 @@ static bool execute(Action a) return action switch { - VimAction.MoveDown => execute(() => ChangeSelectionByOffset(0, 1, false)), - VimAction.MoveUp => execute(() => ChangeSelectionByOffset(0, -1, false)), - VimAction.MoveLeft => execute(() => ChangeSelectionByOffset(-1, 0, false)), - VimAction.MoveRight => execute(() => ChangeSelectionByOffset(1, 0, false)), - VimAction.PageDown => execute(() => ChangeSelectionByOffset(0, Viewport.Height, false)), - VimAction.PageUp => execute(() => ChangeSelectionByOffset(0, -Viewport.Height, false)), - VimAction.GoToFirst => execute(() => ChangeSelectionByOffset(0, -SelectedRow, false)), - VimAction.GoToEnd => execute(() => ChangeSelectionByOffset(0, Table.Rows - 1 - SelectedRow, false)), + VimAction.MoveDown => execute(() => InvokeCommand(Command.Down)), + VimAction.MoveUp => execute(() => InvokeCommand(Command.Up)), + VimAction.MoveLeft => execute(() => InvokeCommand(Command.Left)), + VimAction.MoveRight => execute(() => InvokeCommand(Command.Right)), + VimAction.PageDown => execute(() => InvokeCommand(Command.PageDown)), + VimAction.PageUp => execute(() => InvokeCommand(Command.PageUp)), + VimAction.GoToFirst => execute(() => moveToRow(0)), + VimAction.GoToEnd => execute(() => moveToRow(Table.Rows - 1)), VimAction.PendingGSequence => true, _ => HandleNonVimKey(key), }; @@ -76,9 +90,25 @@ private bool HandleNonVimKey(Key key) protected override void Dispose(bool disposing) { - if (disposing && Table is IDisposable disposableTable) + if (disposing) { - disposableTable.Dispose(); + // Idiomatic safe disposal sequence: + // Unbind data source + IDisposable? tableToDispose = null; + if (Table is IDisposable d) + { + tableToDispose = d; + } + Table = null; + + // Clear selection state (critical to prevent RenderRow crash) + Value = null; + + // Mark for redraw + SetNeedsDraw(); + + // Dispose data source safely + tableToDispose?.Dispose(); } base.Dispose(disposing); diff --git a/tests/DataMorph.Tests/App/AppKeyHandlerTests.cs b/tests/DataMorph.Tests/App/AppKeyHandlerTests.cs index 68830a1..2dce781 100644 --- a/tests/DataMorph.Tests/App/AppKeyHandlerTests.cs +++ b/tests/DataMorph.Tests/App/AppKeyHandlerTests.cs @@ -69,7 +69,7 @@ public void HandleActionMenu_WhenCurrentViewIsNotMorphTableView_ReturnsFalse() using var state = new AppState(); using var window = new Window(); var modeController = new ModeController(state); - using var viewManager = new ViewManager(window, state, modeController); + using var viewManager = new ViewManager(window, state, modeController, action => action()); var fileDialogHandler = new FileDialogHandler(app, state, viewManager, _ => { }); var recipeCommandHandler = new RecipeCommandHandler(app, state, viewManager); using var handler = new AppKeyHandler(app, state, viewManager, fileDialogHandler, recipeCommandHandler, null); @@ -89,7 +89,7 @@ public void HandleActionMenu_WhenTableIsNull_ReturnsFalse() using var state = new AppState(); using var window = new Window(); var modeController = new ModeController(state); - using var viewManager = new ViewManager(window, state, modeController); + using var viewManager = new ViewManager(window, state, modeController, action => action()); using var view = new TestTableView { Table = null }; window.Add(view); var fileDialogHandler = new FileDialogHandler(app, state, viewManager, _ => { }); @@ -111,7 +111,7 @@ public void HandleActionMenu_WhenGetRawColumnNameIsNull_ReturnsFalse() using var state = new AppState(); using var window = new Window(); var modeController = new ModeController(state); - using var viewManager = new ViewManager(window, state, modeController); + using var viewManager = new ViewManager(window, state, modeController, action => action()); using var view = new TestTableView { Table = new TestTableSource() }; window.Add(view); var fileDialogHandler = new FileDialogHandler(app, state, viewManager, _ => { }); @@ -133,7 +133,7 @@ public void HandleActionMenu_WhenOnMorphActionIsNull_ReturnsFalse() using var state = new AppState(); using var window = new Window(); var modeController = new ModeController(state); - using var viewManager = new ViewManager(window, state, modeController); + using var viewManager = new ViewManager(window, state, modeController, action => action()); using var view = new TestTableView { Table = new TestTableSource(), @@ -159,14 +159,14 @@ public void HandleActionMenu_WhenSelectedColumnIsNegative_ReturnsFalse() using var state = new AppState(); using var window = new Window(); var modeController = new ModeController(state); - using var viewManager = new ViewManager(window, state, modeController); + using var viewManager = new ViewManager(window, state, modeController, action => action()); using var view = new TestTableView { Table = new TestTableSource(), GetRawColumnName = _ => "test", OnMorphAction = _ => { } }; - view.SelectedColumn = -1; + view.Value = null; window.Add(view); var fileDialogHandler = new FileDialogHandler(app, state, viewManager, _ => { }); var recipeCommandHandler = new RecipeCommandHandler(app, state, viewManager); @@ -187,7 +187,7 @@ public void HandleClearActions_WhenActionStackIsEmpty_ReturnsFalse() using var state = new AppState(); using var window = new Window(); var modeController = new ModeController(state); - using var viewManager = new ViewManager(window, state, modeController); + using var viewManager = new ViewManager(window, state, modeController, action => action()); var fileDialogHandler = new FileDialogHandler(app, state, viewManager, _ => { }); var recipeCommandHandler = new RecipeCommandHandler(app, state, viewManager); using var handler = new AppKeyHandler(app, state, viewManager, fileDialogHandler, recipeCommandHandler, null); diff --git a/tests/DataMorph.Tests/App/FileDialogHandlerTests.cs b/tests/DataMorph.Tests/App/FileDialogHandlerTests.cs index 16d177d..b6e4c02 100644 --- a/tests/DataMorph.Tests/App/FileDialogHandlerTests.cs +++ b/tests/DataMorph.Tests/App/FileDialogHandlerTests.cs @@ -42,7 +42,7 @@ public void Constructor_WithValidDependencies_DoesNotThrow() using var state = new AppState(); using var window = new Window(); var modeController = new ModeController(state); - using var viewManager = new ViewManager(window, state, modeController); + using var viewManager = new ViewManager(window, state, modeController, action => action()); // Act Action act = () => @@ -62,7 +62,7 @@ public async Task HandleFileSelectedAsync_JsonLinesFile_SwitchesToTreeViewAfterF using var state = new AppState(); using var window = new Window(); var modeController = new ModeController(state); - using var viewManager = new ViewManager(window, state, modeController); + using var viewManager = new ViewManager(window, state, modeController, action => action()); IRowIndexer? capturedIndexer = null; var handler = new FileDialogHandler(app, state, viewManager, indexer => @@ -73,7 +73,10 @@ public async Task HandleFileSelectedAsync_JsonLinesFile_SwitchesToTreeViewAfterF }); // Act + app.Begin(window); await handler.HandleFileSelectedAsync(_testFile); + app.StopAfterFirstIteration = true; + app.Run(window); // Assert state.CurrentMode.Should().Be(ViewMode.JsonLinesTree); @@ -90,7 +93,7 @@ public async Task HandleFileSelectedAsync_JsonLinesFileBeforeFirstCheckpoint_Doe using var state = new AppState(); using var window = new Window(); var modeController = new ModeController(state); - using var viewManager = new ViewManager(window, state, modeController); + using var viewManager = new ViewManager(window, state, modeController, action => action()); viewManager.SwitchToFileSelection(); // Ensure initial view is not null var tcs = new TaskCompletionSource(); @@ -101,9 +104,14 @@ public async Task HandleFileSelectedAsync_JsonLinesFileBeforeFirstCheckpoint_Doe }); // Act + app.Begin(window); var handleTask = handler.HandleFileSelectedAsync(_testFile); await tcs.Task; // Wait until _onIndexerStart is called + // Process any potential early Invokes + app.StopAfterFirstIteration = true; + app.Run(window); + // Assert state.CurrentMode.Should().NotBe(ViewMode.JsonLinesTree); viewManager.GetCurrentView().Should().NotBeOfType(); diff --git a/tests/DataMorph.Tests/App/RecipeCommandHandlerTests.cs b/tests/DataMorph.Tests/App/RecipeCommandHandlerTests.cs index 4b34499..f54a452 100644 --- a/tests/DataMorph.Tests/App/RecipeCommandHandlerTests.cs +++ b/tests/DataMorph.Tests/App/RecipeCommandHandlerTests.cs @@ -23,7 +23,7 @@ public async Task SaveAsync_WithNonTableMode_DoesNothing() using var state = new AppState { CurrentMode = ViewMode.FileSelection }; using var window = new Window(); var modeController = new ModeController(state); - using var viewManager = new ViewManager(window, state, modeController); + using var viewManager = new ViewManager(window, state, modeController, action => action()); var handler = new RecipeCommandHandler(app, state, viewManager); // Act @@ -41,7 +41,7 @@ public async Task LoadAsync_WithNoFilePath_DoesNothing() using var state = new AppState { CurrentFilePath = string.Empty }; using var window = new Window(); var modeController = new ModeController(state); - using var viewManager = new ViewManager(window, state, modeController); + using var viewManager = new ViewManager(window, state, modeController, action => action()); var handler = new RecipeCommandHandler(app, state, viewManager); // Act diff --git a/tests/DataMorph.Tests/App/ViewManagerTests.cs b/tests/DataMorph.Tests/App/ViewManagerTests.cs index aa27b22..b1ff13b 100644 --- a/tests/DataMorph.Tests/App/ViewManagerTests.cs +++ b/tests/DataMorph.Tests/App/ViewManagerTests.cs @@ -28,7 +28,7 @@ public void RefreshStatusBarHints_WithNoFilePath_UsesDefaultHints() using var statusBar = new StatusBar(); window.Add(statusBar); var modeController = new ModeController(state); - using var viewManager = new ViewManager(window, state, modeController); + using var viewManager = new ViewManager(window, state, modeController, action => action()); // Act viewManager.RefreshStatusBarHints(); @@ -58,7 +58,7 @@ public void RefreshStatusBarHints_WithCsvFilePath_UsesDefaultHints() using var statusBar = new StatusBar(); window.Add(statusBar); var modeController = new ModeController(state); - using var viewManager = new ViewManager(window, state, modeController); + using var viewManager = new ViewManager(window, state, modeController, action => action()); // Act viewManager.RefreshStatusBarHints(); @@ -96,7 +96,7 @@ public void RefreshStatusBarHints_WithJsonLinesPath_IncludesToggleHint() using var statusBar = new StatusBar(); window.Add(statusBar); var modeController = new ModeController(state); - using var viewManager = new ViewManager(window, state, modeController); + using var viewManager = new ViewManager(window, state, modeController, action => action()); // Act viewManager.RefreshStatusBarHints(); @@ -132,7 +132,7 @@ public void RefreshStatusBarHints_WithMorphTableView_IncludesMenuHint() using var statusBar = new StatusBar(); window.Add(statusBar); var modeController = new ModeController(state); - using var viewManager = new ViewManager(window, state, modeController); + using var viewManager = new ViewManager(window, state, modeController, action => action()); var schema = new TableSchema { @@ -174,7 +174,7 @@ public async Task ToggleJsonLinesModeAsync_WhenToggleFails_ShowsError() }; using var window = new Window(); var modeController = new ModeController(state); - using var viewManager = new ViewManager(window, state, modeController); + using var viewManager = new ViewManager(window, state, modeController, action => action()); // Act await viewManager.ToggleJsonLinesModeAsync(); @@ -196,7 +196,7 @@ public async Task ToggleJsonLinesModeAsync_WhenModeBecomesTree_SwitchesToTreeVie using var state = new AppState { CurrentFilePath = filePath, CurrentMode = ViewMode.JsonLinesTable }; using var window = new Window(); var modeController = new ModeController(state); - using var viewManager = new ViewManager(window, state, modeController); + using var viewManager = new ViewManager(window, state, modeController, action => action()); // Setup a valid table state var schema = new TableSchema @@ -235,7 +235,7 @@ public async Task ToggleJsonLinesModeAsync_WhenModeBecomesTable_SwitchesToTableV using var state = new AppState { CurrentFilePath = filePath, CurrentMode = ViewMode.JsonLinesTree }; using var window = new Window(); var modeController = new ModeController(state); - using var viewManager = new ViewManager(window, state, modeController); + using var viewManager = new ViewManager(window, state, modeController, action => action()); // Setup a valid tree state var schema = new TableSchema diff --git a/tests/DataMorph.Tests/App/Views/Dialogs/HelpDialogTests.cs b/tests/DataMorph.Tests/App/Views/Dialogs/HelpDialogTests.cs index b304915..3447191 100644 --- a/tests/DataMorph.Tests/App/Views/Dialogs/HelpDialogTests.cs +++ b/tests/DataMorph.Tests/App/Views/Dialogs/HelpDialogTests.cs @@ -16,7 +16,7 @@ private static IApplication CreateTestApp() } [Fact] - public void OnKeyDown_WithEscKey_CallsRequestStop() + public void OnKeyDown_WithEscKey_ReturnsTrue() { // Arrange using var app = CreateTestApp(); @@ -31,7 +31,7 @@ public void OnKeyDown_WithEscKey_CallsRequestStop() } [Fact] - public void OnKeyDown_WithQKey_CallsRequestStop() + public void OnKeyDown_WithQKey_ReturnsTrue() { // Arrange using var app = CreateTestApp(); @@ -46,7 +46,7 @@ public void OnKeyDown_WithQKey_CallsRequestStop() } [Fact] - public void OnKeyDown_WithLowercaseQKey_CallsRequestStop() + public void OnKeyDown_WithLowercaseQKey_ReturnsTrue() { // Arrange using var app = CreateTestApp(); @@ -61,7 +61,7 @@ public void OnKeyDown_WithLowercaseQKey_CallsRequestStop() } [Fact] - public void OnKeyDown_WithQuestionMarkKey_CallsRequestStop() + public void OnKeyDown_WithQuestionMarkKey_ReturnsTrue() { // Arrange using var app = CreateTestApp(); @@ -90,7 +90,7 @@ public void Constructor_InitializesDialogCorrectly() } [Fact] - public void OnKeyDown_WithOtherKey_DoesNotCallRequestStop() + public void OnKeyDown_WithOtherKey_DoesNotCloseDialog() { // Arrange using var app = CreateTestApp(); @@ -98,9 +98,10 @@ public void OnKeyDown_WithOtherKey_DoesNotCallRequestStop() // Act app.Begin(dialog); - var handled = dialog.NewKeyDownEvent(Key.A); + _ = dialog.NewKeyDownEvent(Key.A); // Assert - handled.Should().BeFalse(); + // Verify that the dialog remains as the TopRunnableView + app.TopRunnableView.Should().Be(dialog); } }