use super::*;
use iced::Size;
use iced::widget::Id;
use iced_test::Simulator;
use snora_core::{AppLayout, Dialog};
use snora_design::{Color as SnColor, Tokens, contrast::contrast_ratio};
const AA: f32 = 4.5;
#[derive(Debug, Clone, PartialEq, Eq)]
enum Msg {
Noop,
}
const VISIBILITY_FLOOR: f32 = 1.3;
fn named_presets() -> [(&'static str, Tokens); 4] {
[
("light", Tokens::light()),
("dark", Tokens::dark()),
("high_contrast_light", Tokens::high_contrast_light()),
("high_contrast_dark", Tokens::high_contrast_dark()),
]
}
fn to_sn(c: iced::Color) -> SnColor {
SnColor::rgba(c.r, c.g, c.b, c.a)
}
fn composite(fg: iced::Color, bg: iced::Color) -> iced::Color {
let a = fg.a;
iced::Color {
r: fg.r * a + bg.r * (1.0 - a),
g: fg.g * a + bg.g * (1.0 - a),
b: fg.b * a + bg.b * (1.0 - a),
a: 1.0,
}
}
#[test]
fn dim_visible_against_background_all_presets() {
for (name, t) in named_presets() {
let background = to_iced_color(t.palette.background);
let dim = dim_color(&t);
let composited = composite(dim, background);
let r = contrast_ratio(to_sn(composited), to_sn(background));
assert!(
r >= VISIBILITY_FLOOR,
"{name}: dim composited over background contrast {r:.3} < {VISIBILITY_FLOOR} \
— the modal dim is not visibly distinguishable from the page"
);
}
}
#[test]
fn dim_alpha_is_always_dim_alpha_constant() {
for (name, t) in named_presets() {
let dim = dim_color(&t);
assert_eq!(
dim.a, DIM_ALPHA,
"{name}: dim_color's alpha must stay fixed at DIM_ALPHA regardless of preset"
);
}
}
#[test]
fn dim_color_picks_the_opposite_pole() {
let mut white_bg_tokens = Tokens::light();
white_bg_tokens.palette.background = SnColor::rgb(1.0, 1.0, 1.0);
let dim = dim_color(&white_bg_tokens);
assert_eq!(
(dim.r, dim.g, dim.b),
(0.0, 0.0, 0.0),
"a light (white) background must pick the black pole"
);
let mut black_bg_tokens = Tokens::high_contrast_dark();
black_bg_tokens.palette.background = SnColor::rgb(0.0, 0.0, 0.0);
let dim = dim_color(&black_bg_tokens);
assert_eq!(
(dim.r, dim.g, dim.b),
(1.0, 1.0, 1.0),
"a dark (black) background must pick the white pole"
);
}
#[test]
fn card_border_distinguishable_from_background_all_presets() {
for (name, t) in named_presets() {
let background = to_iced_color(t.palette.background);
let card = dialog_card_style(&t);
let r = contrast_ratio(to_sn(card.style.border.color), to_sn(background));
assert!(
r >= VISIBILITY_FLOOR,
"{name}: card border vs background contrast {r:.3} < {VISIBILITY_FLOOR} \
— a border this close to the page would make the card's edge invisible"
);
}
}
#[test]
fn fill_equals_background_in_light_presets_by_token_design() {
for name in ["light", "high_contrast_light"] {
let t = if name == "light" {
Tokens::light()
} else {
Tokens::high_contrast_light()
};
assert_eq!(
t.palette.surface_raised, t.palette.background,
"{name}: this test's premise (fill == background in these two presets) no longer \
holds — re-evaluate whether card_border_distinguishable_from_background_all_presets \
is still the right test, or whether a fill-distinguishable test is now possible too"
);
}
}
#[test]
fn card_style_matches_token_roles_exactly() {
for (name, t) in named_presets() {
let card = dialog_card_style(&t);
assert_eq!(
card.style.background,
Some(to_iced_color(t.palette.surface_raised).into()),
"{name}: card fill must equal surface_raised exactly"
);
assert_eq!(
card.style.border.color,
to_iced_color(t.palette.border),
"{name}: card border color must equal the border role exactly"
);
assert_eq!(
card.style.border.radius,
t.radius.lg.into(),
"{name}: card border radius must equal radius.lg exactly"
);
assert_eq!(
card.padding, t.spacing.lg,
"{name}: card padding must equal spacing.lg exactly"
);
assert_eq!(
card.style.shadow,
iced::Shadow::default(),
"{name}: card must be border-defined, not shadow-defined (RFC-039) — \
card_raised's shadow must be zeroed, not inherited"
);
}
}
#[test]
fn card_text_meets_aa_all_presets() {
for (name, t) in named_presets() {
let card = dialog_card_style(&t);
let text = card
.style
.text_color
.expect("card style must set a text color");
let fill = to_iced_color(t.palette.surface_raised);
let r = contrast_ratio(to_sn(text), to_sn(fill));
assert!(
r >= AA,
"{name}: card text contrast {r:.2} < {AA} (WCAG AA)"
);
}
}
#[test]
fn dialog_card_identifier_resolves_to_the_card_not_the_window() {
let tokens = Tokens::light();
let window_size = Size::new(1024.0, 768.0);
let dialog: Dialog<Element<'_, Msg>, Msg> =
Dialog::new(iced::widget::text("dialog content").into());
let layout = AppLayout::new(iced::widget::text("body").into())
.dialog(dialog)
.on_close_modals(Msg::Noop);
let element = render(layout, &tokens);
let mut sim = Simulator::with_size(iced_test::core::Settings::default(), window_size, element);
let wrapper_bounds = sim
.find(Id::new(crate::identifiers::DIALOG))
.expect("snora-dialog must resolve on the design path")
.bounds();
let card_bounds = sim
.find(Id::new(crate::identifiers::DIALOG_CARD))
.expect("snora-dialog-card must resolve on the design path")
.bounds();
assert!(
(wrapper_bounds.width - window_size.width).abs() < 1.0
&& (wrapper_bounds.height - window_size.height).abs() < 1.0,
"sanity check: snora-dialog's centring wrapper is expected to fill the window \
({wrapper_bounds:?} vs window {window_size:?}) — if this no longer holds, this \
test needs a different baseline to compare the card's bounds against"
);
assert!(
card_bounds.width < wrapper_bounds.width && card_bounds.height < wrapper_bounds.height,
"snora-dialog-card must resolve to bounds strictly smaller than snora-dialog's \
({card_bounds:?} vs wrapper {wrapper_bounds:?}) — equal bounds would mean it is \
still resolving to the full-window wrapper, the RFC-049 defect this test exists \
to catch"
);
}
#[test]
fn responsive_render_keeps_the_design_chrome() {
let tokens = Tokens::light();
let window_size = Size::new(1024.0, 768.0);
let element = responsive_render(
move |_width| {
let dialog: Dialog<Element<'_, Msg>, Msg> =
Dialog::new(iced::widget::text("dialog content").into());
AppLayout::new(iced::widget::text("body").into())
.dialog(dialog)
.on_close_modals(Msg::Noop)
},
&tokens,
);
let mut sim = Simulator::with_size(iced_test::core::Settings::default(), window_size, element);
sim.find(Id::new(crate::identifiers::DIALOG_CARD)).expect(
"snora-dialog-card must resolve through design::responsive_render — if it does not, \
this function is silently rendering through the engine path instead of design::render, \
which is exactly the RFC-053 defect this test exists to catch",
);
}