Files
doormile_milderapp/test/delivery_proof_test.dart
Thiru-tenext d7348e253f Miler rider app: surface system, visible design language, backend lifecycle
Design system
- MilerSurface ladder (canvas → working → raised → floating) with MilerPanel
  as layer 1; canvas moved to #DEE3EA so white separates at 1.290:1.
- Visible vocabulary applied across Home, Deliveries, Activity, Account and
  the sheets: hero heads (tabular numeral + small caption, clamped at 1.3x),
  canvas wells for anything that opens, small filled tags for shelf labels,
  demoted placeholders. Recorded in DESIGN_SYSTEM.md §6.
- One icon family: 222 Material glyphs migrated to Lucide; none left outside
  lib/xpress.
- Colour semantics corrected: amber only for what is genuinely owed, brand red
  reserved for the live stop, disabled primaries go neutral rather than pale.

Data and lifecycle
- lib/data/lifecycle.dart reads mutations for what they prove; route_order.dart
  makes admin sequence the single ordering authority; service_day.dart, and
  stop_area.dart rewritten against live Coimbatore addresses (digit-token
  stripping, city stoplist, street suffixes, stammer collapse).
- countLabel states the load once, in bags.

Testing
- 1440 tests passing; golden shot harnesses for Home, Deliveries, Activity,
  sheets and verify, with test/failures/ now gitignored (diff debris).
- New pins: home_gutter_test, stop_area_test, plus updated structural bounds.

Note: this commit also carries pre-existing working-tree deletions that were
present before this work (API_SPEC.md, README.md, demo test fixtures).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-22 05:40:35 +05:30

166 lines
6.1 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:miler/data/proof_store.dart';
import 'package:miler/data/service_profile.dart';
/// ─────────────────────────────────────────────────────────────────────────
/// PROOF OF DELIVERY
///
/// A photo taken at a door is evidence, and evidence that disappears is worse
/// than none — it makes the rider believe he has cover he does not have.
/// These pin the two properties that make it real: it outlives the OS cache
/// `ImagePicker` hands back, and it belongs to the session that took it.
/// ─────────────────────────────────────────────────────────────────────────
void main() {
late Directory docs;
setUp(() async {
ServiceProfile.setActive(ServiceProfile.milkMan);
SharedPreferences.setMockInitialValues({'userid': 38});
// path_provider has no host under the test binding.
docs = await Directory.systemTemp.createTemp('proof_docs');
TestWidgetsFlutterBinding.ensureInitialized();
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(
const MethodChannel('plugins.flutter.io/path_provider'),
(call) async => docs.path,
);
});
tearDown(() async {
ServiceProfile.setActive(ServiceProfile.parcel);
if (await docs.exists()) await docs.delete(recursive: true);
});
/// A real 1×1 PNG. The store tests never decode it, but the widget test
/// does: `Image.file` on 64 bytes of nonsense never completes its frame, so
/// `pumpAndSettle` waits for a decode that will never land and the test
/// hangs for the full ten minutes rather than failing.
const List<int> onePixelPng = [
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, //
0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52,
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0x15, 0xC4,
0x89, 0x00, 0x00, 0x00, 0x0A, 0x49, 0x44, 0x41,
0x54, 0x78, 0x9C, 0x63, 0x00, 0x01, 0x00, 0x00,
0x05, 0x00, 0x01, 0x0D, 0x0A, 0x2D, 0xB4, 0x00,
0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE,
0x42, 0x60, 0x82,
];
/// Stands in for what the camera hands back: a file in a volatile place.
Future<File> cameraShot(String name) async {
final cache = await Directory.systemTemp.createTemp('picker_cache');
final f = File('${cache.path}/$name.jpg');
await f.writeAsBytes(onePixelPng);
return f;
}
group('the store', () {
test('the photo is copied out of the camera cache', () async {
final shot = await cameraShot('a');
final saved = await ProofStore.save('ORDER-1', shot.path);
expect(saved, isNotNull);
expect(
saved,
isNot(shot.path),
reason:
'ImagePicker returns an OS cache path the system reclaims — '
'a record pointing at it loses its evidence',
);
expect(File(saved!).existsSync(), isTrue);
// The original can vanish and the proof survives, which is the point.
await shot.parent.delete(recursive: true);
expect(File(saved).existsSync(), isTrue);
expect(await ProofStore.pathFor('ORDER-1'), saved);
});
test('a retake replaces rather than accumulates', () async {
final first = await ProofStore.save(
'ORDER-1',
(await cameraShot('a')).path,
);
final second = await ProofStore.save(
'ORDER-1',
(await cameraShot('b')).path,
);
expect(second, first, reason: 'one stop, one proof');
expect(await ProofStore.pathFor('ORDER-1'), second);
});
test('a proof whose file has gone is not proof', () async {
final saved = await ProofStore.save(
'ORDER-1',
(await cameraShot('a')).path,
);
await File(saved!).delete();
expect(
await ProofStore.pathFor('ORDER-1'),
isNull,
reason: 'returning a dead path would draw a broken image as evidence',
);
});
test('nothing is invented from nothing', () async {
expect(await ProofStore.save('', 'whatever'), isNull);
expect(await ProofStore.save('ORDER-1', ''), isNull);
expect(await ProofStore.save('ORDER-1', '/no/such/file.jpg'), isNull);
expect(await ProofStore.pathFor('ORDER-NONE'), isNull);
});
test('a device path is never offered as a remote url', () async {
await ProofStore.save('ORDER-1', (await cameraShot('a')).path);
expect(
await ProofStore.remoteUrlFor('ORDER-1'),
'',
reason:
'the Miler API has no upload route; a filesystem path written '
"into the hub's photourl would look like evidence and resolve to "
'nothing',
);
});
});
group('ownership', () {
test("another rider cannot open this rider's doorstep photos", () async {
await ProofStore.save('ORDER-1', (await cameraShot('a')).path);
expect(await ProofStore.pathFor('ORDER-1'), isNotNull);
SharedPreferences.setMockInitialValues({'userid': 99});
expect(
await ProofStore.pathFor('ORDER-1'),
isNull,
reason: 'proof is scoped like every other record — see WorkScope',
);
});
test('the other line cannot either', () async {
await ProofStore.save('ORDER-1', (await cameraShot('a')).path);
ServiceProfile.setActive(ServiceProfile.parcel);
expect(await ProofStore.pathFor('ORDER-1'), isNull);
});
test('logout deletes the photos, not just the index', () async {
final saved = await ProofStore.save(
'ORDER-1',
(await cameraShot('a')).path,
);
await ProofStore.clearScope();
expect(await ProofStore.pathFor('ORDER-1'), isNull);
expect(
File(saved!).existsSync(),
isFalse,
reason: "a doorstep photo must not outlive the session that took it",
);
});
});
}