Drops the app bar, moves settings onto the map with the other controls, and takes the attribution bar off the main screen. The app bar held only the app name — which the launcher already shows — and one action, so removing it hands its height to the map rather than leaving an empty strip. The settings button joins search and recentre in the column over the map, where the rest of the controls that act on the map already live. The attribution needed checking before it could move, because it is a licence obligation rather than a layout choice. The OSMF guidelines settle it: for a browsable map the credit does not have to be permanently visible, provided "the user must still be able to find the licence information if they look for it, for example from an '(i)' button in the corner of the map or an 'About' option in a menu". Map to settings to "Fonti e licenze" is exactly that, and MapLibre's own (i) control now sits in the map's top-right corner as a second route — it had been landing underneath the new button column. Two things had to follow the bar rather than disappear with it. The radar manifest carries its own credit for the frames on screen, set by whoever published them and absent from the region config, so the Sources screen now shows it alongside the configured attributions. And the settings entry that leads there carries a subtitle saying what it holds, because "Fonti e licenze" alone does not tell a reader that this is where the licence information went. settings_screen_test asserts that entry exists and opens the screen. Removing it would be a licence breach, so it should fail the build rather than ship quietly. Verified on the emulator: no title bar, the map runs to the top with the demo notice above it, the three controls stack bottom-right without colliding with the (i), and settings shows the sources entry with its subtitle. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
118 lines
4.1 KiB
Dart
118 lines
4.1 KiB
Dart
import 'dart:io';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:nuvolari/core/region/region_config.dart';
|
|
import 'package:nuvolari/core/region/region_repository.dart';
|
|
import 'package:nuvolari/features/sources/sources_screen.dart';
|
|
import 'package:nuvolari/l10n/app_localizations.dart';
|
|
|
|
RegionConfig loadPiemonte() =>
|
|
RegionConfig.parse(File('assets/regions/piemonte.json').readAsStringSync());
|
|
|
|
Widget wrap(Widget child, RegionConfig region) => ProviderScope(
|
|
overrides: [regionConfigProvider.overrideWith((ref) async => region)],
|
|
child: MaterialApp(
|
|
locale: const Locale('it'),
|
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
|
supportedLocales: AppLocalizations.supportedLocales,
|
|
home: child,
|
|
),
|
|
);
|
|
|
|
void main() {
|
|
late RegionConfig region;
|
|
|
|
setUpAll(() {
|
|
region = loadPiemonte();
|
|
});
|
|
|
|
group('SourcesScreen', () {
|
|
testWidgets('lists every configured source with its licence', (
|
|
tester,
|
|
) async {
|
|
await tester.pumpWidget(wrap(const SourcesScreen(), region));
|
|
await tester.pumpAndSettle();
|
|
|
|
// The list is longer than the test viewport, so each entry is scrolled to
|
|
// rather than expected to be on screen at once. Asserting only on what
|
|
// happens to fit would let a credit fall off the bottom unnoticed.
|
|
for (final attribution in region.attributions) {
|
|
await tester.scrollUntilVisible(
|
|
find.text(attribution.text),
|
|
200,
|
|
scrollable: find.byType(Scrollable).first,
|
|
);
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
find.text(attribution.text),
|
|
findsOneWidget,
|
|
reason: '${attribution.id} is missing from the Sources screen',
|
|
);
|
|
|
|
// Assert the licence while its row is on screen. Checking at the end
|
|
// would only prove whatever the last scroll happened to leave visible.
|
|
final license = attribution.license;
|
|
expect(
|
|
find.text(
|
|
license == null
|
|
? 'Nessuna licenza dichiarata dalla fonte'
|
|
: 'Licenza: $license',
|
|
),
|
|
findsWidgets,
|
|
reason: 'the licence for ${attribution.id} is not shown beside it',
|
|
);
|
|
}
|
|
});
|
|
|
|
// ARPA publishes no licence for the alert bulletin. Showing an invented one
|
|
// would be worse than showing none, so the screen must say so explicitly.
|
|
testWidgets('says when a source states no licence', (tester) async {
|
|
await tester.pumpWidget(wrap(const SourcesScreen(), region));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Nessuna licenza dichiarata dalla fonte'), findsWidgets);
|
|
});
|
|
|
|
testWidgets('shows the independence disclaimer', (tester) async {
|
|
await tester.pumpWidget(wrap(const SourcesScreen(), region));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('App non ufficiale'), findsOneWidget);
|
|
expect(find.textContaining('indipendente'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('explains the share-alike obligation', (tester) async {
|
|
await tester.pumpWidget(wrap(const SourcesScreen(), region));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Below the fold in the test viewport once the base-map credits are listed.
|
|
await tester.scrollUntilVisible(
|
|
find.text('Dati derivati'),
|
|
200,
|
|
scrollable: find.byType(Scrollable).first,
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Dati derivati'), findsOneWidget);
|
|
expect(find.textContaining('CC BY-SA'), findsWidgets);
|
|
});
|
|
|
|
testWidgets('links the official bulletin', (tester) async {
|
|
await tester.pumpWidget(wrap(const SourcesScreen(), region));
|
|
await tester.pumpAndSettle();
|
|
|
|
// The button sits below the fold in the test viewport.
|
|
await tester.scrollUntilVisible(
|
|
find.text('Bollettino ufficiale Arpa Piemonte'),
|
|
200,
|
|
scrollable: find.byType(Scrollable).first,
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Bollettino ufficiale Arpa Piemonte'), findsOneWidget);
|
|
});
|
|
});
|
|
}
|