halkyon/packages/halkyon_app/test/whats_new/whats_new_test.dart
Tizian 「ティツィアーン」 7727317c68
feat(app): what's new after an update, drafted by the release tool (ADR 0030, D61)
halkyon_app's WhatsNew reads what each release changed from the app's
assets/whats_new/ (<version>.<language>.txt, English and German, a change
a line): after an update a sheet lists every release since the one the
device showed last, newest first, once; Settings shows the running
version's again, above About. Nothing on a fresh install.

tool/release.dart drafts the missing texts from the commits that count
after "Go on?" (German marked TRANSLATE:), opens them in the editor until
they fit (no mark left, F-Droid's 500 characters) and commits them with
the version. The English is the Forgejo notes' What's new, each language
F-Droid's changelog of the build, written at publishing and copied in by
tool/fdroid_repo.sh. Learn and Train bundle their texts, Learn's 0.22.0
and Train's 0.1.0 written to start.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-31 19:36:20 +01:00

243 lines
6.7 KiB
Dart

import 'dart:io';
import 'package:flutter_test/flutter_test.dart';
import 'package:halkyon_app/halkyon_app.dart';
import 'package:halkyon_app/testing.dart';
import 'package:material_ui/material_ui.dart';
import 'package:path/path.dart' as p;
void main() {
final files = {
'0.18.0.en.txt': '- Eighteen\n',
'0.19.0.en.txt': '- Nineteen, first\n- Nineteen, second\n',
'0.19.0.de.txt': '- Neunzehn\n',
'0.20.0.en.txt': '- Twenty\n\n',
'0.21.0.en.txt': '- Not out yet\n',
'notes.txt': '- Not a release\n',
};
/// [releases] as lists, which compare by their contents.
List<Object> flat(Iterable<ReleaseChanges?> releases) => [
for (final release in releases) [release?.version, release?.changes],
];
WhatsNew whatsNew(MemoryWhatsNewStore store, {String version = '0.20.0'}) =>
WhatsNew(MemoryWhatsNewSource(files), store, version: version);
group('unseen', () {
test('after an update, every release since the one shown last, newest '
'first, and they count as seen', () async {
final store = MemoryWhatsNewStore(seen: '0.18.0');
final releases = await whatsNew(
store,
).unseen(language: 'en', usedBefore: true);
expect(flat(releases), [
[
'0.20.0',
['Twenty'],
],
[
'0.19.0',
['Nineteen, first', 'Nineteen, second'],
],
]);
expect(store.seen, '0.20.0');
});
test(
'in the language asked for where there is one, else English',
() async {
final releases = await whatsNew(
MemoryWhatsNewStore(seen: '0.18.0'),
).unseen(language: 'de', usedBefore: true);
expect(flat(releases), [
[
'0.20.0',
['Twenty'],
],
[
'0.19.0',
['Neunzehn'],
],
]);
},
);
test('nothing once seen', () async {
final store = MemoryWhatsNewStore(seen: '0.18.0');
await whatsNew(store).unseen(language: 'en', usedBefore: true);
expect(
await whatsNew(store).unseen(language: 'en', usedBefore: true),
isEmpty,
);
});
test('nothing on a fresh install, which counts as seen', () async {
final store = MemoryWhatsNewStore();
expect(
await whatsNew(store).unseen(language: 'en', usedBefore: false),
isEmpty,
);
expect(store.seen, '0.20.0');
});
test('a device used before the app showed what is new gets the version '
'that runs alone', () async {
final releases = await whatsNew(
MemoryWhatsNewStore(),
).unseen(language: 'en', usedBefore: true);
expect(flat(releases), [
[
'0.20.0',
['Twenty'],
],
]);
});
test('nothing after going back to an older version, which counts as seen '
'from then', () async {
final store = MemoryWhatsNewStore(seen: '0.21.0');
expect(
await whatsNew(store).unseen(language: 'en', usedBefore: true),
isEmpty,
);
expect(store.seen, '0.20.0');
});
test('nothing for a version without a release number, such as a '
"development build's", () async {
final store = MemoryWhatsNewStore(seen: '0.18.0');
expect(
await whatsNew(
store,
version: 'dev',
).unseen(language: 'en', usedBefore: true),
isEmpty,
);
expect(store.seen, '0.18.0');
});
});
test("current is the running version's changes, null without", () async {
expect(
flat([await whatsNew(MemoryWhatsNewStore()).current(language: 'de')]),
[
[
'0.20.0',
['Twenty'],
],
],
);
expect(
await whatsNew(
MemoryWhatsNewStore(),
version: '0.17.0',
).current(language: 'en'),
isNull,
);
});
test("changesOf takes the lines starting with '- '", () {
expect(changesOf('Intro\n- One\n - Two \n\n-Three\n'), ['One', 'Two']);
});
group('FileWhatsNewStore', () {
late Directory folder;
setUp(() => folder = Directory.systemTemp.createTempSync('whats_new'));
tearDown(() => folder.deleteSync(recursive: true));
test('keeps the version shown last; none before', () async {
final file = File(p.join(folder.path, 'whats_new.json'));
expect(await FileWhatsNewStore(file).read(), isNull);
await FileWhatsNewStore(file).write('0.20.0');
expect(await FileWhatsNewStore(file).read(), '0.20.0');
});
test('reads nothing from a broken file', () async {
final file = File(p.join(folder.path, 'whats_new.json'))
..writeAsStringSync('{');
expect(await FileWhatsNewStore(file).read(), isNull);
});
});
group('widgets', () {
Future<MemoryWhatsNewStore> show(
WidgetTester tester, {
required String? seen,
Widget child = const WhatsNewOnUpdate(child: Text('Tabs')),
}) async {
final store = MemoryWhatsNewStore(seen: seen);
await tester.pumpWidget(
WhatsNewScope(
whatsNew: whatsNew(store),
usedBefore: true,
child: testApp(Scaffold(body: child)),
),
);
await tester.pumpAndSettle();
return store;
}
testWidgets('after an update, the sheet lists each release since', (
tester,
) async {
await show(tester, seen: '0.18.0');
expect(find.text("What's new"), findsOneWidget);
expect(find.text('Version 0.20.0'), findsOneWidget);
expect(find.text('Twenty'), findsOneWidget);
expect(find.text('Version 0.19.0'), findsOneWidget);
expect(find.text('Nineteen, second'), findsOneWidget);
expect(
tester.getTopLeft(find.text('Version 0.20.0')).dy,
lessThan(tester.getTopLeft(find.text('Version 0.19.0')).dy),
);
});
testWidgets('once seen, no sheet', (tester) async {
await show(tester, seen: '0.20.0');
expect(find.text("What's new"), findsNothing);
expect(find.text('Tabs'), findsOneWidget);
});
testWidgets("Settings' tile shows the running version's changes", (
tester,
) async {
await show(tester, seen: '0.20.0', child: const WhatsNewTile());
expect(find.text('Version 0.20.0'), findsOneWidget);
await tester.tap(find.text("What's new"));
await tester.pumpAndSettle();
expect(find.text('Twenty'), findsOneWidget);
});
testWidgets('without a scope, neither the sheet nor the tile', (
tester,
) async {
await tester.pumpWidget(
testApp(
const Scaffold(
body: WhatsNewOnUpdate(child: WhatsNewTile()),
),
),
);
await tester.pumpAndSettle();
expect(find.text("What's new"), findsNothing);
});
});
}