A pub workspace of the Dart packages Halkyon's apps share: halkyon_ui, Halkyon's look (the theme, fonts, layout and shared widgets, with texts in English and German), halkyon_core's low-level primitives and halkyon_interop's cross-app contracts. Dart 3.13 with LeanCode's lints, configured once at the root; apps take a package by git, pinned to a signed commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
174 lines
4.9 KiB
Dart
174 lines
4.9 KiB
Dart
import 'package:halkyon_interop/halkyon_interop.dart';
|
|
import 'package:test/test.dart';
|
|
|
|
void main() {
|
|
group('TaskShape', () {
|
|
final fixedTime = DateTime.utc(2026, 1, 15, 12);
|
|
final deepLink = Uri.parse('halkyon-learn://review/spanish-vocab');
|
|
|
|
TaskShape build({
|
|
String sourceAppId = 'eu.halkyon.learn',
|
|
String externalId = 'review-12',
|
|
String title = 'Review 12 cards',
|
|
String? subtitle = 'Spanish vocabulary',
|
|
DateTime? dueAt,
|
|
Uri? linkOverride,
|
|
}) => TaskShape(
|
|
sourceAppId: sourceAppId,
|
|
externalId: externalId,
|
|
title: title,
|
|
subtitle: subtitle,
|
|
dueAt: dueAt ?? fixedTime,
|
|
deepLink: linkOverride ?? deepLink,
|
|
);
|
|
|
|
test('exposes provided fields', () {
|
|
final task = build();
|
|
expect(task.sourceAppId, 'eu.halkyon.learn');
|
|
expect(task.externalId, 'review-12');
|
|
expect(task.title, 'Review 12 cards');
|
|
expect(task.subtitle, 'Spanish vocabulary');
|
|
expect(task.dueAt, fixedTime);
|
|
expect(task.deepLink, deepLink);
|
|
});
|
|
|
|
test('allows null for optional fields', () {
|
|
final task = TaskShape(
|
|
sourceAppId: 'eu.halkyon.learn',
|
|
externalId: 'review-12',
|
|
title: 'Review',
|
|
deepLink: deepLink,
|
|
);
|
|
expect(task.subtitle, isNull);
|
|
expect(task.dueAt, isNull);
|
|
});
|
|
|
|
test('two instances with identical fields are equal', () {
|
|
expect(build(), equals(build()));
|
|
expect(build().hashCode, equals(build().hashCode));
|
|
});
|
|
|
|
test('differs on any field change', () {
|
|
expect(build(), isNot(equals(build(title: 'Different'))));
|
|
expect(build(), isNot(equals(build(externalId: 'different-id'))));
|
|
expect(build(), isNot(equals(build(sourceAppId: 'eu.halkyon.todo'))));
|
|
expect(build(), isNot(equals(build(dueAt: DateTime.utc(2026, 2)))));
|
|
expect(
|
|
build(),
|
|
isNot(equals(build(linkOverride: Uri.parse('halkyon-todo://x')))),
|
|
);
|
|
});
|
|
|
|
test('toString includes identifying fields', () {
|
|
final str = build().toString();
|
|
expect(str, contains('eu.halkyon.learn'));
|
|
expect(str, contains('review-12'));
|
|
expect(str, contains('Review 12 cards'));
|
|
});
|
|
});
|
|
|
|
group('HalkyonEvent / ReviewDueEvent', () {
|
|
final fixedTime = DateTime.utc(2026, 1, 15, 12);
|
|
|
|
test('ReviewDueEvent is a HalkyonEvent', () {
|
|
final event = ReviewDueEvent(
|
|
occurredAt: fixedTime,
|
|
studysetId: 'spanish-vocab',
|
|
dueCount: 12,
|
|
);
|
|
expect(event, isA<HalkyonEvent>());
|
|
});
|
|
|
|
test('exposes provided fields', () {
|
|
final event = ReviewDueEvent(
|
|
occurredAt: fixedTime,
|
|
studysetId: 'spanish-vocab',
|
|
dueCount: 12,
|
|
);
|
|
expect(event.occurredAt, fixedTime);
|
|
expect(event.studysetId, 'spanish-vocab');
|
|
expect(event.dueCount, 12);
|
|
});
|
|
|
|
test('events with identical fields are equal', () {
|
|
final a = ReviewDueEvent(
|
|
occurredAt: fixedTime,
|
|
studysetId: 'spanish-vocab',
|
|
dueCount: 12,
|
|
);
|
|
final b = ReviewDueEvent(
|
|
occurredAt: fixedTime,
|
|
studysetId: 'spanish-vocab',
|
|
dueCount: 12,
|
|
);
|
|
expect(a, equals(b));
|
|
expect(a.hashCode, equals(b.hashCode));
|
|
});
|
|
|
|
test('differs on field changes', () {
|
|
final base = ReviewDueEvent(
|
|
occurredAt: fixedTime,
|
|
studysetId: 'spanish-vocab',
|
|
dueCount: 12,
|
|
);
|
|
expect(
|
|
base,
|
|
isNot(
|
|
equals(
|
|
ReviewDueEvent(
|
|
occurredAt: fixedTime,
|
|
studysetId: 'spanish-vocab',
|
|
dueCount: 13,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
expect(
|
|
base,
|
|
isNot(
|
|
equals(
|
|
ReviewDueEvent(
|
|
occurredAt: fixedTime,
|
|
studysetId: 'french-vocab',
|
|
dueCount: 12,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
});
|
|
|
|
test('switch on sealed type is exhaustive', () {
|
|
final HalkyonEvent event = ReviewDueEvent(
|
|
occurredAt: fixedTime,
|
|
studysetId: 'spanish-vocab',
|
|
dueCount: 12,
|
|
);
|
|
|
|
// If a new HalkyonEvent subtype is added without updating this switch,
|
|
// the analyzer/compiler will flag it. That's the value of `sealed`.
|
|
final description = switch (event) {
|
|
ReviewDueEvent(:final studysetId, :final dueCount) =>
|
|
'$dueCount cards due in $studysetId',
|
|
};
|
|
|
|
expect(description, '12 cards due in spanish-vocab');
|
|
});
|
|
});
|
|
|
|
group('DormantPublisher', () {
|
|
test('accepts publish calls without throwing', () async {
|
|
const publisher = DormantPublisher();
|
|
final event = ReviewDueEvent(
|
|
occurredAt: DateTime.utc(2026, 1, 15),
|
|
studysetId: 'spanish-vocab',
|
|
dueCount: 5,
|
|
);
|
|
await expectLater(publisher.publish(event), completes);
|
|
});
|
|
|
|
test('implements EventPublisher', () {
|
|
const publisher = DormantPublisher();
|
|
expect(publisher, isA<EventPublisher>());
|
|
});
|
|
});
|
|
}
|