The studyset's four tabs become three parts, as Vaia has them, chosen with a switch in the header that ticks as the navigation bar does: below expanded windows in a line of its own under the title, above the path and Topics; from there on at the end of the path's line, which the topics leave free there. - Material: the flashcards, notes and files one below the other, the first three of each with Show all; Browse, the tag filter (where all cards show, or while they are narrowed) and Reorder in each section's header; an outlined box where a section is empty. - Progress, once anything in the studyset was studied (a card rated, a path finished): how much is learnt (mature cards), how many studied cards are due today, and how long it was studied this week on this device, with a line of the seven days (StudysetActivityCubit, from the reviews' durations). - Study plan: a ring of the paths finished, Next up (study the cards due, go on with the first path not finished), then the paths. It opens on the part it was left on; a tab kept before counts as its part. Opened for the first time, it opens on the study plan while a path is unfinished, else on the material. Material added lands in its part. 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>
182 lines
6.7 KiB
Dart
182 lines
6.7 KiB
Dart
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:halkyon_learn_flutter/src/core/content/rich_content.dart';
|
|
import 'package:halkyon_learn_flutter/src/core/router/route_paths.dart';
|
|
import 'package:halkyon_learn_flutter/src/core/widget/text_limits.dart';
|
|
import 'package:halkyon_learn_flutter/src/feature/card/data/card_repository.dart';
|
|
import 'package:halkyon_learn_flutter/src/feature/card/model/card_content.dart';
|
|
import 'package:halkyon_learn_flutter/src/feature/learning_path/data/learning_path_repository.dart';
|
|
import 'package:halkyon_learn_flutter/src/feature/learning_path/model/learning_path.dart';
|
|
import 'package:halkyon_learn_flutter/src/feature/note/data/note_repository.dart';
|
|
import 'package:halkyon_learn_flutter/src/feature/note/model/note_content.dart';
|
|
import 'package:halkyon_learn_flutter/src/feature/pomodoro/model/pomodoro_profile.dart';
|
|
import 'package:halkyon_learn_flutter/src/feature/settings/data/settings_repository.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/tag/data/tag_repository.dart';
|
|
import 'package:material_ui/material_ui.dart';
|
|
|
|
import '../support/fake_card_gateway.dart';
|
|
import '../support/fake_learning_path_gateway.dart';
|
|
import '../support/fake_note_gateway.dart';
|
|
import '../support/fake_settings_gateway.dart';
|
|
import '../support/fake_studyset_gateway.dart';
|
|
import '../support/fake_tag_gateway.dart';
|
|
import '../support/pump_app.dart';
|
|
import '../support/test_dependencies.dart';
|
|
|
|
/// Names longer than the fields allow ([TextLimits]), as an older app, an
|
|
/// import or another person's release can bring them: every screen that
|
|
/// shows them cuts them short rather than overflowing, which fails a test.
|
|
void main() {
|
|
/// [length] characters of a name made of words, as long names are.
|
|
String long(String word, int length) =>
|
|
List.filled(length, word).join(' ').substring(0, length);
|
|
|
|
final studysetTitle = long('Japanese', TextLimits.title * 2);
|
|
final kicker = long('日本語', TextLimits.secondary * 2);
|
|
final topicTitle = long('Grammar', TextLimits.title * 2);
|
|
final tagName = long('Particles', TextLimits.name * 3);
|
|
final profileName = long('Deep focus', TextLimits.name * 3);
|
|
|
|
late FakeStudysetGateway studysets;
|
|
late FakeCardGateway cardGateway;
|
|
late FakeNoteGateway noteGateway;
|
|
late FakeLearningPathGateway pathGateway;
|
|
late FakeTagGateway tagGateway;
|
|
late FakeSettingsGateway settingsGateway;
|
|
|
|
setUp(() async {
|
|
studysets = FakeStudysetGateway()
|
|
..receiveRemoteChange(
|
|
'japanese',
|
|
encodeStudysetPayload(
|
|
StudysetContent(
|
|
title: studysetTitle,
|
|
secondaryTitle: kicker,
|
|
description: RichContent.plain(long('About', 400)),
|
|
),
|
|
),
|
|
)
|
|
..receiveRemoteChange(
|
|
'grammar',
|
|
encodeStudysetPayload(StudysetContent.titled(topicTitle)),
|
|
parentId: 'japanese',
|
|
)
|
|
..receiveRemoteChange(
|
|
'inner',
|
|
encodeStudysetPayload(StudysetContent.titled(topicTitle)),
|
|
parentId: 'grammar',
|
|
);
|
|
cardGateway = FakeCardGateway();
|
|
noteGateway = FakeNoteGateway();
|
|
pathGateway = FakeLearningPathGateway();
|
|
tagGateway = FakeTagGateway();
|
|
settingsGateway = FakeSettingsGateway();
|
|
|
|
final cards = CardRepository(cardGateway);
|
|
final cardId = await cards.create(
|
|
'japanese',
|
|
CardContent(
|
|
front: RichContent.plain(long('front', 300)),
|
|
back: RichContent.plain(long('back', 300)),
|
|
),
|
|
);
|
|
final tags = TagRepository(tagGateway, cardChanges: cards.changes);
|
|
await tags.setTagsOf(cardId, {await tags.create(tagName)});
|
|
await NoteRepository(noteGateway).create(
|
|
'japanese',
|
|
NoteContent(
|
|
title: long('Note', TextLimits.title * 2),
|
|
body: RichContent.empty(),
|
|
),
|
|
);
|
|
await LearningPathRepository(pathGateway).createPath(
|
|
'japanese',
|
|
LearningPathContent.titled(
|
|
long('Path', TextLimits.title * 2),
|
|
).withSecondaryTitle(long('Book', TextLimits.secondary * 2)),
|
|
);
|
|
final profile = PomodoroProfile(id: 'long', name: profileName);
|
|
await SettingsRepository(settingsGateway).update(
|
|
(settings) => settings.withPomodoro(
|
|
settings.pomodoro.withProfile(profile).withSelected(profile.id),
|
|
),
|
|
);
|
|
await Future.wait([tags.dispose(), cards.dispose()]);
|
|
});
|
|
|
|
for (final (window, width) in [('a phone', 360.0), ('a desktop', 1200.0)]) {
|
|
Future<void> show(WidgetTester tester, String location) async {
|
|
tester.view.physicalSize = Size(width, 800);
|
|
tester.view.devicePixelRatio = 1;
|
|
addTearDown(tester.view.reset);
|
|
await pumpApp(
|
|
tester,
|
|
testDependencies(
|
|
studysetGateway: studysets,
|
|
cardGateway: cardGateway,
|
|
noteGateway: noteGateway,
|
|
pathGateway: pathGateway,
|
|
tagGateway: tagGateway,
|
|
settingsGateway: settingsGateway,
|
|
),
|
|
location: location,
|
|
);
|
|
await tester.pump(const Duration(seconds: 1));
|
|
}
|
|
|
|
testWidgets('on $window, Home with a long Pomodoro profile', (
|
|
tester,
|
|
) async {
|
|
await show(tester, RoutePaths.home);
|
|
|
|
expect(find.textContaining('Deep focus'), findsWidgets);
|
|
await tester.tap(find.byTooltip(_profilesTooltip));
|
|
await tester.pumpAndSettle();
|
|
});
|
|
|
|
testWidgets('on $window, the library and a long studyset', (
|
|
tester,
|
|
) async {
|
|
await show(tester, RoutePaths.library);
|
|
|
|
expect(find.textContaining('Japanese'), findsWidgets);
|
|
});
|
|
|
|
testWidgets("on $window, a long studyset's screen, its parts and topics", (
|
|
tester,
|
|
) async {
|
|
await show(tester, RoutePaths.studyset('grammar'));
|
|
|
|
for (final part in ['Study plan', 'Material']) {
|
|
await openPart(tester, part);
|
|
}
|
|
if (width < 840) {
|
|
await tester.tap(find.byTooltip('Show topics'));
|
|
await tester.pumpAndSettle();
|
|
}
|
|
});
|
|
|
|
testWidgets('on $window, the card browser with a long tag', (
|
|
tester,
|
|
) async {
|
|
await show(tester, RoutePaths.cardBrowser('japanese'));
|
|
|
|
await tester.tap(find.byTooltip('Detail'));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.byTooltip('Select'));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.byTooltip('More'));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.text('Manage tags'));
|
|
await tester.pumpAndSettle();
|
|
});
|
|
|
|
testWidgets('on $window, studying a long studyset', (tester) async {
|
|
await show(tester, RoutePaths.study('japanese'));
|
|
});
|
|
}
|
|
}
|
|
|
|
/// The tooltip of the Pomodoro card's profiles button.
|
|
const _profilesTooltip = 'Pomodoro profiles';
|