mirror of
https://github.com/kuhyx/todo-app.git
synced 2026-11-11 20:53:59 +01:00
Both delete paths -- the per-note actions sheet and the detail screen -- deleted immediately, with no confirmation and no undo. With a pointer that is merely risky: you have to travel to the control and press it. With a keyboard it is one Tab away from a Return you were about to press anyway, and the note is gone with nothing to recover it. The confirmation is built so the SAFE option is what a reflexive keypress hits: Cancel is autofocused, Escape dismisses as "no", and the destructive button is error-coloured but deliberately not the default action. Escape is bound explicitly rather than relying on the barrier, because the desktop surface here is a Chrome --app window where that cannot be assumed -- and a confirmation the keyboard cannot back out of is worse than no confirmation at all. The actions sheet also gains isScrollControlled. Default bottom sheets cap at 9/16 of the viewport, i.e. ~432px on a 768px screen, and this sheet's height grows with the note title and its chip rows -- so Delete could be clipped out of reach on the very screen this app is used on. The two existing delete tests correctly failed once confirmation landed; both now go through the dialog, and each gains a cancel-keeps-the-note counterpart so the negative path is covered too. 204 tests, 100% line coverage held. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
106 lines
3.3 KiB
Dart
106 lines
3.3 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:todo/ui/confirm.dart';
|
|
|
|
/// Tests for the destructive-action confirmation.
|
|
///
|
|
/// Both delete paths (the actions sheet and the detail screen) used to delete
|
|
/// immediately with no confirmation and no undo. With a keyboard that is one
|
|
/// Tab away from a Return the user was about to press anyway, so the defaults
|
|
/// here — Cancel autofocused, Escape dismisses — are the actual safety
|
|
/// property, not decoration.
|
|
void main() {
|
|
Future<bool?> showFor(WidgetTester tester) async {
|
|
bool? result;
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: Builder(
|
|
builder: (context) => ElevatedButton(
|
|
onPressed: () async {
|
|
result = await confirmDestructive(
|
|
context,
|
|
title: 'Delete note?',
|
|
message: 'This cannot be undone.',
|
|
);
|
|
},
|
|
child: const Text('go'),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.tap(find.text('go'));
|
|
await tester.pumpAndSettle();
|
|
return result;
|
|
}
|
|
|
|
testWidgets('shows the title and message', (tester) async {
|
|
await showFor(tester);
|
|
expect(find.text('Delete note?'), findsOneWidget);
|
|
expect(find.text('This cannot be undone.'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('Cancel is autofocused so a reflexive Return is safe', (
|
|
tester,
|
|
) async {
|
|
await showFor(tester);
|
|
final cancel = tester.widget<TextButton>(
|
|
find.ancestor(of: find.text('Cancel'), matching: find.byType(TextButton)),
|
|
);
|
|
expect(cancel.autofocus, isTrue);
|
|
});
|
|
|
|
testWidgets('confirming returns true', (tester) async {
|
|
await showFor(tester);
|
|
await tester.tap(find.text('Delete'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('Delete note?'), findsNothing);
|
|
});
|
|
|
|
testWidgets('cancelling returns false', (tester) async {
|
|
await showFor(tester);
|
|
await tester.tap(find.text('Cancel'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('Delete note?'), findsNothing);
|
|
});
|
|
|
|
testWidgets('Escape dismisses without confirming', (tester) async {
|
|
await showFor(tester);
|
|
expect(find.text('Delete note?'), findsOneWidget);
|
|
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.escape);
|
|
await tester.pumpAndSettle();
|
|
|
|
// A confirmation the keyboard cannot back out of is worse than none.
|
|
expect(find.text('Delete note?'), findsNothing);
|
|
});
|
|
|
|
testWidgets('custom labels are honoured', (tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: Builder(
|
|
builder: (context) => ElevatedButton(
|
|
onPressed: () => confirmDestructive(
|
|
context,
|
|
title: 'Discard?',
|
|
message: 'Unsaved work will be lost.',
|
|
confirmLabel: 'Discard',
|
|
cancelLabel: 'Keep editing',
|
|
),
|
|
child: const Text('go'),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.tap(find.text('go'));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Discard'), findsOneWidget);
|
|
expect(find.text('Keep editing'), findsOneWidget);
|
|
});
|
|
}
|