#[cfg(test)]
use super::text::DWrite;
use crate::event::Rect;
use crate::primitives::board::{BoardLayout, BoardModel};
use crate::primitives::layout_metrics::pixel_board_layout;
#[cfg(test)]
use crate::theme::Theme;
pub fn win_board_layout(model: &BoardModel, rect: Rect) -> BoardLayout {
pixel_board_layout(model, rect.x, rect.y, rect.width, rect.height)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::primitives::board::{BadgeStatus, BoardColumn, BoardHit, CardBadge};
use crate::types::{Color, WidgetId};
use crate::win::testing::HeadlessSurface;
const W: f32 = 460.0;
const H: f32 = 300.0;
fn card(id: &str, title: &str) -> crate::primitives::board::BoardCard {
crate::primitives::board::BoardCard {
id: WidgetId::new(id),
title: title.into(),
labels: vec!["#1".into()],
badges: vec![CardBadge {
label: "P".into(),
status: BadgeStatus::Passed,
}],
hint: None,
}
}
fn sample_model() -> BoardModel {
BoardModel {
id: WidgetId::new("board"),
columns: vec![
BoardColumn {
id: WidgetId::new("col:backlog"),
title: "Backlog".into(),
cards: vec![card("card:a", "First"), card("card:b", "Second")],
scroll_offset: 0,
},
BoardColumn {
id: WidgetId::new("col:done"),
title: "Done".into(),
cards: vec![card("card:c", "Third")],
scroll_offset: 0,
},
],
selected_card_id: Some(WidgetId::new("card:a")),
col_scroll_offset: 0,
}
}
fn paint(
surface: &HeadlessSurface,
dwrite: &DWrite,
rect: Rect,
model: &BoardModel,
theme: &Theme,
) -> BoardLayout {
surface
.paint(|target| {
let mut raw = super::super::surface::D2dSurface {
target,
dwrite: Some(dwrite),
};
crate::primitives::board::native_surface_paint::paint(model, &mut raw, theme, rect);
})
.map(|_| win_board_layout(model, rect))
.expect("paint board")
}
#[test]
fn draw_board_paints_text_and_returns_layout() {
let surface = HeadlessSurface::new(W as u32, H as u32).expect("create surface");
let (dwrite, _, _) = DWrite::new("Segoe UI", 10.0, None).expect("create DWrite");
let theme = Theme {
background: Color::rgb(255, 255, 255),
surface_bg: Color::rgb(255, 255, 255),
foreground: Color::rgb(0, 0, 0),
..Theme::default()
};
let model = sample_model();
let rect = Rect::new(0.0, 0.0, W, H);
let layout = paint(&surface, &dwrite, rect, &model, &theme);
assert!(!layout.columns.is_empty());
let cb = layout.columns[0].cards[0].bounds;
let mut painted_any = false;
for x in (cb.x as u32)..(cb.x + cb.width) as u32 {
for y in (cb.y as u32)..(cb.y + cb.height) as u32 {
let px = surface.pixel_at(x, y);
if (px.r, px.g, px.b) != (255, 255, 255) {
painted_any = true;
}
}
}
assert!(painted_any, "expected draw_board to paint visible glyphs");
}
#[test]
fn paint_and_click_round_trip_at_nonzero_origin() {
let origin_x = 12.0_f32;
let origin_y = 5.0_f32;
let surface = HeadlessSurface::new(W as u32, H as u32).expect("create surface");
let (dwrite, _, _) = DWrite::new("Segoe UI", 10.0, None).expect("create DWrite");
let theme = Theme::default();
let model = sample_model();
let rect = Rect::new(origin_x, origin_y, W - origin_x, H - origin_y);
let layout = paint(&surface, &dwrite, rect, &model, &theme);
assert!(!layout.columns.is_empty());
for col in &layout.columns {
let hb = col.header_bounds;
assert!(
hb.x >= origin_x - 0.001 && hb.y >= origin_y - 0.001,
"column header escaped the requested origin ({origin_x}, {origin_y})",
);
let hit = layout.hit_test(hb.x + 1.0, hb.y + hb.height / 2.0);
assert_eq!(hit, BoardHit::ColumnHeader(col.col_id));
for cardl in &col.cards {
let cx = cardl.bounds.x + cardl.bounds.width / 2.0;
let cy = cardl.bounds.y + cardl.bounds.height / 2.0;
assert_eq!(
layout.hit_test(cx, cy),
BoardHit::Card(cardl.id),
"card click must resolve to its card",
);
}
}
}
#[test]
fn no_paint_layout_matches_paint_layout() {
let model = sample_model();
let rect = Rect::new(0.0, 0.0, W, H);
let (dwrite, _, _) = DWrite::new("Segoe UI", 10.0, None).expect("create DWrite");
let surface = HeadlessSurface::new(W as u32, H as u32).expect("create surface");
let painted = paint(&surface, &dwrite, rect, &model, &Theme::default());
let no_paint = win_board_layout(&model, rect);
assert_eq!(painted, no_paint);
}
#[test]
fn zero_size_rect_is_a_no_op() {
let surface = HeadlessSurface::new(W as u32, H as u32).expect("create surface");
let (dwrite, _, _) = DWrite::new("Segoe UI", 10.0, None).expect("create DWrite");
let theme = Theme::default();
let model = sample_model();
let rect = Rect::new(0.0, 0.0, 0.0, H);
surface
.fill_rect(Rect::new(0.0, 0.0, W, H), Color::rgb(255, 255, 255))
.expect("fill background");
paint(&surface, &dwrite, rect, &model, &theme);
let px = surface.pixel_at(1, 1);
assert_eq!(
(px.r, px.g, px.b),
(255, 255, 255),
"a zero-width board should paint nothing at all",
);
}
#[test]
fn selected_card_uses_the_selection_background() {
let surface = HeadlessSurface::new(W as u32, H as u32).expect("create surface");
let (dwrite, _, _) = DWrite::new("Segoe UI", 10.0, None).expect("create DWrite");
let theme = Theme::default();
let model = sample_model();
let rect = Rect::new(0.0, 0.0, W, H);
let layout = paint(&surface, &dwrite, rect, &model, &theme);
let cb = layout.columns[0].cards[0].bounds;
let px = surface.pixel_at(
(cb.x + cb.width - 6.0) as u32,
(cb.y + cb.height - 6.0) as u32,
);
assert_eq!(
(px.r, px.g, px.b),
(
theme.board_selected_card_bg.r,
theme.board_selected_card_bg.g,
theme.board_selected_card_bg.b
),
);
let cb2 = layout.columns[0].cards[1].bounds;
let px2 = surface.pixel_at(
(cb2.x + cb2.width - 6.0) as u32,
(cb2.y + cb2.height - 6.0) as u32,
);
assert_ne!(
(px2.r, px2.g, px2.b),
(
theme.board_selected_card_bg.r,
theme.board_selected_card_bg.g,
theme.board_selected_card_bg.b
),
"an unselected card must not use the selection background",
);
}
}