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.
420 lines
19 KiB
Swift
420 lines
19 KiB
Swift
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")
|
||
}
|
||
}
|