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/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 @@
-
+
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);
}
}