Book a pickup, track it to delivery — the rebuilt customer app. - Design language from doormile-screens.html: brand #8F0F06, Manrope + Geist Mono (variable fonts), bordered cards instead of shadows, crimson brand headers, sliding tab indicator, mono for anything read digit by digit. - lib/data (one live API implementation, plus a debug-only offline fake), lib/state, lib/ui (tokens, widgets, screens). - 84 tests, plus a design snapshot harness that renders every screen with the real fonts: flutter test test/design_snapshot_test.dart --run-skipped --update-goldens This replaces the previous app (pubspec 'doormile', app id com.doormile.customer). That tree remains in history at 6c7d656; note its android/app/google-services.json is not carried over, and the application id here is in.doormile.customer. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
97 lines
3.5 KiB
Dart
97 lines
3.5 KiB
Dart
import 'dart:io';
|
|
|
|
import 'package:doormile_cx/data/map_config.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
void main() {
|
|
group('tile provider', () {
|
|
test('CARTO serves anonymously and takes a key when one is supplied', () {
|
|
const anonymous = MapTileProvider.carto;
|
|
expect(anonymous.usable, isTrue);
|
|
expect(anonymous.hasKey, isFalse);
|
|
expect(anonymous.urlFor(DmMapStyle.streets), isNot(contains('api_key')));
|
|
|
|
final keyed = anonymous.withKey('abc123');
|
|
expect(keyed.hasKey, isTrue);
|
|
expect(
|
|
keyed.urlFor(DmMapStyle.streets),
|
|
endsWith('?api_key=abc123'),
|
|
);
|
|
// Nothing else about the source changes when the key arrives.
|
|
expect(keyed.attribution, anonymous.attribution);
|
|
expect(keyed.subdomains, anonymous.subdomains);
|
|
});
|
|
|
|
test('the two styles are distinct sources', () {
|
|
expect(
|
|
MapTileProvider.carto.urlFor(DmMapStyle.streets),
|
|
isNot(MapTileProvider.carto.urlFor(DmMapStyle.muted)),
|
|
);
|
|
});
|
|
|
|
test('a key-only provider reports itself unusable without one', () {
|
|
expect(MapTileProvider.maptiler.usable, isFalse);
|
|
expect(MapTileProvider.maptiler.withKey('k').usable, isTrue);
|
|
expect(
|
|
MapTileProvider.maptiler.withKey('k').urlFor(DmMapStyle.streets),
|
|
endsWith('?key=k'),
|
|
);
|
|
expect(MapTileProvider.stadia.usable, isFalse);
|
|
});
|
|
|
|
test('every preset credits its data source', () {
|
|
for (final provider in MapTileProvider.presets.values) {
|
|
expect(
|
|
provider.attribution,
|
|
contains('OpenStreetMap'),
|
|
reason: '${provider.id} must credit OpenStreetMap',
|
|
);
|
|
expect(provider.attributionUrl, startsWith('https://'));
|
|
expect(provider.templates[DmMapStyle.streets], isNotNull);
|
|
}
|
|
});
|
|
});
|
|
|
|
group('resolved configuration', () {
|
|
test('defaults to CARTO and identifies the app to the provider', () {
|
|
final config = DmMapConfig.fromEnvironment();
|
|
expect(config.provider.id, 'carto');
|
|
expect(config.warning, isNull);
|
|
expect(config.userAgent, contains('Doormile/'));
|
|
expect(config.userAgent, contains(DmMapConfig.packageName));
|
|
expect(config.attribution, contains('CARTO'));
|
|
});
|
|
|
|
test('is swappable without touching a screen', () {
|
|
final original = DmMapConfig.instance;
|
|
addTearDown(() => DmMapConfig.instance = original);
|
|
|
|
DmMapConfig.instance = DmMapConfig(
|
|
provider: MapTileProvider.osm,
|
|
userAgent: 'test/1.0',
|
|
);
|
|
expect(DmMapConfig.instance.attribution, '© OpenStreetMap contributors');
|
|
expect(DmMapConfig.instance.retina, isFalse);
|
|
expect(DmMapConfig.instance.maxNativeZoom, 19);
|
|
});
|
|
});
|
|
|
|
test('no tile URL, subdomain list or map key lives in the UI layer', () {
|
|
// The whole point of the provider layer: swapping CARTO for a keyed plan or
|
|
// another vendor must never mean editing a map screen.
|
|
final offenders = <String>[];
|
|
for (final entry in Directory('lib/ui').listSync(recursive: true)) {
|
|
if (entry is! File || !entry.path.endsWith('.dart')) continue;
|
|
final source = entry.readAsStringSync();
|
|
if (source.contains('cartocdn') ||
|
|
source.contains('tile.openstreetmap.org') ||
|
|
source.contains('api.maptiler.com') ||
|
|
source.contains('tiles.stadiamaps.com') ||
|
|
source.contains('{z}/{x}/{y}')) {
|
|
offenders.add(entry.path);
|
|
}
|
|
}
|
|
expect(offenders, isEmpty, reason: 'tile URLs belong in map_config.dart');
|
|
});
|
|
}
|