fix: render 3-digit shorthand label colors instead of falling back to gray

Forgejo validates label colors against both the 6-digit and 3-digit
shorthand forms and only expands the shorthand when a label is written,
so labels created before that normalization can still serve #abc-style
colors through the API. The hex parser hard-required 6 digits, painting
those labels gray with white text regardless of their configured color.

Expand the shorthand the same way Forgejo's NormalizeColor does, share
the parser between the background and the luminance-based text color,
and cover both forms with tests.
This commit is contained in:
systemblue 2026-06-05 22:04:13 -04:00
parent 2a679140e6
commit fcff44a362
2 changed files with 58 additions and 8 deletions

View file

@ -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

View file

@ -0,0 +1,41 @@
@testable import Forji
import SwiftUI
import Testing
struct LabelColorHexTests {
// MARK: - Six-digit colors
@Test func `six digit hex parses`() {
#expect(Color(hex: "#ff0000") != nil)
#expect(Color(hex: "00ff00") != nil)
}
@Test func `six digit value matches`() throws {
let rgb = try #require(Color.rgbValue(hex: "#3366cc"))
#expect(rgb == 0x3366CC)
}
// MARK: - Three-digit shorthand
@Test func `three digit shorthand parses`() {
#expect(Color(hex: "#f00") != nil)
#expect(Color(hex: "abc") != nil)
}
@Test func `three digit shorthand expands like six digit`() 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 func `invalid hex returns nil`() {
#expect(Color(hex: "") == nil)
#expect(Color(hex: "#ff00") == nil)
#expect(Color(hex: "12345") == nil)
#expect(Color(hex: "xyzxyz") == nil)
#expect(Color(hex: "#xyz") == nil)
}
}