715dd4ec5e
- Header elegante: logo fijado a la izquierda y titulo centrado de verdad (ZStack), fecha a la derecha - School transpuesto: dias como columnas y comidas como filas — antes con 1-2 comidas quedaba media pagina A4 apaisada en blanco - Default: las celdas del grid se expanden para llenar la pagina (mismo sintoma de hueco que el school) - Estilo nuevo "Story" (9:16, 2160x3840) para stories de IG/WhatsApp y "Minimal (imprimir)" en A4 con lineas finas sin fondos (ahorro tinta) - Picker del preview pasa de segmentado a chips desplazables (5 estilos) - WeekPlanTextExporter + boton "Copiar como texto (WhatsApp)" en el preview: menu semanal con *negritas* y emojis listo para pegar en WhatsApp/Telegram (evento analytics format=text) - Harness de render en tests: genera PNG de los 5 estilos con datos de ejemplo en /tmp/mealmood-export-previews para revisar el diseno - Nombres de estilos en espanol corregidos (estaban en ingles) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013H6bXqGX1ygwib1Dm3n3UG
108 lines
4.2 KiB
Swift
108 lines
4.2 KiB
Swift
import XCTest
|
|
import SwiftUI
|
|
import SwiftData
|
|
@testable import MealMood
|
|
|
|
/// Not a real assertion suite: renders every export style with sample data to
|
|
/// /tmp/mealmood-export-previews/ so design changes can be reviewed as images.
|
|
final class ExportStyleRenderTests: XCTestCase {
|
|
|
|
@MainActor
|
|
func testRenderAllExportStyles() throws {
|
|
let config = ModelConfiguration(isStoredInMemoryOnly: true)
|
|
let container = try ModelContainer(
|
|
for: AppSettings.self, Dish.self, Tag.self, WeekPlan.self, MealSlot.self,
|
|
configurations: config
|
|
)
|
|
let context = container.mainContext
|
|
|
|
let settings = AppSettings()
|
|
settings.activeMealTypes = [.lunch, .dinner]
|
|
settings.includeWeekends = true
|
|
settings.language = "es"
|
|
context.insert(settings)
|
|
|
|
let names = [
|
|
"Tortilla de patatas", "Salmón al horno", "Ensalada César",
|
|
"Lentejas con chorizo", "Pizza casera", "Pollo asado con patatas",
|
|
"Crema de calabaza", "Espaguetis boloñesa", "Merluza en salsa verde",
|
|
"Arroz tres delicias", "Fajitas de pollo", "Gazpacho", "Croquetas caseras", "Sopa de fideos"
|
|
]
|
|
let dishes = names.map { Dish(name: $0) }
|
|
dishes.forEach { context.insert($0) }
|
|
|
|
let monday = Calendar(identifier: .gregorian).date(from: DateComponents(year: 2026, month: 8, day: 31))!
|
|
let plan = WeekPlan(weekStartDate: monday)
|
|
context.insert(plan)
|
|
var dishIndex = 0
|
|
for day in 0...6 {
|
|
for meal in [MealType.lunch, .dinner] {
|
|
let slot = MealSlot(dayOfWeek: day, mealType: meal.rawValue)
|
|
if day == 5 && meal == .dinner {
|
|
slot.isEatingOut = true
|
|
} else {
|
|
slot.dishId = dishes[dishIndex % dishes.count].id
|
|
if day == 2 && meal == .dinner {
|
|
slot.secondaryDishId = dishes[(dishIndex + 5) % dishes.count].id
|
|
}
|
|
dishIndex += 1
|
|
}
|
|
slot.weekPlan = plan
|
|
plan.slotList.append(slot)
|
|
context.insert(slot)
|
|
}
|
|
}
|
|
try context.save()
|
|
|
|
let outputDir = URL(fileURLWithPath: "/tmp/mealmood-export-previews")
|
|
try? FileManager.default.createDirectory(at: outputDir, withIntermediateDirectories: true)
|
|
|
|
for style in WeekExportStyle.allCases {
|
|
let renderer = ImageRenderer(content: WeekPlanShareView(
|
|
plan: plan, settings: settings, dishes: dishes, tags: [],
|
|
exportStyleOverride: style
|
|
))
|
|
let size = WeekPlanShareView.canvasSize(for: style)
|
|
renderer.proposedSize = ProposedViewSize(width: size.width, height: size.height)
|
|
renderer.scale = 1
|
|
let image = try XCTUnwrap(renderer.uiImage, "render failed for \(style.rawValue)")
|
|
let data = try XCTUnwrap(image.pngData())
|
|
try data.write(to: outputDir.appendingPathComponent("export-\(style.rawValue).png"))
|
|
}
|
|
}
|
|
}
|
|
|
|
final class DateHelpersTests: XCTestCase {
|
|
|
|
func testStartOfWeekReturnsMonday() {
|
|
var components = DateComponents()
|
|
components.year = 2026
|
|
components.month = 2
|
|
components.day = 18 // Wednesday
|
|
let calendar = Calendar(identifier: .gregorian)
|
|
let input = calendar.date(from: components)!
|
|
|
|
let start = input.startOfWeek()
|
|
let weekday = calendar.component(.weekday, from: start)
|
|
|
|
XCTAssertEqual(weekday, 2) // Monday in Gregorian calendar
|
|
}
|
|
|
|
func testCombineDateAndTime() {
|
|
let calendar = Calendar(identifier: .gregorian)
|
|
|
|
let date = calendar.date(from: DateComponents(year: 2026, month: 2, day: 20))!
|
|
let time = calendar.date(from: DateComponents(hour: 21, minute: 30))!
|
|
|
|
let combined = combineDateAndTime(date: date, time: time)
|
|
let parts = calendar.dateComponents([.year, .month, .day, .hour, .minute], from: combined)
|
|
|
|
XCTAssertEqual(parts.year, 2026)
|
|
XCTAssertEqual(parts.month, 2)
|
|
XCTAssertEqual(parts.day, 20)
|
|
XCTAssertEqual(parts.hour, 21)
|
|
XCTAssertEqual(parts.minute, 30)
|
|
}
|
|
}
|
|
|