Refresh the Flutter snapshot: v3 tickets V3-04 through V3-16, plus a fresh installable APK
V3-04 through V3-07, V3-10, V3-16 shipped complete; V3-11/V3-12/V3-14 shipped code-complete pending device/account verification; V3-08/V3-09 deferred behind a new V3-17 (self-hosted OSRM investigation). 316 tests passing, up from 221. The APK is a fresh release build (debug-signed, no release signing config exists yet) with two build fixes applied: core library desugaring enabled for flutter_local_notifications, and sentry_flutter bumped to 9.27.0 (8.14.2's bundled Kotlin plugin was incompatible with this project's Kotlin 2.4.0 toolchain).
This commit is contained in:
115
rippr-flutter-src/test/format_test.dart
Normal file
115
rippr-flutter-src/test/format_test.dart
Normal file
@@ -0,0 +1,115 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:rippr/src/domain/models.dart';
|
||||
import 'package:rippr/src/export/ride_export.dart';
|
||||
import 'package:rippr/src/ui/format.dart';
|
||||
|
||||
/// V3-03: distance and speed units. Metric is the default everywhere, so every existing
|
||||
/// call site that omits `unit:` keeps behaving exactly as before — these tests are about
|
||||
/// what changes when a caller opts into imperial.
|
||||
void main() {
|
||||
group('formatDistance', () {
|
||||
test('metric switches from metres to kilometres at 1000 m', () {
|
||||
expect(formatDistance(999), '999 m');
|
||||
expect(formatDistance(1000), '1.0 km');
|
||||
expect(formatDistance(12345.6), '12.3 km');
|
||||
});
|
||||
|
||||
test('imperial switches from feet to miles at 1000 ft', () {
|
||||
// 1000 ft is ~304.8 m.
|
||||
expect(formatDistance(100, unit: UnitSystem.imperial), '328 ft');
|
||||
expect(
|
||||
formatDistance(305, unit: UnitSystem.imperial),
|
||||
'0.2 mi',
|
||||
reason: 'just past the 1000 ft threshold (1000 ft is not 1 mile — 5280 ft is)',
|
||||
);
|
||||
// 12345.6 m is ~7.67 mi.
|
||||
expect(formatDistance(12345.6, unit: UnitSystem.imperial), '7.7 mi');
|
||||
});
|
||||
|
||||
test('metric is the default when unit is omitted', () {
|
||||
expect(formatDistance(1500), formatDistance(1500, unit: UnitSystem.metric));
|
||||
});
|
||||
});
|
||||
|
||||
group('formatSpeed', () {
|
||||
test('metric prints km/h to one decimal', () {
|
||||
expect(formatSpeed(88.5), '88.5 km/h');
|
||||
});
|
||||
|
||||
test('imperial converts to mph', () {
|
||||
// 100 km/h is ~62.1 mph.
|
||||
expect(formatSpeed(100, unit: UnitSystem.imperial), '62.1 mph');
|
||||
});
|
||||
});
|
||||
|
||||
group('formatElevation', () {
|
||||
test('metric prints whole metres', () {
|
||||
expect(formatElevation(1045.7), '1045 m');
|
||||
});
|
||||
|
||||
test('imperial converts to whole feet', () {
|
||||
// 1000 m is ~3280.84 ft.
|
||||
expect(formatElevation(1000, unit: UnitSystem.imperial), '3280 ft');
|
||||
});
|
||||
});
|
||||
|
||||
group('parts helpers used by BigStat', () {
|
||||
test('formatDistanceParts always uses the "big" unit, km or mi', () {
|
||||
expect(formatDistanceParts(500), ('0.5', 'km'));
|
||||
expect(formatDistanceParts(1609.344, unit: UnitSystem.imperial), ('1.0', 'mi'));
|
||||
});
|
||||
|
||||
test('formatSpeedParts has zero decimal places, matching the live speedo', () {
|
||||
expect(formatSpeedParts(88.6), ('89', 'km/h'));
|
||||
expect(formatSpeedParts(100, unit: UnitSystem.imperial), ('62', 'mph'));
|
||||
});
|
||||
});
|
||||
|
||||
group('formatSpeedRangeLabel', () {
|
||||
test('metric passes the km/h boundaries through unchanged', () {
|
||||
expect(formatSpeedRangeLabel(10, 20), '10–20');
|
||||
});
|
||||
|
||||
test('imperial converts and rounds the boundaries, not the underlying bucket', () {
|
||||
// 10 km/h ~ 6 mph, 20 km/h ~ 12 mph.
|
||||
expect(formatSpeedRangeLabel(10, 20, unit: UnitSystem.imperial), '6–12');
|
||||
});
|
||||
});
|
||||
|
||||
group('unit system never reaches export (the risk this ticket names)', () {
|
||||
// gpx() and geoJson() take no UnitSystem parameter at all -- there is no argument to
|
||||
// thread incorrectly. This proves the invariant one level up: formatting the same
|
||||
// stored value under both units produces different text, but export always reads the
|
||||
// one stored value, regardless of what a caller displays it as.
|
||||
const trip = Trip(
|
||||
id: 1,
|
||||
startedAt: 1700000000000,
|
||||
endedAt: 1700000600000,
|
||||
state: TripState.completed,
|
||||
distanceM: 12345.6,
|
||||
maxSpeedKmh: 88.5,
|
||||
);
|
||||
|
||||
test('display formatting differs by unit for the same stored value', () {
|
||||
expect(
|
||||
formatDistance(trip.distanceM, unit: UnitSystem.metric),
|
||||
isNot(formatDistance(trip.distanceM, unit: UnitSystem.imperial)),
|
||||
);
|
||||
});
|
||||
|
||||
test('export output is identical regardless of what unit the UI last showed', () {
|
||||
// GPX carries no trip-level distance field at all (it is built from individual
|
||||
// <trkpt> elements) -- deterministic regardless of unit is the property under
|
||||
// test, not any particular substring.
|
||||
final first = gpx(trip, const [], const [], 'Ride');
|
||||
final second = gpx(trip, const [], const [], 'Ride');
|
||||
expect(first, second);
|
||||
|
||||
// GeoJSON does carry trip.distanceM in its properties, and it is the raw metric
|
||||
// figure -- 12345.6, never a converted "7.7 mi".
|
||||
final json = geoJson(trip, const [], const [], 'Ride');
|
||||
expect(json, contains('"distance_m": 12345.6'));
|
||||
expect(json, isNot(contains('mi')), reason: 'export is metric by specification');
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user