Skip to content

Commit 2d94a24

Browse files
Merge pull request #934 from QueryaHub/issue/870-mongo-refresh-dirty-guard
fix(mongodb): guard document editor Refresh against unsaved JSON edits
2 parents 3024f5f + d8d8ac6 commit 2d94a24

2 files changed

Lines changed: 98 additions & 1 deletion

File tree

‎lib/features/mongodb/mongo_document_editor.dart‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,21 @@ class _MongoDocumentEditorState extends material.State<MongoDocumentEditor> {
6666
void didUpdateWidget(covariant MongoDocumentEditor oldWidget) {
6767
super.didUpdateWidget(oldWidget);
6868
if (oldWidget.refreshToken != widget.refreshToken) {
69-
_reloadFromServer();
69+
material.WidgetsBinding.instance.addPostFrameCallback((_) {
70+
if (mounted) unawaited(_reloadFromServer());
71+
});
7072
}
7173
}
7274

7375
/// Fetches the latest document from the server (toolbar Refresh).
7476
Future<void> _reloadFromServer() async {
7577
final id = widget.document['_id'];
7678
if (id == null) return;
79+
if (_dirty) {
80+
if (!await confirmDiscardUnsavedWorkIfNeeded(context)) return;
81+
if (!mounted) return;
82+
setState(() => _dirty = false);
83+
}
7784
setState(() {
7885
_error = null;
7986
_success = null;

‎test/features/mongodb/mongo_document_editor_test.dart‎

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,4 +173,94 @@ void main() {
173173
);
174174
expect(UnsavedWorkRegistry.instance.hasUnsaved, isFalse);
175175
});
176+
177+
testWidgets(
178+
'dirty Refresh Cancel keeps edits; Discard reloads from server',
179+
(tester) async {
180+
await tester.binding.setSurfaceSize(const material.Size(800, 700));
181+
182+
await tester.pumpWidget(
183+
queryaThemeTestShell(
184+
child: material.SizedBox(
185+
width: 800,
186+
height: 700,
187+
child: MongoDocumentEditor(
188+
connection: connection,
189+
database: 'db',
190+
collection: 'items',
191+
document: const {'_id': 'abc', 'a': 1},
192+
refreshToken: 0,
193+
),
194+
),
195+
),
196+
);
197+
await tester.pumpAndSettle();
198+
await pumpSyntaxHighlightDebounce(tester);
199+
200+
await tester.enterText(
201+
find.byType(material.EditableText),
202+
'{"_id":"abc","a":999}',
203+
);
204+
await tester.pump();
205+
expect(UnsavedWorkRegistry.instance.hasUnsaved, isTrue);
206+
207+
// Bump refreshToken (simulate breadcrumb toolbar Refresh)
208+
await tester.pumpWidget(
209+
queryaThemeTestShell(
210+
child: material.SizedBox(
211+
width: 800,
212+
height: 700,
213+
child: MongoDocumentEditor(
214+
connection: connection,
215+
database: 'db',
216+
collection: 'items',
217+
document: const {'_id': 'abc', 'a': 1},
218+
refreshToken: 1,
219+
),
220+
),
221+
),
222+
);
223+
await tester.pumpAndSettle();
224+
225+
// Dialog must appear
226+
expect(find.text('Unsaved changes'), findsOneWidget);
227+
228+
// Cancel must keep edits
229+
await tester.tap(find.text('Cancel'));
230+
await tester.pumpAndSettle();
231+
232+
final editable = tester.widget<material.EditableText>(
233+
find.byType(material.EditableText),
234+
);
235+
expect(editable.controller.text, contains('999'));
236+
expect(UnsavedWorkRegistry.instance.hasUnsaved, isTrue);
237+
238+
// Bump refreshToken again
239+
await tester.pumpWidget(
240+
queryaThemeTestShell(
241+
child: material.SizedBox(
242+
width: 800,
243+
height: 700,
244+
child: MongoDocumentEditor(
245+
connection: connection,
246+
database: 'db',
247+
collection: 'items',
248+
document: const {'_id': 'abc', 'a': 1},
249+
refreshToken: 2,
250+
),
251+
),
252+
),
253+
);
254+
await tester.pumpAndSettle();
255+
256+
expect(find.text('Unsaved changes'), findsOneWidget);
257+
await tester.tap(find.text('Discard'));
258+
await tester.pumpAndSettle();
259+
260+
expect(UnsavedWorkRegistry.instance.hasUnsaved, isFalse);
261+
262+
await tester.pumpWidget(
263+
queryaThemeTestShell(child: const material.SizedBox()),
264+
);
265+
});
176266
}

0 commit comments

Comments
 (0)