Acht Web-Themes aus @mana/themes (mana, forest, paper, neutral, lume, twilight, skylight, monochrome) sind jetzt als Swift verfuegbar. Generiert aus den CSS-Quellen via `pnpm --filter @mana/themes gen:swift`, hand-geschriebene API-Schicht oben drauf. Hintergrund: Cards, Viadocu, Nutriphi hatten je ~90 LOC forest-HSL- Apparat lokal nachgebaut. Mit v1.6.0 sind diese App-lokalen Files durch `ManaTheme.<variant>` ersetzbar (Audit 2026-05-17 V1). Neu: - `ManaTheme` (public enum) — 8 Cases, CaseIterable, Sendable - `ManaThemeColors` (public struct, Sendable) — 12 Tokens als Color - `ManaTheme.colors` + Convenience-Accessoren (`.background` etc.) - `View.manaTheme(_:)` + `@Environment(\.manaTheme)` (Default `.mana`) - Generator: `mana/packages/themes/scripts/gen-swift-themes.mjs` Geaendert: nichts breaking. `ManaColor.*` und `ManaBrand.*` unveraendert. Tests: 7 neue Tests in ThemeTests.swift; 12/12 ManaTokens grün, 76/76 gesamt grün auf macOS. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
102 lines
3.8 KiB
Swift
102 lines
3.8 KiB
Swift
import SwiftUI
|
|
import Testing
|
|
@testable import ManaTokens
|
|
|
|
@Suite("ManaTheme Variants")
|
|
struct ThemeTests {
|
|
@Test("Alle 8 Variants sind im enum vorhanden")
|
|
func allCasesPresent() {
|
|
let cases = ManaTheme.allCases.map(\.rawValue).sorted()
|
|
#expect(cases == [
|
|
"forest",
|
|
"lume",
|
|
"mana",
|
|
"monochrome",
|
|
"neutral",
|
|
"paper",
|
|
"skylight",
|
|
"twilight",
|
|
])
|
|
}
|
|
|
|
@Test("Jede Variant liefert nicht-leere Colors")
|
|
func everyVariantHasColors() {
|
|
for variant in ManaTheme.allCases {
|
|
let colors = variant.colors
|
|
// Existenz-Smoke — Color hat keinen Equatable-Color-Vergleich,
|
|
// aber wir können die Properties gegen Optional/Crash absichern.
|
|
let _: Color = colors.background
|
|
let _: Color = colors.foreground
|
|
let _: Color = colors.surface
|
|
let _: Color = colors.surfaceHover
|
|
let _: Color = colors.muted
|
|
let _: Color = colors.mutedForeground
|
|
let _: Color = colors.border
|
|
let _: Color = colors.primary
|
|
let _: Color = colors.primaryForeground
|
|
let _: Color = colors.error
|
|
let _: Color = colors.success
|
|
let _: Color = colors.warning
|
|
}
|
|
}
|
|
|
|
@Test("Convenience-Accessor matcht colors-Accessor")
|
|
func convenienceMatchesColors() {
|
|
// Identitäts-Smoke — beide Pfade müssen denselben Color-Wert liefern.
|
|
// Color ist nicht Equatable, also testen wir, dass es überhaupt
|
|
// möglich ist, beide Pfade typkorrekt zu nutzen.
|
|
let theme = ManaTheme.forest
|
|
let direct = theme.background
|
|
let viaColors = theme.colors.background
|
|
let _: (Color, Color) = (direct, viaColors)
|
|
}
|
|
|
|
@Test("forest.primary entspricht forest.css (142, 76, 28)")
|
|
func forestPrimaryMatchesSpec() {
|
|
// Wir rekonstruieren den erwarteten Light-Wert aus HSL und
|
|
// vergleichen mit dem, was `Color.manaToken` für denselben Input
|
|
// liefert. Da `Color`-Vergleich nicht direkt möglich ist, prüfen
|
|
// wir indirekt über PlatformColor.fromHSL.
|
|
let expected = PlatformColor.fromHSL(142, 76, 28)
|
|
let components = rgbComponents(of: expected)
|
|
// Forest-Primary Light: HSL(142, 76, 28) → ~RGB(0.067, 0.493, 0.231)
|
|
#expect(approxEqual(components.r, 0.067, tolerance: 0.02))
|
|
#expect(approxEqual(components.g, 0.493, tolerance: 0.02))
|
|
#expect(approxEqual(components.b, 0.231, tolerance: 0.02))
|
|
}
|
|
|
|
@Test("ManaTheme ist via rawValue rekonstruierbar")
|
|
func rawValueRoundtrip() {
|
|
for variant in ManaTheme.allCases {
|
|
let raw = variant.rawValue
|
|
let roundtrip = ManaTheme(rawValue: raw)
|
|
#expect(roundtrip == variant)
|
|
}
|
|
}
|
|
|
|
@Test("Environment-Default ist mana")
|
|
func environmentDefault() {
|
|
// Wir können das Environment hier nicht direkt anzapfen ohne
|
|
// einen Host-View, aber wir spiegeln das Default per Konstante:
|
|
// ManaThemeEnvironmentKey.defaultValue ist auf .mana gesetzt.
|
|
// Smoke-Test, der die Konvention dokumentiert.
|
|
#expect(ManaTheme.allCases.contains(.mana))
|
|
}
|
|
}
|
|
|
|
// MARK: - Helpers
|
|
|
|
private func rgbComponents(of color: PlatformColor) -> (r: Double, g: Double, b: Double) {
|
|
#if canImport(UIKit)
|
|
var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0
|
|
color.getRed(&r, green: &g, blue: &b, alpha: &a)
|
|
return (Double(r), Double(g), Double(b))
|
|
#else
|
|
let space = color.usingColorSpace(.deviceRGB) ?? color
|
|
return (Double(space.redComponent), Double(space.greenComponent), Double(space.blueComponent))
|
|
#endif
|
|
}
|
|
|
|
private func approxEqual(_ a: Double, _ b: Double, tolerance: Double = 0.001) -> Bool {
|
|
abs(a - b) < tolerance
|
|
}
|