Add dark styles for most elements

This commit is contained in:
Carson Katri 2020-08-02 20:22:52 -04:00
parent c7b5e75e1a
commit cdc452b7a5
24 changed files with 238 additions and 99 deletions

View File

@ -14,16 +14,23 @@
/// A value with a modifier applied to it. /// A value with a modifier applied to it.
public struct ModifiedContent<Content, Modifier> { public struct ModifiedContent<Content, Modifier> {
@Environment(\.self) public var environment
public typealias Body = Never public typealias Body = Never
public let content: Content public private(set) var content: Content
public let modifier: Modifier public private(set) var modifier: Modifier
@inlinable public init(content: Content, modifier: Modifier) { public init(content: Content, modifier: Modifier) {
self.content = content self.content = content
self.modifier = modifier self.modifier = modifier
} }
} }
extension ModifiedContent: EnvironmentReader where Modifier: EnvironmentReader {
mutating func setContent(from values: EnvironmentValues) {
modifier.setContent(from: values)
}
}
extension ModifiedContent: View, ParentView where Content: View, Modifier: ViewModifier { extension ModifiedContent: View, ParentView where Content: View, Modifier: ViewModifier {
public var body: Body { public var body: Body {
neverBody("ModifiedContent<View, ViewModifier>") neverBody("ModifiedContent<View, ViewModifier>")

View File

@ -15,7 +15,9 @@
// Created by Carson Katri on 6/29/20. // Created by Carson Katri on 6/29/20.
// //
public struct _BackgroundModifier<Background>: ViewModifier where Background: View { public struct _BackgroundModifier<Background>: ViewModifier, EnvironmentReader
where Background: View {
public var environment: EnvironmentValues!
public var background: Background public var background: Background
public var alignment: Alignment public var alignment: Alignment
@ -27,9 +29,18 @@ public struct _BackgroundModifier<Background>: ViewModifier where Background: Vi
public func body(content: Content) -> some View { public func body(content: Content) -> some View {
content content
} }
mutating func setContent(from values: EnvironmentValues) {
environment = values
}
} }
extension _BackgroundModifier: Equatable where Background: Equatable {} extension _BackgroundModifier: Equatable where Background: Equatable {
public static func == (lhs: _BackgroundModifier<Background>,
rhs: _BackgroundModifier<Background>) -> Bool {
lhs.background == rhs.background
}
}
extension View { extension View {
public func background<Background>(_ background: Background, alignment: Alignment = .center) -> some View where Background: View { public func background<Background>(_ background: Background, alignment: Alignment = .center) -> some View where Background: View {
@ -37,7 +48,9 @@ extension View {
} }
} }
public struct _OverlayModifier<Overlay>: ViewModifier where Overlay: View { public struct _OverlayModifier<Overlay>: ViewModifier, EnvironmentReader
where Overlay: View {
public var environment: EnvironmentValues!
public var overlay: Overlay public var overlay: Overlay
public var alignment: Alignment public var alignment: Alignment
@ -52,9 +65,18 @@ public struct _OverlayModifier<Overlay>: ViewModifier where Overlay: View {
overlay overlay
} }
} }
mutating func setContent(from values: EnvironmentValues) {
environment = values
}
} }
extension _OverlayModifier: Equatable where Overlay: Equatable {} extension _OverlayModifier: Equatable where Overlay: Equatable {
public static func == (lhs: _OverlayModifier<Overlay>,
rhs: _OverlayModifier<Overlay>) -> Bool {
lhs.overlay == rhs.overlay
}
}
extension View { extension View {
public func overlay<Overlay>(_ overlay: Overlay, public func overlay<Overlay>(_ overlay: Overlay,

View File

@ -16,6 +16,7 @@
// //
public struct _StrokedShape<S>: Shape where S: Shape { public struct _StrokedShape<S>: Shape where S: Shape {
@Environment(\.self) public var environment
public var shape: S public var shape: S
public var style: StrokeStyle public var style: StrokeStyle

View File

@ -47,6 +47,7 @@ public struct FillStyle: Equatable, ShapeStyle {
} }
public struct _ShapeView<Content, Style>: View where Content: Shape, Style: ShapeStyle { public struct _ShapeView<Content, Style>: View where Content: Shape, Style: ShapeStyle {
@Environment(\.self) public var environment
@Environment(\.foregroundColor) public var foregroundColor @Environment(\.foregroundColor) public var foregroundColor
public var shape: Content public var shape: Content
public var style: Style public var style: Style

View File

@ -16,8 +16,18 @@
// //
public struct Color: Hashable, Equatable { public struct Color: Hashable, Equatable {
// FIXME: This is not injected. public static func == (lhs: Self, rhs: Self) -> Bool {
@Environment(\.accentColor) static var envAccentColor var lightEnv = EnvironmentValues()
lightEnv.colorScheme = .light
var darkEnv = EnvironmentValues()
darkEnv.colorScheme = .dark
return lhs._evaluate(lightEnv) == rhs._evaluate(lightEnv) &&
lhs._evaluate(darkEnv) == rhs._evaluate(darkEnv)
}
public func hash(into hasher: inout Hasher) {
hasher.combine(evaluator(EnvironmentValues()))
}
public enum RGBColorSpace { public enum RGBColorSpace {
case sRGB case sRGB
@ -25,32 +35,42 @@ public struct Color: Hashable, Equatable {
case displayP3 case displayP3
} }
public let red: Double public struct _RGBA: Hashable, Equatable {
public let green: Double public let red: Double
public let blue: Double public let green: Double
public let opacity: Double public let blue: Double
public let space: RGBColorSpace public let opacity: Double
public let space: RGBColorSpace
}
let evaluator: (EnvironmentValues) -> _RGBA
private init(_ evaluator: @escaping (EnvironmentValues) -> _RGBA) {
self.evaluator = evaluator
}
public init(_ colorSpace: RGBColorSpace = .sRGB, public init(_ colorSpace: RGBColorSpace = .sRGB,
red: Double, red: Double,
green: Double, green: Double,
blue: Double, blue: Double,
opacity: Double = 1) { opacity: Double = 1) {
self.red = red self.init { _ in
self.green = green _RGBA(red: red,
self.blue = blue green: green,
self.opacity = opacity blue: blue,
space = colorSpace opacity: opacity,
space: colorSpace)
}
} }
public init(_ colorSpace: RGBColorSpace = .sRGB, public init(_ colorSpace: RGBColorSpace = .sRGB,
white: Double, white: Double,
opacity: Double = 1) { opacity: Double = 1) {
red = white self.init(colorSpace,
green = white red: white,
blue = white green: white,
self.opacity = opacity blue: white,
space = colorSpace opacity: opacity)
} }
// Source for the formula: // Source for the formula:
@ -60,15 +80,26 @@ public struct Color: Hashable, Equatable {
brightness: Double, brightness: Double,
opacity: Double = 1) { opacity: Double = 1) {
let a = saturation * min(brightness / 2, 1 - (brightness / 2)) let a = saturation * min(brightness / 2, 1 - (brightness / 2))
let f: (Int) -> Double = { n in let f = { (n: Int) -> Double in
let k = Double((n + Int(hue * 12)) % 12) let k = Double((n + Int(hue * 12)) % 12)
return brightness - (a * max(-1, min(k - 3, 9 - k, 1))) return brightness - (a * max(-1, min(k - 3, 9 - k, 1)))
} }
red = f(0) self.init(.sRGB,
green = f(8) red: f(0),
blue = f(4) green: f(8),
self.opacity = opacity blue: f(4),
space = .sRGB opacity: opacity)
}
/// Create a `Color` dependent on the current `ColorScheme`.
public static func _withScheme(_ evaluator: @escaping (ColorScheme) -> Self) -> Self {
.init {
evaluator($0.colorScheme)._evaluate($0)
}
}
public func _evaluate(_ environment: EnvironmentValues) -> _RGBA {
evaluator(environment)
} }
} }
@ -84,9 +115,27 @@ extension Color {
public static let yellow: Self = .init(red: 1.00, green: 0.84, blue: 0.04) public static let yellow: Self = .init(red: 1.00, green: 0.84, blue: 0.04)
public static let pink: Self = .init(red: 1.00, green: 0.22, blue: 0.37) public static let pink: Self = .init(red: 1.00, green: 0.22, blue: 0.37)
public static let purple: Self = .init(red: 0.75, green: 0.36, blue: 0.95) public static let purple: Self = .init(red: 0.75, green: 0.36, blue: 0.95)
// FIXME: Switch to use colorScheme public static let primary: Self = .init {
public static let primary: Self = .black switch $0.colorScheme {
case .light:
return .init(red: 0,
green: 0,
blue: 0,
opacity: 1,
space: .sRGB)
case .dark:
return .init(red: 1,
green: 1,
blue: 1,
opacity: 1,
space: .sRGB)
}
}
public static let secondary: Self = .gray public static let secondary: Self = .gray
public static let accentColor: Self = .init {
($0.accentColor ?? Self.blue)._evaluate($0)
}
public init(_ color: UIColor) { public init(_ color: UIColor) {
self = color.color self = color.color
@ -96,11 +145,11 @@ extension Color {
extension Color: ExpressibleByIntegerLiteral { extension Color: ExpressibleByIntegerLiteral {
/// Allows initializing value of `Color` type from hex values /// Allows initializing value of `Color` type from hex values
public init(integerLiteral bitMask: UInt32) { public init(integerLiteral bitMask: UInt32) {
red = Double((bitMask & 0xFF0000) >> 16) / 255 self.init(.sRGB,
green = Double((bitMask & 0x00FF00) >> 8) / 255 red: Double((bitMask & 0xFF0000) >> 16) / 255,
blue = Double(bitMask & 0x0000FF) / 255 green: Double((bitMask & 0x00FF00) >> 8) / 255,
opacity = 1 blue: Double(bitMask & 0x0000FF) / 255,
space = .sRGB opacity: 1)
} }
} }
@ -117,11 +166,11 @@ extension Color {
else { else {
return nil return nil
} }
self.red = Double(red) / 255 self.init(.sRGB,
self.green = Double(green) / 255 red: Double(red) / 255,
self.blue = Double(blue) / 255 green: Double(green) / 255,
opacity = 1 blue: Double(blue) / 255,
space = .sRGB opacity: 1)
} }
} }
@ -153,12 +202,6 @@ extension View {
} }
} }
extension Color {
public static var accentColor: Self {
envAccentColor ?? .blue
}
}
struct ForegroundColorKey: EnvironmentKey { struct ForegroundColorKey: EnvironmentKey {
static let defaultValue: Color? = nil static let defaultValue: Color? = nil
} }

View File

@ -17,6 +17,7 @@
/// A horizontal line for separating content. /// A horizontal line for separating content.
public struct Divider: View { public struct Divider: View {
@Environment(\.self) public var environment
public init() {} public init() {}
public var body: Never { public var body: Never {
neverBody("Divider") neverBody("Divider")

View File

@ -33,6 +33,7 @@ public struct Text: View {
let storage: _Storage let storage: _Storage
let modifiers: [_Modifier] let modifiers: [_Modifier]
@Environment(\.self) public var environment
@Environment(\.font) var font @Environment(\.font) var font
@Environment(\.foregroundColor) var foregroundColor @Environment(\.foregroundColor) var foregroundColor
@Environment(\.redactionReasons) var redactionReasons @Environment(\.redactionReasons) var redactionReasons

View File

@ -30,7 +30,7 @@ extension App {
/// ///
public static func _launch(_ app: Self, _ rootEnvironment: EnvironmentValues) { public static func _launch(_ app: Self, _ rootEnvironment: EnvironmentValues) {
let body = TokamakDOM.body let body = TokamakDOM.body
if body.style == .undefined { if body.style.object!.all == "" {
body.style = "margin: 0;" body.style = "margin: 0;"
} }
let rootStyle = document.createElement!("style").object! let rootStyle = document.createElement!("style").object!
@ -44,7 +44,7 @@ extension App {
_ = body.appendChild!(div) _ = body.appendChild!(div)
ScenePhaseObserver.observe() ScenePhaseObserver.observe()
ColorSchemeObserver.observe() ColorSchemeObserver.observe(div)
} }
public static func _setTitle(_ title: String) { public static func _setTitle(_ title: String) {

View File

@ -21,13 +21,23 @@ enum ColorSchemeObserver {
) )
private static var closure: JSClosure? private static var closure: JSClosure?
private static var cancellable: AnyCancellable?
static func observe() { static func observe(_ rootElement: JSObjectRef) {
let closure = JSClosure { let closure = JSClosure {
publisher.value = .init(matchMediaDarkScheme: $0[0].object!) publisher.value = .init(matchMediaDarkScheme: $0[0].object!)
return .undefined return .undefined
} }
_ = matchMediaDarkScheme.addEventListener!("change", closure) _ = matchMediaDarkScheme.addEventListener!("change", closure)
Self.closure = closure Self.closure = closure
Self.cancellable = Self.publisher.sink { colorScheme in
let systemBackground = { () -> String in
switch colorScheme {
case .light: return "#FFFFFF"
case .dark: return "rgb(38, 38, 38)"
}
}()
rootElement.style.object!.backgroundColor = .string("\(systemBackground)")
}
} }
} }

View File

@ -21,7 +21,7 @@ extension _Button: ViewDeferredToRenderer where Label == Text {
public var deferredBody: AnyView { public var deferredBody: AnyView {
let attributes: [String: String] let attributes: [String: String]
if buttonStyle.type == DefaultButtonStyle.self { if buttonStyle.type == DefaultButtonStyle.self {
attributes = [:] attributes = ["class": "_tokamak-buttonstyle-default"]
} else { } else {
attributes = ["class": "_tokamak-buttonstyle-reset"] attributes = ["class": "_tokamak-buttonstyle-reset"]
} }

View File

@ -21,7 +21,7 @@ extension _PickerContainer: ViewDeferredToRenderer {
AnyView(HTML("label") { AnyView(HTML("label") {
label label
Text(" ") Text(" ")
DynamicHTML("select", listeners: ["change": { DynamicHTML("select", ["class": "_tokamak-picker"], listeners: ["change": {
guard guard
let valueString = $0.target.object!.value.string, let valueString = $0.target.object!.value.string,
let value = Int(valueString) as? SelectionValue let value = Int(valueString) as? SelectionValue

View File

@ -24,6 +24,7 @@ extension SecureField: ViewDeferredToRenderer where Label == Text {
"type": "password", "type": "password",
"value": proxy.textBinding.wrappedValue, "value": proxy.textBinding.wrappedValue,
"placeholder": proxy.label.rawText, "placeholder": proxy.label.rawText,
"class": "_tokamak-securefield",
], listeners: [ ], listeners: [
"keypress": { event in if event.key == "Enter" { proxy.onCommit() } }, "keypress": { event in if event.key == "Enter" { proxy.onCommit() } },
"input": { event in "input": { event in

View File

@ -17,18 +17,29 @@
import TokamakCore import TokamakCore
func css(for style: TextFieldStyle) -> String {
if style is PlainTextFieldStyle {
return """
background: transparent;
border: none;
"""
} else {
return ""
}
}
extension TextField: ViewDeferredToRenderer where Label == Text { extension TextField: ViewDeferredToRenderer where Label == Text {
func css(for style: TextFieldStyle) -> String {
if style is PlainTextFieldStyle {
return """
background: transparent;
border: none;
"""
} else {
return ""
}
}
func className(for style: TextFieldStyle) -> String {
switch style {
case is DefaultTextFieldStyle:
return "_tokamak-textfield-default"
case is RoundedBorderTextFieldStyle:
return "_tokamak-textfield-roundedborder"
default:
return ""
}
}
public var deferredBody: AnyView { public var deferredBody: AnyView {
let proxy = _TextFieldProxy(self) let proxy = _TextFieldProxy(self)
@ -37,6 +48,7 @@ extension TextField: ViewDeferredToRenderer where Label == Text {
"value": proxy.textBinding.wrappedValue, "value": proxy.textBinding.wrappedValue,
"placeholder": proxy.label.rawText, "placeholder": proxy.label.rawText,
"style": css(for: proxy.textFieldStyle), "style": css(for: proxy.textFieldStyle),
"class": className(for: proxy.textFieldStyle),
], listeners: [ ], listeners: [
"focus": { _ in proxy.onEditingChanged(true) }, "focus": { _ in proxy.onEditingChanged(true) },
"blur": { _ in proxy.onEditingChanged(false) }, "blur": { _ in proxy.onEditingChanged(false) },

View File

@ -36,20 +36,20 @@ public struct ColorDemo: View {
case rgb, hsb case rgb, hsb
} }
let colors: [Color] = [ let colors: [(String, Color)] = [
.clear, ("Clear", .clear),
.black, ("Black", .black),
.white, ("White", .white),
.gray, ("Gray", .gray),
.red, ("Red", .red),
.green, ("Green", .green),
.blue, ("Blue", .blue),
.orange, ("Orange", .orange),
.yellow, ("Yellow", .yellow),
.pink, ("Pink", .pink),
.purple, ("Purple", .purple),
.primary, ("Primary", .primary),
.secondary, ("Secondary", .secondary),
] ]
@State private var colorForm: ColorForm = .hsb @State private var colorForm: ColorForm = .hsb
@ -71,16 +71,16 @@ public struct ColorDemo: View {
.bold() .bold()
.padding() .padding()
.background(color) .background(color)
Text("Accent Color: \(Color.accentColor.description)") Text("Accent Color: \(String(describing: Color.accentColor))")
.bold() .bold()
.padding() .padding()
.background(Color.accentColor) .background(Color.accentColor)
ForEach(colors, id: \.self) { ForEach(colors, id: \.0) {
Text($0.description) Text($0.0)
.font(.caption) .font(.caption)
.bold() .bold()
.padding() .padding()
.background($0) .background($0.1)
} }
}.padding(.horizontal) }.padding(.horizontal)
} }

View File

@ -47,6 +47,6 @@ extension _ZIndexModifier: DOMViewModifier {
extension _BackgroundModifier: DOMViewModifier where Background == Color { extension _BackgroundModifier: DOMViewModifier where Background == Color {
public var isOrderDependent: Bool { true } public var isOrderDependent: Bool { true }
public var attributes: [String: String] { public var attributes: [String: String] {
["style": "background-color: \(background.description)"] ["style": "background-color: \(background.cssValue(environment))"]
} }
} }

View File

@ -76,6 +76,34 @@ public let tokamakStyles = """
height: 1.2em; height: 1.2em;
border-radius: .1em; border-radius: .1em;
} }
@media (prefers-color-scheme:dark) {
._tokamak-buttonstyle-default {
background-color: rgb(99, 95, 98);
}
._tokamak-securefield {
background-color: rgb(99, 95, 98);
color: #FFFFFF;
}
._tokamak-textfield-default {
background-color: rgb(99, 95, 98);
color: #FFFFFF;
}
._tokamak-textfield-roundedborder {
background-color: rgb(99, 95, 98);
color: #FFFFFF;
}
._tokamak-picker {
background-color: rgb(99, 95, 98);
color: #FFFFFF;
}
._tokamak-text-redacted::after {
background-color: rgb(100, 100, 100);
}
}
""" """
public let rootNodeStyles = """ public let rootNodeStyles = """

View File

@ -25,7 +25,7 @@ extension _OverlayModifier: DOMViewModifier
return ["style": """ return ["style": """
border-style: \(style); border-style: \(style);
border-width: \(overlay.shape.style.lineWidth); border-width: \(overlay.shape.style.lineWidth);
border-color: \(overlay.style.description); border-color: \(overlay.style.cssValue(environment));
border-radius: inherit; border-radius: inherit;
"""] """]
} }

View File

@ -24,7 +24,7 @@ protocol ShapeAttributes {
extension _StrokedShape: ShapeAttributes { extension _StrokedShape: ShapeAttributes {
func attributes(_ style: ShapeStyle) -> [String: String] { func attributes(_ style: ShapeStyle) -> [String: String] {
if let color = style as? Color { if let color = style as? Color {
return ["style": "stroke: \(color); fill: none;"] return ["style": "stroke: \(color.cssValue(environment)); fill: none;"]
} else { } else {
return ["style": "stroke: black; fill: none;"] return ["style": "stroke: black; fill: none;"]
} }
@ -37,9 +37,9 @@ extension _ShapeView: ViewDeferredToRenderer {
if let shapeAttributes = shape as? ShapeAttributes { if let shapeAttributes = shape as? ShapeAttributes {
return AnyView(HTML("div", shapeAttributes.attributes(style)) { path }) return AnyView(HTML("div", shapeAttributes.attributes(style)) { path })
} else if let color = style as? Color { } else if let color = style as? Color {
return AnyView(HTML("div", ["style": "fill: \(color);"]) { path }) return AnyView(HTML("div", ["style": "fill: \(color.cssValue(environment));"]) { path })
} else if let foregroundColor = foregroundColor { } else if let foregroundColor = foregroundColor {
return AnyView(HTML("div", ["style": "fill: \(foregroundColor);"]) { path }) return AnyView(HTML("div", ["style": "fill: \(foregroundColor.cssValue(environment));"]) { path })
} else { } else {
return path return path
} }

View File

@ -14,9 +14,10 @@
import TokamakCore import TokamakCore
extension Color: CustomStringConvertible { extension Color {
public var description: String { func cssValue(_ environment: EnvironmentValues) -> String {
"rgb(\(red * 255), \(green * 255), \(blue * 255), \(opacity * 255))" let rgba = _evaluate(environment)
return "rgba(\(rgba.red * 255), \(rgba.green * 255), \(rgba.blue * 255), \(rgba.opacity))"
} }
} }

View File

@ -94,7 +94,12 @@ extension SidebarListStyle: ListStyleDeferredToRenderer {
AnyView(content AnyView(content
.padding(.all) .padding(.all)
.padding(.leading, 20) .padding(.leading, 20)
.background(Color(0xF2F2F7)) .background(Color._withScheme {
switch $0 {
case .light: return Color(0xF2F2F7)
case .dark: return Color(.sRGB, red: 45 / 255, green: 43 / 255, blue: 48 / 255)
}
})
) )
} }
} }

View File

@ -23,7 +23,12 @@ extension Divider: AnyHTML {
width: 100%; height: 0; margin: 0; width: 100%; height: 0; margin: 0;
border-top: none; border-top: none;
border-right: none; border-right: none;
border-bottom: 1px solid rgba(0, 0, 0, 0.2); border-bottom: 1px solid \(Color._withScheme {
switch $0 {
case .light: return .init(.sRGB, white: 0, opacity: 0.2)
case .dark: return .init(.sRGB, white: 1, opacity: 0.2)
}
}.cssValue(environment));
border-left: none; border-left: none;
""", """,
] ]

View File

@ -151,13 +151,13 @@ extension Text: AnyHTML {
} }
}.inlineStyles ?? "") }.inlineStyles ?? "")
\(font == nil ? "font-family: \(Font.Design.default.description);" : "") \(font == nil ? "font-family: \(Font.Design.default.description);" : "")
color: \(color?.description ?? "inherit"); color: \((color ?? .primary).cssValue(environment));
font-style: \(italic ? "italic" : "normal"); font-style: \(italic ? "italic" : "normal");
font-weight: \(weight?.value ?? font?._weight.value ?? 400); font-weight: \(weight?.value ?? font?._weight.value ?? 400);
letter-spacing: \(kerning); letter-spacing: \(kerning);
vertical-align: \(baseline == nil ? "baseline" : "\(baseline!)em"); vertical-align: \(baseline == nil ? "baseline" : "\(baseline!)em");
text-decoration: \(textDecoration); text-decoration: \(textDecoration);
text-decoration-color: \(strikethrough?.1?.description ?? underline?.1?.description text-decoration-color: \(strikethrough?.1?.cssValue(environment) ?? underline?.1?.cssValue(environment)
?? "inherit") ?? "inherit")
""", """,
"class": isRedacted ? "_tokamak-text-redacted" : "", "class": isRedacted ? "_tokamak-text-redacted" : "",

View File

@ -17,7 +17,8 @@ import XCTest
final class ColorTests: XCTestCase { final class ColorTests: XCTestCase {
func testHexColors() { func testHexColors() {
guard let color = Color(hex: "#FF00FF") else { let env = EnvironmentValues()
guard let color = Color(hex: "#FF00FF")?._evaluate(env) else {
XCTFail("Hexadecimal decoding failed") XCTFail("Hexadecimal decoding failed")
return return
} }
@ -28,11 +29,11 @@ final class ColorTests: XCTestCase {
XCTAssertEqual( XCTAssertEqual(
color, color,
Color(hex: "FF00FF"), Color(hex: "FF00FF")?._evaluate(env),
"The '#' before a hex code produced a different output than without it" "The '#' before a hex code produced a different output than without it"
) )
guard let red = Color(hex: "#FF0000") else { guard let red = Color(hex: "#FF0000")?._evaluate(env) else {
XCTFail("Hexadecimal decoding failed") XCTFail("Hexadecimal decoding failed")
return return
} }
@ -41,7 +42,7 @@ final class ColorTests: XCTestCase {
XCTAssertEqual(red.green, 0) XCTAssertEqual(red.green, 0)
XCTAssertEqual(red.blue, 0) XCTAssertEqual(red.blue, 0)
guard let green = Color(hex: "#00FF00") else { guard let green = Color(hex: "#00FF00")?._evaluate(env) else {
XCTFail("Hexadecimal decoding failed") XCTFail("Hexadecimal decoding failed")
return return
} }
@ -50,7 +51,7 @@ final class ColorTests: XCTestCase {
XCTAssertEqual(green.green, 1) XCTAssertEqual(green.green, 1)
XCTAssertEqual(green.blue, 0) XCTAssertEqual(green.blue, 0)
guard let blue = Color(hex: "#0000FF") else { guard let blue = Color(hex: "#0000FF")?._evaluate(env) else {
XCTFail("Hexadecimal decoding failed") XCTFail("Hexadecimal decoding failed")
return return
} }