feat(studyset): deleted studysets wait 14 days in Recently deleted
Deleting a studyset blanked it with everything inside it once a four-second Undo was over. It now only marks it (`studyset.deletedAt`, a column as `archivedAt`): it leaves every list, count and session with what is inside it and waits in the library's Recently deleted for 14 days, saying how many are left, with Restore and Delete now. Deleting no longer asks first and offers Undo; Delete now asks. Once the 14 days are over, whichever of the person's devices looks next (as their data opens, after every sync) deletes it for good, blanking first as before; the server can't blank a payload and never writes a person's space (ADR 0007). The connector, publishing and the learner count leave deleted studysets out too. The migration adds one nullable column: app versions before it stop syncing until they update. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
11a16f5686
commit
4644125cc4
5 changed files with 26969 additions and 313 deletions
|
|
@ -19,7 +19,6 @@ import 'package:halkyon_learn_flutter/src/feature/study/model/rating.dart';
|
|||
import 'package:halkyon_learn_flutter/src/feature/study/model/study_progress.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/study/model/studyset_study_options.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/studyset/bloc/studyset_list_bloc.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/studyset/data/studyset_deletions.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/studyset/data/studyset_payload.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/studyset/data/studyset_repository.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/studyset/model/studyset_content.dart';
|
||||
|
|
@ -43,7 +42,6 @@ void main() {
|
|||
late CardRepository cards;
|
||||
late NoteRepository notes;
|
||||
late LearningPathRepository paths;
|
||||
late StudysetDeletions deletions;
|
||||
late ReviewRepository reviews;
|
||||
late StudysetListBloc bloc;
|
||||
late FakeSettingsGateway settingsGateway;
|
||||
|
|
@ -73,7 +71,6 @@ void main() {
|
|||
});
|
||||
tearDown(() async {
|
||||
await bloc.close();
|
||||
await deletions.dispose();
|
||||
await studysets.dispose();
|
||||
await cards.dispose();
|
||||
await notes.dispose();
|
||||
|
|
@ -82,19 +79,8 @@ void main() {
|
|||
await pdfs.dispose();
|
||||
});
|
||||
|
||||
/// Opens the library, where a deletion can be undone for [undoWindow].
|
||||
void start({
|
||||
Duration undoWindow = const Duration(milliseconds: 1),
|
||||
String? parentId,
|
||||
}) {
|
||||
deletions = StudysetDeletions(
|
||||
studysets,
|
||||
cards,
|
||||
notes,
|
||||
paths,
|
||||
pdfs,
|
||||
undoWindow: undoWindow,
|
||||
);
|
||||
/// Opens the library, or inside the studyset [parentId] its topics.
|
||||
void start({String? parentId}) {
|
||||
bloc = StudysetListBloc(
|
||||
studysets,
|
||||
cards,
|
||||
|
|
@ -102,7 +88,6 @@ void main() {
|
|||
paths,
|
||||
pdfs,
|
||||
reviews,
|
||||
deletions,
|
||||
settings,
|
||||
parentId: parentId,
|
||||
)..add(const StudysetListEventStarted());
|
||||
|
|
@ -446,17 +431,34 @@ void main() {
|
|||
);
|
||||
});
|
||||
|
||||
test('leaves out a studyset whose deletion waits, and shows it again '
|
||||
'when that is undone', () async {
|
||||
test('a deleted studyset leaves the list for Recently deleted, and '
|
||||
'comes back restored', () async {
|
||||
final id = await studysets.create(StudysetContent.titled('Japanese'));
|
||||
start(undoWindow: const Duration(hours: 1));
|
||||
start();
|
||||
await listing(['Japanese']);
|
||||
|
||||
deletions.delete(id);
|
||||
await listing([]);
|
||||
deletions.undo(id);
|
||||
await studysets.markDeleted(id);
|
||||
final waiting = await bloc.stream.firstWhere(
|
||||
(state) =>
|
||||
state is StudysetListStateLoaded &&
|
||||
state.studysets.isEmpty &&
|
||||
state.deleted.isNotEmpty,
|
||||
);
|
||||
expect(
|
||||
(waiting as StudysetListStateLoaded).deleted.single.content.title,
|
||||
'Japanese',
|
||||
);
|
||||
|
||||
bloc.add(StudysetListEventRestoreRequested(id));
|
||||
await listing(['Japanese']);
|
||||
expect(
|
||||
bloc.state,
|
||||
isA<StudysetListStateLoaded>().having(
|
||||
(state) => state.deleted,
|
||||
'deleted',
|
||||
isEmpty,
|
||||
),
|
||||
);
|
||||
expect(gateway.deleted, isEmpty);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:clock/clock.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/asset/data/asset_repository.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/content/rich_content.dart';
|
||||
|
|
@ -66,24 +68,30 @@ void main() {
|
|||
await assets.dispose();
|
||||
});
|
||||
|
||||
void open(Duration undoWindow) => deletions = StudysetDeletions(
|
||||
studysets,
|
||||
cards,
|
||||
notes,
|
||||
paths,
|
||||
pdfs,
|
||||
undoWindow: undoWindow,
|
||||
);
|
||||
void open() =>
|
||||
deletions = StudysetDeletions(studysets, cards, notes, paths, pdfs);
|
||||
|
||||
Future<String> card(String studysetId) => cards.create(
|
||||
studysetId,
|
||||
CardContent(front: RichContent.empty(), back: RichContent.empty()),
|
||||
);
|
||||
|
||||
test('a deletion waits for the time to undo, then deletes the whole '
|
||||
'subtree, what is inside first, each with its cards, notes, paths and '
|
||||
'PDFs blanked', () async {
|
||||
open(const Duration(milliseconds: 50));
|
||||
final deletedAt = DateTime.utc(2026, 10, 12, 9);
|
||||
|
||||
/// Deletes the studyset [id] at [deletedAt].
|
||||
Future<void> deleteAt(String id) =>
|
||||
withClock(Clock.fixed(deletedAt), () => deletions.delete(id));
|
||||
|
||||
/// Checks for expired deletions [after] the deletion.
|
||||
Future<void> checkAfter(Duration after) => withClock(
|
||||
Clock.fixed(deletedAt.add(after)),
|
||||
deletions.deleteExpired,
|
||||
);
|
||||
|
||||
test('a deleted studyset waits 14 days, restorable, and is then deleted '
|
||||
'for good with its whole subtree, what is inside first, each with its '
|
||||
'cards, notes, paths and PDFs blanked', () async {
|
||||
open();
|
||||
final japanese = await studysets.create(StudysetContent.titled('Japanese'));
|
||||
final kana = await studysets.create(
|
||||
StudysetContent.titled('Kana'),
|
||||
|
|
@ -104,11 +112,13 @@ void main() {
|
|||
fileName: 'kana.pdf',
|
||||
) as PdfImported).id;
|
||||
|
||||
deletions.delete(japanese);
|
||||
expect(deletions.pending, {japanese});
|
||||
await deleteAt(japanese);
|
||||
expect(await studysets.all(), isEmpty);
|
||||
await checkAfter(const Duration(days: 13, hours: 23));
|
||||
expect(studysetGateway.deleted, isEmpty);
|
||||
expect(cardGateway.deletions, isEmpty);
|
||||
|
||||
await eventually(() => deletions.pending.isEmpty);
|
||||
await checkAfter(const Duration(days: 14));
|
||||
expect(studysetGateway.deleted, [kana, japanese]);
|
||||
expect(cardGateway.deletions, ['blank $kanaCard', 'delete $kanaCard']);
|
||||
expect(noteGateway.deletions, [
|
||||
|
|
@ -120,54 +130,70 @@ void main() {
|
|||
'delete path $kanaPath',
|
||||
]);
|
||||
expect(pdfGateway.deletions, ['blank $kanaPdf', 'delete $kanaPdf']);
|
||||
expect(await studysets.all(), isEmpty);
|
||||
expect(await studysets.withDeleted(), isEmpty);
|
||||
});
|
||||
|
||||
test('undoing in time keeps the studyset', () async {
|
||||
open(const Duration(hours: 1));
|
||||
test('a studyset restored in time stays', () async {
|
||||
open();
|
||||
final japanese = await studysets.create(StudysetContent.titled('Japanese'));
|
||||
|
||||
deletions
|
||||
..delete(japanese)
|
||||
..undo(japanese);
|
||||
await deleteAt(japanese);
|
||||
await deletions.restore(japanese);
|
||||
await checkAfter(const Duration(days: 30));
|
||||
|
||||
expect(deletions.pending, isEmpty);
|
||||
expect(studysetGateway.deleted, isEmpty);
|
||||
expect(await studysets.all(), hasLength(1));
|
||||
});
|
||||
|
||||
test('tells what waits, now and after every change', () async {
|
||||
open(const Duration(hours: 1));
|
||||
test('Delete now deletes for good at once', () async {
|
||||
open();
|
||||
final japanese = await studysets.create(StudysetContent.titled('Japanese'));
|
||||
final told = <Set<String>>[];
|
||||
final subscription = deletions.watchPending().listen(told.add);
|
||||
addTearDown(subscription.cancel);
|
||||
final greek = await studysets.create(StudysetContent.titled('Greek'));
|
||||
|
||||
deletions.delete(japanese);
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
deletions.undo(japanese);
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
await deleteAt(japanese);
|
||||
await deletions.deleteForGood(japanese);
|
||||
|
||||
expect(told, [
|
||||
<String>{},
|
||||
{japanese},
|
||||
<String>{},
|
||||
]);
|
||||
expect(studysetGateway.deleted, [japanese]);
|
||||
expect([for (final s in await studysets.withDeleted()) s.id], [greek]);
|
||||
});
|
||||
|
||||
test(
|
||||
'closing drops the deletions still waiting, and the studysets stay',
|
||||
() async {
|
||||
open(const Duration(milliseconds: 50));
|
||||
final japanese = await studysets.create(
|
||||
StudysetContent.titled('Japanese'),
|
||||
);
|
||||
test('a studyset deleted inside one deleted later goes on its own '
|
||||
'clock, and with the other at the latest', () async {
|
||||
open();
|
||||
final japanese = await studysets.create(StudysetContent.titled('Japanese'));
|
||||
final kana = await studysets.create(
|
||||
StudysetContent.titled('Kana'),
|
||||
parentId: japanese,
|
||||
);
|
||||
|
||||
deletions.delete(japanese);
|
||||
await deletions.dispose();
|
||||
await Future<void>.delayed(const Duration(milliseconds: 200));
|
||||
await deleteAt(kana);
|
||||
await withClock(
|
||||
Clock.fixed(deletedAt.add(const Duration(days: 3))),
|
||||
() => deletions.delete(japanese),
|
||||
);
|
||||
await checkAfter(const Duration(days: 14));
|
||||
expect(studysetGateway.deleted, isEmpty);
|
||||
await checkAfter(const Duration(days: 17));
|
||||
expect(studysetGateway.deleted, [kana, japanese]);
|
||||
});
|
||||
|
||||
test('checks as it starts and whenever told to, as after a sync', () async {
|
||||
open();
|
||||
final japanese = await studysets.create(StudysetContent.titled('Japanese'));
|
||||
await deleteAt(japanese);
|
||||
final syncs = StreamController<void>();
|
||||
addTearDown(syncs.close);
|
||||
|
||||
var now = deletedAt.add(const Duration(days: 1));
|
||||
await withClock(Clock(() => now), () async {
|
||||
deletions.deleteExpiredOn(syncs.stream);
|
||||
await pumpEventQueue();
|
||||
expect(studysetGateway.deleted, isEmpty);
|
||||
expect(await studysets.all(), hasLength(1));
|
||||
},
|
||||
);
|
||||
|
||||
now = deletedAt.add(const Duration(days: 15));
|
||||
syncs.add(null);
|
||||
await eventually(() => studysetGateway.deleted.isNotEmpty);
|
||||
});
|
||||
expect(studysetGateway.deleted, [japanese]);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -237,12 +237,55 @@ void main() {
|
|||
await repository.reorder('s4', toIndex: 1);
|
||||
expect(await childrenTitles(japanese), ['A', 'D', 'B', 'C']);
|
||||
|
||||
// B's deletion waits: the list shows D, C, and A goes after C.
|
||||
await repository.reorder('s1', toIndex: 2, hidden: {'s2'});
|
||||
// B waits in Recently deleted: the list shows A, D, C, and A goes
|
||||
// after C; restored, B is back where it was.
|
||||
await repository.markDeleted('s2');
|
||||
await repository.reorder('s1', toIndex: 2);
|
||||
expect(await childrenTitles(japanese), ['D', 'C', 'A']);
|
||||
await repository.restore('s2');
|
||||
expect(await childrenTitles(japanese), ['D', 'B', 'C', 'A']);
|
||||
});
|
||||
});
|
||||
|
||||
test('a deleted studyset waits in Recently deleted with everything '
|
||||
'inside it, out of every other list, until restored', () async {
|
||||
final japanese = await repository.create(titled('Japanese'));
|
||||
final kana = await repository.create(titled('Kana'), parentId: japanese);
|
||||
final greek = await repository.create(titled('Greek'));
|
||||
final deleted = <List<String>>[];
|
||||
final live = <List<String>>[];
|
||||
final subscriptions = [
|
||||
repository.watchDeleted().listen(
|
||||
(studysets) => deleted.add([for (final s in studysets) s.id]),
|
||||
),
|
||||
repository.watchAll().listen(
|
||||
(studysets) => live.add([for (final s in studysets) s.id]),
|
||||
),
|
||||
];
|
||||
addTearDown(() => Future.wait([for (final s in subscriptions) s.cancel()]));
|
||||
|
||||
await withClock(Clock.fixed(DateTime.utc(2026, 10, 12)), () async {
|
||||
await repository.markDeleted(kana);
|
||||
await repository.markDeleted(japanese);
|
||||
});
|
||||
await pumpEventQueue();
|
||||
expect([for (final s in await repository.all()) s.id], [greek]);
|
||||
expect(live.last, [greek]);
|
||||
// Kana waits inside Japanese, which brings it back.
|
||||
expect(deleted.last, [japanese]);
|
||||
expect(
|
||||
(await repository.withDeleted())
|
||||
.firstWhere((s) => s.id == japanese)
|
||||
.deletedAt,
|
||||
DateTime.utc(2026, 10, 12),
|
||||
);
|
||||
|
||||
await repository.restore(japanese);
|
||||
await pumpEventQueue();
|
||||
expect(live.last, [japanese, greek]);
|
||||
expect(deleted.last, [kana]);
|
||||
});
|
||||
|
||||
test('blanks the content before deleting', () async {
|
||||
final id = await repository.create(titled('Secret'));
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:clock/clock.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/content/published_ref.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/content/rich_content.dart';
|
||||
|
|
@ -10,6 +11,7 @@ import 'package:halkyon_learn_flutter/src/feature/publish/model/publication.dart
|
|||
import 'package:halkyon_learn_flutter/src/feature/study/model/studyset_study_options.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/studyset/data/studyset_payload.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/studyset/model/studyset_content.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/studyset/widget/deleted_studyset_card.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/studyset/widget/studyset_card.dart';
|
||||
import 'package:material_ui/material_ui.dart';
|
||||
|
||||
|
|
@ -595,22 +597,62 @@ void main() {
|
|||
expect(tester.widget<Text>(find.text('Edit')).style?.color, isNot(error));
|
||||
});
|
||||
|
||||
testWidgets('deleting asks first; Cancel keeps the studyset', (tester) async {
|
||||
Finder onDeletedCard(String title, Finder finder) => find.descendant(
|
||||
of: find.widgetWithText(DeletedStudysetCard, title),
|
||||
matching: finder,
|
||||
);
|
||||
|
||||
testWidgets("deleting doesn't ask: the studyset waits in Recently deleted, "
|
||||
'saying for how long; Undo brings it back', (tester) async {
|
||||
stored('japanese', 'Japanese');
|
||||
await showLibrary(tester);
|
||||
|
||||
await choose(tester, 'Japanese', 'Delete');
|
||||
expect(find.text('Delete studyset?'), findsOneWidget);
|
||||
await tester.tap(find.text('Cancel'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(AlertDialog), findsNothing);
|
||||
expect(find.text('Moved to Recently deleted'), findsOneWidget);
|
||||
expect(find.widgetWithText(StudysetCard, 'Japanese'), findsNothing);
|
||||
expect(find.text('Recently deleted'), findsOneWidget);
|
||||
expect(
|
||||
onDeletedCard('Japanese', find.text('14 days left')),
|
||||
findsOneWidget,
|
||||
);
|
||||
|
||||
expect(find.text('Japanese'), findsOneWidget);
|
||||
await tester.tap(find.text('Undo'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.widgetWithText(StudysetCard, 'Japanese'), findsOneWidget);
|
||||
expect(find.text('Recently deleted'), findsNothing);
|
||||
expect(gateway.deleted, isEmpty);
|
||||
});
|
||||
|
||||
testWidgets('a confirmed delete hides the studyset, offers to undo, and '
|
||||
'then deletes it with its cards', (tester) async {
|
||||
stored('japanese', 'Japanese');
|
||||
testWidgets('a deleted studyset is restored from Recently deleted, even '
|
||||
'when it was the only one', (tester) async {
|
||||
gateway.receiveRemoteChange(
|
||||
'japanese',
|
||||
encodeStudysetPayload(StudysetContent.titled('Japanese')),
|
||||
deletedAt: clock.now().toUtc().subtract(const Duration(days: 10)),
|
||||
);
|
||||
await showLibrary(tester);
|
||||
expect(find.text('No studysets yet'), findsNothing);
|
||||
expect(
|
||||
onDeletedCard('Japanese', find.text('4 days left')),
|
||||
findsOneWidget,
|
||||
);
|
||||
|
||||
await tester.tap(onDeletedCard('Japanese', find.text('Restore')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Studyset restored'), findsOneWidget);
|
||||
expect(find.widgetWithText(StudysetCard, 'Japanese'), findsOneWidget);
|
||||
expect(find.text('Recently deleted'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('Delete now asks, then deletes the studyset for good with its '
|
||||
'cards', (tester) async {
|
||||
gateway.receiveRemoteChange(
|
||||
'japanese',
|
||||
encodeStudysetPayload(StudysetContent.titled('Japanese')),
|
||||
deletedAt: clock.now().toUtc(),
|
||||
);
|
||||
final cards = CardRepository(cardGateway);
|
||||
addTearDown(cards.dispose);
|
||||
final card = await cards.create(
|
||||
|
|
@ -619,36 +661,26 @@ void main() {
|
|||
);
|
||||
await showLibrary(tester);
|
||||
|
||||
await choose(tester, 'Japanese', 'Delete');
|
||||
await tester.tap(find.widgetWithText(FilledButton, 'Delete'));
|
||||
Future<void> deleteNow() async {
|
||||
await tester.tap(onDeletedCard('Japanese', find.byTooltip('Options')));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Delete now'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Delete for good?'), findsOneWidget);
|
||||
}
|
||||
|
||||
await deleteNow();
|
||||
await tester.tap(find.text('Cancel'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Studyset deleted'), findsOneWidget);
|
||||
expect(find.text('No studysets yet'), findsOneWidget);
|
||||
expect(gateway.deleted, isEmpty);
|
||||
|
||||
await tester.pump(const Duration(seconds: 5));
|
||||
await deleteNow();
|
||||
await tester.tap(find.widgetWithText(FilledButton, 'Delete for good'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(gateway.deleted, ['japanese']);
|
||||
expect(cardGateway.deletions, ['blank $card', 'delete $card']);
|
||||
});
|
||||
|
||||
testWidgets('Undo brings a deleted studyset back, and nothing is deleted', (
|
||||
tester,
|
||||
) async {
|
||||
stored('japanese', 'Japanese');
|
||||
await showLibrary(tester);
|
||||
await choose(tester, 'Japanese', 'Delete');
|
||||
await tester.tap(find.widgetWithText(FilledButton, 'Delete'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('Undo'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.pump(const Duration(seconds: 5));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Japanese'), findsOneWidget);
|
||||
expect(gateway.deleted, isEmpty);
|
||||
expect(find.text('Recently deleted'), findsNothing);
|
||||
expect(find.text('No studysets yet'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('a card invites studying its due cards, and counts anew after '
|
||||
|
|
|
|||
|
|
@ -1132,27 +1132,24 @@ void main() {
|
|||
expect(find.text('Moved "Japanese" to Languages.'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('deleting from its own screen asks, closes it and offers to '
|
||||
'undo; then it goes with everything inside it', (tester) async {
|
||||
testWidgets('deleting from its own screen closes it, offers to undo, and '
|
||||
'puts it in Recently deleted with everything inside it', (tester) async {
|
||||
stored('japanese', 'Japanese');
|
||||
stored('kana', 'Kana', parent: 'japanese');
|
||||
final kanaCard = await card('kana', 'あ');
|
||||
await card('kana', 'あ');
|
||||
await openFromLibrary(tester, 'Japanese');
|
||||
|
||||
await choose(tester, 'Delete');
|
||||
expect(find.text('Delete studyset?'), findsOneWidget);
|
||||
await tester.tap(find.widgetWithText(FilledButton, 'Delete'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(AlertDialog), findsNothing);
|
||||
expect(find.byType(StudysetScreen), findsNothing);
|
||||
expect(find.text('Studyset deleted'), findsOneWidget);
|
||||
expect(find.text('No studysets yet'), findsOneWidget);
|
||||
expect(gateway.deleted, isEmpty);
|
||||
expect(find.text('Moved to Recently deleted'), findsOneWidget);
|
||||
expect(find.text('Recently deleted'), findsOneWidget);
|
||||
expect(find.text('Kana'), findsNothing);
|
||||
|
||||
await tester.pump(const Duration(seconds: 5));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(gateway.deleted, ['kana', 'japanese']);
|
||||
expect(cardGateway.deletions, ['blank $kanaCard', 'delete $kanaCard']);
|
||||
expect(gateway.deleted, isEmpty);
|
||||
expect(cardGateway.deletions, isEmpty);
|
||||
});
|
||||
|
||||
testWidgets('Undo after deleting from its own screen keeps it', (
|
||||
|
|
@ -1161,8 +1158,6 @@ void main() {
|
|||
stored('japanese', 'Japanese');
|
||||
await openFromLibrary(tester, 'Japanese');
|
||||
await choose(tester, 'Delete');
|
||||
await tester.tap(find.widgetWithText(FilledButton, 'Delete'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('Undo'));
|
||||
await tester.pumpAndSettle();
|
||||
|
|
@ -1471,8 +1466,6 @@ void main() {
|
|||
);
|
||||
|
||||
await choose(tester, 'Delete');
|
||||
await tester.tap(find.widgetWithText(FilledButton, 'Delete'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(titled('Grammar'), findsOneWidget);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue