chore: Halkyon Learn moves into apps/learn
Its server, client, app and curator, and the tools that write its assets (maps, Anki fixtures, icons), as they are; paths are fixed in the commits that follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
11abd9afce
commit
649499be23
1 changed files with 0 additions and 0 deletions
|
|
@ -0,0 +1,270 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/content/rich_content.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/home/widget/goal_card.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/home/widget/streak.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/pomodoro/widget/pomodoro_card.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/study/data/review_repository.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/study/model/rating.dart';
|
||||
import 'package:halkyon_ui/halkyon_ui.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||
import 'package:material_ui/material_ui.dart';
|
||||
|
||||
import '../../../support/fake_card_gateway.dart';
|
||||
import '../../../support/fake_review_gateway.dart';
|
||||
import '../../../support/fake_settings_gateway.dart';
|
||||
import '../../../support/pump_app.dart';
|
||||
import '../../../support/test_dependencies.dart';
|
||||
|
||||
void main() {
|
||||
late FakeCardGateway cardGateway;
|
||||
late FakeReviewGateway reviewGateway;
|
||||
late FakeSettingsGateway settingsGateway;
|
||||
|
||||
setUp(() {
|
||||
cardGateway = FakeCardGateway();
|
||||
reviewGateway = FakeReviewGateway();
|
||||
settingsGateway = FakeSettingsGateway();
|
||||
});
|
||||
|
||||
Future<void> showHome(WidgetTester tester, {double width = 1200}) async {
|
||||
tester.view.physicalSize = Size(width, 1600);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.reset);
|
||||
await pumpApp(
|
||||
tester,
|
||||
testDependencies(
|
||||
cardGateway: cardGateway,
|
||||
reviewGateway: reviewGateway,
|
||||
settingsGateway: settingsGateway,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
int streak(WidgetTester tester) =>
|
||||
tester.widget<StreakChip>(find.byType(StreakChip)).streak;
|
||||
|
||||
/// Rates a new card now.
|
||||
Future<void> rateToday() async {
|
||||
final cards = CardRepository(cardGateway);
|
||||
addTearDown(cards.dispose);
|
||||
final id = await cards.create(
|
||||
'japanese',
|
||||
CardContent(front: RichContent.empty(), back: RichContent.empty()),
|
||||
);
|
||||
await ReviewRepository(reviewGateway).record(
|
||||
cardId: id,
|
||||
rating: Rating.good,
|
||||
reviewedAt: DateTime.now(),
|
||||
duration: Duration.zero,
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('cobalt washes down from the top, behind a clear app bar', (
|
||||
tester,
|
||||
) async {
|
||||
await showHome(tester);
|
||||
|
||||
expect(
|
||||
tester.widget<TopGradient>(find.byType(TopGradient)).color,
|
||||
Brand.primary,
|
||||
);
|
||||
expect(
|
||||
tester.widget<AppBar>(find.byType(AppBar)).backgroundColor,
|
||||
Colors.transparent,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('opens with the goal first, then the Pomodoro timer, each on '
|
||||
'a card', (tester) async {
|
||||
await showHome(tester);
|
||||
|
||||
expect(
|
||||
tester.getRect(find.byType(GoalCard)).top,
|
||||
lessThan(tester.getRect(find.byType(PomodoroCard)).top),
|
||||
);
|
||||
expect(
|
||||
find.ancestor(of: find.byType(GoalCard), matching: find.byType(Card)),
|
||||
findsNothing,
|
||||
);
|
||||
expect(
|
||||
find.descendant(of: find.byType(GoalCard), matching: find.byType(Card)),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('the streak sheet: the count inside the flame, the week as '
|
||||
'letters, and what keeps it going', (tester) async {
|
||||
await rateToday();
|
||||
await showHome(tester, width: 400);
|
||||
|
||||
await tester.tap(find.byType(StreakChip));
|
||||
await tester.pumpAndSettle();
|
||||
final sheet = find.byType(BottomSheet);
|
||||
|
||||
expect(
|
||||
find.descendant(of: sheet, matching: find.text('1 day streak')),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(
|
||||
find.descendant(of: sheet, matching: find.text('1')),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(
|
||||
find.descendant(of: sheet, matching: find.text('M')),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(
|
||||
find.descendant(
|
||||
of: sheet,
|
||||
matching: find.text('Come back tomorrow to keep your streak.'),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('the week names the day with its month short, and the '
|
||||
"heatmap's days say how much was done", (tester) async {
|
||||
await showHome(tester);
|
||||
final now = DateTime.now();
|
||||
|
||||
expect(
|
||||
find.text(
|
||||
'${DateFormat.EEEE('en_US').format(now)}, '
|
||||
'${DateFormat.MMMd('en_US').format(now)}',
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(
|
||||
find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is Tooltip &&
|
||||
(widget.message?.startsWith('0 activities · ') ?? false),
|
||||
),
|
||||
findsWidgets,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('a card rated today lights the streak and the heatmap', (
|
||||
tester,
|
||||
) async {
|
||||
await rateToday();
|
||||
|
||||
await showHome(tester);
|
||||
|
||||
expect(streak(tester), 1);
|
||||
// The card written and the rating.
|
||||
expect(find.text('2 activities in the last year'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('a goal is set in its sheet and shows its progress', (
|
||||
tester,
|
||||
) async {
|
||||
await showHome(tester);
|
||||
|
||||
await tester.tap(find.text('Set a study goal'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Per week'));
|
||||
await tester.pump();
|
||||
await tester.tap(find.byTooltip('Increase'));
|
||||
await tester.pump();
|
||||
await tester.tap(find.text('Save goal'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Weekly goal'), findsOneWidget);
|
||||
// In steps of 5, as the study options.
|
||||
expect(find.text('0 of 25 cards this week'), findsOneWidget);
|
||||
expect(find.byIcon(LucideIcons.pencil), findsOneWidget);
|
||||
|
||||
await tester.tap(find.text('Weekly goal'));
|
||||
await tester.pumpAndSettle();
|
||||
// Removing sits below saving.
|
||||
expect(
|
||||
tester.getRect(find.text('Remove goal')).top,
|
||||
greaterThan(tester.getRect(find.text('Save goal')).bottom),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('resting today keeps the streak', (tester) async {
|
||||
await showHome(tester);
|
||||
expect(streak(tester), 0);
|
||||
|
||||
await tester.tap(find.text('Rest day'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(streak(tester), 1);
|
||||
});
|
||||
|
||||
testWidgets("the rest day sits in the week's header, beside the day "
|
||||
'shown', (tester) async {
|
||||
await showHome(tester);
|
||||
|
||||
final restDay = tester.getRect(find.text('Rest day'));
|
||||
final previous = tester.getRect(find.byTooltip('Previous day'));
|
||||
expect(restDay.center.dy, closeTo(previous.center.dy, 2));
|
||||
expect(restDay.right, lessThan(previous.left));
|
||||
});
|
||||
|
||||
testWidgets('on a phone, the rest day is a snowflake button in the '
|
||||
"week's header", (tester) async {
|
||||
await showHome(tester, width: 360);
|
||||
expect(find.text('Rest day'), findsNothing);
|
||||
|
||||
final restDay = find.byTooltip('Rest day');
|
||||
expect(
|
||||
tester.getRect(restDay).center.dy,
|
||||
closeTo(tester.getRect(find.byTooltip('Previous day')).center.dy, 2),
|
||||
);
|
||||
await tester.tap(restDay);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(streak(tester), 1);
|
||||
});
|
||||
|
||||
testWidgets('a long press arranges the dashboard: a widget hidden stays '
|
||||
'hidden', (tester) async {
|
||||
await showHome(tester);
|
||||
|
||||
await tester.longPress(find.text('Set a study goal'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Arrange widgets'), findsOneWidget);
|
||||
await tester.tap(find.byTooltip('Hide Daily goal'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Done'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Set a study goal'), findsNothing);
|
||||
expect(find.text('Your activity'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('with every widget hidden, Customise brings the arranging '
|
||||
'back', (tester) async {
|
||||
await showHome(tester);
|
||||
await tester.longPress(find.text('Set a study goal'));
|
||||
await tester.pumpAndSettle();
|
||||
for (final name in [
|
||||
'Daily goal',
|
||||
'Pomodoro',
|
||||
'PDF reading deadlines',
|
||||
'Week overview',
|
||||
'Activity heatmap',
|
||||
]) {
|
||||
await tester.tap(find.byTooltip('Hide $name'));
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
await tester.tap(find.text('Done'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('All widgets are hidden'), findsOneWidget);
|
||||
expect(
|
||||
find.text('Bring some back to fill your dashboard.'),
|
||||
findsOneWidget,
|
||||
);
|
||||
// Tests run in American English.
|
||||
await tester.tap(find.text('Customize'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Arrange widgets'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
Loading…
Reference in a new issue