Files
coach-ios/tests-linux/Tests/CoachModelTests/QuickDrinkTests.swift
Sylvain Bettinelli 4cff1d4603 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>
2026-08-19 11:42:19 +00:00

84 lines
3.7 KiB
Swift
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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)
}
}