device infos

This commit is contained in:
2026-08-28 15:07:30 +05:30
parent 5723d373b2
commit 69f4f3e909
84 changed files with 4337 additions and 2339 deletions

View File

@@ -0,0 +1,248 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:get/get.dart';
import 'package:miler/data/milk_run.dart';
import 'package:miler/data/service_profile.dart';
import 'package:miler/views/Dashboard/home/route_timeline.dart';
import 'package:miler/views/Dashboard/home/trip.dart';
import 'package:miler/views/Dashboard/home/trip_card.dart';
/// ─────────────────────────────────────────────────────────────────────────
/// EIGHT ORDERS OFF ONE COUNTER MAKE ONE DROPDOWN OF EIGHT
///
/// ── What the rider saw ──
///
/// The hub assigned eight orders from one kitchen. Home drew a dropdown holding
/// **seven**, and the eighth sat above it as a bare header titled with the
/// customer's name — reading as a second place he had to ride to, off the same
/// shelf as the other seven.
///
/// ── Two causes, one level apart ──
///
/// **The grouping key.** `routeGroups` built its own — `stopSourceId`, falling
/// back to `stopSourceName` — while [MilkRun.sourceKeyOf] is the function that
/// exists to answer *which counter is this*, and is what the bulk collect keys
/// on. They disagreed: `sourceKeyOf` reads `pickuplocationid` and the card's
/// own expression did not. A row identified only by its location id was one
/// place according to the collect and another according to the card offering
/// it. And a row carrying no source identity at all got its own bucket, with
/// nothing consulted about *where it is collected from* — which is on every
/// row, as the pickup address.
///
/// **The `flat` flag.** A lone stop was drawn as a bare header — the header
/// *is* the stop — whenever the payload carried no source name. A missing name
/// is a gap in the data, not evidence that there is no counter, so the one
/// order that lost its kitchen name was demoted from a place to a row.
///
/// Both now answer from the same fact: a place is identified by its id, then
/// its name, then **where it is**.
/// ─────────────────────────────────────────────────────────────────────────
void main() {
tearDown(() {
Get.reset();
ServiceProfile.setActive(ServiceProfile.parcel);
});
const kitchen = '9 Mettupalayam Road, RS Puram, Coimbatore 641002';
/// One order off the kitchen. [sourceId] and [sourceName] are omitted on the
/// row that lost them, which is the case the whole file is about.
Map<String, dynamic> order(
int id, {
String? sourceId = 'K1',
String? sourceName = 'Vidhya Kitchen',
String address = kitchen,
String status = 'assigned',
}) => <String, dynamic>{
'orderid': 'M-$id',
'pickupid': id,
'step': id,
if (sourceId != null) 'sourceid': sourceId,
if (sourceName != null) 'kitchenname': sourceName,
'pickupcustomer': 'Customer $id',
'pickupaddress': address,
'dropaddress': 'Door $id, Peelamedu, Coimbatore',
'orderstatus': status,
'type': 'delivery',
'pickuplat': 11.01,
'pickuplon': 76.95,
};
List<RouteGroup> groupsFor(List<Map<String, dynamic>> stops) {
ServiceProfile.setActive(ServiceProfile.milkMan);
return TripCard(trip: Trip.fromStops(id: 'T1', stops: stops)).routeGroups();
}
group('the reported case: eight assigned, seven in the list', () {
test('the odd row out joins its siblings', () {
// Seven carry the kitchen's id; the eighth lost both id and name and has
// only the address it shares with them.
final stops = [
for (var i = 1; i <= 7; i++) order(i),
order(8, sourceId: null, sourceName: null),
];
final groups = groupsFor(stops);
expect(groups.length, 1, reason: 'one counter, one dropdown');
expect(groups.single.stops.length, 8);
expect(groups.single.flat, isFalse);
});
test('the count the rider reads says eight', () {
final stops = [
for (var i = 1; i <= 7; i++) order(i),
order(8, sourceId: null, sourceName: null),
];
final group = groupsFor(stops).single;
// The load line and the list under it are the same eight.
expect(group.countLabel, '8 orders');
expect(group.manifest.length, 8);
expect(group.pendingCount, 8);
});
test('a row from a genuinely different place still splits', () {
// The fix must not merge two counters. Grouping by address is the weakest
// true statement available, not a licence to collapse the route.
final stops = [
for (var i = 1; i <= 7; i++) order(i),
order(
8,
sourceId: null,
sourceName: null,
address: '12 SNS Colony, Peelamedu, Coimbatore 641004',
),
];
expect(groupsFor(stops).length, 2);
});
});
group('the heading names the place, never an order', () {
test('a counter with no name is headed by its address', () {
final stops = [
for (var i = 1; i <= 3; i++)
order(i, sourceId: null, sourceName: null),
];
final group = groupsFor(stops).single;
// It used to fall through to `MilkRun.navigationLabel`, which answers
// with the customer — so the heading read as one more order.
expect(group.name, isNot(contains('Customer')));
expect(group.name, contains('Mettupalayam'));
});
test('a named counter still leads with its name, address beneath', () {
final group = groupsFor([for (var i = 1; i <= 3; i++) order(i)]).single;
expect(group.name, 'Vidhya Kitchen');
expect(group.location, contains('Mettupalayam'));
});
test('the address is not printed twice when it is the heading', () {
final stops = [
for (var i = 1; i <= 3; i++)
order(i, sourceId: null, sourceName: null),
];
final group = groupsFor(stops).single;
expect(group.name, isNotEmpty);
expect(
group.location,
isEmpty,
reason: 'the heading already says where it is',
);
});
});
group('a lone stop is a place when it has one', () {
test('one order off an unnamed counter still opens as a list', () {
// The same demotion, one level down: a single order whose row lost its
// kitchen name was drawn as a bare customer header rather than as a place
// holding one order.
final group = groupsFor([
order(1, sourceId: null, sourceName: null),
]).single;
expect(group.flat, isFalse, reason: 'it has an address to be a place by');
expect(group.name, contains('Mettupalayam'));
expect(group.manifest.length, 1);
});
test('with nothing at all to name a place by, it is the stop', () {
// Unchanged, and deliberately: no counter, no name, no address means
// there is no place above the order worth drawing, so the header is the
// stop and opens its detail sheet.
final group = groupsFor([
order(1, sourceId: null, sourceName: null, address: ''),
]).single;
expect(group.flat, isTrue);
});
test('a parcel route is always flat, whatever the address says', () {
// Every stop is its own group there — the rider really does ride to each
// customer for one consignment, and a header plus a single child would
// print the same name twice.
ServiceProfile.setActive(ServiceProfile.parcel);
final groups = TripCard(
trip: Trip.fromStops(
id: 'T1',
stops: [for (var i = 1; i <= 3; i++) order(i)],
),
).routeGroups();
expect(groups.length, 3);
expect(groups.every((g) => g.flat), isTrue);
});
});
group('adopting a counter, and the limits on it', () {
test('an identified stop never consults the day', () {
// Adoption only ever fills a gap. A stop carrying its own id keeps it,
// so two counters that happen to share a street cannot merge and a row
// cannot be pulled off the id the hub gave it.
final mine = <String, dynamic>{
'orderid': 'M-1',
'sourceid': 'K1',
'pickupaddress': kitchen,
};
final neighbour = <String, dynamic>{
'orderid': 'M-2',
'sourceid': 'K2',
'pickupaddress': kitchen,
};
expect(MilkRun.sourceKeyOf(mine, within: [mine, neighbour]), 'id:K1');
expect(MilkRun.sourceKeyOf(neighbour, within: [mine, neighbour]), 'id:K2');
});
test('with no day to consult it falls back to the address', () {
final orphan = <String, dynamic>{
'orderid': 'M-3',
'pickupaddress': kitchen,
};
expect(MilkRun.sourceKeyOf(orphan), startsWith('at:'));
});
test('the bulk collect gathers the adopted order too', () {
// The collect must not leave behind the order the card has just learned
// to group in — that is the two-readers failure in a new place.
final day = [
for (var i = 1; i <= 7; i++) order(i),
order(8, sourceId: null, sourceName: null),
];
expect(MilkRun.sameSourceAs(day.first, day).length, 8);
expect(MilkRun.sameSourceAs(day.last, day).length, 8);
});
});
group('one reader for which counter this is', () {
test('the card groups on the same key the bulk collect does', () {
// The two disagreed, so a kitchen could be split on screen while the
// collect still treated it as one operation.
final byLocationId = <String, dynamic>{
'orderid': 'M-9',
'pickuplocationid': 'L7',
'pickupaddress': kitchen,
};
expect(MilkRun.sourceKeyOf(byLocationId), 'id:L7');
});
});
}