@@ -34,6 +34,7 @@ class _Store implements DraftRepository {
3434 );
3535 Completer <DraftRead >? delayed;
3636 bool failClear = false ;
37+ DraftStorageFailure ? failWrite;
3738 @override
3839 Future <DraftRead > load () async => delayed == null ? slot : delayed! .future;
3940 @override
@@ -50,6 +51,7 @@ class _Store implements DraftRepository {
5051 DraftSnapshot snapshot, {
5152 required int expectedGeneration,
5253 }) async {
54+ if (failWrite != null ) throw DraftStorageException (failWrite! );
5355 slot = DraftRead (generation: snapshot.generation, snapshot: snapshot);
5456 }
5557
@@ -309,4 +311,135 @@ void main() {
309311 expect (tester.takeException (), isNull);
310312 },
311313 );
314+ testWidgets (
315+ 'runtime unsafe storage failure offers Retry and volatile continuation' ,
316+ (WidgetTester tester) async {
317+ final _Store store = _Store ();
318+ final FakeConnection connection = FakeConnection ();
319+ addTearDown (connection.dispose);
320+ await pumpSurface (
321+ tester,
322+ const RecoveryGate (child: EditorView ()),
323+ connection: connection,
324+ extra: < Override > [draftRepositoryProvider.overrideWithValue (store)],
325+ );
326+ await tester.pump ();
327+ await tester.tap (find.byKey (kRecoveryRestoreKey));
328+ await tester.pump ();
329+ store.failWrite = DraftStorageFailure .conflict;
330+ containerOf (
331+ tester,
332+ ).read (editorDocumentProvider.notifier).setContent ('live newer text' );
333+ await tester.pump ();
334+ await tester.tap (find.byKey (kRecoverySaveKey));
335+ await tester.pumpAndSettle ();
336+ expect (find.byKey (kRecoveryRetryKey), findsOneWidget);
337+ expect (find.byKey (kRecoveryContinueKey), findsOneWidget);
338+ expect (
339+ containerOf (tester).read (editorDocumentProvider).content,
340+ 'live newer text' ,
341+ );
342+ await tester.tap (find.byKey (kRecoveryRetryKey));
343+ await tester.pumpAndSettle ();
344+ expect (find.text ('prior text' ), findsOneWidget);
345+ expect (find.text ('live newer text' ), findsOneWidget);
346+ expect (find.byKey (kRecoveryRestoreKey), findsOneWidget);
347+ expect (store.slot.snapshot! .content, 'prior text' );
348+ },
349+ );
350+
351+ testWidgets (
352+ 'New in an explicitly volatile session needs no second storage-failure choice' ,
353+ (WidgetTester tester) async {
354+ final _Store store = _Store ();
355+ final FakeConnection connection = FakeConnection ();
356+ addTearDown (connection.dispose);
357+ await pumpSurface (
358+ tester,
359+ const RecoveryGate (child: EditorView ()),
360+ connection: connection,
361+ extra: < Override > [draftRepositoryProvider.overrideWithValue (store)],
362+ );
363+ await tester.pump ();
364+ await tester.tap (find.byKey (kRecoveryRestoreKey));
365+ await tester.pump ();
366+ containerOf (
367+ tester,
368+ ).read (draftRecoveryProvider.notifier).continueWithoutRecovery ();
369+ await tester.pump ();
370+ await tester.tap (find.byKey (kEditorNewButtonKey));
371+ await tester.pumpAndSettle ();
372+ await tester.tap (find.text ('Replace draft' ));
373+ await tester.pumpAndSettle ();
374+ expect (containerOf (tester).read (editorDocumentProvider).content, isEmpty);
375+ expect (find.byType (AlertDialog ), findsNothing);
376+ expect (store.slot.snapshot! .content, 'prior text' );
377+ },
378+ );
379+
380+ testWidgets (
381+ 'disconnected shell retains a deliberately empty named file preview' ,
382+ (WidgetTester tester) async {
383+ final _Store store = _Store ()..slot = const DraftRead (generation: 0 );
384+ final FakeConnection connection = FakeConnection ();
385+ final FakeScanner scanner = FakeScanner ();
386+ final FakeSeamReadiness readiness = FakeSeamReadiness ();
387+ final PbleConnectionManager manager = PbleConnectionManager (
388+ scanner: scanner,
389+ readiness: readiness,
390+ connectionFactory: (String _) async => connection,
391+ );
392+ addTearDown (manager.dispose);
393+ addTearDown (scanner.dispose);
394+ addTearDown (readiness.dispose);
395+ addTearDown (connection.dispose);
396+ await tester.pumpWidget (
397+ ProviderScope (
398+ overrides: < Override > [
399+ draftRepositoryProvider.overrideWithValue (store),
400+ connectionManagerProvider.overrideWithValue (manager),
401+ ],
402+ child: const PybleApp (),
403+ ),
404+ );
405+ await tester.pump ();
406+ await tester.pump ();
407+ containerOf (tester)
408+ .read (editorDocumentProvider.notifier)
409+ .openFromBoard (path: '/empty.py' , content: '' );
410+ await tester.pump ();
411+ expect (find.text ('Your current text draft' ), findsOneWidget);
412+ expect (find.text ('empty.py' ), findsOneWidget);
413+ expect (find.byType (ConnectScreen ), findsNothing);
414+ },
415+ );
416+ testWidgets (
417+ 'volatile editor remains usable at 2x text with the keyboard present' ,
418+ (WidgetTester tester) async {
419+ tester.platformDispatcher.textScaleFactorTestValue = 2 ;
420+ addTearDown (tester.platformDispatcher.clearTextScaleFactorTestValue);
421+ tester.view.viewInsets = const FakeViewPadding (bottom: 350 );
422+ addTearDown (tester.view.resetViewInsets);
423+ final _Store store = _Store ()..slot = const DraftRead (generation: 0 );
424+ final FakeConnection connection = FakeConnection ();
425+ addTearDown (connection.dispose);
426+ await pumpSurface (
427+ tester,
428+ const RecoveryGate (child: EditorView ()),
429+ connection: connection,
430+ size: const Size (400 , 800 ),
431+ extra: < Override > [draftRepositoryProvider.overrideWithValue (store)],
432+ );
433+ await tester.pump ();
434+ containerOf (
435+ tester,
436+ ).read (editorDocumentProvider.notifier).setContent ('live text' );
437+ containerOf (
438+ tester,
439+ ).read (draftRecoveryProvider.notifier).continueWithoutRecovery ();
440+ await tester.pump ();
441+ expect (tester.takeException (), isNull);
442+ expect (tester.getSize (find.byType (EditableText )).height, greaterThan (80 ));
443+ },
444+ );
312445}
0 commit comments