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>
123 lines
4.4 KiB
Dart
123 lines
4.4 KiB
Dart
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
import 'package:miler/data/miler_api.dart';
|
|
import 'package:miler/data/service_profile.dart';
|
|
import 'package:miler/views/Dashboard/pickups/stop_type.dart';
|
|
|
|
/// One app, two lines of work. The tests that matter here are about the
|
|
/// direction the resolver fails in: an unconfigured or unrecognised tenant must
|
|
/// land on the parcel line, because that is the larger business and the failure
|
|
/// it cannot afford is a rider losing the screen that collects a customer's
|
|
/// cash. Showing a meal rider a payment prompt he can dismiss is the cheap
|
|
/// mistake.
|
|
void main() {
|
|
TestWidgetsFlutterBinding.ensureInitialized();
|
|
|
|
/// Resolves through the free function, not the GetX controller — see the
|
|
/// note on [ServiceProfile.active] for why data code must not need a
|
|
/// container. [ServiceProfile.setActive] is what `TenantController.load`
|
|
/// would have done.
|
|
Future<ServiceProfile> resolveWith(Map<String, Object> prefs) async {
|
|
SharedPreferences.setMockInitialValues(prefs);
|
|
final profile = await resolveServiceProfile();
|
|
ServiceProfile.setActive(profile);
|
|
return profile;
|
|
}
|
|
|
|
tearDown(() => ServiceProfile.setActive(ServiceProfile.parcel));
|
|
|
|
group('tenant resolution', () {
|
|
test(
|
|
'an unknown tenant falls back to parcel, not to the meal flow',
|
|
() async {
|
|
final p = await resolveWith({'tenantid': 4242});
|
|
expect(p.line, ServiceLine.parcel);
|
|
expect(p.collectsCash, isTrue);
|
|
expect(p.needsVerification, isTrue);
|
|
},
|
|
);
|
|
|
|
test('a login with no tenant at all still gets a working app', () async {
|
|
final p = await resolveWith({});
|
|
expect(p.line, ServiceLine.parcel);
|
|
});
|
|
|
|
test('the tenant name selects meals, case and spacing aside', () async {
|
|
final p = await resolveWith({
|
|
'tenantid': 0,
|
|
'tenantname': ' DailyGrubs ',
|
|
});
|
|
expect(p.line, ServiceLine.milkMan);
|
|
});
|
|
|
|
test(
|
|
'a meal rider is not asked for money, proof or a verification form',
|
|
() async {
|
|
final p = await resolveWith({'tenantname': 'meals'});
|
|
expect(p.collectsCash, isFalse);
|
|
expect(p.needsVerification, isFalse);
|
|
expect(p.needsProofPhoto, isFalse);
|
|
// He cannot decline one subscriber's lunch.
|
|
expect(p.acceptsPerStop, isFalse);
|
|
expect(p.sourceIsKitchen, isTrue);
|
|
},
|
|
);
|
|
|
|
test(
|
|
'an unrecognised name falls through to the id rather than deciding',
|
|
() async {
|
|
// A client the app has never been told about must not guess its way onto
|
|
// the meal flow.
|
|
final p = await resolveWith({
|
|
'tenantname': 'someclient',
|
|
'tenantid': 1,
|
|
});
|
|
expect(p.line, ServiceLine.parcel);
|
|
},
|
|
);
|
|
});
|
|
|
|
group('the money gate', () {
|
|
final stop = <String, dynamic>{'collectionamt': 450};
|
|
|
|
test(
|
|
'a parcel route still collects what the stop says it collects',
|
|
() async {
|
|
await resolveWith({'tenantid': 1});
|
|
expect(stopCollectionAmount(stop), 450);
|
|
},
|
|
);
|
|
|
|
test('a meal tenant collects nothing, whatever the payload says', () async {
|
|
await resolveWith({'tenantname': 'meals'});
|
|
// The one gate every "is anything owed?" branch in the app reads.
|
|
expect(stopCollectionAmount(stop), 0);
|
|
expect(stopCollectionAmount({'codamount': '₹1,200'}), 0);
|
|
});
|
|
|
|
test('the alternate spellings still work on a parcel route', () async {
|
|
await resolveWith({'tenantid': 1});
|
|
expect(stopCollectionAmount({'pickupamt': '₹1,200'}), 1200);
|
|
expect(stopCollectionAmount({'codamount': 75.5}), 75.5);
|
|
});
|
|
});
|
|
|
|
group('the tenant id sent at login', () {
|
|
test('an ordinary build declares none, and the field is omitted', () {
|
|
// `--dart-define=TENANT_ID` is unset here, as it is in every default
|
|
// build. `hasTenantId` is what gates the field onto the request body, so
|
|
// a plain APK asks the server to decide rather than asserting a tenant.
|
|
expect(MilerApi.tenantId, 0);
|
|
expect(MilerApi.hasTenantId, isFalse);
|
|
});
|
|
|
|
test('zero is never a tenant', () {
|
|
// Sending `tenantid: 0` would look like a real lookup key to the server.
|
|
// The only way to say "you decide" is to leave the field out, so the
|
|
// guard is > 0 rather than a null check.
|
|
expect(MilerApi.hasTenantId, MilerApi.tenantId > 0);
|
|
});
|
|
});
|
|
}
|