mana-swift-core/Tests/ManaTokensTests/ThemeTests.swift
Till JS 20c30fc321 feat(tokens): ManaTheme + 8 Web-Theme-Variants (v1.6.0)
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>
2026-05-17 21:11:25 +02:00

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
}