use culors::parse;
use culors::spaces::{Hsl, Hwb, Lab, Lch, LinearRgb, Oklab, Oklch, Rgb, Xyz50, Xyz65};
use culors::Color;
#[path = "common/mod.rs"]
mod common;
const EPS: f64 = 1e-12;
fn rgb(c: Color) -> Rgb {
let Color::Rgb(c) = c else {
panic!("expected Rgb, got {c:?}")
};
c
}
fn hsl(c: Color) -> Hsl {
let Color::Hsl(c) = c else {
panic!("expected Hsl, got {c:?}")
};
c
}
fn hwb(c: Color) -> Hwb {
let Color::Hwb(c) = c else {
panic!("expected Hwb, got {c:?}")
};
c
}
fn lab(c: Color) -> Lab {
let Color::Lab(c) = c else {
panic!("expected Lab, got {c:?}")
};
c
}
fn lch(c: Color) -> Lch {
let Color::Lch(c) = c else {
panic!("expected Lch, got {c:?}")
};
c
}
fn oklab(c: Color) -> Oklab {
let Color::Oklab(c) = c else {
panic!("expected Oklab, got {c:?}")
};
c
}
fn oklch(c: Color) -> Oklch {
let Color::Oklch(c) = c else {
panic!("expected Oklch, got {c:?}")
};
c
}
fn lrgb(c: Color) -> LinearRgb {
let Color::LinearRgb(c) = c else {
panic!("expected LinearRgb, got {c:?}")
};
c
}
fn xyz65(c: Color) -> Xyz65 {
let Color::Xyz65(c) = c else {
panic!("expected Xyz65, got {c:?}")
};
c
}
fn xyz50(c: Color) -> Xyz50 {
let Color::Xyz50(c) = c else {
panic!("expected Xyz50, got {c:?}")
};
c
}
#[test]
fn named_red() {
let c = rgb(parse("red").unwrap());
common::assert_close(c.r, 1.0, EPS);
common::assert_close(c.g, 0.0, EPS);
common::assert_close(c.b, 0.0, EPS);
assert_eq!(c.alpha, None);
}
#[test]
fn named_cornflowerblue_case_insensitive() {
let c = rgb(parse("Cornflowerblue").unwrap());
common::assert_close(c.r, 100.0 / 255.0, EPS);
common::assert_close(c.g, 149.0 / 255.0, EPS);
common::assert_close(c.b, 237.0 / 255.0, EPS);
}
#[test]
fn named_uppercase() {
assert_eq!(parse("RED"), parse("red"));
}
#[test]
fn named_with_surrounding_whitespace_fails() {
assert!(parse(" red ").is_none());
assert!(parse(" red").is_none());
}
#[test]
fn transparent_keyword() {
let c = rgb(parse("transparent").unwrap());
assert_eq!(c.r, 0.0);
assert_eq!(c.g, 0.0);
assert_eq!(c.b, 0.0);
assert_eq!(c.alpha, Some(0.0));
}
#[test]
fn currentcolor_unknown() {
assert!(parse("currentcolor").is_none());
}
#[test]
fn hex_three_digit() {
assert_eq!(parse("#f00"), parse("red"));
}
#[test]
fn hex_six_digit() {
assert_eq!(parse("#ff0000"), parse("#f00"));
}
#[test]
fn hex_eight_digit_with_alpha() {
let c = rgb(parse("#ff0000ff").unwrap());
common::assert_close(c.r, 1.0, EPS);
assert_eq!(c.alpha, Some(1.0));
}
#[test]
fn hex_four_digit_zero_alpha() {
let c = rgb(parse("#0000").unwrap());
assert_eq!(c.r, 0.0);
assert_eq!(c.alpha, Some(0.0));
}
#[test]
fn hex_with_whitespace_fails() {
assert!(parse(" #f00 ").is_none());
assert!(parse("# f00").is_none());
}
#[test]
fn rgb_modern() {
let c = rgb(parse("rgb(255 0 0)").unwrap());
common::assert_close(c.r, 1.0, EPS);
common::assert_close(c.g, 0.0, EPS);
common::assert_close(c.b, 0.0, EPS);
assert_eq!(c.alpha, None);
}
#[test]
fn rgb_modern_with_surrounding_whitespace() {
let c = rgb(parse(" rgb(255 0 0) ").unwrap());
common::assert_close(c.r, 1.0, EPS);
common::assert_close(c.g, 0.0, EPS);
common::assert_close(c.b, 0.0, EPS);
assert_eq!(c.alpha, None);
}
#[test]
fn rgb_modern_with_alpha() {
let c = rgb(parse("rgb(255 0 0 / 0.5)").unwrap());
assert_eq!(c.alpha, Some(0.5));
}
#[test]
fn rgb_modern_with_pct_alpha() {
let c = rgb(parse("rgb(255 0 0 / 50%)").unwrap());
assert_eq!(c.alpha, Some(0.5));
}
#[test]
fn rgb_legacy_numbers() {
let c = rgb(parse("rgb(255, 0, 0)").unwrap());
common::assert_close(c.r, 1.0, EPS);
assert_eq!(c.alpha, None);
}
#[test]
fn rgb_legacy_percentages() {
let c = rgb(parse("rgb(100%, 0%, 0%)").unwrap());
common::assert_close(c.r, 1.0, EPS);
common::assert_close(c.g, 0.0, EPS);
common::assert_close(c.b, 0.0, EPS);
}
#[test]
fn rgba_legacy() {
let c = rgb(parse("rgba(255, 0, 0, 0.5)").unwrap());
common::assert_close(c.r, 1.0, EPS);
assert_eq!(c.alpha, Some(0.5));
}
#[test]
fn rgb_function_case_sensitive() {
assert!(parse("RGB(255 0 0)").is_none());
assert!(parse("Rgb(255 0 0)").is_none());
}
#[test]
fn rgb_none_channel_is_nan() {
let c = rgb(parse("rgb(none 0 0)").unwrap());
assert!(c.r.is_nan());
assert_eq!(c.g, 0.0);
assert_eq!(c.b, 0.0);
assert_eq!(c.alpha, None);
}
#[test]
fn rgb_none_alpha_is_none() {
let c = rgb(parse("rgb(255 0 0 / none)").unwrap());
assert_eq!(c.r, 1.0);
assert_eq!(c.alpha, None);
}
#[test]
fn rgb_oor_passes_through() {
let c = rgb(parse("rgb(300 0 0)").unwrap());
common::assert_close(c.r, 300.0 / 255.0, EPS);
}
#[test]
fn rgb_too_few_args() {
assert!(parse("rgb(255 0)").is_none());
assert!(parse("rgb(not enough)").is_none());
}
#[test]
fn rgb_round_trip_named_vs_function() {
assert_eq!(parse("red"), parse("rgb(255 0 0)"));
assert_eq!(parse("red"), parse("#ff0000"));
}
#[test]
fn hsl_modern() {
let c = hsl(parse("hsl(120deg 50% 50%)").unwrap());
assert_eq!(c.h, 120.0);
assert_eq!(c.s, 0.5);
assert_eq!(c.l, 0.5);
}
#[test]
fn hsl_legacy() {
let c = hsl(parse("hsl(120, 50%, 50%)").unwrap());
assert_eq!(c.h, 120.0);
assert_eq!(c.s, 0.5);
assert_eq!(c.l, 0.5);
}
#[test]
fn hsl_turn_unit_converts_to_degrees() {
let c = hsl(parse("hsl(0.5turn 100% 50%)").unwrap());
assert_eq!(c.h, 180.0);
assert_eq!(c.s, 1.0);
assert_eq!(c.l, 0.5);
}
#[test]
fn hsl_with_alpha() {
let c = hsl(parse("hsl(120 50% 50% / 0.5)").unwrap());
assert_eq!(c.alpha, Some(0.5));
}
#[test]
fn hwb_modern() {
let c = hwb(parse("hwb(120 30% 30%)").unwrap());
assert_eq!(c.h, 120.0);
assert_eq!(c.w, 0.3);
assert_eq!(c.b, 0.3);
}
#[test]
fn hwb_legacy_form_unsupported() {
assert!(parse("hwb(120, 30%, 30%)").is_none());
}
#[test]
fn lab_pct_l() {
let c = lab(parse("lab(50% 40 -30)").unwrap());
assert_eq!(c.l, 50.0);
assert_eq!(c.a, 40.0);
assert_eq!(c.b, -30.0);
}
#[test]
fn lab_number_l() {
let c = lab(parse("lab(50 40 -30)").unwrap());
assert_eq!(c.l, 50.0);
}
#[test]
fn lab_pct_ab_scales_to_125() {
let c = lab(parse("lab(50% 50% -50% / 50%)").unwrap());
assert_eq!(c.a, 62.5);
assert_eq!(c.b, -62.5);
assert_eq!(c.alpha, Some(0.5));
}
#[test]
fn lch_basic() {
let c = lch(parse("lch(50% 40 30deg)").unwrap());
assert_eq!(c.l, 50.0);
assert_eq!(c.c, 40.0);
assert_eq!(c.h, 30.0);
}
#[test]
fn oklab_number_l() {
let c = oklab(parse("oklab(0.5 0.1 -0.1)").unwrap());
assert_eq!(c.l, 0.5);
assert_eq!(c.a, 0.1);
assert_eq!(c.b, -0.1);
}
#[test]
fn oklab_pct_l_maps_to_unit() {
let c = oklab(parse("oklab(50% 0.1 -0.1)").unwrap());
assert_eq!(c.l, 0.5);
}
#[test]
fn oklch_basic() {
let c = oklch(parse("oklch(70% 0.15 30deg)").unwrap());
assert_eq!(c.l, 0.7);
assert_eq!(c.c, 0.15);
assert_eq!(c.h, 30.0);
}
#[test]
fn color_srgb() {
let c = rgb(parse("color(srgb 1 0 0)").unwrap());
assert_eq!(c.r, 1.0);
assert_eq!(c.g, 0.0);
assert_eq!(c.b, 0.0);
}
#[test]
fn color_srgb_linear() {
let c = lrgb(parse("color(srgb-linear 1 0 0)").unwrap());
assert_eq!(c.r, 1.0);
assert_eq!(c.g, 0.0);
assert_eq!(c.b, 0.0);
}
#[test]
fn color_xyz_aliases_to_d65() {
let c = xyz65(parse("color(xyz 0.5 0.5 0.5)").unwrap());
assert_eq!(c.x, 0.5);
let c2 = xyz65(parse("color(xyz-d65 0.5 0.5 0.5)").unwrap());
assert_eq!(c2.x, 0.5);
}
#[test]
fn color_xyz_d50() {
let c = xyz50(parse("color(xyz-d50 0.5 0.5 0.5)").unwrap());
assert_eq!(c.x, 0.5);
}
#[test]
fn color_wide_gamut_profiles() {
let Color::P3(c) = parse("color(display-p3 1 0 0)").unwrap() else {
panic!("expected P3");
};
assert_eq!(c.r, 1.0);
assert_eq!(c.g, 0.0);
assert_eq!(c.b, 0.0);
let Color::Rec2020(_) = parse("color(rec2020 0.25 0.5 0.75)").unwrap() else {
panic!("expected Rec2020");
};
let Color::ProphotoRgb(_) = parse("color(prophoto-rgb 1 1 1)").unwrap() else {
panic!("expected ProphotoRgb");
};
let Color::A98(c) = parse("color(a98-rgb 0.5 0.5 0.5 / 0.4)").unwrap() else {
panic!("expected A98");
};
assert_eq!(c.alpha, Some(0.4));
}
#[test]
fn unknown_input_returns_none() {
assert!(parse("not a color").is_none());
assert!(parse("").is_none());
}
#[test]
fn lab_none_channels_become_nan() {
let c = lab(parse("lab(none none none / 0.5)").unwrap());
assert!(c.l.is_nan());
assert!(c.a.is_nan());
assert!(c.b.is_nan());
assert_eq!(c.alpha, Some(0.5));
}
#[test]
fn rgb_modern_four_positional_no_slash_rejected() {
assert!(parse("rgb(255 0 0 0)").is_none());
assert!(parse("rgb(255 0 0 0.5)").is_none());
assert!(parse("hsl(180 50% 50% 0.5)").is_none());
}
#[test]
fn rgb_legacy_trailing_or_misplaced_commas_rejected() {
assert!(parse("rgb(255, 0, 0,)").is_none());
assert!(parse("rgb(255 0 0,)").is_none());
}
#[test]
fn rgb_legacy_mixed_separators_rejected() {
assert!(parse("hsl(180 50%, 50%)").is_none());
assert!(parse("hsl(180, 50% 50%)").is_none());
}
#[test]
fn rgb_legacy_requires_uniform_channel_types() {
assert!(parse("rgb(50%, 50, 0%)").is_none());
assert!(parse("rgb(50%, 50%, 0)").is_none());
assert!(parse("rgb(50, 50%, 0)").is_none());
assert_eq!(rgb(parse("rgb(255, 0, 0)").unwrap()).r, 1.0);
assert_eq!(rgb(parse("rgb(100%, 0%, 0%)").unwrap()).r, 1.0);
}
#[test]
fn hsl_legacy_clamps_saturation_lightness() {
let neg = hsl(parse("hsl(180, -50%, 50%)").unwrap());
assert_eq!(neg.s, 0.0);
let big = hsl(parse("hsl(180, 150%, 50%)").unwrap());
assert_eq!(big.s, 1.0);
let neg_modern = hsl(parse("hsl(180 -50% 50%)").unwrap());
assert_eq!(neg_modern.s, -0.5);
let big_modern = hsl(parse("hsl(180 150% 50%)").unwrap());
assert_eq!(big_modern.s, 1.5);
}
#[test]
fn hsl_legacy_requires_percentage_sl() {
assert!(parse("hsl(180, 50, 50)").is_none());
assert!(parse("hsl(180, 0.5, 0.5)").is_none());
let bare = hsl(parse("hsl(180 50 50)").unwrap());
assert_eq!(bare.s, 0.5);
assert_eq!(bare.l, 0.5);
}
#[test]
fn color_syntax_too_few_values_is_none() {
assert!(parse("color(srgb)").is_none());
assert!(parse("color(srgb )").is_none());
assert!(parse("color(srgb/)").is_none());
assert!(parse("color(srgb /0.5)").is_none());
assert!(parse("color(srgb 0.25)").is_none());
assert!(parse("color(srgb 0.25 50%)").is_none());
assert!(parse("color( srgb 25% .5 / 0.2)").is_none());
assert!(parse("color(srgb 1 0)").is_none());
}
#[test]
fn color_syntax_too_many_values_is_none() {
assert!(parse("color(srgb 25% .5 75% 0.33 0.66)").is_none());
assert!(parse("color(srgb 25% .5 75% 0.33 0.66 / 70% )").is_none());
assert!(parse("color(srgb 25% .5 75% 0.33 / 0.7)").is_none());
assert!(parse("color(srgb 1 0 0 0)").is_none());
}
#[test]
fn color_syntax_hue_units_in_rgb_channels_rejected() {
assert!(parse("color(srgb 0.5 0.5 0deg)").is_none());
}
#[test]
fn color_syntax_clamps_negative_alpha() {
let parsed = parse("color(srgb 1 0 0 / -0.5)").expect("parses");
let r = rgb(parsed);
assert_eq!(r.alpha, Some(0.0));
assert_eq!(r.r, 1.0);
assert_eq!(r.g, 0.0);
assert_eq!(r.b, 0.0);
}
#[test]
fn color_syntax_clamps_alpha_above_one() {
let parsed = parse("color(srgb 1 0 0 / 1.5)").expect("parses");
let r = rgb(parsed);
assert_eq!(r.alpha, Some(1.0));
}
#[test]
fn color_syntax_alpha_clamp_preserves_out_of_range_channels() {
let neg = rgb(parse("color(srgb 1.5 -0.4 0.2 / -0.5)").expect("parses"));
assert_eq!(neg.r, 1.5);
assert_eq!(neg.g, -0.4);
assert_eq!(neg.b, 0.2);
assert_eq!(neg.alpha, Some(0.0));
let big = rgb(parse("color(srgb 1.5 -0.4 0.2 / 1.5)").expect("parses"));
assert_eq!(big.r, 1.5);
assert_eq!(big.g, -0.4);
assert_eq!(big.b, 0.2);
assert_eq!(big.alpha, Some(1.0));
}
#[test]
fn transparent_keyword_uppercase_rejected() {
assert!(parse("TRANSPARENT").is_none());
assert!(parse("Transparent").is_none());
assert!(parse(" transparent").is_none());
}
#[test]
fn no_hash_hex_three_six_eight_digit() {
let three = rgb(parse("369").expect("parses"));
common::assert_close(three.r, 0.2, EPS);
common::assert_close(three.g, 0.4, EPS);
common::assert_close(three.b, 0.6, EPS);
assert_eq!(three.alpha, None);
assert_eq!(parse("ffffff"), parse("#ffffff"));
assert_eq!(parse("369abc"), parse("#369abc"));
assert_eq!(parse("369ABC"), parse("#369abc"));
}
#[test]
fn no_hash_hex_four_and_eight_digit_with_alpha() {
let four = rgb(parse("1234").expect("parses"));
common::assert_close(four.r, 1.0 / 15.0, EPS);
common::assert_close(four.g, 2.0 / 15.0, EPS);
common::assert_close(four.b, 3.0 / 15.0, EPS);
assert_eq!(four.alpha, Some(4.0 / 15.0));
assert_eq!(parse("12345678"), parse("#12345678"));
}
#[test]
fn invalid_hex_lengths_rejected() {
assert!(parse("12345").is_none());
assert!(parse("1234567").is_none());
assert!(parse("12").is_none());
assert!(parse("1").is_none());
}
#[test]
fn hsl_modern_accepts_bare_numbers_legacy_rejects() {
let modern = hsl(parse("hsl(180 50 50)").expect("parses"));
assert_eq!(modern.h, 180.0);
assert_eq!(modern.s, 0.5);
assert_eq!(modern.l, 0.5);
let small = hsl(parse("hsl(180 0.5 0.5)").expect("parses"));
assert_eq!(small.s, 0.005);
assert_eq!(small.l, 0.005);
assert!(parse("hsl(180, 50, 50)").is_none());
}
#[test]
fn rgb_modern_alpha_clamps_above_one_and_below_zero() {
let big = rgb(parse("rgb(255 0 0 / 1.5)").expect("parses"));
assert_eq!(big.alpha, Some(1.0));
let neg = rgb(parse("rgb(255 0 0 / -1)").expect("parses"));
assert_eq!(neg.alpha, Some(0.0));
}
#[test]
fn hsl_modern_alpha_clamps_above_one() {
let big = hsl(parse("hsl(180 50% 50% / 1.5)").expect("parses"));
assert_eq!(big.alpha, Some(1.0));
}
#[test]
fn hwb_alpha_clamps_below_zero() {
let neg = hwb(parse("hwb(180 30% 30% / -0.3)").expect("parses"));
assert_eq!(neg.alpha, Some(0.0));
}
#[test]
fn lab_alpha_clamps_in_modern_syntax() {
let big = lab(parse("lab(50 0 0 / 1.5)").expect("parses"));
assert_eq!(big.alpha, Some(1.0));
let neg = lab(parse("lab(50 0 0 / -0.5)").expect("parses"));
assert_eq!(neg.alpha, Some(0.0));
}
#[test]
fn lch_alpha_clamps_in_modern_syntax() {
let big = lch(parse("lch(50% 30 60 / 1.5)").expect("parses"));
assert_eq!(big.alpha, Some(1.0));
}
#[test]
fn oklab_alpha_clamps_in_modern_syntax() {
let neg = oklab(parse("oklab(0.5 0 0 / -0.3)").expect("parses"));
assert_eq!(neg.alpha, Some(0.0));
}
#[test]
fn oklch_alpha_clamps_in_modern_syntax() {
let big = oklch(parse("oklch(0.7 0.1 30 / 2)").expect("parses"));
assert_eq!(big.alpha, Some(1.0));
}
#[test]
fn xyz_color_alpha_clamps_above_one() {
let big = xyz65(parse("color(xyz 0.5 0.5 0.5 / 1.5)").expect("parses"));
assert_eq!(big.alpha, Some(1.0));
}