From c7c0ab80f7bab904192f2695486774d76e332401 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 25 Oct 2026 12:37:06 +0300 Subject: [PATCH 1/4] fix(sql-workspace): cancelling the destructive-query dialog keeps the staged table edits (#1771) --- .../workspace/generic_sql_workspace.dart | 11 ++-- ...ql_workspace_execute_dirty_guard_test.dart | 52 +++++++++++++++++++ 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/lib/features/workspace/generic_sql_workspace.dart b/lib/features/workspace/generic_sql_workspace.dart index 751db788..07d99d1b 100644 --- a/lib/features/workspace/generic_sql_workspace.dart +++ b/lib/features/workspace/generic_sql_workspace.dart @@ -617,11 +617,6 @@ class GenericSqlWorkspaceState extends material.State { tableTitle: session.title, ); if (!safeToProceed) return; - if (session.stagingBuffer != null && session.stagingBuffer!.isDirty) { - session.stagingBuffer?.dispose(); - session.stagingBuffer = null; - } - // Checked once, over the text that will run. final confirmDestructive = await AppSettings.instance.getConfirmDestructiveOperations(); @@ -642,6 +637,12 @@ class GenericSqlWorkspaceState extends material.State { } } + // Only now: cancelling the destructive dialog above must keep the edits. + if (session.stagingBuffer != null && session.stagingBuffer!.isDirty) { + session.stagingBuffer?.dispose(); + session.stagingBuffer = null; + } + invalidatePane(session); setState(() { session.running = true; diff --git a/test/features/workspace/sql_workspace_execute_dirty_guard_test.dart b/test/features/workspace/sql_workspace_execute_dirty_guard_test.dart index ea717cb5..53256045 100644 --- a/test/features/workspace/sql_workspace_execute_dirty_guard_test.dart +++ b/test/features/workspace/sql_workspace_execute_dirty_guard_test.dart @@ -156,4 +156,56 @@ void main() { await tester.pumpWidget(const SizedBox()); await tester.pumpAndSettle(); }); + + testWidgets( + 'cancelling the destructive-query dialog keeps the staged edits (#1771)', + (tester) async { + await tester.binding.setSurfaceSize(const material.Size(1200, 800)); + + final conn = ConnectionRow( + id: 501, + type: 'sqlite', + name: 'Test SQLite', + host: dbPath, + createdAt: '2026-09-24T00:00:00Z', + ); + + await tester.pumpWidget( + queryaThemeTestShell( + child: material.SizedBox.expand( + child: SqliteSqlWorkspace(connectionRow: conn), + ), + ), + ); + await tester.pumpAndSettle(); + + final dynamic dynamicState = tester.state(find.byType(SqliteSqlWorkspace)); + final dynamic activeSession = dynamicState.activeSession; + activeSession.controller.text = 'DELETE FROM users;'; + activeSession.columns = ['id', 'name']; + activeSession.rows = [['1', 'Alice']]; + final buffer = DataGridStagingBuffer( + columns: ['id', 'name'], + rows: [['1', 'Alice']], + ); + activeSession.stagingBuffer = buffer; + buffer.setCell(0, 1, 'Bob'); + + await tester.tap(find.widgetWithText(QueryaButton, 'Execute (F5)')); + await tester.pumpAndSettle(); + await tester.tap(find.text('Discard')); + await tester.pumpAndSettle(); + + expect(find.text('Destructive Operation Detected'), findsOneWidget); + await tester.tap(find.text('Cancel')); + await tester.pumpAndSettle(); + + expect(find.text('Destructive Operation Detected'), findsNothing); + expect(buffer.isDirty, isTrue); + expect(activeSession.stagingBuffer, same(buffer)); + + material.FocusManager.instance.primaryFocus?.unfocus(); + await tester.pumpWidget(const SizedBox()); + await tester.pumpAndSettle(); + }); } From 867ea3b820d8be6288d92d822e232e3983dd952b Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 25 Oct 2026 12:41:19 +0300 Subject: [PATCH 2/4] test(sql-workspace): wait for the real settings read after Discard (#1771) --- .../sql_workspace_execute_dirty_guard_test.dart | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/test/features/workspace/sql_workspace_execute_dirty_guard_test.dart b/test/features/workspace/sql_workspace_execute_dirty_guard_test.dart index 53256045..672be8a0 100644 --- a/test/features/workspace/sql_workspace_execute_dirty_guard_test.dart +++ b/test/features/workspace/sql_workspace_execute_dirty_guard_test.dart @@ -38,6 +38,18 @@ class _FakePathProvider extends PathProviderPlatform { Future getDownloadsPath() async => _root; } +/// Lets the real async work of a Run (the settings read in sqflite) finish: +/// `pumpAndSettle` alone runs on fake time and never waits for it. +Future _settleRealAsync(WidgetTester tester) async { + for (var i = 0; i < 10; i++) { + await tester.runAsync( + () => Future.delayed(const Duration(milliseconds: 50)), + ); + await tester.pump(); + } + await tester.pumpAndSettle(); +} + void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -143,8 +155,7 @@ void main() { expect(find.text('Unsaved changes in "Query 1"'), findsOneWidget); await tester.tap(find.text('Discard')); - await tester.runAsync(() => Future.delayed(const Duration(milliseconds: 150))); - await tester.pumpAndSettle(); + await _settleRealAsync(tester); // Query was allowed to run, previous dirty buffer was discarded expect(find.text('Unsaved changes in "Query 1"'), findsNothing); @@ -194,7 +205,7 @@ void main() { await tester.tap(find.widgetWithText(QueryaButton, 'Execute (F5)')); await tester.pumpAndSettle(); await tester.tap(find.text('Discard')); - await tester.pumpAndSettle(); + await _settleRealAsync(tester); expect(find.text('Destructive Operation Detected'), findsOneWidget); await tester.tap(find.text('Cancel')); From 0bce64d75ea0a3891612c9d84ddc1832a5ebae3a Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 25 Oct 2026 12:49:07 +0300 Subject: [PATCH 3/4] test(sql-workspace): do not wait for a spinner to settle, run the pool's release timer (#1771) --- .../workspace/sql_workspace_execute_dirty_guard_test.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/features/workspace/sql_workspace_execute_dirty_guard_test.dart b/test/features/workspace/sql_workspace_execute_dirty_guard_test.dart index 672be8a0..c072063c 100644 --- a/test/features/workspace/sql_workspace_execute_dirty_guard_test.dart +++ b/test/features/workspace/sql_workspace_execute_dirty_guard_test.dart @@ -47,7 +47,7 @@ Future _settleRealAsync(WidgetTester tester) async { ); await tester.pump(); } - await tester.pumpAndSettle(); + await tester.pump(const Duration(milliseconds: 100)); } void main() { @@ -217,6 +217,8 @@ void main() { material.FocusManager.instance.primaryFocus?.unfocus(); await tester.pumpWidget(const SizedBox()); + // The connection pool arms a release timer when the workspace goes away. + await tester.pump(const Duration(seconds: 5)); await tester.pumpAndSettle(); }); } From 42db761ce0afac1c333e5b53e60a9b413b74dac4 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 25 Oct 2026 12:52:42 +0300 Subject: [PATCH 4/4] test(sql-workspace): let the closing dialog finish its transition before asserting (#1771) --- .../workspace/sql_workspace_execute_dirty_guard_test.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/features/workspace/sql_workspace_execute_dirty_guard_test.dart b/test/features/workspace/sql_workspace_execute_dirty_guard_test.dart index c072063c..6f22b713 100644 --- a/test/features/workspace/sql_workspace_execute_dirty_guard_test.dart +++ b/test/features/workspace/sql_workspace_execute_dirty_guard_test.dart @@ -47,7 +47,8 @@ Future _settleRealAsync(WidgetTester tester) async { ); await tester.pump(); } - await tester.pump(const Duration(milliseconds: 100)); + // Long enough for the closing dialog to finish its exit transition. + await tester.pump(const Duration(seconds: 1)); } void main() {