diff --git a/Forji/Forji/Views/IssueLabelView.swift b/Forji/Forji/Views/IssueLabelView.swift index d0bb484..a280f94 100644 --- a/Forji/Forji/Views/IssueLabelView.swift +++ b/Forji/Forji/Views/IssueLabelView.swift @@ -19,10 +19,7 @@ struct IssueLabelView: View { } private var textColor: Color { - let hex = label.color.trimmingCharacters(in: CharacterSet(charactersIn: "#")) - guard hex.count == 6, - let rgb = UInt64(hex, radix: 16) - else { + guard let rgb = Color.rgbValue(hex: label.color) else { return .white } let red = Double((rgb >> 16) & 0xFF) / 255.0 @@ -35,10 +32,7 @@ struct IssueLabelView: View { extension Color { init?(hex: String) { - let hex = hex.trimmingCharacters(in: CharacterSet(charactersIn: "#")) - guard hex.count == 6, - let rgb = UInt64(hex, radix: 16) - else { + guard let rgb = Color.rgbValue(hex: hex) else { return nil } self.init( @@ -47,6 +41,21 @@ extension Color { blue: Double(rgb & 0xFF) / 255.0, ) } + + /// Parses a 6-digit or 3-digit shorthand hex color into its RGB value, + /// expanding the shorthand the same way Forgejo normalizes label colors + /// (#f00 -> #ff0000). Labels created before Forgejo normalized colors on + /// write can still carry the shorthand form. + static func rgbValue(hex: String) -> UInt64? { + var hex = hex.trimmingCharacters(in: CharacterSet(charactersIn: "#")) + if hex.count == 3 { + hex = hex.map { "\($0)\($0)" }.joined() + } + guard hex.count == 6 else { + return nil + } + return UInt64(hex, radix: 16) + } } #if DEBUG diff --git a/Forji/ForjiTests/LabelColorHexTests.swift b/Forji/ForjiTests/LabelColorHexTests.swift new file mode 100644 index 0000000..dcc677c --- /dev/null +++ b/Forji/ForjiTests/LabelColorHexTests.swift @@ -0,0 +1,41 @@ +@testable import Forji +import SwiftUI +import Testing + +struct LabelColorHexTests { + // MARK: - Six-digit colors + + @Test("six digit hex parses") func sixDigitHexParses() { + #expect(Color(hex: "#ff0000") != nil) + #expect(Color(hex: "00ff00") != nil) + } + + @Test("six digit value matches") func sixDigitValueMatches() throws { + let rgb = try #require(Color.rgbValue(hex: "#3366cc")) + #expect(rgb == 0x3366CC) + } + + // MARK: - Three-digit shorthand + + @Test("three digit shorthand parses") func threeDigitShorthandParses() { + #expect(Color(hex: "#f00") != nil) + #expect(Color(hex: "abc") != nil) + } + + @Test("three digit shorthand expands like six digit") func threeDigitShorthandExpandsLikeSixDigit() throws { + #expect(Color(hex: "#f00") == Color(hex: "#ff0000")) + #expect(Color(hex: "abc") == Color(hex: "aabbcc")) + let rgb = try #require(Color.rgbValue(hex: "#f00")) + #expect(rgb == 0xFF0000) + } + + // MARK: - Invalid input + + @Test("invalid hex returns nil") func invalidHexReturnsNil() { + #expect(Color(hex: "") == nil) + #expect(Color(hex: "#ff00") == nil) + #expect(Color(hex: "12345") == nil) + #expect(Color(hex: "xyzxyz") == nil) + #expect(Color(hex: "#xyz") == nil) + } +}