diff --git a/lib/features/mongodb/mongo_document_editor.dart b/lib/features/mongodb/mongo_document_editor.dart index f183485..fa60288 100644 --- a/lib/features/mongodb/mongo_document_editor.dart +++ b/lib/features/mongodb/mongo_document_editor.dart @@ -66,7 +66,9 @@ class _MongoDocumentEditorState extends material.State { void didUpdateWidget(covariant MongoDocumentEditor oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.refreshToken != widget.refreshToken) { - _reloadFromServer(); + material.WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) unawaited(_reloadFromServer()); + }); } } @@ -74,6 +76,11 @@ class _MongoDocumentEditorState extends material.State { Future _reloadFromServer() async { final id = widget.document['_id']; if (id == null) return; + if (_dirty) { + if (!await confirmDiscardUnsavedWorkIfNeeded(context)) return; + if (!mounted) return; + setState(() => _dirty = false); + } setState(() { _error = null; _success = null; diff --git a/test/features/mongodb/mongo_document_editor_test.dart b/test/features/mongodb/mongo_document_editor_test.dart index 1cb72c2..772ee6f 100644 --- a/test/features/mongodb/mongo_document_editor_test.dart +++ b/test/features/mongodb/mongo_document_editor_test.dart @@ -173,4 +173,94 @@ void main() { ); expect(UnsavedWorkRegistry.instance.hasUnsaved, isFalse); }); + + testWidgets( + 'dirty Refresh Cancel keeps edits; Discard reloads from server', + (tester) async { + await tester.binding.setSurfaceSize(const material.Size(800, 700)); + + await tester.pumpWidget( + queryaThemeTestShell( + child: material.SizedBox( + width: 800, + height: 700, + child: MongoDocumentEditor( + connection: connection, + database: 'db', + collection: 'items', + document: const {'_id': 'abc', 'a': 1}, + refreshToken: 0, + ), + ), + ), + ); + await tester.pumpAndSettle(); + await pumpSyntaxHighlightDebounce(tester); + + await tester.enterText( + find.byType(material.EditableText), + '{"_id":"abc","a":999}', + ); + await tester.pump(); + expect(UnsavedWorkRegistry.instance.hasUnsaved, isTrue); + + // Bump refreshToken (simulate breadcrumb toolbar Refresh) + await tester.pumpWidget( + queryaThemeTestShell( + child: material.SizedBox( + width: 800, + height: 700, + child: MongoDocumentEditor( + connection: connection, + database: 'db', + collection: 'items', + document: const {'_id': 'abc', 'a': 1}, + refreshToken: 1, + ), + ), + ), + ); + await tester.pumpAndSettle(); + + // Dialog must appear + expect(find.text('Unsaved changes'), findsOneWidget); + + // Cancel must keep edits + await tester.tap(find.text('Cancel')); + await tester.pumpAndSettle(); + + final editable = tester.widget( + find.byType(material.EditableText), + ); + expect(editable.controller.text, contains('999')); + expect(UnsavedWorkRegistry.instance.hasUnsaved, isTrue); + + // Bump refreshToken again + await tester.pumpWidget( + queryaThemeTestShell( + child: material.SizedBox( + width: 800, + height: 700, + child: MongoDocumentEditor( + connection: connection, + database: 'db', + collection: 'items', + document: const {'_id': 'abc', 'a': 1}, + refreshToken: 2, + ), + ), + ), + ); + await tester.pumpAndSettle(); + + expect(find.text('Unsaved changes'), findsOneWidget); + await tester.tap(find.text('Discard')); + await tester.pumpAndSettle(); + + expect(UnsavedWorkRegistry.instance.hasUnsaved, isFalse); + + await tester.pumpWidget( + queryaThemeTestShell(child: const material.SizedBox()), + ); + }); }