use super::*;
use crate::resolve::Ctx;
#[test]
fn new_presentation_is_valid() {
let p = Presentation::default();
assert_eq!(p.slides.len(), 1);
assert_eq!(p.masters.len(), 1);
assert_eq!(p.masters[0].layouts.len(), 11);
assert!(p.validate().is_empty(), "{:?}", p.validate());
let s = &p.slides[0];
let (m, l) = p.master_for(s).unwrap();
assert_eq!(l.unwrap().kind, LayoutType::Title);
assert_eq!(m.theme.name, "DeckCraft");
let kinds: Vec<_> = s.shapes.iter().filter_map(|x| x.ph_type()).collect();
assert_eq!(kinds, vec![PhType::CtrTitle, PhType::SubTitle]);
}
#[test]
fn placeholder_inherits_position_and_styles() {
let p = Presentation::default();
let s = &p.slides[0];
let ctx = Ctx::for_slide(&p, s).unwrap();
let title = &s.shapes[0];
let x = resolve::xfrm(&ctx, title);
assert!((x.x - 120.0).abs() < 1e-9 && (x.w - 720.0).abs() < 1e-9);
let lvl = resolve::level_style(&ctx, title, 0);
assert_eq!(lvl.run.size, Some(60.0)); assert_eq!(lvl.para.align, Some(text::Align::Center));
assert_eq!(resolve::font_family(&ctx, &lvl.run), "Inter");
let body = resolve::body(&ctx, title);
assert_eq!(body.anchor, Some(text::Anchor::Bottom));
}
#[test]
fn body_levels_from_master() {
let mut p = Presentation::default();
let lay = defaults::layout_of_kind(&p, LayoutType::TitleAndContent).unwrap();
let s = defaults::new_slide(&mut p, lay);
let ctx = Ctx::for_slide(&p, &s).unwrap();
let body = &s.shapes[1];
assert_eq!(body.ph_type(), Some(PhType::Obj));
let l0 = resolve::level_style(&ctx, body, 0);
let l1 = resolve::level_style(&ctx, body, 1);
assert_eq!(l0.run.size, Some(28.0));
assert_eq!(l1.run.size, Some(24.0));
assert!(matches!(l0.para.bullet, Some(text::Bullet::Char { .. })));
}
#[test]
fn shape_style_fill_resolves_through_theme() {
let p = Presentation::default();
let s = &p.slides[0];
let ctx = Ctx::for_slide(&p, s).unwrap();
let sh = Shape {
style: Some(ShapeStyle::accent(SchemeSlot::Accent1)),
xfrm: Some(Xfrm::new(0.0, 0.0, 10.0, 10.0)),
..Default::default()
};
let (f, ph) = resolve::fill(&ctx, &sh);
let Some(Fill::Solid { color }) = f else {
panic!("expected solid")
};
assert_eq!(
ctx.color(&color, ph),
p.masters[0].theme.colors.get(SchemeSlot::Accent1)
);
let (l, ph) = resolve::line(&ctx, &sh);
assert!(l.width.is_some());
assert!(ph.is_some());
}
#[test]
fn a_gradient_without_stops_takes_them_from_its_style() {
let p = Presentation::default();
let s = &p.slides[0];
let ctx = Ctx::for_slide(&p, s).unwrap();
let own = style::Gradient {
stops: vec![],
shape: style::GradientShape::Linear {
angle: 45.0,
scaled: true,
},
rotate_with_shape: true,
};
let mut st = ShapeStyle::accent(SchemeSlot::Accent2);
st.fill_ref.0 = 3;
let sh = Shape {
style: Some(st.clone()),
fill: Some(Fill::Gradient(own.clone())),
..Default::default()
};
let (f, ph) = resolve::fill(&ctx, &sh);
let Some(Fill::Gradient(g)) = f else {
panic!("expected a gradient")
};
let Some(Fill::Gradient(theme)) = p.masters[0].theme.format.fills.get(2).cloned() else {
panic!("theme fill 3 is a gradient")
};
assert_eq!(g.stops, theme.stops);
assert_eq!(g.shape, own.shape, "the shape's own direction");
assert_eq!(ph, Some(p.masters[0].theme.colors.get(SchemeSlot::Accent2)));
st.fill_ref.0 = 1;
let sh = Shape {
style: Some(st),
fill: Some(Fill::Gradient(own.clone())),
..Default::default()
};
assert_eq!(resolve::fill(&ctx, &sh).0, Some(Fill::Gradient(own)));
}
#[test]
fn serde_roundtrip() {
let p = Presentation::default();
let j = serde_json::to_string(&p).unwrap();
let q: Presentation = serde_json::from_str(&j).unwrap();
assert_eq!(p, q);
let q: Presentation = serde_json::from_str(r#"{"slides":[],"zzz":1}"#).unwrap();
assert!(q.slides.is_empty());
}
#[test]
fn ids_are_unique_and_fix_next_id() {
let mut p = Presentation::default();
let a = p.alloc_id();
let b = p.alloc_id();
assert!(b > a);
p.next_id = 0;
p.fix_next_id();
assert!(p.next_id >= b.min(p.next_id));
let c = p.alloc_id();
let mut all = vec![];
for s in &p.slides {
all.push(s.id.0);
walk(&s.shapes, &mut |sh, _| all.push(sh.id.0));
}
assert!(!all.contains(&c));
}
#[test]
fn group_find_and_walk() {
let child = Shape {
id: ShapeId(900),
..Default::default()
};
let g = Shape {
id: ShapeId(901),
kind: ShapeKind::Group {
children: vec![child],
child: Xfrm::new(0.0, 0.0, 1.0, 1.0),
},
..Default::default()
};
let mut slide = Slide {
shapes: vec![g],
..Default::default()
};
assert!(slide.shape(ShapeId(900)).is_some());
assert!(slide.shape_mut(ShapeId(900)).is_some());
let mut n = 0;
walk(&slide.shapes, &mut |_, _| n += 1);
assert_eq!(n, 2);
}
#[test]
fn text_body_helpers() {
let mut t = TextBody::from_text("Hello\nWorld");
assert_eq!(t.text(), "Hello\nWorld");
assert_eq!(t.char_len(), 11);
t.paragraphs[0].runs.push(Run::new(" there"));
t.paragraphs[0].normalize();
assert_eq!(t.paragraphs[0].runs.len(), 1);
assert_eq!(t.paragraphs[0].text(), "Hello there");
}
#[test]
fn builtin_themes_have_distinct_names() {
let t = theme::builtin_themes();
let mut names: Vec<_> = t.iter().map(|t| t.name.clone()).collect();
names.sort();
names.dedup();
assert_eq!(names.len(), t.len());
assert!(t.len() >= 8);
}
#[test]
fn four_by_three_master_scales() {
let p = defaults::blank_presentation(defaults::STANDARD, Theme::default(), true);
let m = &p.masters[0];
let title = m
.shapes
.iter()
.find(|s| s.ph_type() == Some(PhType::Title))
.unwrap();
let x = title.xfrm.unwrap();
assert!(x.x + x.w <= 720.0 + 1e-6);
}
#[test]
fn hyperlinked_runs_take_the_theme_hyperlink_look() {
use crate::text::{Action, Hyperlink, Paragraph, RunProps};
let p = Presentation::default();
let s = &p.slides[0];
let ctx = Ctx::for_slide(&p, s).unwrap();
let title = &s.shapes[0];
let para = Paragraph::default();
let link = Some(Hyperlink {
action: Action::Url {
url: "https://example.org".into(),
},
tooltip: String::new(),
highlight_click: false,
});
let hlink = Some(Fill::solid(ColorRef::scheme(SchemeSlot::Hlink)));
assert_eq!(
resolve::run(&ctx, title, ¶, &RunProps::default()).fill,
Some(Fill::solid(ColorRef::scheme(SchemeSlot::Tx1)))
);
let r = resolve::run(
&ctx,
title,
¶,
&RunProps {
link: link.clone(),
..Default::default()
},
);
assert_eq!(
(r.fill, r.underline.as_deref()),
(hlink.clone(), Some("sng"))
);
assert_eq!(
ctx.color(&ColorRef::scheme(SchemeSlot::Hlink), None),
p.masters[0].theme.colors.get(SchemeSlot::Hlink)
);
let slide = Some(Hyperlink {
action: Action::Slide { slide: s.id },
tooltip: String::new(),
highlight_click: false,
});
assert_eq!(
resolve::run(
&ctx,
title,
¶,
&RunProps {
link: slide,
..Default::default()
}
)
.fill,
hlink
);
let red = Some(Fill::solid(ColorRef::rgb(deckcraft_color::Rgba::rgb(
255, 0, 0,
))));
let own = RunProps {
link: link.clone(),
fill: red.clone(),
underline: Some("none".into()),
..Default::default()
};
let r = resolve::run(&ctx, title, ¶, &own);
assert_eq!((r.fill, r.underline.as_deref()), (red, Some("none")));
let dbl = resolve::run(
&ctx,
title,
¶,
&RunProps {
link,
underline: Some("dbl".into()),
..Default::default()
},
);
assert_eq!((dbl.fill, dbl.underline.as_deref()), (hlink, Some("dbl")));
}
#[test]
fn a_layout_colour_map_override_applies_to_its_slides() {
let mut p = Presentation::default();
let dark: Vec<(String, String)> = [
("bg1", "dk1"),
("tx1", "lt1"),
("bg2", "dk2"),
("tx2", "lt2"),
]
.iter()
.map(|(a, b)| (a.to_string(), b.to_string()))
.collect();
let layout = p.slides[0].layout;
let m = std::sync::Arc::make_mut(&mut p.masters[0]);
let colors = m.theme.colors.clone();
m.layouts
.iter_mut()
.find(|l| l.id == layout)
.unwrap()
.color_map = Some(dark.clone());
let s: Slide = (*p.slides[0]).clone();
let ctx = Ctx::for_slide(&p, &s).unwrap();
assert_eq!(
ctx.color(&ColorRef::scheme(SchemeSlot::Bg1), None),
colors.get(SchemeSlot::Dk1)
);
assert_eq!(
ctx.color(&ColorRef::scheme(SchemeSlot::Tx2), None),
colors.get(SchemeSlot::Lt2)
);
assert_eq!(ctx.scheme.get(SchemeSlot::Lt1), colors.get(SchemeSlot::Dk1));
let mut own = s.clone();
own.color_map = Some(
[("bg1", "lt1"), ("tx1", "dk1")]
.iter()
.map(|(a, b)| (a.to_string(), b.to_string()))
.collect(),
);
let ctx = Ctx::for_slide(&p, &own).unwrap();
assert_eq!(
ctx.color(&ColorRef::scheme(SchemeSlot::Bg1), None),
colors.get(SchemeSlot::Lt1)
);
let m = &p.masters[0];
let l = m.layout(layout).unwrap();
assert_eq!(
Ctx::for_layout(&p, m, l).color(&ColorRef::scheme(SchemeSlot::Bg1), None),
colors.get(SchemeSlot::Dk1)
);
assert_eq!(
Ctx::for_master(&p, m).color(&ColorRef::scheme(SchemeSlot::Bg1), None),
colors.get(SchemeSlot::Lt1)
);
}
#[test]
fn sibling_lists_and_parents_reach_into_groups() {
let leaf = |id: u32| Shape {
id: ShapeId(id),
..Default::default()
};
let group = |id: u32, children: Vec<Shape>| Shape {
id: ShapeId(id),
kind: ShapeKind::Group {
children,
child: Default::default(),
},
..Default::default()
};
let mut tree = vec![
leaf(1),
group(2, vec![leaf(3), group(4, vec![leaf(5)])]),
leaf(6),
];
assert_eq!(parent_of(&tree, ShapeId(1)), Some(None));
assert_eq!(parent_of(&tree, ShapeId(3)), Some(Some(ShapeId(2))));
assert_eq!(parent_of(&tree, ShapeId(5)), Some(Some(ShapeId(4))));
assert_eq!(parent_of(&tree, ShapeId(9)), None);
let ids = |v: &Vec<Shape>| v.iter().map(|s| s.id.0).collect::<Vec<_>>();
assert_eq!(
sibling_list_mut(&mut tree, ShapeId(6)).map(|v| ids(v)),
Some(vec![1, 2, 6])
);
assert_eq!(
sibling_list_mut(&mut tree, ShapeId(4)).map(|v| ids(v)),
Some(vec![3, 4])
);
assert_eq!(
sibling_list_mut(&mut tree, ShapeId(5)).map(|v| ids(v)),
Some(vec![5])
);
assert!(sibling_list_mut(&mut tree, ShapeId(9)).is_none());
}