Les widgets reçoivent la bande et les préréglages au lieu de les inventer
La couleur du score de forme était décidée ici, avec un barème périmé (vert >= 75, jaune >= 50). Le serveur avait recalé le sien sur la distribution réelle (65/55/40) parce que le score est borné à [25, 75] : une bande haute à 75 est inatteignable. Mesuré sur 115 jours de production, le vert n'est jamais sorti et le rouge couvrait 68 jours. La correction n'avait pas atteint le binaire. `FormeScore` porte désormais la bande servie par le serveur, et `resolvedBand` ne sert que de repli — aligné sur les seuils serveur, pas sur les anciens. Le widget de saisie rapide enregistrait un café à 100 ml, volume qui ne correspond à aucun préréglage : le serveur avait séparé l'expresso (60) du mug (250) parce qu'un volume unique faussait le journal, et ces préréglages sont personnalisables. Les boutons viennent maintenant du snapshot ; `fallbackQuickDrinks` reprend les préréglages par défaut du serveur. 15 tests ajoutés au banc d'essai Linux, qui compile réellement ces deux fichiers : 25 tests verts. Le reste (SwiftUI, WidgetKit) n'est pas compilable ici et n'a été que relu. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
83
tests-linux/Tests/CoachModelTests/QuickDrinkTests.swift
Normal file
83
tests-linux/Tests/CoachModelTests/QuickDrinkTests.swift
Normal file
@@ -0,0 +1,83 @@
|
||||
// Les boutons de saisie rapide viennent des préréglages de l'utilisateur.
|
||||
//
|
||||
// ⚠️ **Ce qui est en jeu.** Le widget enregistrait un café à 100 ml, en dur. Le
|
||||
// serveur avait pourtant tranché la question, et documenté pourquoi : « 60 ml,
|
||||
// c'est un expresso. L'ancien bouton "Café" imposait ce volume à tous les
|
||||
// cafés, y compris un mug de 250 ml : le journal était faux. » Il propose
|
||||
// désormais expresso 60 et mug 250, et ces préréglages sont **personnalisables**
|
||||
// (`GET/PUT /api/drinks/presets`). Le widget ignorait tout cela — 100 ml ne
|
||||
// correspond à aucun préréglage.
|
||||
//
|
||||
// Les volumes voyagent donc dans le snapshot. Le repli, pour un snapshot écrit
|
||||
// par une version antérieure, reprend les valeurs du serveur — jamais les 100 ml.
|
||||
import XCTest
|
||||
@testable import CoachModel
|
||||
|
||||
final class QuickDrinkTests: XCTestCase {
|
||||
|
||||
private func snapshot(_ drinks: [CoachWidgetSnapshot.QuickDrink]?) -> CoachWidgetSnapshot {
|
||||
var s = CoachWidgetSnapshot(updatedAt: Date(timeIntervalSince1970: 1_755_000_000))
|
||||
s.quickDrinks = drinks
|
||||
return s
|
||||
}
|
||||
|
||||
func testTheUsersPresetsAreUsedAsIs() {
|
||||
let presets = [
|
||||
CoachWidgetSnapshot.QuickDrink(ref: "water", label: "Verre", volumeMl: 200),
|
||||
CoachWidgetSnapshot.QuickDrink(ref: "coffee", label: "Expresso", volumeMl: 60),
|
||||
]
|
||||
XCTAssertEqual(snapshot(presets).resolvedQuickDrinks, presets)
|
||||
}
|
||||
|
||||
func testWithoutPresetsTheFallbackMatchesTheServer() {
|
||||
let fallback = snapshot(nil).resolvedQuickDrinks
|
||||
XCTAssertEqual(fallback.count, 3)
|
||||
XCTAssertEqual(fallback.map(\.volumeMl), [200, 500, 60])
|
||||
XCTAssertEqual(fallback.map(\.ref), ["water", "water", "coffee"])
|
||||
}
|
||||
|
||||
func testTheCoffeeIsNeverOneHundredMillilitres() {
|
||||
// La valeur qui ne correspondait à aucun préréglage.
|
||||
for drink in snapshot(nil).resolvedQuickDrinks where drink.ref == "coffee" {
|
||||
XCTAssertNotEqual(drink.volumeMl, 100)
|
||||
}
|
||||
}
|
||||
|
||||
func testAnEmptyListFallsBackRatherThanShowNothing() {
|
||||
XCTAssertEqual(snapshot([]).resolvedQuickDrinks.count, 3)
|
||||
}
|
||||
|
||||
func testAtMostThreeButtonsFit() {
|
||||
let many = (1...8).map { i in
|
||||
CoachWidgetSnapshot.QuickDrink(ref: "water", label: "P\(i)",
|
||||
volumeMl: Double(i) * 100)
|
||||
}
|
||||
XCTAssertEqual(snapshot(many).resolvedQuickDrinks.count, 3)
|
||||
}
|
||||
|
||||
func testAnAbsurdVolumeIsRefused() {
|
||||
// Un préréglage corrompu ne doit pas produire un bouton qui enregistre
|
||||
// n'importe quoi : on retombe sur le repli.
|
||||
let bad = [CoachWidgetSnapshot.QuickDrink(ref: "water", label: "X", volumeMl: 0)]
|
||||
XCTAssertEqual(snapshot(bad).resolvedQuickDrinks.map(\.volumeMl), [200, 500, 60])
|
||||
}
|
||||
|
||||
func testAnOlderSnapshotStillDecodes() throws {
|
||||
let json = """
|
||||
{"updatedAt": 1755000000, "waterMlToday": 500}
|
||||
""".data(using: .utf8)!
|
||||
let decoded = try JSONDecoder().decode(CoachWidgetSnapshot.self, from: json)
|
||||
XCTAssertEqual(decoded.waterMlToday, 500)
|
||||
XCTAssertEqual(decoded.resolvedQuickDrinks.count, 3)
|
||||
}
|
||||
|
||||
func testPresetsSurviveTheDayRollover() throws {
|
||||
// Les préréglages ne sont pas une donnée du jour : ils ne se périment pas.
|
||||
var s = snapshot([CoachWidgetSnapshot.QuickDrink(ref: "coffee", label: "Mug", volumeMl: 250)])
|
||||
s.waterMlToday = 800
|
||||
let tomorrow = Calendar.current.date(byAdding: .day, value: 1, to: s.updatedAt)!
|
||||
let aged = s.asOf(tomorrow)
|
||||
XCTAssertEqual(aged.waterMlToday, 0, "l'eau du jour se remet à zéro")
|
||||
XCTAssertEqual(aged.resolvedQuickDrinks.first?.volumeMl, 250)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user