From 453e0b9af88196d90efcc38ee48d15948d9f51d6 Mon Sep 17 00:00:00 2001 From: Trevor Brown Date: Tue, 3 Nov 2026 23:48:36 -0500 Subject: [PATCH] M8: a whole-turn hue is 0, a negative hue counts back, and calc() is refused (tests) Review asks on #1773: `css_hue_wraps_a_whole_turn_and_calc_is_not_a_number` checks that 360, 1turn, 400grad, 2 pi rad and -1turn all read as hue 0, that -90deg, -0.25turn, -100grad and 630 all read as 270, the same in oklch() and lch(), and that `calc(NaN)`, `calc(infinity)`, `calc(1)` and `calc(1turn)` are refused as non-numbers rather than read as 0 or clamped. Co-Authored-By: Claude Fable 5.1 --- crates/color/src/tests.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/crates/color/src/tests.rs b/crates/color/src/tests.rs index 41e7acaf7..d4bb69676 100644 --- a/crates/color/src/tests.rs +++ b/crates/color/src/tests.rs @@ -271,3 +271,26 @@ fn to_css_sanitizes_an_unclamped_alpha() { } assert_eq!(Color { r: 1.0, g: 0.0, b: 0.0, a: f32::INFINITY }.to_css(), "#FF0000"); } + +#[test] +fn css_hue_wraps_a_whole_turn_and_calc_is_not_a_number() { + // A hue is an angle (CSS Color 4 ยง4.3): a whole turn (360, 1turn, 400grad, 2 pi rad) is 0 and a + // negative hue counts back from it, in hsl() and in the polar spaces alike. `calc()` is not + // read, so `calc(NaN)` and `calc(infinity)` are refused like any other non-number rather than + // read as 0 or clamped. + let red = Color::parse_css("hsl(0 100% 50%)").unwrap().to_rgba8(); + for s in ["hsl(360 100% 50%)", "hsl(1turn 100% 50%)", "hsl(400grad 100% 50%)", "hsl(6.2831855rad 100% 50%)", "hsl(-1turn 100% 50%)"] { + assert_eq!(Color::parse_css(s).map(|c| c.to_rgba8()), Some(red), "{s}"); + } + let violet = Color::parse_css("hsl(270 100% 50%)").unwrap().to_rgba8(); + for s in ["hsl(-90deg 100% 50%)", "hsl(-0.25turn 100% 50%)", "hsl(-100grad 100% 50%)", "hsl(630 100% 50%)"] { + assert_eq!(Color::parse_css(s).map(|c| c.to_rgba8()), Some(violet), "{s}"); + } + assert_eq!(Color::parse_css("oklch(0.5 0.1 1turn)").map(|c| c.to_rgba8()), Color::parse_css("oklch(0.5 0.1 0)").map(|c| c.to_rgba8())); + assert_eq!(Color::parse_css("lch(50 50 400grad)").map(|c| c.to_rgba8()), Color::parse_css("lch(50 50 0)").map(|c| c.to_rgba8())); + assert_eq!(Color::parse_css("oklch(0.5 0.1 -90deg)").map(|c| c.to_rgba8()), Color::parse_css("oklch(0.5 0.1 270)").map(|c| c.to_rgba8())); + for s in ["hsl(calc(NaN) 100% 50%)", "rgb(calc(infinity) 0 0)", "rgb(0 0 0 / calc(1))", "oklch(0.5 0.1 calc(20deg))", "hsl(calc(1turn) 100% 50%)"] + { + assert_eq!(Color::parse_css(s), None, "{s}"); + } +}