import AppKit import SwiftUI import Testing @testable import Kanban /// **10-accessibility.md's one hard number**, as arithmetic a suite can hold still: "The ≥ 4.5:1 /// automatic-contrast rule binds where text does sit on a user-chosen color: the **board** /// background (palette pairs verified at design time; arbitrary hex computes its text color at /// runtime against that threshold). An `#RRGGBBAA` background with alpha computes against the color /// **composited over its effective backdrop** in the active appearance." /// /// Three layers, tested at three altitudes: /// /// 1. **`ContrastMath`** — WCAG relative luminance, contrast ratio and source-over compositing, pure /// functions of four numbers. Pinned against the standard's own published values, because the /// whole point of citing WCAG is that the numbers are not ours to choose. /// 2. **The decision** — which of the two label vocabularies the text takes, including the case the /// design's threshold cannot be met by either, which is a real outcome for a mid-grey hex and not /// a defensive branch. /// 3. **`BoardTextInk`** — the board's application: that the rule binds to *every* colour the board /// paints (palette name and hand-written hex alike, one path), that a translucent one resolves /// differently in the two appearances, and that a board painting nothing is left alone. /// /// The palette's own suite closes the loop. 03-board-ui.md ▸ Styling ▸ Controls promises the twelve /// wells are "AA-verified at design time … pinned by a computed-contrast unit test over all 12 /// pairs"; a pair is a background *and its ink*, so the promise is only keepable by checking the ink /// the seam chooses — which `PaletteContrastTests` does, in both appearances. That is the whole of /// the difference between the two paths: the palette is a fixed set and can be checked in advance, /// a hand-written hex arrives from a file and can only be computed as it renders. /// /// The candidate inks are **injected** into the pure layer rather than read from AppKit, so every /// branch is reachable and no assertion depends on the exact alpha Apple ships `labelColor` at this /// year. The live colours are exercised separately, in claims that stay true whatever those values /// are. // MARK: - Relative luminance @Suite("Contrast ▸ WCAG relative luminance") struct RelativeLuminanceTests { private func luminance(_ hex: String) throws -> Double { let color = try #require(NSColor(paletteHex: hex), "'\(hex)' did not parse") let srgb = try #require(SRGBColor(color), "'\(hex)' is not sRGB-convertible") return ContrastMath.relativeLuminance(of: srgb) } /// The three values every implementation of this formula is checked against. `#808080` is the /// interesting one: the channel is 0.502, and the answer is 0.216 rather than 0.5 — the gap /// between "half the bits" and "half the light" is exactly what makes eyeballing contrast /// unreliable and this function necessary. @Test("White is 1, black is 0, mid-grey is 0.2159") func theStandardsPublishedValues() throws { #expect(abs(try luminance("#FFFFFF") - 1) < 0.0001) #expect(abs(try luminance("#000000") - 0) < 0.0001) #expect(abs(try luminance("#808080") - 0.2159) < 0.0005) } /// The channel weights, isolated: a pure primary's luminance *is* its coefficient, since the /// other two channels linearise to zero and the primary linearises to one. A transposed pair /// (green and red are the easy ones to swap) would sail past every grey test above. @Test("Each primary weighs its WCAG coefficient") func theChannelWeights() throws { #expect(abs(try luminance("#FF0000") - 0.2126) < 0.0001) #expect(abs(try luminance("#00FF00") - 0.7152) < 0.0001) #expect(abs(try luminance("#0000FF") - 0.0722) < 0.0001) } /// **The linear leg below the knee** — WCAG's transfer function is a piecewise curve, and a /// `pow(c, 2.2)` shortcut gets the near-blacks wrong. `#050505` is 0.0196, under the 0.03928 /// threshold, so the answer is a plain division by 12.92; the shortcut would say ~0.00019, an /// eightfold error on exactly the values a dark hand-written board background lands on. @Test("Near-black uses the linear leg of the transfer function") func theTransferFunctionsLinearLeg() throws { #expect(abs(try luminance("#050505") - (5.0 / 255.0) / 12.92) < 0.000001) } /// Monotone: a lighter colour has a higher luminance. Cheap, and it catches a sign or an inverted /// branch that the three fixed points above could conceivably straddle. @Test("Luminance rises with lightness") func luminanceIsMonotone() throws { let ramp = ["#000000", "#202020", "#404040", "#808080", "#C0C0C0", "#FFFFFF"] let values = try ramp.map { try luminance($0) } #expect(values == values.sorted()) #expect(Set(values).count == ramp.count) } } // MARK: - Contrast ratio @Suite("Contrast ▸ ratio") struct ContrastRatioTests { private func color(_ hex: String) throws -> SRGBColor { try #require(NSColor(paletteHex: hex).flatMap(SRGBColor.init), "'\(hex)' did not parse") } /// The range's two ends: 21:1 is the maximum the formula can produce, 1:1 is a colour against /// itself. @Test("Black on white is 21:1 and a colour on itself is 1:1") func theRangesEnds() throws { let white = try color("#FFFFFF") let black = try color("#000000") #expect(abs(ContrastMath.contrastRatio(white, black) - 21) < 0.001) #expect(abs(ContrastMath.contrastRatio(white, white) - 1) < 0.0001) #expect(abs(ContrastMath.contrastRatio(black, black) - 1) < 0.0001) } /// Symmetric — the lighter colour always takes the numerator, so no caller has to know which /// argument is the text and which is the surface. @Test("The ratio is symmetric in its arguments") func theRatioIsSymmetric() throws { let a = try color("#1F2E45") let b = try color("#D5D5D5") #expect(ContrastMath.contrastRatio(a, b) == ContrastMath.contrastRatio(b, a)) } /// The threshold is the design's, spelled once (10-accessibility.md). @Test("The AA threshold is 4.5") func theThresholdIsTheDesigns() { #expect(ContrastMath.aaThreshold == 4.5) } } // MARK: - Alpha compositing @Suite("Contrast ▸ source-over compositing") struct CompositeTests { private let white = SRGBColor(red: 1, green: 1, blue: 1) private let black = SRGBColor(red: 0, green: 0, blue: 0) @Test("A fully opaque source replaces the backdrop, a fully transparent one vanishes") func theTwoDegenerateAlphas() { let red = SRGBColor(red: 1, green: 0, blue: 0) #expect(ContrastMath.composite(red, over: white) == red) #expect(ContrastMath.composite(SRGBColor(red: 1, green: 0, blue: 0, alpha: 0), over: white) == white) } /// Half of a colour and half of what is behind it — and the arithmetic that says the *same* /// translucent value is two different colours over two different backdrops, which is the whole /// reason 10-accessibility.md asks for the composite rather than the written value. @Test("A half-alpha source lands halfway to its backdrop") func aHalfAlphaSourceMeetsItsBackdropHalfway() { let halfBlack = SRGBColor(red: 0, green: 0, blue: 0, alpha: 0.5) let overWhite = ContrastMath.composite(halfBlack, over: white) let overBlack = ContrastMath.composite(halfBlack, over: black) #expect(abs(overWhite.red - 0.5) < 0.0001) #expect(abs(overBlack.red - 0) < 0.0001) #expect(overWhite.alpha == 1) } /// Straight (non-premultiplied) alpha, with a **translucent backdrop** — the general form. The /// board never composites two washes today, but a seam that only worked against an opaque /// backdrop would be one special case pretending to be a rule. @Test("Two translucent layers combine their alphas") func aTranslucentBackdropCombines() { let source = SRGBColor(red: 1, green: 0, blue: 0, alpha: 0.5) let backdrop = SRGBColor(red: 0, green: 0, blue: 1, alpha: 0.5) let result = ContrastMath.composite(source, over: backdrop) #expect(abs(result.alpha - 0.75) < 0.0001) // 0.5 of the source over 0.25 of the backdrop, renormalised by the 0.75 output alpha. #expect(abs(result.red - (0.5 / 0.75)) < 0.0001) #expect(abs(result.blue - (0.25 / 0.75)) < 0.0001) } /// Total rather than crashing: two invisible layers have no colour, and the seam has to say so /// without dividing by zero. @Test("Compositing nothing over nothing stays transparent") func theFullyTransparentCase() { let nothing = SRGBColor(red: 1, green: 1, blue: 1, alpha: 0) #expect(ContrastMath.composite(nothing, over: nothing).alpha == 0) } } // MARK: - The decision @Suite("Contrast ▸ which ink the text takes") struct InkChoiceTests { /// The system's inks as of writing — 85% black and 85% white (`NSColor.labelColor`). Written /// down here rather than resolved so the branch each case exercises is the branch it claims to; /// the live values are checked separately in `BoardTextInkTests`. private let labels = ContrastMath.Ink( light: SRGBColor(red: 0, green: 0, blue: 0, alpha: 0.85), dark: SRGBColor(red: 1, green: 1, blue: 1, alpha: 0.85) ) /// Full-strength ink, which is what Increase Contrast moves the labels towards — and the only /// way to reach the both-candidates-pass branch, since two 85% labels never both clear 4.5:1. private let opaqueLabels = ContrastMath.Ink( light: SRGBColor(red: 0, green: 0, blue: 0), dark: SRGBColor(red: 1, green: 1, blue: 1) ) private let opaque = SRGBColor(red: 0.5, green: 0.5, blue: 0.5) private func hex(_ value: String) throws -> SRGBColor { try #require(NSColor(paletteHex: value).flatMap(SRGBColor.init), "'\(value)' did not parse") } /// **The card's case, from the light side.** A near-black hand-written background in a light /// window: the native ink is dark glyphs on a dark surface, which is unreadable, so the decision /// crosses to the dark appearance's ink and the header renders in light glyphs. @Test("A dark hex in the light appearance takes the dark appearance's ink") func aDarkBackgroundFlipsTheLightAppearance() throws { let choice = ContrastMath.inkChoice( background: try hex("#101010"), backdrop: try hex("#ECECEC"), ink: labels, native: .light ) #expect(choice.scheme == .dark) #expect(choice.meetsAA) } /// The same case from the other side — a pale background in a dark window. @Test("A light hex in the dark appearance takes the light appearance's ink") func aLightBackgroundFlipsTheDarkAppearance() throws { let choice = ContrastMath.inkChoice( background: try hex("#F5F5DC"), backdrop: try hex("#1E1E1E"), ink: labels, native: .dark ) #expect(choice.scheme == .light) #expect(choice.meetsAA) } /// The rule's *quiet* half: when the native ink already clears the threshold, nothing moves. A /// dark board in a dark window is the common case, and a decision that flipped it anyway would /// be a redesign rather than an accommodation. @Test("A background the native ink already reads on is left alone") func theNativeInkIsKeptWhenItPasses() throws { let dark = ContrastMath.inkChoice( background: try hex("#101010"), backdrop: try hex("#1E1E1E"), ink: labels, native: .dark ) #expect(dark.scheme == .dark) #expect(dark.meetsAA) let light = ContrastMath.inkChoice( background: try hex("#F5F5DC"), backdrop: try hex("#ECECEC"), ink: labels, native: .light ) #expect(light.scheme == .light) #expect(light.meetsAA) } /// **Both candidates pass → the appearance-native one wins**, in each direction. /// /// Reachable only with full-strength ink (see `opaqueLabels`), and only in a sliver: pure black /// clears 4.5:1 above L = 0.175 and pure white clears it below L = 0.1833, so the window where /// both pass is eight thousandths of a luminance wide. `#767676` (channel 0.4603) sits in the /// middle of it at L ≈ 0.179, where both score ≈ 4.58. Two 85%-alpha labels never both clear the /// threshold at all, which is why this case needs the opaque pair to exist — and why the rule is /// stated anyway: Increase Contrast is exactly what moves the system's labels here. @Test("When both inks clear the threshold the native one is preferred") func bothPassingPrefersTheNativeAppearance() { let crossover = SRGBColor(red: 0.4603, green: 0.4603, blue: 0.4603) let asLight = ContrastMath.inkChoice( background: crossover, backdrop: opaque, ink: opaqueLabels, native: .light ) let asDark = ContrastMath.inkChoice( background: crossover, backdrop: opaque, ink: opaqueLabels, native: .dark ) #expect(asLight.meetsAA && asDark.meetsAA, "the crossover surface should clear 4.5:1 both ways") #expect(asLight.scheme == .light) #expect(asDark.scheme == .dark) } /// **The documented fallback.** A hand-written mid-grey has no readable ink in the system's /// vocabulary: 85%-alpha labels top out in the low fours against `#6E6E6E` in *both* /// appearances. The rule is to paint the better of the two anyway and report the miss — the /// alternatives being to override the user's colour (which "the bytes stay as written" forbids) /// or to invent an ink no other window in the app uses. @Test("When neither ink clears the threshold the higher-ratio one is used") func neitherPassingTakesTheHigherRatio() throws { let grey = try hex("#6E6E6E") for native in [ColorScheme.light, .dark] { let choice = ContrastMath.inkChoice( background: grey, backdrop: opaque, ink: labels, native: native ) #expect(!choice.meetsAA, "\(native) unexpectedly cleared AA on #6E6E6E") // The answer is the same whichever appearance asked: with neither passing, the decision // is the arithmetic's alone. #expect(choice.scheme == .dark) let rejected = ContrastMath.ratio(of: labels.light, on: grey) #expect(choice.ratio > rejected) } } /// **The threshold is a boundary, not a region.** Walking a grey ramp, the decision's `meetsAA` /// flag must agree with the ratio it reports on every step — a decision that claimed a pass at /// 4.49 or a miss at 4.51 would make the design's number decorative. @Test("meetsAA agrees with the reported ratio at the boundary") func theThresholdIsExact() { for step in 0...255 { let level = Double(step) / 255 let surface = SRGBColor(red: level, green: level, blue: level) let choice = ContrastMath.inkChoice( background: surface, backdrop: opaque, ink: labels, native: .light ) #expect(choice.meetsAA == (choice.ratio >= ContrastMath.aaThreshold)) // And the chosen ink is never worse than the one passed over. let other: ColorScheme = choice.scheme == .dark ? .light : .dark let rejected = ContrastMath.ratio(of: labels[other], on: surface) #expect(choice.ratio >= rejected || choice.meetsAA) } } /// **Alpha is what makes the appearance matter** — 10-accessibility.md's "light and dark resolve /// differently", as two decisions that disagree about the same frontmatter value. /// /// `background: #00000080` is a mid-grey over the light window background and a near-black over /// the dark one, and the two surfaces are not merely different shades: the light one lands in the /// dead zone where *no* label ink clears AA (≈ 4.1:1 at best), while the dark one clears it four /// times over. Composite against the wrong appearance's backdrop and this board is reported as /// fine when it is not, or as unreadable when it is. @Test("A translucent hex resolves against the appearance's own backdrop") func alphaMakesTheAppearanceDecisive() throws { let translucent = try hex("#00000080") let inLight = ContrastMath.inkChoice( background: translucent, backdrop: try hex("#ECECEC"), ink: labels, native: .light ) let inDark = ContrastMath.inkChoice( background: translucent, backdrop: try hex("#1E1E1E"), ink: labels, native: .dark ) #expect(inLight.scheme == .light) #expect(!inLight.meetsAA, "half-black over the light window background is the mid-grey dead zone") #expect(inDark.scheme == .dark) #expect(inDark.meetsAA) #expect(inDark.ratio > inLight.ratio * 2) } /// The backdrop **flipping a decision on its own**: `#FFFFFF80` is a pale surface in either /// appearance, because half of white is still lighter than the dark window background. So the /// dark appearance's native ink fails on it and the header renders in dark glyphs inside a dark /// window — a board the composite gets right and the written value alone gets backwards (`#FFFFFF` /// at 50% would read as "half transparent, so leave it alone"). @Test("A pale translucent hex forces dark glyphs even in a dark window") func aPaleTranslucentHexFlipsTheDarkAppearance() throws { let translucent = try hex("#FFFFFF80") let inDark = ContrastMath.inkChoice( background: translucent, backdrop: try hex("#1E1E1E"), ink: labels, native: .dark ) #expect(inDark.scheme == .light) #expect(inDark.meetsAA) } /// The backdrop is *only* consulted where there is alpha to resolve: an opaque `#RRGGBB` value /// covers the window background completely, so the two appearances see the same surface and /// reach the same ink. @Test("An opaque hex ignores its backdrop") func anOpaqueHexIsBackdropIndependent() throws { let value = try hex("#005152") let overLight = ContrastMath.inkChoice( background: value, backdrop: try hex("#ECECEC"), ink: labels, native: .light ) let overDark = ContrastMath.inkChoice( background: value, backdrop: try hex("#1E1E1E"), ink: labels, native: .light ) #expect(overLight == overDark) } } // MARK: - The board's application @Suite("Contrast ▸ the board's painted background") @MainActor struct BoardTextInkTests { /// **The predicate is "does the board paint anything", not "where did the value come from"** — /// the same `if let` `BoardView.boardBackground` takes, routed through the same /// `Palette.nsColor(for:)`. A palette name and a hex both name a surface the text has to be /// legible on; two spellings of the question could disagree about a value, and one cannot. @Test("Every value the board paints has a colour, and nothing else does") func whatCountsAsAPaintedBackground() { #expect(BoardTextInk.paintedColor(.valid("#1E1E1E")) != nil) #expect(BoardTextInk.paintedColor(.valid("#1E1E1E80")) != nil) #expect(BoardTextInk.paintedColor(.valid("#1e1e1e")) != nil) #expect(BoardTextInk.paintedColor(.valid("smokey-ocean")) != nil) #expect(BoardTextInk.paintedColor(.valid("chalk")) != nil) // A foreground-table name in the `background` field resolves, because the 12+12 split is a // picker split and not a namespace (`Palette`) — so it paints, so it decides an ink. #expect(BoardTextInk.paintedColor(.valid("carnation")) != nil) #expect(BoardTextInk.paintedColor(.valid("#12345")) == nil) #expect(BoardTextInk.paintedColor(.valid("FF0000")) == nil) #expect(BoardTextInk.paintedColor(.valid("chartreuse")) == nil) #expect(BoardTextInk.paintedColor(FieldValue.missing) == nil) #expect(BoardTextInk.paintedColor(.malformed(raw: "[a, b]")) == nil) } /// Only a board that paints **nothing** keeps the window's own appearance — the empty key, the /// malformed value, the typo. The surface is then the system's own, and the system's answer is /// the right one. @Test("A missing key, a malformed value and a typo leave the appearance alone") func anUnpaintedBoardIsLeftAlone() { for field: FieldValue in [.missing, .malformed(raw: "[a, b]"), .valid("chartreuse")] { #expect(BoardTextInk.scheme(forBoardBackground: field, appearance: .light) == .light) #expect(BoardTextInk.scheme(forBoardBackground: field, appearance: .dark) == .dark) #expect(BoardTextInk.choice(forBoardBackground: field, appearance: .light) == nil) } } /// A **palette name** goes through the same door as a hex — which is the fix this card closed. /// `smokey-ocean` is a near-black navy: under Aqua's own label it scored 1.58:1, and the board /// now renders it in light glyphs instead. @Test("A palette background decides an ink like any other painted colour") func aPaletteNameRoutesThroughTheSameRule() throws { let choice = try #require( BoardTextInk.choice(forBoardBackground: .valid("smokey-ocean"), appearance: .light) ) #expect(choice.scheme == .dark) #expect(choice.meetsAA) #expect(BoardTextInk.scheme(forBoardBackground: .valid("chalk"), appearance: .dark) == .light) } /// The end-to-end claim, against the **live** system colours rather than written-down ones: a /// near-black board reads in light glyphs and a near-white one in dark glyphs, in either /// appearance. Whatever alpha Apple ships `labelColor` at, these two must hold — they are the /// reason the card exists. @Test("The extremes resolve to the readable ink in both appearances") func theExtremesResolveCorrectly() { for appearance in [ColorScheme.light, .dark] { #expect(BoardTextInk.scheme(forBoardBackground: .valid("#050505"), appearance: appearance) == .dark) #expect(BoardTextInk.scheme(forBoardBackground: .valid("#FAFAFA"), appearance: appearance) == .light) } } /// `#00000080` over the two window backgrounds — the design's own worked example, through the /// live `NSColor.windowBackgroundColor` in each appearance. The value on disk is one string; the /// surface it makes is not, and the ink follows the surface. @Test("A translucent board colour resolves against the appearance's window background") func theBackdropIsResolvedInTheActiveAppearance() { let halfBlack = FieldValue.valid("#00000080") #expect(BoardTextInk.scheme(forBoardBackground: halfBlack, appearance: .light) == .light) #expect(BoardTextInk.scheme(forBoardBackground: halfBlack, appearance: .dark) == .dark) // The same value over the *pale* half of a light window is a mid-grey; over the dark half it // is nearly black. Two surfaces, so two luminances. let light = BoardTextInk.windowBackdrop(in: .light) let dark = BoardTextInk.windowBackdrop(in: .dark) #expect(ContrastMath.relativeLuminance(of: light) > ContrastMath.relativeLuminance(of: dark)) #expect(light.alpha == 1 && dark.alpha == 1, "the window background must be opaque to be a backdrop") } /// The live inks, as a sanity claim that survives any future tuning of the system palette: the /// light appearance's label is the darker of the two, and each reads on its own appearance's /// window background. @Test("The live label inks are dark-on-light and light-on-dark") func theLiveInksAreOrientedCorrectly() { let ink = BoardTextInk.labelInk() let light = ContrastMath.composite(ink.light, over: BoardTextInk.windowBackdrop(in: .light)) let dark = ContrastMath.composite(ink.dark, over: BoardTextInk.windowBackdrop(in: .dark)) #expect(ContrastMath.relativeLuminance(of: light) < ContrastMath.relativeLuminance(of: dark)) #expect(ContrastMath.ratio(of: ink.light, on: BoardTextInk.windowBackdrop(in: .light)) > 4.5) #expect(ContrastMath.ratio(of: ink.dark, on: BoardTextInk.windowBackdrop(in: .dark)) > 4.5) } /// The fixture board's hand-written background (`#1E1E1E`, `PaletteHexTests`' own example) — /// the value a real board on disk carries, decided end to end. @Test("The rich fixture's hand-written background reads in light glyphs") func theFixturesBackgroundResolves() throws { let choice = try #require( BoardTextInk.choice(forBoardBackground: .valid("#1E1E1E"), appearance: .light) ) #expect(choice.scheme == .dark) #expect(choice.meetsAA) } } // MARK: - The palette's AA claim, made true @Suite("Contrast ▸ the palette's AA claim") @MainActor struct PaletteContrastTests { /// **03-board-ui.md ▸ Styling ▸ Controls' promise, computed — and this test *is* the promise**: /// "the background grid offers the 12 palette colors — every pair AA-verified at design time /// (10-accessibility.md), the claim pinned by a computed-contrast unit test over all 12 pairs so /// palette drift can never silently break it." /// /// A **pair** is a background and the ink its text is drawn in, so the claim cannot be settled by /// a table of colours alone — only by the code that chooses the ink. What is asserted here is /// therefore end to end and in both appearances: for every well, the scheme `BoardTextInk` /// *selects* clears 4.5:1 on the colour that well paints. Nothing weaker would be the design's /// claim; nothing stronger is true, since no single ink reads on all twelve. /// /// Two ways to fail, both of them the point. A **new well** whose colour has no readable ink at /// all — a mid-grey, the dead zone `InkChoiceTests.neitherPassingTakesTheHigherRatio` documents — /// fails here instead of shipping. And a regression in the *selection* fails here too: with the /// appearance-native label, which is what the board drew before this card, every one of the /// twelve failed in one appearance (ten dark wells under Aqua, `chalk` and `aluminum` under Dark /// Aqua), so this test would have caught the m4 bug it now guards against returning. @Test("The ink the board picks clears 4.5:1 on every palette background, in both appearances") func everyPaletteBackgroundHasAReadableInk() throws { let ink = BoardTextInk.labelInk() for entry in Palette.backgrounds { let color = try #require( NSColor(paletteHex: entry.hex).flatMap(SRGBColor.init), "palette background '\(entry.name)' did not parse" ) for appearance in [ColorScheme.light, .dark] { let choice = try #require( BoardTextInk.choice(forBoardBackground: .valid(entry.name), appearance: appearance), "palette background '\(entry.name)' decided no ink" ) // The decision is taken on the very colour the well paints — a name that resolved to // something else would make every ratio below a measurement of the wrong surface. #expect(BoardTextInk.paintedColor(.valid(entry.name)) == color) #expect( choice.meetsAA, """ '\(entry.name)' (\(entry.hex)) in the \(appearance) appearance: the chosen \ \(choice.scheme) ink reaches only \(choice.ratio):1 """ ) // And the ink that was chosen is the one that scores — not merely a passing ink that // some other branch would have picked. #expect(abs(ContrastMath.ratio(of: ink[choice.scheme], on: color) - choice.ratio) < 0.0001) } } } /// The other half of "verified at design time": **the appearance-native label is not enough**, /// which is why the selection has to happen at all. /// /// Every one of the twelve wells is a colour the system's own label fails on in one of the two /// appearances — the ten dark ones under Aqua, `chalk` and `aluminum` under Dark Aqua. Stating /// it as a test keeps the reasoning from decaying into folklore: if a future palette were tame /// enough that the native label always worked, this would fail and the seam's board-side wiring /// could be reconsidered rather than carried on faith. @Test("No palette background is readable under the appearance-native label in both appearances") func theNativeLabelIsNeverEnough() throws { for entry in Palette.backgrounds { let color = try #require(NSColor(paletteHex: entry.hex).flatMap(SRGBColor.init)) let ink = BoardTextInk.labelInk() let native = [ColorScheme.light, .dark].filter { ContrastMath.ratio(of: ink[$0], on: color) >= ContrastMath.aaThreshold } #expect( native.count == 1, "'\(entry.name)' reads under \(native.count) native labels — the palette has changed shape" ) } } /// The twelve are opaque, which is why the design can verify them at all: a palette well with /// alpha would make its own contrast a function of the appearance's window background, and /// "verified at design time" would stop being a statement anyone could check. @Test("No palette background carries alpha") func thePaletteIsOpaque() throws { for entry in Palette.backgrounds { let color = try #require(NSColor(paletteHex: entry.hex).flatMap(SRGBColor.init)) #expect(color.alpha == 1, "'\(entry.name)' is translucent") } } }