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>
This commit is contained in:
parent
581cf1f1cd
commit
7727317c68
1 changed files with 1272 additions and 76 deletions
243
packages/halkyon_app/test/whats_new/whats_new_test.dart
Normal file
243
packages/halkyon_app/test/whats_new/whats_new_test.dart
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
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);
|
||||
});
|
||||
});
|
||||
}
|
||||
Loading…
Reference in a new issue