── The circle says what pressing it does ── "ONE TOUCH" named the *mechanism* — one gesture, no form — which is something the product team knows and a customer has to be taught. Nobody arrives at that screen wanting a touch; they want a parcel collected. The caption under the sphere still carries what makes it different from the form below. Renamed in the comments too. A codebase explaining "One Touch" against a button that says BOOK NOW is a trap for whoever reads it next. ── OpenStreetMap everywhere ── One line: the default provider was CARTO, it is `osm`. Nothing else moves — `DmMapTiles` already reads the template, subdomains, retina flag and attribution off the provider, so the credit line follows on its own. One thing recorded on the provider itself rather than left to be discovered: these are donated servers and the OSM Foundation's tile policy does not permit a distributed app to lean on them. A block looks like every tile turning into the ground colour at once, with no other symptom. Moving off it is one define — `DM_MAP_PROVIDER=carto|maptiler|stadia`, all serving OpenStreetMap data — and the map_config test now asserts the identifying User-Agent rather than only the URL, because that is what attributable traffic depends on. ── Orders had a second copy of the segmented control ── Its own `_Tab`, a pill radius, 3pt of padding and the count folded into the label's text — beside the pickup window's day switcher, which is DmChoiceChip in a rounded groove with 4pt of padding and the count in a bubble. Two controls doing one job, drifting apart a padding value at a time. It is the same control now, and `_Tab` is gone. DmChoiceChip's horizontal padding drops 12 → 9: three of them split a 390pt phone and "Cancelled" truncated to "Cancell…" at the old value. The day switcher has two chips and acres of room, so it loses nothing.
103 lines
3.9 KiB
Dart
103 lines
3.9 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 OpenStreetMap and identifies the app to it', () {
|
|
// ── The User-Agent is not decoration here ──
|
|
//
|
|
// OSM's tile usage policy asks a distributed app for a valid
|
|
// identifying agent, and blocks traffic it cannot attribute. A block
|
|
// looks like every tile turning into the ground colour at once, with no
|
|
// other symptom — so this asserts the identification, not just the URL.
|
|
final config = DmMapConfig.fromEnvironment();
|
|
expect(config.provider.id, 'osm');
|
|
expect(config.warning, isNull);
|
|
expect(config.userAgent, contains('Doormile/'));
|
|
expect(config.userAgent, contains(DmMapConfig.packageName));
|
|
expect(config.attribution, contains('OpenStreetMap'));
|
|
});
|
|
|
|
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');
|
|
});
|
|
}
|