Distance confidence - Every distance surface takes the figure AND its "Approx." marker from one FuelStore.stationDistanceDisplay call (station rows, detail sheet, widget, Live Activity, watch, Siri), so a surface can never mark one figure approximate while showing a routed one. Radius filters and Closest ordering test that same displayed distance, so no row can read a distance outside the radius that filtered it (the reported "14.6 mi inside a 10-mile list"). Accurate distances (Settings -> Distances, default off) - Off: the browse layer serves the straight line, marked Approx., for every row, so two installs agree to the metre and the app makes no route requests at all. - On: routed distances for the whole search area, unmarked, served only from a COMPLETE set anchored within 600 m — a partial fill reverts every row at once rather than mixing road and straight-line bases. - Best Value maths sits deliberately outside the toggle: it keeps using measured road routes and its own confidence marking, so money figures never move with the switch. - Fills run only while the toggle is on and the app is frontmost; capacity raised to 600 stations per pass; completeness tolerates pins Apple Maps has no route for, which otherwise left an area permanently incomplete and the whole list stuck on estimates. Performance (the reported slowdown switching to Closest / launching with it) - FuelStore.accurateDistancesEnabled was a UserDefaults(suiteName:) read per call: a synchronous cfprefsd round-trip measured at ~1.9 us, ~200k calls per Closest sort. Memoised, with the setter keeping it in step. - Both keyed sorts decorate their keys — one distance per station, carried as an index instead of copying whole stations into the decorated array — and the comparison baseline plus per-station journeys now come from ONE pass instead of two full scans per tab. The Stations tab also reuses its single sorted list for the count and the TOP badge instead of sorting the country three times. - Measured on iPhone SE (7,914-station Closest pool): sort 36.6 ms -> 17.1 ms per list, Cheapest 6 ms, Best Value 11 ms. - DEBUG probes: FB_PERF list timing, FB_PERF_BENCH toggle-read benchmark, and FB_DIST_FOCUS now reports the served value, marker and mode (and re-prints when a fill lands). FB_ACCURATE_DISTANCES / FB_SORT_MODE env forms for devices, where devicectl eats a leading-dash argument. Tests: 198, 0 failures. Device and simulator Debug builds green.
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 extra return travel")
|
||
XCTAssertEqual(FuelSavingsFormat.extraTravelLabel(extraDistanceMiles: 2.00, unit: .miles),
|
||
"2.0 mi extra return travel")
|
||
}
|
||
|
||
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(), "£1.22 better off 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")
|
||
XCTAssertEqual(mixed?.result?.journeyConfidence, .estimated)
|
||
XCTAssertEqual(mixed?.result?.headline(compact: true), "Approx. £0.17 worse off after travel")
|
||
}
|
||
|
||
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")
|
||
}
|
||
}
|