Files
fuelboard/FuelBoardTests/Tests/FuelBoardSharedTests/BestValueAnnouncementTests.swift
FuelBoard Contributor bfa9209d68 Best Value: announce the page to existing users, once per announcement version
Users who finished onboarding before Best Value existed are "completed, not
fresh" forever, so the first-run gate never fires again for them and the feature
stayed invisible unless they went looking in Settings. They now get the Best
Value page on its own, once per announcement version — deliberately NOT a replay
of the onboarding flow, which would re-run permission pages they answered long
ago and has no skip.

- FuelStore: "fuelboard.bestValueIntroVersion", stored through the same
  saveString/loadString path as the onboarding flag and compared against a
  CODE-SIDE "currentBestValueIntroVersion". Deliberately not
  CFBundleShortVersionString: keying on the bundle version would re-announce on
  every bugfix release. Bump the constant to re-announce on purpose.
- FuelStore.shouldAnnounceBestValue(settings:hasSeen:onboardingDue:isSuppressed:)
  holds the rule, with every fact injectable so it is unit-tested instead of
  being driven through a simulator. It declines when onboarding is due or on
  screen, when this announcement version has been seen, and when the user has
  already configured Best Value themselves.
- BestValueAnnouncementHost wraps the app's content and owns the presentation
  state, so ContentView's change is a single wrapper and the whole feature lives
  in the new file. It reloads the stored settings on dismiss, because the page
  writes straight to the store rather than through ContentView's binding.
- The page is now ONE view, BestValueIntroContent, used by first-run onboarding
  (page 5 of 6) and by the announcement, so the copy, the sliders and the
  "declining is a decision" rule cannot drift. OnboardingView drops its private
  duplicate of the page, its bindings and its read-outs, and finish() marks the
  announcement seen so a new user never sees the page twice.

QA hooks (DEBUG): -showBestValueIntro forces the announcement, and -skipOnboarding
suppresses it. Worth knowing now that a returning install announces on a PLAIN
launch, so tab screenshots need -skipOnboarding.

Verified on the simulator (OCR of the rendered screens): a plain launch of an
install that has completed onboarding shows the single announcement page — Best
Value title, explanation, "Turn on Best Value", the "Settings, under Fuel
Savings" note and a "Not now" button, with no page dots; -skipOnboarding shows
no cover at all; -showBestValueIntro forces it. 180 tests, 0 failures (8 new).
2026-10-11 20:53:09 +01:00

87 lines
4.0 KiB
Swift

import XCTest
@testable import FuelBoardShared
/// The Best Value announcement gate — shown to users who updated into the
/// feature after finishing onboarding, once per announcement version.
///
/// Every fact the rule depends on is injectable, so the whole truth table is
/// checked here instead of driving a simulator through four combinations (which
/// is how a rule like this quietly rots).
final class BestValueAnnouncementTests: XCTestCase {
/// Nothing set up: the case that should be announced to.
private var unconfigured: FuelSavingsSettings { FuelSavingsSettings.defaults }
/// Enabled with the default 45 MPG / £40, which is a valid configuration.
private var configured: FuelSavingsSettings {
var settings = FuelSavingsSettings.defaults
settings.enabled = true
return settings
}
func testAnnouncesToAnExistingUserWhoHasNotSeenIt() {
XCTAssertTrue(
FuelStore.shouldAnnounceBestValue(settings: unconfigured,
hasSeen: false,
onboardingDue: false,
isSuppressed: false),
"an existing user, nothing configured, announcement unseen: show it"
)
}
func testNeverAnnouncesTwice() {
XCTAssertFalse(FuelStore.shouldAnnounceBestValue(settings: unconfigured,
hasSeen: true,
onboardingDue: false))
}
func testNeverWhileOnboardingIsDue() {
// A new install runs the full onboarding, which already contains this
// page — showing both would be the obvious first bug.
XCTAssertFalse(FuelStore.shouldAnnounceBestValue(settings: unconfigured,
hasSeen: false,
onboardingDue: true))
}
func testNeverWhileOnboardingIsOnScreen() {
XCTAssertFalse(FuelStore.shouldAnnounceBestValue(settings: unconfigured,
hasSeen: false,
onboardingDue: false,
isSuppressed: true))
}
func testNeverForSomebodyWhoAlreadyConfiguredIt() {
XCTAssertFalse(FuelStore.shouldAnnounceBestValue(settings: configured,
hasSeen: false,
onboardingDue: false),
"never announce a feature the user set up themselves")
}
func testStillAnnouncesWhenEnabledButNotActuallyConfigured() {
// Enabled with an out-of-range MPG is NOT configured, so the
// announcement still has something useful to offer.
var settings = FuelSavingsSettings.defaults
settings.enabled = true
settings.mpg = 2 // below the validity floor
XCTAssertTrue(FuelStore.shouldAnnounceBestValue(settings: settings,
hasSeen: false,
onboardingDue: false))
}
func testMarkingSeenIsWhatStopsTheNextLaunch() {
// The stored contract the gate reads.
FuelStore.markBestValueIntroSeen()
XCTAssertTrue(FuelStore.hasSeenBestValueIntro())
XCTAssertFalse(FuelStore.shouldAnnounceBestValue(settings: unconfigured,
onboardingDue: false))
}
func testTheStoredValueIsTheAnnouncementVersionNotTheAppVersion() {
// Keyed on the announcement version so a bugfix release cannot
// re-announce anything. Bump `currentBestValueIntroVersion` only when
// Best Value changes in a way worth re-explaining — this assertion is
// the reminder that doing so re-shows the page to everyone.
XCTAssertEqual(FuelStore.currentBestValueIntroVersion, 1)
}
}