4579b9bb69
Faltaba la otra mitad del "no se actualiza": makePayload devolvia nil cuando la semana en curso no tenia plan creado, asi que el iPhone no mandaba nada — ni respondia cuando el reloj pedia datos — y el reloj se quedaba con el ultimo snapshot, el del miercoles. Ahora la semana viaja igualmente, vacia, y el reloj dice que no hay nada planificado en vez de enseñar lo de hace dias. Ademas el reloj muestra cuando se actualizo por ultima vez. Sin eso, un snapshot viejo y uno recien llegado se ven igual, y no hay forma de saber si el problema es el envio o lo que se pinta. Refs #34, #35 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013su1ttRiMeMYxkZJ1Y3246
192 lines
8.1 KiB
Swift
192 lines
8.1 KiB
Swift
import XCTest
|
||
import SwiftData
|
||
@testable import MealMood
|
||
|
||
/// Regression suite for "the watch showed Wednesday's meal on Saturday".
|
||
///
|
||
/// The snapshot the iPhone sends carries an `isToday` flag, which is true only
|
||
/// at the moment it was built. The watch outlives that moment — it keeps the
|
||
/// last snapshot in its app group — so "today" has to be resolved when the view
|
||
/// renders, never read from the payload.
|
||
final class WatchWeekPayloadTests: XCTestCase {
|
||
|
||
private let calendar = Calendar(identifier: .gregorian)
|
||
|
||
private func date(_ year: Int, _ month: Int, _ day: Int) -> Date {
|
||
calendar.date(from: DateComponents(year: year, month: month, day: day, hour: 12))!
|
||
}
|
||
|
||
private func makePayload(weekStart: Date, todayFlagOn flaggedDay: Int?) -> WatchWeekPayload {
|
||
let days = (0...6).map { offset in
|
||
WatchWeekPayload.Day(
|
||
dayOfWeek: offset,
|
||
title: "Day \(offset)",
|
||
isToday: offset == flaggedDay,
|
||
date: calendar.date(byAdding: .day, value: offset, to: weekStart)!,
|
||
meals: [
|
||
WatchWeekPayload.Meal(type: "dinner", label: "Dinner", name: "Dish \(offset)")
|
||
]
|
||
)
|
||
}
|
||
return WatchWeekPayload(
|
||
weekTitle: "Week",
|
||
days: days,
|
||
updatedAt: weekStart,
|
||
emptyMealText: "Not planned",
|
||
emptyDayText: "Nothing planned for today",
|
||
staleText: "Open MealMood on your iPhone"
|
||
)
|
||
}
|
||
|
||
// MARK: - The reported bug
|
||
|
||
func testSaturdayShowsSaturdayEvenIfTheSnapshotWasBuiltOnWednesday() {
|
||
let monday = date(2026, 9, 7)
|
||
// Snapshot built on Wednesday: that's the day it flagged as "today".
|
||
let payload = makePayload(weekStart: monday, todayFlagOn: 2)
|
||
let saturday = date(2026, 9, 12)
|
||
|
||
let today = payload.currentDay(now: saturday, calendar: calendar)
|
||
|
||
XCTAssertEqual(today?.dayOfWeek, 5, "Saturday must win over the frozen Wednesday flag")
|
||
XCTAssertEqual(today?.meals.first?.name, "Dish 5")
|
||
XCTAssertFalse(payload.isStale(now: saturday, calendar: calendar))
|
||
}
|
||
|
||
func testSnapshotFromAnotherWeekHasNoToday() {
|
||
let lastMonday = date(2026, 9, 7)
|
||
let payload = makePayload(weekStart: lastMonday, todayFlagOn: 2)
|
||
let nextSaturday = date(2026, 9, 19)
|
||
|
||
XCTAssertNil(payload.currentDay(now: nextSaturday, calendar: calendar))
|
||
XCTAssertTrue(payload.isStale(now: nextSaturday, calendar: calendar),
|
||
"showing last week's meals as today's is exactly what must not happen")
|
||
}
|
||
|
||
func testEachDayKnowsWhetherItIsToday() {
|
||
let monday = date(2026, 9, 7)
|
||
let payload = makePayload(weekStart: monday, todayFlagOn: 2)
|
||
let thursday = date(2026, 9, 10)
|
||
|
||
XCTAssertTrue(payload.days[3].isCurrentDay(now: thursday, calendar: calendar))
|
||
XCTAssertFalse(payload.days[2].isCurrentDay(now: thursday, calendar: calendar),
|
||
"the day flagged when the snapshot was built is not today any more")
|
||
}
|
||
|
||
// MARK: - Empty states
|
||
|
||
func testDayWithNoDishesIsEmpty() {
|
||
let empty = WatchWeekPayload.Day(
|
||
dayOfWeek: 1, title: "Tue", isToday: true, date: date(2026, 9, 8),
|
||
meals: [
|
||
WatchWeekPayload.Meal(type: "lunch", label: "Lunch", name: nil),
|
||
WatchWeekPayload.Meal(type: "dinner", label: "Dinner", name: nil)
|
||
]
|
||
)
|
||
XCTAssertTrue(empty.isEmpty)
|
||
|
||
let planned = WatchWeekPayload.Day(
|
||
dayOfWeek: 1, title: "Tue", isToday: true, date: date(2026, 9, 8),
|
||
meals: [
|
||
WatchWeekPayload.Meal(type: "lunch", label: "Lunch", name: nil),
|
||
WatchWeekPayload.Meal(type: "dinner", label: "Dinner", name: "Tortilla")
|
||
]
|
||
)
|
||
XCTAssertFalse(planned.isEmpty, "one planned meal is enough for the day not to be empty")
|
||
}
|
||
|
||
func testPayloadCarriesTheLocalizedEmptyTexts() {
|
||
// The watch bundle has no strings of its own, so these have to travel.
|
||
let payload = makePayload(weekStart: date(2026, 9, 7), todayFlagOn: 0)
|
||
|
||
XCTAssertEqual(payload.emptyMealText, "Not planned")
|
||
XCTAssertEqual(payload.emptyDayText, "Nothing planned for today")
|
||
XCTAssertEqual(payload.staleText, "Open MealMood on your iPhone")
|
||
}
|
||
|
||
// MARK: - Compatibility with snapshots already on people's watches
|
||
|
||
func testSnapshotWrittenBeforeTheFixStillDecodes() throws {
|
||
// Shape written by 2.1.0: no `date`, no texts. Stamped inside the
|
||
// current week, which is when its weekday offsets still mean something.
|
||
let wednesday = date(2026, 9, 9)
|
||
let json = """
|
||
{
|
||
"weekTitle": "7 – 13 sep",
|
||
"updatedAt": \(wednesday.timeIntervalSinceReferenceDate),
|
||
"days": [
|
||
{"dayOfWeek": 0, "title": "Mon 7", "isToday": false,
|
||
"meals": [{"type": "dinner", "label": "Dinner", "name": "Pizza"}]},
|
||
{"dayOfWeek": 2, "title": "Wed 9", "isToday": true,
|
||
"meals": [{"type": "dinner", "label": "Dinner", "name": "Salmon"}]}
|
||
]
|
||
}
|
||
"""
|
||
let payload = try XCTUnwrap(WatchWeekPayload.decode(Data(json.utf8)))
|
||
|
||
XCTAssertEqual(payload.days.count, 2)
|
||
XCTAssertNil(payload.days[0].date)
|
||
XCTAssertNil(payload.emptyMealText, "old snapshots have no texts; the UI falls back")
|
||
|
||
// Without dates it can only fall back to the weekday offset.
|
||
XCTAssertEqual(payload.currentDay(now: wednesday, calendar: calendar)?.dayOfWeek, 2)
|
||
}
|
||
|
||
func testDatelessSnapshotFromAnotherWeekIsStale() throws {
|
||
// The watch that reported the bug had one of these: no dates, built in a
|
||
// previous week. Trusting its weekday offsets is what showed the wrong
|
||
// day, so it now counts as stale instead.
|
||
let lastWednesday = date(2026, 9, 2)
|
||
let json = """
|
||
{
|
||
"weekTitle": "31 ago – 6 sep",
|
||
"updatedAt": \(lastWednesday.timeIntervalSinceReferenceDate),
|
||
"days": [
|
||
{"dayOfWeek": 5, "title": "Sat 5", "isToday": false,
|
||
"meals": [{"type": "dinner", "label": "Dinner", "name": "Old dinner"}]}
|
||
]
|
||
}
|
||
"""
|
||
let payload = try XCTUnwrap(WatchWeekPayload.decode(Data(json.utf8)))
|
||
let saturday = date(2026, 9, 12)
|
||
|
||
XCTAssertNil(payload.currentDay(now: saturday, calendar: calendar))
|
||
XCTAssertTrue(payload.isStale(now: saturday, calendar: calendar))
|
||
}
|
||
|
||
|
||
// MARK: - A week with no plan yet
|
||
|
||
@MainActor
|
||
func testWeekWithoutAPlanStillProducesASnapshot() throws {
|
||
// Staying quiet here is what left the watch showing the previous
|
||
// week: no plan meant no push at all, so the old snapshot survived.
|
||
let container = try ModelContainer(
|
||
for: AppSettings.self, Dish.self, Tag.self, WeekPlan.self, MealSlot.self, ShoppingItem.self,
|
||
configurations: ModelConfiguration(isStoredInMemoryOnly: true)
|
||
)
|
||
let context = ModelContext(container)
|
||
let settings = AppSettings()
|
||
settings.activeMealTypes = [.lunch, .dinner]
|
||
settings.includeWeekends = true
|
||
context.insert(settings)
|
||
|
||
let payload = try XCTUnwrap(
|
||
WatchSyncService.makePayload(plan: nil, dishes: [], settings: settings),
|
||
"a missing plan must still produce the current week"
|
||
)
|
||
|
||
XCTAssertEqual(payload.days.count, 7)
|
||
XCTAssertNotNil(payload.currentDay(), "today must be in there")
|
||
XCTAssertTrue(payload.currentDay()?.isEmpty ?? false, "and it must read as nothing planned")
|
||
XCTAssertFalse(payload.isStale(), "an empty current week is not stale")
|
||
_ = container
|
||
}
|
||
|
||
func testWeekdayOffsetMapsMondayToZeroAndSundayToSix() {
|
||
XCTAssertEqual(WatchWeekPayload.weekdayOffset(for: date(2026, 9, 7), calendar: calendar), 0)
|
||
XCTAssertEqual(WatchWeekPayload.weekdayOffset(for: date(2026, 9, 12), calendar: calendar), 5)
|
||
XCTAssertEqual(WatchWeekPayload.weekdayOffset(for: date(2026, 9, 13), calendar: calendar), 6)
|
||
}
|
||
}
|