feat(instance): an instance says where it keeps people's data
Its operator sets DATA_LOCATION and BACKUP_LOCATION in deploy/.env (a
country's code or EU, the backups also none), and instance.info() hands
them on. The first start's tile and Switch account show the flag of
where the data is and the words, as labels ("Data: European Union",
"Backups: Switzerland"), the countries by the world map's CLDR names:
where the disks are, never which law governs. The flags come from the
flag package, which draws flag-icons; its MIT notice joins the licence
list.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
9b0f28d7c1
commit
642c1c7e9f
5 changed files with 867 additions and 63 deletions
|
|
@ -1,8 +1,11 @@
|
|||
import 'package:flag/flag.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:halkyon_learn_flutter/src/app/instance_gate.dart';
|
||||
import 'package:halkyon_learn_flutter/src/app/person_app.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/country/country_names.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/instance/instance.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/instance/instance_choice.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/instance/instance_locations.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/auth/widget/session_gate.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/settings/widget/settings_screen.dart';
|
||||
import 'package:material_ui/material_ui.dart';
|
||||
|
|
@ -27,11 +30,13 @@ void main() {
|
|||
});
|
||||
|
||||
/// Starts the app on a device that [usedBefore] the choice existed, with
|
||||
/// [answering] instances answering a check.
|
||||
/// [answering] instances answering a check, saying where they keep data
|
||||
/// as [locations] has it.
|
||||
Future<InstanceChoice> start(
|
||||
WidgetTester tester, {
|
||||
bool usedBefore = false,
|
||||
Set<Instance> answering = const {},
|
||||
Map<Instance, InstanceLocations> locations = const {},
|
||||
}) async {
|
||||
tester.view.physicalSize = const Size(412, 900);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
|
|
@ -45,7 +50,10 @@ void main() {
|
|||
await tester.pumpWidget(
|
||||
InstanceGate(
|
||||
choice: choice,
|
||||
checker: FakeInstanceChecker(answering),
|
||||
checker: FakeInstanceChecker(answering, locations),
|
||||
countryNames: MemoryCountryNames(const {
|
||||
'CH': {'en': 'Switzerland'},
|
||||
}),
|
||||
deviceSettings: MemoryDeviceSettingsStore(),
|
||||
open: (instance) async {
|
||||
opened.add(instance);
|
||||
|
|
@ -97,6 +105,28 @@ void main() {
|
|||
expect(tab('Home'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets("the build's instance shows where it keeps people's data, "
|
||||
'once it says', (tester) async {
|
||||
await start(
|
||||
tester,
|
||||
answering: {builtIn},
|
||||
locations: {
|
||||
builtIn: const InstanceLocations(data: 'EU', backups: 'CH'),
|
||||
},
|
||||
);
|
||||
|
||||
expect(find.text('Data: European Union\nBackups: Switzerland'), findsOne);
|
||||
expect(find.byType(Flag), findsOne);
|
||||
});
|
||||
|
||||
testWidgets('an instance saying nothing, or not answering, comes with the '
|
||||
'app, without a flag', (tester) async {
|
||||
await start(tester);
|
||||
|
||||
expect(find.text('Comes with the app'), findsOne);
|
||||
expect(find.byType(Flag), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('another instance is checked before the app moves there', (
|
||||
tester,
|
||||
) async {
|
||||
|
|
@ -138,9 +168,21 @@ void main() {
|
|||
testWidgets('Settings switches between the accounts on the device, and '
|
||||
'removes one not in use', (tester) async {
|
||||
store = MemoryInstanceStore((current: builtIn, known: [builtIn, other]));
|
||||
await start(tester);
|
||||
await start(
|
||||
tester,
|
||||
answering: {builtIn, other},
|
||||
locations: {
|
||||
other: const InstanceLocations(
|
||||
data: 'CH',
|
||||
backups: InstanceLocations.noBackups,
|
||||
),
|
||||
},
|
||||
);
|
||||
await openSwitcher(tester);
|
||||
expect(find.text('learn.halkyon.eu'), findsWidgets);
|
||||
// Each account with where its instance says it keeps people's data.
|
||||
expect(find.text('Data: Switzerland\nNo backups'), findsOne);
|
||||
expect(find.byType(Flag), findsOne);
|
||||
await tester.tap(find.text('learn.example.org'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(opened, [builtIn, other]);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/country/country_names.dart';
|
||||
|
||||
void main() {
|
||||
test("a map's countries are named by their code, in each language it "
|
||||
'has', () {
|
||||
final names = namesIn('''
|
||||
{"type": "FeatureCollection", "features": [
|
||||
{"type": "Feature", "properties": {"code": "CH",
|
||||
"names": {"de": "Schweiz", "en": "Switzerland"}}, "geometry": null},
|
||||
{"type": "Feature", "properties": {"code": "-99",
|
||||
"names": {"en": "Somewhere without a code"}}, "geometry": null}
|
||||
]}
|
||||
''');
|
||||
|
||||
expect(names, {
|
||||
'CH': {'de': 'Schweiz', 'en': 'Switzerland'},
|
||||
});
|
||||
});
|
||||
|
||||
test('a country is named in the language asked for, else in English, and '
|
||||
'one the map lacks not at all', () async {
|
||||
final names = MemoryCountryNames({
|
||||
'CH': {'de': 'Schweiz', 'en': 'Switzerland'},
|
||||
});
|
||||
|
||||
expect(await names.nameOf('CH', 'de'), 'Schweiz');
|
||||
expect(await names.nameOf('ch', 'de'), 'Schweiz');
|
||||
expect(await names.nameOf('CH', 'ja'), 'Switzerland');
|
||||
expect(await names.nameOf('XX', 'en'), isNull);
|
||||
});
|
||||
|
||||
testWidgets("the world map's names are read from the app's own", (
|
||||
tester,
|
||||
) async {
|
||||
final name = await tester.runAsync(
|
||||
() => AssetCountryNames().nameOf('CH', 'de'),
|
||||
);
|
||||
|
||||
expect(name, 'Schweiz');
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/instance/data/flag_icons_license.dart';
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
test("the flags' MIT notice joins the licence list, as their licence "
|
||||
'asks', () async {
|
||||
registerFlagIconsLicense();
|
||||
|
||||
final notices = [
|
||||
await for (final entry in LicenseRegistry.licenses)
|
||||
if (entry.packages.contains('flag-icons'))
|
||||
entry.paragraphs.map((paragraph) => paragraph.text).join('\n'),
|
||||
];
|
||||
expect(notices, hasLength(1));
|
||||
expect(notices.single, contains('Copyright (c) 2013 Panayiotis Lipiridis'));
|
||||
expect(
|
||||
notices.single,
|
||||
contains(
|
||||
'The above copyright notice and this permission notice shall be '
|
||||
'included',
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:halkyon_learn_flutter/l10n/app_localizations_de.dart';
|
||||
import 'package:halkyon_learn_flutter/l10n/app_localizations_en.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/instance/instance_locations.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/instance/widget/instance_location_words.dart';
|
||||
|
||||
void main() {
|
||||
final en = AppLocalizationsEn();
|
||||
final de = AppLocalizationsDe();
|
||||
const names = {'CH': 'Switzerland', 'DE': 'Germany'};
|
||||
String? words(InstanceLocations locations) =>
|
||||
instanceLocationWords(en, locations, names);
|
||||
|
||||
test('data and backups in one place are named once', () {
|
||||
expect(
|
||||
words(const InstanceLocations(data: 'EU', backups: 'EU')),
|
||||
'Data and backups: European Union',
|
||||
);
|
||||
});
|
||||
|
||||
test('data and backups apart are named a line each', () {
|
||||
expect(
|
||||
words(const InstanceLocations(data: 'EU', backups: 'CH')),
|
||||
'Data: European Union\nBackups: Switzerland',
|
||||
);
|
||||
});
|
||||
|
||||
test('an instance keeping no backups says so', () {
|
||||
expect(
|
||||
words(
|
||||
const InstanceLocations(
|
||||
data: 'CH',
|
||||
backups: InstanceLocations.noBackups,
|
||||
),
|
||||
),
|
||||
'Data: Switzerland\nNo backups',
|
||||
);
|
||||
});
|
||||
|
||||
test('what the instance leaves unsaid is left out', () {
|
||||
expect(words(const InstanceLocations(data: 'DE')), 'Data: Germany');
|
||||
expect(
|
||||
words(const InstanceLocations(backups: 'CH')),
|
||||
'Backups: Switzerland',
|
||||
);
|
||||
expect(words(const InstanceLocations()), isNull);
|
||||
});
|
||||
|
||||
test('a country without a name here goes by its code', () {
|
||||
expect(words(const InstanceLocations(data: 'XK')), 'Data: XK');
|
||||
});
|
||||
|
||||
test('in German too, without the articles some countries take', () {
|
||||
expect(
|
||||
instanceLocationWords(
|
||||
de,
|
||||
const InstanceLocations(data: 'EU', backups: 'CH'),
|
||||
const {'CH': 'Schweiz'},
|
||||
),
|
||||
'Daten: Europäische Union\nSicherungen: Schweiz',
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
@ -1,9 +1,13 @@
|
|||
import 'package:halkyon_learn_flutter/src/core/instance/instance.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/instance/instance_checker.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/instance/instance_locations.dart';
|
||||
|
||||
/// Says that the instances in [answering] answer, and no others.
|
||||
class FakeInstanceChecker([final Set<Instance> answering = const {}])
|
||||
implements InstanceChecker {
|
||||
/// Says that the instances in [answering] answer, and no others; those in
|
||||
/// [locations] say where they keep people's data, the others nothing.
|
||||
class FakeInstanceChecker([
|
||||
final Set<Instance> answering = const {},
|
||||
final Map<Instance, InstanceLocations> locations = const {},
|
||||
]) implements InstanceChecker {
|
||||
/// The instances asked, in order.
|
||||
final asked = <Instance>[];
|
||||
|
||||
|
|
@ -12,4 +16,10 @@ class FakeInstanceChecker([final Set<Instance> answering = const {}])
|
|||
asked.add(instance);
|
||||
return answering.contains(instance);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<InstanceLocations?> locationsOf(Instance instance) async =>
|
||||
answering.contains(instance)
|
||||
? locations[instance] ?? const InstanceLocations()
|
||||
: null;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue