import Testing import Foundation @testable import OnyxWidgetKit // Die Layout-Datei überlebt App-Updates. Sie kann also Widgets nennen, die es // nicht mehr gibt, oder aus einer neueren Version stammen — und sie kann halb // geschrieben auf der Platte liegen, wenn der Rechner mitten im Speichern // abstürzt. Keiner dieser Fälle darf zu einem leeren Panel führen, ohne dass // jemand merkt warum. @Suite("Layout-Persistenz") struct LayoutStoreTests { private func temporaryURL() -> URL { FileManager.default.temporaryDirectory .appendingPathComponent("onyx-test-\(UUID().uuidString)") .appendingPathComponent("layout.json") } private let known: Set = ["calendar", "weather", "media", "cpu"] @Test("Gespeichertes Layout kommt unverändert zurück") func roundTrip() throws { let url = temporaryURL() let store = LayoutStore(url: url) let layout = [ WidgetPlacement(widgetID: "calendar"), WidgetPlacement(widgetID: "media"), ] try store.save(layout) let loaded = store.load(knownWidgetIDs: known) #expect(loaded.map(\.widgetID) == ["calendar", "media"]) } @Test("Die Kennung eines Widgets bleibt über das Speichern hinweg dieselbe") func identitySurvives() throws { let url = temporaryURL() let store = LayoutStore(url: url) let placement = WidgetPlacement(widgetID: "cpu") try store.save([placement]) #expect(store.load(knownWidgetIDs: known).first?.id == placement.id) } @Test("Fehlende Datei ergibt das Standardlayout, keinen Fehler") func missingFileFallsBack() { let loaded = LayoutStore(url: temporaryURL()).load(knownWidgetIDs: known) #expect(!loaded.isEmpty) #expect(loaded.allSatisfy { known.contains($0.widgetID) }) } @Test("Beschädigte Datei ergibt das Standardlayout statt eines Absturzes") func corruptFileFallsBack() throws { let url = temporaryURL() try FileManager.default.createDirectory(at: url.deletingLastPathComponent(), withIntermediateDirectories: true) try Data("{ das ist kein JSON".utf8).write(to: url) let loaded = LayoutStore(url: url).load(knownWidgetIDs: known) #expect(!loaded.isEmpty) } @Test("Ein entferntes Widget wird aus dem Layout gefiltert, der Rest bleibt") func unknownWidgetIsDropped() throws { let url = temporaryURL() let store = LayoutStore(url: url) try store.save([ WidgetPlacement(widgetID: "calendar"), WidgetPlacement(widgetID: "widget-aus-einer-alten-version"), WidgetPlacement(widgetID: "media"), ]) let loaded = store.load(knownWidgetIDs: known) #expect(loaded.map(\.widgetID) == ["calendar", "media"]) } @Test("Eine Datei, in der ALLE Widgets unbekannt sind, ergibt das Standardlayout") func allUnknownFallsBack() throws { // Sonst startet die App nach einem Umbau der Widget-Kennungen mit einem // leeren Panel und keiner Erklärung. let url = temporaryURL() let store = LayoutStore(url: url) try store.save([WidgetPlacement(widgetID: "voellig-unbekannt")]) let loaded = store.load(knownWidgetIDs: known) #expect(!loaded.isEmpty) #expect(loaded.allSatisfy { known.contains($0.widgetID) }) } @Test("Ein absichtlich leeres Layout bleibt leer") func deliberatelyEmptyStaysEmpty() throws { // Wer alle Widgets entfernt, meint das auch so — das ist etwas anderes // als eine kaputte Datei. let url = temporaryURL() let store = LayoutStore(url: url) try store.save([]) #expect(store.load(knownWidgetIDs: known).isEmpty) } @Test("Speichern legt fehlende Verzeichnisse an") func savingCreatesDirectories() throws { let url = temporaryURL() try LayoutStore(url: url).save([WidgetPlacement(widgetID: "cpu")]) #expect(FileManager.default.fileExists(atPath: url.path)) } @Test("Das Standardlayout nennt nur Widgets, die es wirklich gibt") func defaultLayoutIsValid() { let defaults = LayoutStore.defaultLayout(knownWidgetIDs: known) #expect(!defaults.isEmpty) #expect(defaults.allSatisfy { known.contains($0.widgetID) }) } @Test("Ohne bekannte Widgets bleibt auch das Standardlayout leer") func defaultLayoutWithNoWidgets() { #expect(LayoutStore.defaultLayout(knownWidgetIDs: []).isEmpty) } }