Worth the Drive: calculation audit + listing/detail polish

Calculation audit (brief config: 30 MPG imperial, £60 fill-up, baseline
Rontec Shelf 171.9p at 0.4 mi):

- One mile<->kilometre constant pair (kilometresPerMile 1.609344,
  metresPerMile 1609.344) replaces the mixed 1609.34 / 0.621371 / 1.60934
  used in the savings path. Effect ~2 ppm (invisible), but the conversions
  are now auditable in one place.
- New FuelSavingsCalculationAuditTests (20 deterministic tests, no live
  MKDirections) pins the brief's three stations - Tesco 170.9p/2.0 mi,
  MFG Hare & Hounds 172.9p/2.8 mi, PGG Napoleon 171.9p/2.8 mi - plus
  imperial-vs-US gallon, MPG and budget scaling, the break-even tolerance
  boundary, the nearer-candidate clamp, route-unavailable, mixed-basis
  estimates and display-rounding boundaries.
- Formula unchanged, and verified correct: fixed quantity (budget / baseline
  price), pump saving, round-trip extra distance clamped at zero, travel fuel
  priced at the candidate station, +/-5p break even, baseline labelled
  "Closest station", sorting on the unrounded net saving. The brief's "£0.18"
  for Tesco is the rounded 1 dp extra-distance caption (2.02-2.05 mi reads as
  "2.0 mi"), not a rounding bug in the money.

UI (uncommitted since d7a0ecb):

- Done bar drawn in the bottom safe area from keyboard notifications:
  .toolbar(placement: .keyboard) is device-dependent (present on the 15 Pro,
  absent on the SE on the same iOS build).
- Favourite star in the More-info heading, wired to the same fuel-scoped
  toggle as the listing rows at all three call sites.
- CHEAPEST/TOP badge moved above the price so it reads as part of it.
- Listing rows carry the real session fill-up amount instead of a hard zero
  (the compact banner does not print it today; a future row layout would).

153 tests, 0 failures. Simulator and device builds succeed.
This commit is contained in:
FuelBoard Contributor
2026-10-11 17:19:10 +01:00
parent d7a0ecb79a
commit d0a89d97d5
10 changed files with 780 additions and 51 deletions
@@ -0,0 +1,419 @@
import XCTest
#if SWIFT_PACKAGE
@testable import FuelBoardShared
#endif
/// Worth the Drive calculation audit.
///
/// Written against the audit brief (30 MPG imperial, £60 fill-up, baseline
/// Rontec Shelf at 171.9p, 0.4 miles). The brief's three observed results are
/// reproduced here exactly, so any future change to the formula breaks a test
/// instead of silently shifting the numbers on screen.
///
/// Deterministic journey distances only — never live MKDirections responses.
final class FuelSavingsCalculationAuditTests: XCTestCase {
private func station(_ id: String, price: Double?) -> FuelStation {
FuelStation(
id: id,
name: id,
brand: "X",
address: "",
postcode: "",
lat: 51.5,
lng: -0.1,
prices: price.map { [FuelType.e10: $0] } ?? [:],
priceUpdated: nil
)
}
/// Journeys are OUT AND BACK (the app's convention): the brief's baseline is
/// 0.4 miles away, so its journey is 0.8 miles, and "extra driving" of N
/// miles means a candidate journey of 0.8 + N.
private func journey(_ miles: Double, basis: FuelSavingsDistanceBasis = .roadRoute) -> FuelSavingsJourney {
FuelSavingsJourney(miles: miles, basis: basis)
}
private let baselineJourneyMiles = 0.8
private func briefResult(candidatePrice: Double, extraMiles: Double) throws -> FuelSavingsResult {
try FuelSavingsCalculator.calculate(
mpg: 30,
amount: FuelSavingsFillAmount(value: 60, unit: .pounds),
baselinePricePencePerLitre: 171.9,
candidatePricePencePerLitre: candidatePrice,
baselineJourneyMiles: baselineJourneyMiles,
candidateJourneyMiles: baselineJourneyMiles + extraMiles
)
}
// MARK: - 1. Unit conversion
func testImperialGallonIsUsedNotTheUSGallon() {
XCTAssertEqual(FuelSavingsCalculator.imperialGallonLitres, 4.54609)
XCTAssertNotEqual(FuelSavingsCalculator.imperialGallonLitres, 3.785411784,
"a US gallon must never be used: it would understate every travel cost")
// 30 miles at 30 MPG burns exactly one imperial gallon.
XCTAssertEqual(FuelSavingsCalculator.fuelUsedLitres(distanceMiles: 30, mpg: 30), 4.54609, accuracy: 1e-12)
// 100 miles at 50 MPG.
XCTAssertEqual(FuelSavingsCalculator.fuelUsedLitres(distanceMiles: 100, mpg: 50), 9.09218, accuracy: 1e-12)
// Zero distance burns nothing, and the relationship is linear in distance.
XCTAssertEqual(FuelSavingsCalculator.fuelUsedLitres(distanceMiles: 0, mpg: 30), 0, accuracy: 1e-12)
XCTAssertEqual(FuelSavingsCalculator.fuelUsedLitres(distanceMiles: 60, mpg: 30),
2 * FuelSavingsCalculator.fuelUsedLitres(distanceMiles: 30, mpg: 30),
accuracy: 1e-12)
}
func testMileAndKilometreFactorsAreTheExactOnes() {
XCTAssertEqual(FuelSavingsCalculator.kilometresPerMile, 1.609344)
XCTAssertEqual(FuelSavingsCalculator.metresPerMile, 1609.344)
XCTAssertEqual(FuelSavingsCalculator.metresPerMile,
FuelSavingsCalculator.kilometresPerMile * 1000,
accuracy: 1e-9,
"the metre and kilometre factors must describe the same mile")
}
func testDistanceLabelUsesTheSameConversionAsTheModel() {
// One mile, shown in kilometres, goes through the single factor.
XCTAssertEqual(
FuelSavingsFormat.distance(1, unit: .kilometers),
DistanceUnit.kilometers.format(FuelSavingsCalculator.kilometresPerMile)
)
XCTAssertEqual(FuelSavingsFormat.distance(1, unit: .miles), DistanceUnit.miles.format(1.609344))
}
// MARK: - 2/4. The three stations from the brief
func testBriefCaseTescoBradfordIsMarginallyWorseOff() throws {
let result = try briefResult(candidatePrice: 170.9, extraMiles: 2.0)
XCTAssertEqual(result.fillLitres, 34.904014, accuracy: 1e-6)
XCTAssertEqual(result.grossSavingGBP, 0.349040, accuracy: 1e-6, "1p/litre cheaper on a 34.90 L fill")
XCTAssertEqual(result.extraDistanceMiles, 2.0, accuracy: 1e-9)
XCTAssertEqual(result.extraDrivingCostGBP, 0.517951, accuracy: 1e-6)
XCTAssertEqual(result.netSavingGBP, -0.168911, accuracy: 1e-6)
XCTAssertEqual(result.status, .notWorthTheDrive)
XCTAssertEqual(result.headline(), "£0.17 worse off after travel")
}
/// The brief recorded "£0.18 worse off" for this row against a displayed
/// "2.0 miles" of extra driving; the brief also warns that the displayed
/// mileage is rounded. This pins that explanation instead of treating it as
/// a calculation bug: at an extra distance the caption still shows as
/// "2.0 mi" (2.00-2.05), the headline reads £0.17-£0.18, and the difference
/// is entirely the rounding of the distance, not of the money.
func testTheReportedEighteenPenceIsRoundedExtraDistanceNotARoundingBug() throws {
XCTAssertEqual(try briefResult(candidatePrice: 170.9, extraMiles: 2.00).headline(),
"£0.17 worse off after travel")
XCTAssertEqual(try briefResult(candidatePrice: 170.9, extraMiles: 2.05).headline(),
"£0.18 worse off after travel")
// 2.05 mi shows as "2.0 mi" at one decimal place — the same caption.
XCTAssertEqual(FuelSavingsFormat.extraTravelLabel(extraDistanceMiles: 2.05, unit: .miles),
"2.0 mi more driving than closest")
XCTAssertEqual(FuelSavingsFormat.extraTravelLabel(extraDistanceMiles: 2.00, unit: .miles),
"2.0 mi more driving than closest")
}
func testBriefCaseMFGHareAndHoundsIsWorseOff() throws {
let result = try briefResult(candidatePrice: 172.9, extraMiles: 2.8)
XCTAssertEqual(result.fillLitres, 34.904014, accuracy: 1e-6)
XCTAssertEqual(result.grossSavingGBP, -0.349040, accuracy: 1e-6, "1p/litre dearer costs 34.90 L × 1p")
XCTAssertEqual(result.extraDistanceMiles, 2.8, accuracy: 1e-9)
XCTAssertEqual(result.extraDrivingCostGBP, 0.733618, accuracy: 1e-6)
XCTAssertEqual(result.netSavingGBP, -1.082658, accuracy: 1e-6)
XCTAssertEqual(result.status, .notWorthTheDrive)
XCTAssertEqual(result.headline(), "£1.08 worse off after travel")
}
func testBriefCasePGGNapoleonSamePumpPriceIsTravelCostOnly() throws {
let result = try briefResult(candidatePrice: 171.9, extraMiles: 2.8)
XCTAssertEqual(result.grossSavingGBP, 0, accuracy: 1e-9, "same pump price saves nothing at the pump")
XCTAssertEqual(result.extraDrivingCostGBP, 0.729375, accuracy: 1e-6)
XCTAssertEqual(result.netSavingGBP, -0.729375, accuracy: 1e-6,
"with no price difference the whole result is the extra driving")
XCTAssertEqual(result.status, .notWorthTheDrive)
XCTAssertEqual(result.headline(), "£0.73 worse off after travel")
}
// MARK: - 5/6. Classification, tolerance and rounding
func testSamePriceAndSameDistanceIsBreakEven() throws {
let result = try briefResult(candidatePrice: 171.9, extraMiles: 0)
XCTAssertEqual(result.grossSavingGBP, 0, accuracy: 1e-9)
XCTAssertEqual(result.extraDrivingCostGBP, 0, accuracy: 1e-9)
XCTAssertEqual(result.netSavingGBP, 0, accuracy: 1e-9)
XCTAssertEqual(result.status, .breakEven)
XCTAssertEqual(result.headline(), "Break even")
}
func testSmallSavingInsideTheToleranceIsBreakEvenNotPennies() throws {
// 171.9p vs 170.9p over a 1.23 mi delta: net +£0.0305.
let result = try briefResult(candidatePrice: 170.9, extraMiles: 1.23)
XCTAssertEqual(result.netSavingGBP, 0.030500, accuracy: 1e-6)
XCTAssertEqual(result.status, .breakEven, "±5p reads as break even rather than a meaningless £0.03")
XCTAssertEqual(result.headline(), "Break even")
}
func testBreakEvenToleranceDecidesTheClassificationAtTheBoundary() throws {
let net = -0.168911
// A tolerance wider than the loss → break even; narrower → worse off.
let lenient = try FuelSavingsCalculator.calculate(
mpg: 30,
amount: FuelSavingsFillAmount(value: 60, unit: .pounds),
baselinePricePencePerLitre: 171.9,
candidatePricePencePerLitre: 170.9,
baselineJourneyMiles: baselineJourneyMiles,
candidateJourneyMiles: baselineJourneyMiles + 2.0,
breakEvenToleranceGBP: 0.17
)
XCTAssertEqual(lenient.netSavingGBP, net, accuracy: 1e-6)
XCTAssertEqual(lenient.status, .breakEven)
let strict = try FuelSavingsCalculator.calculate(
mpg: 30,
amount: FuelSavingsFillAmount(value: 60, unit: .pounds),
baselinePricePencePerLitre: 171.9,
candidatePricePencePerLitre: 170.9,
baselineJourneyMiles: baselineJourneyMiles,
candidateJourneyMiles: baselineJourneyMiles + 2.0,
breakEvenToleranceGBP: 0.16
)
XCTAssertEqual(strict.status, .notWorthTheDrive)
}
func testMoneyIsRoundedOnlyForDisplay() throws {
// Full precision retained internally…
let result = try briefResult(candidatePrice: 172.9, extraMiles: 2.8)
XCTAssertEqual(result.netSavingGBP, -1.082658, accuracy: 1e-6)
XCTAssertNotEqual(result.netSavingGBP, -1.08, "the stored value must not be pre-rounded")
// …and rounded to two places only when shown.
XCTAssertEqual(FuelSavingsFormat.money(1.082658), "£1.08")
XCTAssertEqual(FuelSavingsFormat.money(0.729375), "£0.73")
XCTAssertEqual(FuelSavingsFormat.money(0.1749), "£0.17")
XCTAssertEqual(FuelSavingsFormat.money(0.176), "£0.18")
XCTAssertEqual(FuelSavingsFormat.money(0), "£0.00")
}
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.
func ranked(_ id: String, net: Double) -> FuelSavingsRankedStation {
FuelSavingsRankedStation(
stationID: id,
result: .available(FuelSavingsResult(
fillLitres: 34.9,
grossSavingGBP: 0,
extraDrivingCostGBP: abs(net),
extraDistanceMiles: 2.0,
netSavingGBP: net,
status: .notWorthTheDrive
)),
distanceMiles: 2.8,
pricePencePerLitre: 171.9
)
}
let worse = ranked("worse", net: -0.1751)
let lessWorse = ranked("less-worse", net: -0.1749)
XCTAssertEqual(FuelSavingsFormat.money(abs(worse.result.result!.netSavingGBP)), "£0.18")
XCTAssertEqual(FuelSavingsFormat.money(abs(lessWorse.result.result!.netSavingGBP)), "£0.17")
let sorted = FuelSavingsCalculator.sortedForWorthTheDrive([worse, lessWorse])
XCTAssertEqual(sorted.first?.stationID, "less-worse",
"the smaller loss must rank first even when the money rounds to the same figure")
}
// MARK: - 3. Distances and travel cost
func testCheaperFuelFurtherAwayCanStillBeWorthTheDrive() throws {
// 145.9p nearby vs 138.9p five miles further out, £40 fill, 45 MPG.
let result = try FuelSavingsCalculator.calculate(
mpg: 45,
amount: FuelSavingsFillAmount(value: 40, unit: .pounds),
baselinePricePencePerLitre: 145.9,
candidatePricePencePerLitre: 138.9,
baselineJourneyMiles: 2.0,
candidateJourneyMiles: 7.0
)
XCTAssertEqual(result.fillLitres, 27.416038, accuracy: 1e-6)
XCTAssertEqual(result.grossSavingGBP, 1.919123, accuracy: 1e-6)
XCTAssertEqual(result.extraDrivingCostGBP, 0.701613, accuracy: 1e-6)
XCTAssertEqual(result.netSavingGBP, 1.217509, accuracy: 1e-6)
XCTAssertEqual(result.status, .worthTheDrive)
XCTAssertEqual(result.headline(), "Save £1.22 after travel")
}
func testNearerCandidateIsNeverChargedExtraTravel() throws {
// The baseline is the nearest priced station, so this should not happen
// in the app — but the clamp must hold if it ever does.
let result = try FuelSavingsCalculator.calculate(
mpg: 30,
amount: FuelSavingsFillAmount(value: 60, unit: .pounds),
baselinePricePencePerLitre: 171.9,
candidatePricePencePerLitre: 169.9,
baselineJourneyMiles: 2.0,
candidateJourneyMiles: 1.0
)
XCTAssertEqual(result.extraDistanceMiles, 0, "a shorter journey is never a negative extra distance")
XCTAssertEqual(result.extraDrivingCostGBP, 0, accuracy: 1e-12)
XCTAssertEqual(result.netSavingGBP, result.grossSavingGBP, accuracy: 1e-12)
}
func testDifferentMPGValuesScaleOnlyTheTravelCost() throws {
let sixty = try FuelSavingsCalculator.calculate(
mpg: 60,
amount: FuelSavingsFillAmount(value: 60, unit: .pounds),
baselinePricePencePerLitre: 171.9,
candidatePricePencePerLitre: 172.9,
baselineJourneyMiles: 0.8,
candidateJourneyMiles: 3.6
)
XCTAssertEqual(sixty.grossSavingGBP, -0.349040, accuracy: 1e-6, "MPG must not touch the pump saving")
XCTAssertEqual(sixty.extraDrivingCostGBP, 0.366809, accuracy: 1e-6)
XCTAssertEqual(sixty.netSavingGBP, -0.715849, accuracy: 1e-6)
let fortyFive = try FuelSavingsCalculator.calculate(
mpg: 45,
amount: FuelSavingsFillAmount(value: 60, unit: .pounds),
baselinePricePencePerLitre: 171.9,
candidatePricePencePerLitre: 172.9,
baselineJourneyMiles: 0.8,
candidateJourneyMiles: 3.6
)
XCTAssertEqual(fortyFive.extraDrivingCostGBP, 0.489078, accuracy: 1e-6)
XCTAssertEqual(fortyFive.netSavingGBP, -0.838119, accuracy: 1e-6)
}
func testDifferentFillBudgetsScaleOnlyThePumpSaving() throws {
let forty = try FuelSavingsCalculator.calculate(
mpg: 30,
amount: FuelSavingsFillAmount(value: 40, unit: .pounds),
baselinePricePencePerLitre: 171.9,
candidatePricePencePerLitre: 172.9,
baselineJourneyMiles: 0.8,
candidateJourneyMiles: 3.6
)
XCTAssertEqual(forty.fillLitres, 23.269343, accuracy: 1e-6, "£40 buys 23.27 L at 171.9p")
XCTAssertEqual(forty.grossSavingGBP, -0.232693, accuracy: 1e-6)
XCTAssertEqual(forty.extraDrivingCostGBP, 0.733618, accuracy: 1e-6, "the journey cost is budget-independent")
XCTAssertEqual(forty.netSavingGBP, -0.966311, accuracy: 1e-6)
let ninety = try FuelSavingsCalculator.calculate(
mpg: 30,
amount: FuelSavingsFillAmount(value: 90, unit: .pounds),
baselinePricePencePerLitre: 171.9,
candidatePricePencePerLitre: 172.9,
baselineJourneyMiles: 0.8,
candidateJourneyMiles: 3.6
)
XCTAssertEqual(ninety.fillLitres, 52.356021, accuracy: 1e-6)
XCTAssertEqual(ninety.grossSavingGBP, -0.523560, accuracy: 1e-6)
XCTAssertEqual(ninety.netSavingGBP, -1.257178, accuracy: 1e-6)
}
func testFillUpInLitresIsFixedRegardlessOfPrice() throws {
// 40 L is 40 L: no baseline price is involved in the quantity.
let result = try FuelSavingsCalculator.calculate(
mpg: 30,
amount: FuelSavingsFillAmount(value: 40, unit: .litres),
baselinePricePencePerLitre: 171.9,
candidatePricePencePerLitre: 165.9,
baselineJourneyMiles: 0.8,
candidateJourneyMiles: 3.6
)
XCTAssertEqual(result.fillLitres, 40, accuracy: 1e-9)
XCTAssertEqual(result.grossSavingGBP, 40 * 0.06, accuracy: 1e-9)
}
// MARK: - 7. Failures never invent a saving
func testUnavailableRouteProducesNoSavingAtAll() {
let baseline = station("baseline", price: 171.9)
let candidate = station("candidate", price: 170.9)
let settings = FuelSavingsSettings(
enabled: true,
mpg: 30,
preferredAmount: FuelSavingsFillAmount(value: 60, unit: .pounds)
)
let noRoute = FuelSavingsEstimator.availability(
station: candidate,
fuel: .e10,
baseline: baseline,
settings: settings,
amount: FuelSavingsFillAmount(value: 60, unit: .pounds),
journey: nil,
baselineJourney: journey(0.8)
)
XCTAssertEqual(noRoute, .unavailable("Distance unavailable"))
let noBaselineRoute = FuelSavingsEstimator.availability(
station: candidate,
fuel: .e10,
baseline: baseline,
settings: settings,
amount: FuelSavingsFillAmount(value: 60, unit: .pounds),
journey: journey(2.8),
baselineJourney: nil
)
XCTAssertEqual(noBaselineRoute, .unavailable("No comparison station"))
// With both legs present it produces the saving — no silent zero.
let both = FuelSavingsEstimator.availability(
station: candidate,
fuel: .e10,
baseline: baseline,
settings: settings,
amount: FuelSavingsFillAmount(value: 60, unit: .pounds),
journey: journey(2.8),
baselineJourney: journey(0.8)
)
XCTAssertEqual(both?.result?.netSavingGBP ?? 0, -0.168911, accuracy: 1e-6)
}
func testMixedRouteBasisIsReportedAsAnEstimate() {
let baseline = station("baseline", price: 171.9)
let candidate = station("candidate", price: 170.9)
let settings = FuelSavingsSettings(
enabled: true,
mpg: 30,
preferredAmount: FuelSavingsFillAmount(value: 60, unit: .pounds)
)
let mixed = FuelSavingsEstimator.availability(
station: candidate,
fuel: .e10,
baseline: baseline,
settings: settings,
amount: FuelSavingsFillAmount(value: 60, unit: .pounds),
journey: journey(2.8, basis: .straightLine),
baselineJourney: journey(0.8, basis: .roadRoute)
)
XCTAssertEqual(mixed?.result?.distanceBasis, .straightLine,
"one unmeasured leg makes the whole estimate an estimate")
let headline = mixed?.result?.headline(compact: true) ?? ""
XCTAssertTrue(headline.hasSuffix(" (est.)"), "an estimate must be marked as one, got \(headline)")
}
func testBaselineStationIsLabelledClosestStationNotBreakEven() {
let baseline = station("baseline", price: 171.9)
let settings = FuelSavingsSettings(
enabled: true,
mpg: 30,
preferredAmount: FuelSavingsFillAmount(value: 60, unit: .pounds)
)
let result = FuelSavingsEstimator.availability(
station: baseline,
fuel: .e10,
baseline: baseline,
settings: settings,
amount: FuelSavingsFillAmount(value: 60, unit: .pounds),
journey: journey(0.8),
baselineJourney: journey(0.8)
)?.result
XCTAssertEqual(result?.status, .baseline)
XCTAssertEqual(result?.netSavingGBP, 0)
XCTAssertEqual(result?.headline(), "Closest station")
}
}
@@ -374,4 +374,62 @@ final class FuelSavingsEstimateTests: XCTestCase {
"net saving decides the order, not the pump price")
XCTAssertEqual(ranked.last?.id, "cheap-but-far")
}
// MARK: Row copy
/// 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.
func testExtraTravelLabelFollowsTheDisplayUnit() {
XCTAssertEqual(
FuelSavingsFormat.extraTravelLabel(extraDistanceMiles: 19.9, unit: .miles),
"19.9 mi more driving than closest"
)
XCTAssertEqual(
FuelSavingsFormat.extraTravelLabel(extraDistanceMiles: 19.9, unit: .kilometers),
"32.0 km more driving than closest"
)
XCTAssertNil(FuelSavingsFormat.extraTravelLabel(extraDistanceMiles: 0, unit: .miles),
"the baseline row has no extra travel to report")
// The same conversion backs the detail sheet's extra-distance figure,
// which used to be hardcoded miles ("%.1f miles") for km users too.
XCTAssertEqual(FuelSavingsFormat.distance(19.9, unit: .miles), "19.9 mi")
XCTAssertEqual(FuelSavingsFormat.distance(19.9, unit: .kilometers), "32.0 km")
}
/// The baseline capsule has to say what it IS: it previously repeated the
/// status word above it ("Closest station" twice) and never explained that
/// the savings are measured against it.
func testCapsuleHeadlineMarksTheBaselineAndKeepsTheEstimateMarker() {
let baseline = FuelSavingsResult(
fillLitres: 30,
grossSavingGBP: 0,
extraDrivingCostGBP: 0,
extraDistanceMiles: 0,
netSavingGBP: 0,
status: .baseline
)
XCTAssertEqual(baseline.capsuleHeadline(), "Closest station · your baseline")
let saving = FuelSavingsResult(
fillLitres: 30,
grossSavingGBP: 2.40,
extraDrivingCostGBP: 1.40,
extraDistanceMiles: 5.2,
netSavingGBP: 1.00,
status: .worthTheDrive,
distanceBasis: .straightLine
)
XCTAssertEqual(saving.capsuleHeadline(), "Save £1.00 after travel (est.)")
let worse = FuelSavingsResult(
fillLitres: 30,
grossSavingGBP: 1.40,
extraDrivingCostGBP: 1.70,
extraDistanceMiles: 6.7,
netSavingGBP: -0.31,
status: .notWorthTheDrive
)
XCTAssertEqual(worse.capsuleHeadline(), "£0.31 worse off after travel")
}
}