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:
Tizian 「ティツィアーン」 2026-10-19 20:08:06 +02:00
commit 649499be23
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
2 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,161 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:halkyon_learn_flutter/src/core/content/content_blocks.dart';
import 'package:halkyon_learn_flutter/src/core/content/rich_content.dart';
import 'package:halkyon_learn_flutter/src/core/editor/rich_content_document.dart';
import 'package:halkyon_learn_flutter/src/core/router/route_paths.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/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_note_gateway.dart';
import '../../../support/fake_studyset_gateway.dart';
import '../../../support/fake_tag_gateway.dart';
import '../../../support/pump_app.dart';
import '../../../support/test_dependencies.dart';
void main() {
late FakeStudysetGateway studysets;
late FakeCardGateway cardGateway;
late FakeTagGateway tagGateway;
late String verb;
setUp(() async {
studysets = FakeStudysetGateway()
..receiveRemoteChange(
'japanese',
encodeStudysetPayload(StudysetContent.titled('Japanese')),
);
cardGateway = FakeCardGateway();
tagGateway = FakeTagGateway();
final cards = CardRepository(cardGateway);
final tags = TagRepository(tagGateway);
addTearDown(cards.dispose);
addTearDown(tags.dispose);
final taberu = await cards.create(
'japanese',
CardContent(
front: RichContent.plain('食べる'),
back: RichContent.plain('to eat'),
),
);
await cards.create(
'japanese',
CardContent(
front: RichContent.plain('猫'),
back: RichContent.plain('cat'),
),
);
verb = await tags.create('verb');
await tags.setTagsOf(taberu, {verb});
});
Future<void> showApp(WidgetTester tester, String location) async {
tester.view.physicalSize = const Size(1200, 1000);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.reset);
await pumpApp(
tester,
testDependencies(
studysetGateway: studysets,
cardGateway: cardGateway,
tagGateway: tagGateway,
noteGateway: FakeNoteGateway(),
),
location: location,
);
await tester.pump(const Duration(seconds: 1));
}
/// A studyset whose description holds a study button of [attributes].
void describedWithButton(Map<String, Object?> attributes) =>
studysets.receiveRemoteChange(
'lesson',
encodeStudysetPayload(
StudysetContent(
title: 'Lesson',
description: Document(
root: pageNode(
children: [
Node(type: studyButtonBlockType, attributes: attributes),
],
),
).toRichContent(),
),
),
);
testWidgets("goes into a note from Insert: the note's studyset studied "
'first, with a tag, a label, and without its topics', (tester) async {
await showApp(tester, RoutePaths.newNote('japanese'));
await tester.tap(find.byType(AppFlowyEditor));
await tester.pump(const Duration(seconds: 1));
final editorState = tester
.widget<AppFlowyEditor>(find.byType(AppFlowyEditor))
.editorState;
await tester.tap(find.byTooltip('Insert'));
await tester.pumpAndSettle();
await tester.ensureVisible(find.text('Study button'));
await tester.pumpAndSettle();
await tester.tap(find.text('Study button'));
await tester.pumpAndSettle();
expect(find.text('Japanese'), findsOneWidget);
await tester.tap(find.text('All cards'));
await tester.pumpAndSettle();
await tester.tap(find.text('verb').last);
await tester.pumpAndSettle();
await tester.enterText(find.widgetWithText(TextField, 'Label'), 'Verbs');
await tester.tap(find.text('With its topics'));
await tester.pumpAndSettle();
await tester.tap(find.text('Save'));
await tester.pumpAndSettle();
final block = editorState.document.root.children.first;
expect(block.type, studyButtonBlockType);
expect(block.attributes, {
studyButtonStudysetKey: 'japanese',
studyButtonTagKey: verb,
studyButtonTagNameKey: 'verb',
studyButtonLabelKey: 'Verbs',
studyButtonTopicsKey: false,
studyButtonBackFirstKey: false,
});
expect(find.text('Verbs'), findsOneWidget);
expect(find.text('Tagged verb · Without its topics'), findsOneWidget);
});
testWidgets('shown read-only, starts a session of the cards it says, '
'back first if asked', (tester) async {
describedWithButton({
studyButtonStudysetKey: 'japanese',
studyButtonTagKey: verb,
studyButtonTagNameKey: 'verb',
studyButtonLabelKey: 'Verbs',
studyButtonBackFirstKey: true,
});
await showApp(tester, RoutePaths.studyset('lesson'));
await tester.tap(find.widgetWithText(FilledButton, 'Verbs'));
await tester.pumpAndSettle();
// Only the verb, and its back first.
expect(find.textContaining('to eat', findRichText: true), findsOneWidget);
expect(find.textContaining('猫', findRichText: true), findsNothing);
});
testWidgets('a button whose studyset is gone says so', (tester) async {
describedWithButton({studyButtonStudysetKey: 'gone'});
await showApp(tester, RoutePaths.studyset('lesson'));
await tester.tap(find.widgetWithText(FilledButton, 'Study'));
await tester.pump();
expect(find.text('That studyset no longer exists.'), findsOneWidget);
});
}

File diff suppressed because it is too large Load diff