import Testing import CoreGraphics @testable import OnyxWidgetKit // Das Panel-Raster hat vier Spalten. Widgets werden in der gespeicherten // Reihenfolge eingesetzt, jedes an die erste Stelle, an die es passt. Bleibt // links eine Lücke, füllt sie das nächste Widget, das hineinpasst — sonst // stünden im Panel dauernd Löcher, die niemand füllen kann. @Suite("Layout-Engine") struct LayoutEngineTests { private func placement(_ widget: String, _ size: WidgetSize) -> WidgetPlacement { WidgetPlacement(widgetID: widget, size: size) } // MARK: - Grundlegendes Setzen @Test("Ein kleines Widget landet oben links") func singleSmall() { let resolved = LayoutEngine.resolve([placement("cpu", .small)]) #expect(resolved.count == 1) #expect(resolved[0].column == 0) #expect(resolved[0].row == 0) } @Test("Vier kleine Widgets füllen genau die erste Zeile") func fourSmallFillFirstRow() { let resolved = LayoutEngine.resolve((0..<4).map { placement("w\($0)", .small) }) #expect(resolved.map(\.row) == [0, 0, 0, 0]) #expect(resolved.map(\.column) == [0, 1, 2, 3]) } @Test("Das fünfte kleine Widget rutscht in die zweite Zeile") func fifthWraps() { let resolved = LayoutEngine.resolve((0..<5).map { placement("w\($0)", .small) }) #expect(resolved[4].row == 1) #expect(resolved[4].column == 0) } @Test("Ein breites Widget belegt alle vier Spalten") func wideTakesFullRow() { let resolved = LayoutEngine.resolve([placement("media", .wide)]) #expect(resolved[0].column == 0) #expect(resolved[0].columnSpan == 4) } @Test("Ein hohes Widget belegt zwei Zeilen") func largeSpansTwoRows() { let resolved = LayoutEngine.resolve([placement("calendar", .large)]) #expect(resolved[0].columnSpan == 2) #expect(resolved[0].rowSpan == 2) } // MARK: - Packen @Test("Ein breites Widget passt nicht neben ein kleines und beginnt eine neue Zeile") func wideStartsNewRowWhenBlocked() { let resolved = LayoutEngine.resolve([ placement("cpu", .small), placement("media", .wide), ]) #expect(resolved[0].row == 0) #expect(resolved[1].row == 1) #expect(resolved[1].column == 0) } @Test("Kleine Widgets füllen die Lücke neben einem hohen Widget") func smallFillsGapBesideLarge() { // Ein 2×2-Widget links lässt rechts zwei freie 1×1-Plätze in Zeile 0 // und zwei in Zeile 1 — die dürfen nicht verloren gehen. let resolved = LayoutEngine.resolve([ placement("calendar", .large), placement("cpu", .small), placement("ram", .small), placement("gpu", .small), ]) #expect(resolved[1].row == 0) #expect(resolved[1].column == 2) #expect(resolved[2].row == 0) #expect(resolved[2].column == 3) #expect(resolved[3].row == 1) #expect(resolved[3].column == 2) } @Test("Belegte Felder werden nie doppelt vergeben") func noOverlaps() { let resolved = LayoutEngine.resolve([ placement("a", .large), placement("b", .medium), placement("c", .small), placement("d", .wide), placement("e", .small), placement("f", .large), ]) var occupied = Set() for item in resolved { for row in item.row..<(item.row + item.rowSpan) { for column in item.column..<(item.column + item.columnSpan) { let cell = GridCell(row: row, column: column) #expect(!occupied.contains(cell), "Feld \(cell) doppelt belegt") occupied.insert(cell) } } } } @Test("Nichts ragt über den rechten Rand hinaus") func nothingOverflowsRight() { let resolved = LayoutEngine.resolve([ placement("a", .small), placement("b", .medium), placement("c", .large), placement("d", .wide), ]) for item in resolved { #expect(item.column + item.columnSpan <= LayoutEngine.columns) } } @Test("Die gespeicherte Reihenfolge bleibt erhalten") func orderIsStable() { let placements = [placement("a", .small), placement("b", .wide), placement("c", .small)] let resolved = LayoutEngine.resolve(placements) #expect(resolved.map(\.placement.widgetID) == ["a", "b", "c"]) } @Test("Leeres Layout ergibt kein Ergebnis und keinen Absturz") func emptyLayout() { #expect(LayoutEngine.resolve([]).isEmpty) } // MARK: - Panelgröße @Test("Panelgröße folgt der belegten Zeilenzahl, nicht der Widgetzahl") func panelSizeFollowsRows() { let oneRow = LayoutEngine.panelSize(for: LayoutEngine.resolve([ placement("a", .small), placement("b", .small), ])) let twoRows = LayoutEngine.panelSize(for: LayoutEngine.resolve( (0..<5).map { placement("w\($0)", .small) })) #expect(twoRows.height > oneRow.height) // Die Breite steht fest: vier Spalten, immer. #expect(twoRows.width == oneRow.width) } @Test("Ein leeres Panel hat trotzdem eine sinnvolle Mindestgröße") func emptyPanelHasMinimumSize() { let size = LayoutEngine.panelSize(for: []) #expect(size.width > 0) #expect(size.height > 0) } @Test("Ein hohes Widget allein ergibt zwei Zeilen Höhe") func largeAloneIsTwoRowsTall() { let large = LayoutEngine.panelSize(for: LayoutEngine.resolve([placement("a", .large)])) let small = LayoutEngine.panelSize(for: LayoutEngine.resolve([placement("a", .small)])) #expect(large.height > small.height) } // MARK: - Rahmen im Panel @Test("Ein Widget in Spalte 2 sitzt weiter rechts als eines in Spalte 0") func framesFollowColumns() { let resolved = LayoutEngine.resolve([ placement("a", .medium), placement("b", .medium), ]) let panel = LayoutEngine.panelSize(for: resolved) let first = LayoutEngine.frame(for: resolved[0], in: panel) let second = LayoutEngine.frame(for: resolved[1], in: panel) #expect(second.minX > first.minX) #expect(first.minY == second.minY) } @Test("Ein breites Widget ist im Rahmen wirklich breiter als ein kleines") func wideFrameIsWider() { let wide = LayoutEngine.resolve([placement("a", .wide)]) let small = LayoutEngine.resolve([placement("a", .small)]) let wideFrame = LayoutEngine.frame(for: wide[0], in: LayoutEngine.panelSize(for: wide)) let smallFrame = LayoutEngine.frame(for: small[0], in: LayoutEngine.panelSize(for: small)) #expect(wideFrame.width > smallFrame.width) } }