Clarify fuel savings comparison labels
This commit is contained in:
@@ -211,10 +211,10 @@ final class DistanceTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testMilesConversion() {
|
||||
// 5 miles ≈ 8.05 km (app uses × 1.60934)
|
||||
XCTAssertEqual(5 * 1.60934, 8.0467, accuracy: 0.001)
|
||||
// 262 km ≈ 162.8 miles (display uses × 0.621371)
|
||||
XCTAssertEqual(262 * 0.621371, 162.8, accuracy: 0.1)
|
||||
// 5 miles ≈ 8.05 km using the shared exact conversion
|
||||
XCTAssertEqual(5 * FuelSavingsCalculator.kilometresPerMile, 8.04672, accuracy: 0.001)
|
||||
// 262 km ≈ 162.8 miles using the shared exact conversion
|
||||
XCTAssertEqual(262 / FuelSavingsCalculator.kilometresPerMile, 162.8, accuracy: 0.1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,14 +267,14 @@ final class FuelTypeLabelTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testDistanceUnitConversion() {
|
||||
XCTAssertEqual(DistanceUnit.miles.toKM(1), 1.60934, accuracy: 0.00001)
|
||||
XCTAssertEqual(DistanceUnit.miles.toKM(1), 1.609344, accuracy: 0.00001)
|
||||
XCTAssertEqual(DistanceUnit.kilometers.toKM(5), 5)
|
||||
XCTAssertEqual(DistanceUnit.miles.fromKM(1.60934), 1, accuracy: 0.00001)
|
||||
XCTAssertEqual(DistanceUnit.miles.fromKM(1.609344), 1, accuracy: 0.00001)
|
||||
XCTAssertEqual(DistanceUnit.kilometers.fromKM(2.5), 2.5)
|
||||
}
|
||||
|
||||
func testDistanceUnitFormat() {
|
||||
XCTAssertEqual(DistanceUnit.miles.format(1.60934), "1.0 mi")
|
||||
XCTAssertEqual(DistanceUnit.miles.format(1.609344), "1.0 mi")
|
||||
XCTAssertEqual(DistanceUnit.kilometers.format(3.4), "3.4 km")
|
||||
}
|
||||
}
|
||||
@@ -400,7 +400,7 @@ final class AlertRadiusFollowTests: XCTestCase {
|
||||
FuelStore.saveDistanceUnit(.miles)
|
||||
FuelStore.saveStationLimit(5)
|
||||
let eff = FuelStore.effectiveAlertsRadiusKM(followsSearch: true, manualKM: 3)
|
||||
XCTAssertEqual(eff, 5 * 1.60934, accuracy: 0.001)
|
||||
XCTAssertEqual(eff, 5 * FuelSavingsCalculator.kilometresPerMile, accuracy: 0.001)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -731,7 +731,7 @@ final class RoadDistanceCacheTests: XCTestCase {
|
||||
sourceLat: sourceLat, sourceLng: sourceLng,
|
||||
entries: ["far": .init(meters: 23_500, lat: 51.5074, lng: -0.1278, returnMeters: 23_500)])
|
||||
|
||||
let tenMilesKM = 10 * 1.60934
|
||||
let tenMilesKM = 10 * FuelSavingsCalculator.kilometresPerMile
|
||||
XCTAssertLessThan(far.distanceKM(to: sourceLat, lng2: sourceLng), tenMilesKM,
|
||||
"fixture: the straight line must sit INSIDE the 10-mile radius")
|
||||
XCTAssertGreaterThan(FuelStore.displayDistanceKM(station: far, userLat: sourceLat, userLng: sourceLng),
|
||||
@@ -743,7 +743,7 @@ final class RoadDistanceCacheTests: XCTestCase {
|
||||
userLat: sourceLat, userLng: sourceLng),
|
||||
"a station whose row reads 14.6 mi must not appear in a 10-mile list")
|
||||
// The same station is back once the radius actually covers its distance.
|
||||
XCTAssertTrue(FuelStore.isWithinDisplayRadius(station: far, radiusKM: 15 * 1.60934,
|
||||
XCTAssertTrue(FuelStore.isWithinDisplayRadius(station: far, radiusKM: 15 * FuelSavingsCalculator.kilometresPerMile,
|
||||
userLat: sourceLat, userLng: sourceLng))
|
||||
// …and an unknown station (no cached route) still tests its displayed
|
||||
// straight line, so nothing changes for the bulk of the list.
|
||||
@@ -783,10 +783,10 @@ final class RoadDistanceCacheTests: XCTestCase {
|
||||
XCTAssertFalse(confirmed.isApproximate, "a cached road distance is not an approximation")
|
||||
XCTAssertEqual(confirmed.km, 3.2, accuracy: 0.0001)
|
||||
XCTAssertEqual(confirmed.label(unit: .miles), "2.0 mi")
|
||||
XCTAssertEqual(confirmed.labelWithReturn(unit: .miles), "2.0 mi (4.0 mi return)")
|
||||
XCTAssertEqual(confirmed.labelWithReturn(unit: .kilometers), "3.2 km (6.4 km return)")
|
||||
XCTAssertEqual(confirmed.labelWithReturn(unit: .miles), "2.0 mi (4.2 mi return)")
|
||||
XCTAssertEqual(confirmed.labelWithReturn(unit: .kilometers), "3.2 km (6.8 km return)")
|
||||
XCTAssertEqual(confirmed.spokenLabel(unit: .miles), "2.0 miles")
|
||||
XCTAssertEqual(confirmed.spokenLabelWithReturn(unit: .miles), "2.0 miles, 4.0 miles return")
|
||||
XCTAssertEqual(confirmed.spokenLabelWithReturn(unit: .miles), "2.0 miles, 4.2 miles return")
|
||||
|
||||
let approximate = FuelStore.stationDistanceDisplay(station: unrouted, userLat: 51.6, userLng: -0.1)
|
||||
XCTAssertTrue(approximate.isApproximate, "the straight-line fallback must be identified as one")
|
||||
@@ -794,11 +794,40 @@ final class RoadDistanceCacheTests: XCTestCase {
|
||||
XCTAssertEqual(approximate.label(unit: .miles),
|
||||
"Approx. " + DistanceUnit.miles.format(approximate.km))
|
||||
XCTAssertEqual(approximate.labelWithReturn(unit: .miles),
|
||||
"Approx. \(DistanceUnit.miles.format(approximate.km)) (\(DistanceUnit.miles.format(approximate.km * 2)) return)")
|
||||
"Approx. " + DistanceUnit.miles.format(approximate.km) + " (Approx. " + DistanceUnit.miles.format(approximate.km * 2) + " return)")
|
||||
XCTAssertEqual(approximate.spokenLabel(unit: .kilometers),
|
||||
"Approximately " + DistanceUnit.kilometers.spoken(approximate.km))
|
||||
XCTAssertEqual(approximate.spokenLabelWithReturn(unit: .kilometers),
|
||||
"Approximately \(DistanceUnit.kilometers.spoken(approximate.km)), \(DistanceUnit.kilometers.spoken(approximate.km * 2)) return")
|
||||
"Approximately \(DistanceUnit.kilometers.spoken(approximate.km)), Approximately \(DistanceUnit.kilometers.spoken(approximate.km * 2)) return")
|
||||
}
|
||||
|
||||
func testDisplayedReturnUsesTheIndependentlyMeasuredReturnRoute() {
|
||||
FuelStore.setAccurateDistancesEnabled(true)
|
||||
let station = station("separate-legs", 51.5074, -0.1278)
|
||||
FuelStore.saveRoadDistances(
|
||||
sourceLat: 51.6,
|
||||
sourceLng: -0.1,
|
||||
entries: ["separate-legs": .init(
|
||||
meters: 4_650,
|
||||
lat: station.lat,
|
||||
lng: station.lng,
|
||||
returnMeters: 4_820
|
||||
)],
|
||||
complete: true
|
||||
)
|
||||
|
||||
let display = FuelStore.stationDistanceDisplay(
|
||||
station: station,
|
||||
userLat: 51.6,
|
||||
userLng: -0.1
|
||||
)
|
||||
|
||||
XCTAssertEqual(display.km, 4.65, accuracy: 1e-12)
|
||||
XCTAssertEqual(display.returnKM, 9.47, accuracy: 1e-12)
|
||||
XCTAssertFalse(display.isApproximate)
|
||||
XCTAssertFalse(display.returnIsApproximate)
|
||||
XCTAssertEqual(display.labelWithReturn(unit: .miles), "2.9 mi (5.9 mi return)")
|
||||
XCTAssertEqual(display.spokenLabelWithReturn(unit: .miles), "2.9 miles, 5.9 miles return")
|
||||
}
|
||||
|
||||
/// The marker tracks the cache on its own: same station, bare figure once its
|
||||
@@ -961,7 +990,7 @@ final class RoadDistanceCacheTests: XCTestCase {
|
||||
let display = FuelStore.stationDistanceDisplay(station: routed, userLat: 51.6, userLng: -0.1)
|
||||
XCTAssertTrue(display.isApproximate, "the browse layer must never serve routes with the mode off")
|
||||
XCTAssertEqual(display.km, routed.distanceKM(to: 51.6, lng2: -0.1), accuracy: 0.0001)
|
||||
XCTAssertTrue(FuelStore.isWithinDisplayRadius(station: routed, radiusKM: 10 * 1.60934,
|
||||
XCTAssertTrue(FuelStore.isWithinDisplayRadius(station: routed, radiusKM: 10 * FuelSavingsCalculator.kilometresPerMile,
|
||||
userLat: 51.6, userLng: -0.1),
|
||||
"with the mode off the radius is a straight-line radius, agreeing with the rows")
|
||||
|
||||
@@ -1000,7 +1029,7 @@ final class RoadDistanceCacheTests: XCTestCase {
|
||||
let display = FuelStore.stationDistanceDisplay(station: routed, userLat: 51.6, userLng: -0.1)
|
||||
XCTAssertFalse(display.isApproximate)
|
||||
XCTAssertEqual(display.km, 23.5, accuracy: 0.0001)
|
||||
XCTAssertFalse(FuelStore.isWithinDisplayRadius(station: routed, radiusKM: 10 * 1.60934,
|
||||
XCTAssertFalse(FuelStore.isWithinDisplayRadius(station: routed, radiusKM: 10 * FuelSavingsCalculator.kilometresPerMile,
|
||||
userLat: 51.6, userLng: -0.1),
|
||||
"in this mode a 10-mile radius is 10 miles of driving, matching the routed rows")
|
||||
}
|
||||
|
||||
@@ -263,6 +263,23 @@ final class FuelSavingsCalculationAuditTests: XCTestCase {
|
||||
XCTAssertEqual(FuelSavingsFormat.money(0), "£0.00")
|
||||
}
|
||||
|
||||
func testIndependentlyRoundedFinancialComponentsMayDifferFromRoundedTotalByOnePenny() {
|
||||
let gross = 1.234
|
||||
let drivingCost = 0.005
|
||||
let net = gross - drivingCost
|
||||
|
||||
XCTAssertEqual(FuelSavingsFormat.money(gross), "£1.23")
|
||||
XCTAssertEqual(FuelSavingsFormat.money(drivingCost), "£0.01")
|
||||
XCTAssertEqual(FuelSavingsFormat.money(net), "£1.23")
|
||||
let independentlyRoundedSubtraction = 1.23 - 0.01
|
||||
XCTAssertEqual(independentlyRoundedSubtraction, 1.22, accuracy: 1e-12)
|
||||
XCTAssertNotEqual(
|
||||
FuelSavingsFormat.money(independentlyRoundedSubtraction),
|
||||
FuelSavingsFormat.money(net),
|
||||
"the raw total remains authoritative; independently rounded components need not add back exactly"
|
||||
)
|
||||
}
|
||||
|
||||
func testSortingUsesTheUnroundedNetSoRoundedTiesStillOrderCorrectly() {
|
||||
// Two rows that both DISPLAY "£0.18 worse off" but differ in the raw
|
||||
// figure: the sort must use the raw value, never the shown one.
|
||||
|
||||
@@ -454,6 +454,25 @@ final class FuelSavingsEstimateTests: XCTestCase {
|
||||
|
||||
// MARK: Row copy
|
||||
|
||||
func testCompactComparisonLabelUsesGenericNearestStationWording() {
|
||||
XCTAssertEqual(
|
||||
FuelSavingsFormat.comparisonLabel(status: .worthTheDrive),
|
||||
"vs nearest station"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
FuelSavingsFormat.comparisonLabel(status: .notWorthTheDrive),
|
||||
"vs nearest station"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
FuelSavingsFormat.comparisonLabel(status: .breakEven),
|
||||
"vs nearest station"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
FuelSavingsFormat.comparisonLabel(status: .baseline),
|
||||
"your baseline"
|
||||
)
|
||||
}
|
||||
|
||||
/// The row's extra-driving caption must follow the DISPLAY unit: the stored
|
||||
/// figure is always miles, and converting by the current unit instead would
|
||||
/// leave a km user's figure unlabelled and 1.6× too large.
|
||||
|
||||
Reference in New Issue
Block a user