refactor(engine): move insert icon_and_picture_tests to cmd/insert/icon_and_picture_tests.rs

This commit is contained in:
aiongg
2026-10-26 22:08:44 +00:00
parent b48467943b
commit e9a7413c87
2 changed files with 285 additions and 286 deletions
+1 -286
View File
@@ -2019,289 +2019,4 @@ mod tests {
}
#[cfg(test)]
mod icon_and_picture_tests {
use deckcraft_model::ShapeKind;
use deckcraft_model::style::LineJoin;
use super::*;
/// 8×8 red PNG.
fn png() -> Vec<u8> {
vec![
137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 8, 0, 0, 0, 8, 8, 2, 0, 0, 0, 75, 109, 41, 220, 0, 0, 0, 17, 73,
68, 65, 84, 120, 156, 99, 184, 163, 161, 129, 21, 49, 12, 45, 9, 0, 123, 123, 75, 1, 251, 162, 81, 240, 0, 0, 0, 0, 73, 69, 78, 68, 174,
66, 96, 130,
]
}
fn pic(name: &str) -> Value {
json!({"name": name, "data": base64_encode(&png())})
}
fn n_slides(s: &Session) -> usize {
s.active().map(|d| d.doc.slides.len()).unwrap_or(0)
}
fn pictures(s: &Session, index: usize) -> Vec<deckcraft_model::Shape> {
s.active()
.and_then(|d| d.doc.slides.get(index))
.map(|sl| sl.shapes.iter().filter(|sh| matches!(sh.kind, ShapeKind::Picture { .. })).cloned().collect())
.unwrap_or_default()
}
fn box_text(s: &Session, index: usize) -> Vec<String> {
s.active()
.and_then(|d| d.doc.slides.get(index))
.map(|sl| sl.shapes.iter().filter(|sh| sh.text_box).filter_map(|sh| sh.text.as_ref().map(|t| t.text())).collect())
.unwrap_or_default()
}
#[test]
fn two_pictures_one_per_slide_are_one_undo() {
let mut s = Session::with_new();
let before = n_slides(&s);
let r = s.execute("insert.photoAlbum", &json!({"pictures": [pic("hill.png"), pic("lake.png")], "layout": "1"})).expect("album");
assert_eq!(r["slides"], 2);
assert_eq!(n_slides(&s), before + 2);
assert_eq!(pictures(&s, 1).len(), 1);
assert_eq!(pictures(&s, 2).len(), 1);
assert_eq!(s.active().unwrap().selection.slide, 1);
let undo = &s.active().unwrap().history.undo;
assert_eq!(undo.len(), 1);
assert_eq!(undo[0].label, "Photo Album…");
s.execute("edit.undo", &json!({})).expect("undo");
assert_eq!(n_slides(&s), before);
assert!(pictures(&s, 1).is_empty());
}
#[test]
fn two_up_shares_one_slide() {
let mut s = Session::with_new();
s.execute("insert.photoAlbum", &json!({"pictures": [pic("a.png"), pic("b.png")], "layout": "2"})).expect("album");
assert_eq!(n_slides(&s), 2);
assert_eq!(pictures(&s, 1).len(), 2);
assert!(box_text(&s, 1).is_empty());
}
#[test]
fn captions_and_titles_use_the_file_stem() {
let mut s = Session::with_new();
s.execute("insert.photoAlbum", &json!({"pictures": [pic("harbor.png")], "layout": "1", "captions": true})).expect("captions");
assert_eq!(box_text(&s, 1), ["harbor"]);
s.execute("edit.undo", &json!({})).unwrap();
s.execute("insert.photoAlbum", &json!({"pictures": [pic("harbor.png")], "layout": "1title"})).expect("title");
assert_eq!(box_text(&s, 1), ["harbor"]);
assert_eq!(pictures(&s, 1).len(), 1);
let pic_y = pictures(&s, 1)[0].xfrm.unwrap().y;
assert!(pic_y > 40.0, "the picture sits below the title");
}
#[test]
fn frames_set_the_picture_line() {
let mut s = Session::with_new();
s.execute("insert.photoAlbum", &json!({"pictures": [pic("a.png")], "frame": "rect"})).expect("rect");
let line = pictures(&s, 1)[0].line.clone().expect("line");
assert_eq!(line.width, Some(1.5));
s.execute("edit.undo", &json!({})).unwrap();
s.execute("insert.photoAlbum", &json!({"pictures": [pic("a.png")], "frame": "rounded"})).expect("rounded");
let line = pictures(&s, 1)[0].line.clone().expect("line");
assert_eq!(line.width, Some(2.25));
assert_eq!(line.join, Some(LineJoin::Round));
s.execute("edit.undo", &json!({})).unwrap();
s.execute("insert.photoAlbum", &json!({"pictures": [pic("a.png")], "frame": "simple"})).expect("simple");
assert_eq!(pictures(&s, 1)[0].line.as_ref().and_then(|l| l.width), Some(6.0));
}
#[test]
fn a_text_box_breaks_the_picture_row() {
let mut s = Session::with_new();
let r = s
.execute("insert.photoAlbum", &json!({"pictures": [pic("a.png"), {"text": "A day on the water"}, pic("b.png")], "layout": "2"}))
.expect("album");
assert_eq!(r["slides"], 3);
assert_eq!(pictures(&s, 1).len(), 1);
assert_eq!(box_text(&s, 2), ["A day on the water"]);
assert!(pictures(&s, 2).is_empty());
assert_eq!(pictures(&s, 3).len(), 1);
assert_eq!(s.active().unwrap().history.undo.len(), 1);
}
#[test]
fn no_pictures_opens_the_dialog_without_editing() {
let mut s = Session::with_new();
let before = n_slides(&s);
let r = s.execute("insert.photoAlbum", &json!({})).expect("open");
assert_eq!(r["dialog"], "photoAlbum");
assert_eq!(n_slides(&s), before);
assert!(s.active().unwrap().history.undo.is_empty());
assert_eq!(s.ui_requests.len(), 1);
match &s.ui_requests[0] {
crate::UiRequest::Dialog { id, .. } => assert_eq!(id, "photoAlbum"),
other => panic!("dialog request, got {other:?}"),
}
}
#[test]
fn bad_input_adds_no_slides() {
let mut s = Session::with_new();
let before = n_slides(&s);
assert!(s.execute("insert.photoAlbum", &json!({"pictures": [], "layout": "1"})).is_err());
assert!(s.execute("insert.photoAlbum", &json!({"pictures": [pic("a.png")], "layout": "grid"})).is_err());
assert!(s.execute("insert.photoAlbum", &json!({"pictures": [pic("a.png")], "frame": "glow"})).is_err());
assert!(s.execute("insert.photoAlbum", &json!({"pictures": [pic("a.png")], "captions": "yes"})).is_err());
assert!(s.execute("insert.photoAlbum", &json!({"pictures": [{"name": "notes.txt", "data": base64_encode(b"hello")}]} )).is_err());
assert!(s.execute("insert.photoAlbum", &json!({"pictures": [{"text": " "}]})).is_err());
let mut many = Vec::new();
for i in 0..49 {
many.push(json!({"text": format!("box {i}")}));
}
assert!(s.execute("insert.photoAlbum", &json!({"pictures": many})).is_err());
assert_eq!(n_slides(&s), before);
assert!(s.active().unwrap().history.undo.is_empty());
}
#[test]
fn fit_is_larger_than_one_picture_and_four_up_is_one_slide() {
let mut s = Session::with_new();
s.execute("insert.photoAlbum", &json!({"pictures": [pic("a.png")], "layout": "1"})).unwrap();
let one = pictures(&s, 1)[0].xfrm.unwrap().h;
s.execute("edit.undo", &json!({})).unwrap();
s.execute("insert.photoAlbum", &json!({"pictures": [pic("a.png")], "layout": "fit"})).unwrap();
let fitted = pictures(&s, 1)[0].xfrm.unwrap().h;
assert!(fitted > one, "fit {fitted} should be taller than one-up {one}");
s.execute("edit.undo", &json!({})).unwrap();
s.execute("insert.photoAlbum", &json!({"pictures": [pic("a.png"), pic("b.png"), pic("c.png"), pic("d.png")], "layout": "4"})).unwrap();
assert_eq!(n_slides(&s), 2);
assert_eq!(pictures(&s, 1).len(), 4);
}
fn adjust(s: &Session, index: usize) -> deckcraft_model::style::PictureAdjust {
match &pictures(s, index)[0].kind {
ShapeKind::Picture { fill } => fill.adjust.clone(),
_ => panic!("picture"),
}
}
fn theme_name(s: &Session) -> String {
s.active().and_then(|d| d.doc.masters.first()).map(|m| m.theme.name.clone()).unwrap_or_default()
}
#[test]
fn edit_without_pictures_reopens_the_dialog() {
let mut s = Session::with_new();
s.execute("insert.photoAlbum", &json!({"pictures": [pic("hill.png"), pic("lake.png")], "layout": "1"})).unwrap();
s.ui_requests.clear();
let before = n_slides(&s);
let undos = s.active().unwrap().history.undo.len();
let r = s.execute("insert.photoAlbumEdit", &json!({})).expect("reopen");
assert_eq!(r["dialog"], "photoAlbum");
assert_eq!(n_slides(&s), before);
assert_eq!(s.active().unwrap().history.undo.len(), undos);
let params = match &s.ui_requests[0] {
crate::UiRequest::Dialog { id, params } => {
assert_eq!(id, "photoAlbum");
params
}
other => panic!("dialog request, got {other:?}"),
};
assert_eq!(params["items"][0]["name"], "hill.png");
assert_eq!(params["items"][1]["name"], "lake.png");
assert_eq!(params["layout"], "1");
assert!(params["album"].as_u64().unwrap_or(0) > 0);
}
#[test]
fn edit_reflows_the_album_in_one_undo() {
let mut s = Session::with_new();
s.execute("insert.photoAlbum", &json!({"pictures": [pic("hill.png"), pic("lake.png")], "layout": "1"})).unwrap();
assert_eq!(n_slides(&s), 3);
let r = s.execute("insert.photoAlbumEdit", &json!({"pictures": [pic("hill.png"), pic("lake.png")], "layout": "2"})).expect("reflow");
assert_eq!(r["slides"], 1);
assert_eq!(n_slides(&s), 2);
assert_eq!(pictures(&s, 1).len(), 2);
let undo = &s.active().unwrap().history.undo;
assert_eq!(undo.last().map(|u| u.label.as_str()), Some("Edit Photo Album…"));
s.execute("edit.undo", &json!({})).unwrap();
assert_eq!(n_slides(&s), 3);
assert_eq!(pictures(&s, 1).len(), 1);
assert_eq!(pictures(&s, 2).len(), 1);
}
#[test]
fn picture_options_and_theme_are_one_step() {
let mut s = Session::with_new();
s.execute(
"insert.photoAlbum",
&json!({"pictures": [{"name": "hill.png", "data": base64_encode(&png()), "rot": 90, "brightness": 0.2, "contrast": -0.1}], "grayscale": true, "theme": "Harbor"}),
)
.unwrap();
let adj = adjust(&s, 1);
assert!(adj.grayscale);
assert!((adj.brightness - 0.2).abs() < 1e-6);
assert!((adj.contrast + 0.1).abs() < 1e-6);
assert!((pictures(&s, 1)[0].xfrm.unwrap().rot - 90.0).abs() < 0.01);
assert_eq!(theme_name(&s), "Harbor");
let mark = s.active().unwrap().doc.slides[1].raw_ext.clone().unwrap_or_default();
assert!(mark.contains("gray=1"), "{mark}");
assert!(mark.contains("theme=Harbor"), "{mark}");
assert_eq!(s.active().unwrap().doc.slides[1].name, "Photo Album");
s.execute("edit.undo", &json!({})).unwrap();
assert_eq!(theme_name(&s), "DeckCraft");
assert_eq!(n_slides(&s), 1);
}
#[test]
fn unknown_theme_and_a_plain_slide_change_nothing() {
let mut s = Session::with_new();
let before = n_slides(&s);
let err = s.execute("insert.photoAlbum", &json!({"pictures": [pic("a.png")], "theme": "NotATheme"})).unwrap_err();
assert!(err.to_string().contains("unknown theme"), "{err}");
assert!(s.execute("insert.photoAlbum", &json!({"pictures": [pic("a.png"), {"brightness": "high"}]})).is_err());
assert!(s.execute("insert.photoAlbum", &json!({"pictures": [{"name": "a.png", "data": base64_encode(&png()), "brightness": 2}]})).is_err());
let err = s.execute("insert.photoAlbumEdit", &json!({})).unwrap_err();
assert!(err.to_string().contains("not part of a photo album"), "{err}");
s.execute("insert.photoAlbum", &json!({"pictures": [pic("a.png")]})).unwrap();
s.execute("slide.go", &json!({"index": 0})).unwrap();
assert!(s.execute("insert.photoAlbumEdit", &json!({"pictures": [pic("b.png")], "layout": "2"})).is_err());
assert_eq!(n_slides(&s), before + 1);
assert_eq!(pictures(&s, 1).len(), 1);
assert_eq!(theme_name(&s), "DeckCraft");
}
#[test]
fn two_albums_stay_independent() {
let mut s = Session::with_new();
s.execute("insert.photoAlbum", &json!({"pictures": [pic("a.png"), pic("b.png")], "layout": "1"})).unwrap();
let last = n_slides(&s) - 1;
s.execute("slide.go", &json!({"index": last})).unwrap();
s.execute("insert.photoAlbum", &json!({"pictures": [pic("c.png"), pic("d.png")], "layout": "1"})).unwrap();
assert_eq!(n_slides(&s), 5);
s.execute("insert.photoAlbumEdit", &json!({"pictures": [pic("c.png"), pic("d.png")], "layout": "2"})).unwrap();
assert_eq!(n_slides(&s), 4);
assert_eq!(pictures(&s, 1).len(), 1);
assert_eq!(pictures(&s, 2).len(), 1);
assert_eq!(pictures(&s, 3).len(), 2);
let first = s.active().unwrap().doc.slides[1].raw_ext.clone().unwrap_or_default();
let second = s.active().unwrap().doc.slides[3].raw_ext.clone().unwrap_or_default();
assert!(first.starts_with("deckcraft-album:"));
assert!(second.starts_with("deckcraft-album:"));
assert_ne!(first.split(';').next(), second.split(';').next());
}
#[test]
fn a_named_run_without_a_marker_still_reflows() {
let mut s = Session::with_new();
s.execute("insert.photoAlbum", &json!({"pictures": [pic("a.png"), pic("b.png")], "layout": "1"})).unwrap();
{
let doc = std::sync::Arc::make_mut(&mut s.active_mut().unwrap().doc);
for sl in doc.slides.iter_mut().skip(1) {
std::sync::Arc::make_mut(sl).raw_ext = None;
}
}
s.execute("insert.photoAlbumEdit", &json!({"pictures": [pic("a.png"), pic("b.png")], "layout": "2"})).unwrap();
assert_eq!(n_slides(&s), 2);
assert_eq!(pictures(&s, 1).len(), 2);
let mark = s.active().unwrap().doc.slides[1].raw_ext.clone().unwrap_or_default();
assert!(mark.starts_with("deckcraft-album:"), "{mark}");
}
}
mod icon_and_picture_tests;
@@ -0,0 +1,284 @@
use deckcraft_model::ShapeKind;
use deckcraft_model::style::LineJoin;
use super::*;
/// 8×8 red PNG.
fn png() -> Vec<u8> {
vec![
137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 8, 0, 0, 0, 8, 8, 2, 0, 0, 0, 75, 109, 41, 220, 0, 0, 0, 17, 73, 68,
65, 84, 120, 156, 99, 184, 163, 161, 129, 21, 49, 12, 45, 9, 0, 123, 123, 75, 1, 251, 162, 81, 240, 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96,
130,
]
}
fn pic(name: &str) -> Value {
json!({"name": name, "data": base64_encode(&png())})
}
fn n_slides(s: &Session) -> usize {
s.active().map(|d| d.doc.slides.len()).unwrap_or(0)
}
fn pictures(s: &Session, index: usize) -> Vec<deckcraft_model::Shape> {
s.active()
.and_then(|d| d.doc.slides.get(index))
.map(|sl| sl.shapes.iter().filter(|sh| matches!(sh.kind, ShapeKind::Picture { .. })).cloned().collect())
.unwrap_or_default()
}
fn box_text(s: &Session, index: usize) -> Vec<String> {
s.active()
.and_then(|d| d.doc.slides.get(index))
.map(|sl| sl.shapes.iter().filter(|sh| sh.text_box).filter_map(|sh| sh.text.as_ref().map(|t| t.text())).collect())
.unwrap_or_default()
}
#[test]
fn two_pictures_one_per_slide_are_one_undo() {
let mut s = Session::with_new();
let before = n_slides(&s);
let r = s.execute("insert.photoAlbum", &json!({"pictures": [pic("hill.png"), pic("lake.png")], "layout": "1"})).expect("album");
assert_eq!(r["slides"], 2);
assert_eq!(n_slides(&s), before + 2);
assert_eq!(pictures(&s, 1).len(), 1);
assert_eq!(pictures(&s, 2).len(), 1);
assert_eq!(s.active().unwrap().selection.slide, 1);
let undo = &s.active().unwrap().history.undo;
assert_eq!(undo.len(), 1);
assert_eq!(undo[0].label, "Photo Album…");
s.execute("edit.undo", &json!({})).expect("undo");
assert_eq!(n_slides(&s), before);
assert!(pictures(&s, 1).is_empty());
}
#[test]
fn two_up_shares_one_slide() {
let mut s = Session::with_new();
s.execute("insert.photoAlbum", &json!({"pictures": [pic("a.png"), pic("b.png")], "layout": "2"})).expect("album");
assert_eq!(n_slides(&s), 2);
assert_eq!(pictures(&s, 1).len(), 2);
assert!(box_text(&s, 1).is_empty());
}
#[test]
fn captions_and_titles_use_the_file_stem() {
let mut s = Session::with_new();
s.execute("insert.photoAlbum", &json!({"pictures": [pic("harbor.png")], "layout": "1", "captions": true})).expect("captions");
assert_eq!(box_text(&s, 1), ["harbor"]);
s.execute("edit.undo", &json!({})).unwrap();
s.execute("insert.photoAlbum", &json!({"pictures": [pic("harbor.png")], "layout": "1title"})).expect("title");
assert_eq!(box_text(&s, 1), ["harbor"]);
assert_eq!(pictures(&s, 1).len(), 1);
let pic_y = pictures(&s, 1)[0].xfrm.unwrap().y;
assert!(pic_y > 40.0, "the picture sits below the title");
}
#[test]
fn frames_set_the_picture_line() {
let mut s = Session::with_new();
s.execute("insert.photoAlbum", &json!({"pictures": [pic("a.png")], "frame": "rect"})).expect("rect");
let line = pictures(&s, 1)[0].line.clone().expect("line");
assert_eq!(line.width, Some(1.5));
s.execute("edit.undo", &json!({})).unwrap();
s.execute("insert.photoAlbum", &json!({"pictures": [pic("a.png")], "frame": "rounded"})).expect("rounded");
let line = pictures(&s, 1)[0].line.clone().expect("line");
assert_eq!(line.width, Some(2.25));
assert_eq!(line.join, Some(LineJoin::Round));
s.execute("edit.undo", &json!({})).unwrap();
s.execute("insert.photoAlbum", &json!({"pictures": [pic("a.png")], "frame": "simple"})).expect("simple");
assert_eq!(pictures(&s, 1)[0].line.as_ref().and_then(|l| l.width), Some(6.0));
}
#[test]
fn a_text_box_breaks_the_picture_row() {
let mut s = Session::with_new();
let r = s
.execute("insert.photoAlbum", &json!({"pictures": [pic("a.png"), {"text": "A day on the water"}, pic("b.png")], "layout": "2"}))
.expect("album");
assert_eq!(r["slides"], 3);
assert_eq!(pictures(&s, 1).len(), 1);
assert_eq!(box_text(&s, 2), ["A day on the water"]);
assert!(pictures(&s, 2).is_empty());
assert_eq!(pictures(&s, 3).len(), 1);
assert_eq!(s.active().unwrap().history.undo.len(), 1);
}
#[test]
fn no_pictures_opens_the_dialog_without_editing() {
let mut s = Session::with_new();
let before = n_slides(&s);
let r = s.execute("insert.photoAlbum", &json!({})).expect("open");
assert_eq!(r["dialog"], "photoAlbum");
assert_eq!(n_slides(&s), before);
assert!(s.active().unwrap().history.undo.is_empty());
assert_eq!(s.ui_requests.len(), 1);
match &s.ui_requests[0] {
crate::UiRequest::Dialog { id, .. } => assert_eq!(id, "photoAlbum"),
other => panic!("dialog request, got {other:?}"),
}
}
#[test]
fn bad_input_adds_no_slides() {
let mut s = Session::with_new();
let before = n_slides(&s);
assert!(s.execute("insert.photoAlbum", &json!({"pictures": [], "layout": "1"})).is_err());
assert!(s.execute("insert.photoAlbum", &json!({"pictures": [pic("a.png")], "layout": "grid"})).is_err());
assert!(s.execute("insert.photoAlbum", &json!({"pictures": [pic("a.png")], "frame": "glow"})).is_err());
assert!(s.execute("insert.photoAlbum", &json!({"pictures": [pic("a.png")], "captions": "yes"})).is_err());
assert!(s.execute("insert.photoAlbum", &json!({"pictures": [{"name": "notes.txt", "data": base64_encode(b"hello")}]} )).is_err());
assert!(s.execute("insert.photoAlbum", &json!({"pictures": [{"text": " "}]})).is_err());
let mut many = Vec::new();
for i in 0..49 {
many.push(json!({"text": format!("box {i}")}));
}
assert!(s.execute("insert.photoAlbum", &json!({"pictures": many})).is_err());
assert_eq!(n_slides(&s), before);
assert!(s.active().unwrap().history.undo.is_empty());
}
#[test]
fn fit_is_larger_than_one_picture_and_four_up_is_one_slide() {
let mut s = Session::with_new();
s.execute("insert.photoAlbum", &json!({"pictures": [pic("a.png")], "layout": "1"})).unwrap();
let one = pictures(&s, 1)[0].xfrm.unwrap().h;
s.execute("edit.undo", &json!({})).unwrap();
s.execute("insert.photoAlbum", &json!({"pictures": [pic("a.png")], "layout": "fit"})).unwrap();
let fitted = pictures(&s, 1)[0].xfrm.unwrap().h;
assert!(fitted > one, "fit {fitted} should be taller than one-up {one}");
s.execute("edit.undo", &json!({})).unwrap();
s.execute("insert.photoAlbum", &json!({"pictures": [pic("a.png"), pic("b.png"), pic("c.png"), pic("d.png")], "layout": "4"})).unwrap();
assert_eq!(n_slides(&s), 2);
assert_eq!(pictures(&s, 1).len(), 4);
}
fn adjust(s: &Session, index: usize) -> deckcraft_model::style::PictureAdjust {
match &pictures(s, index)[0].kind {
ShapeKind::Picture { fill } => fill.adjust.clone(),
_ => panic!("picture"),
}
}
fn theme_name(s: &Session) -> String {
s.active().and_then(|d| d.doc.masters.first()).map(|m| m.theme.name.clone()).unwrap_or_default()
}
#[test]
fn edit_without_pictures_reopens_the_dialog() {
let mut s = Session::with_new();
s.execute("insert.photoAlbum", &json!({"pictures": [pic("hill.png"), pic("lake.png")], "layout": "1"})).unwrap();
s.ui_requests.clear();
let before = n_slides(&s);
let undos = s.active().unwrap().history.undo.len();
let r = s.execute("insert.photoAlbumEdit", &json!({})).expect("reopen");
assert_eq!(r["dialog"], "photoAlbum");
assert_eq!(n_slides(&s), before);
assert_eq!(s.active().unwrap().history.undo.len(), undos);
let params = match &s.ui_requests[0] {
crate::UiRequest::Dialog { id, params } => {
assert_eq!(id, "photoAlbum");
params
}
other => panic!("dialog request, got {other:?}"),
};
assert_eq!(params["items"][0]["name"], "hill.png");
assert_eq!(params["items"][1]["name"], "lake.png");
assert_eq!(params["layout"], "1");
assert!(params["album"].as_u64().unwrap_or(0) > 0);
}
#[test]
fn edit_reflows_the_album_in_one_undo() {
let mut s = Session::with_new();
s.execute("insert.photoAlbum", &json!({"pictures": [pic("hill.png"), pic("lake.png")], "layout": "1"})).unwrap();
assert_eq!(n_slides(&s), 3);
let r = s.execute("insert.photoAlbumEdit", &json!({"pictures": [pic("hill.png"), pic("lake.png")], "layout": "2"})).expect("reflow");
assert_eq!(r["slides"], 1);
assert_eq!(n_slides(&s), 2);
assert_eq!(pictures(&s, 1).len(), 2);
let undo = &s.active().unwrap().history.undo;
assert_eq!(undo.last().map(|u| u.label.as_str()), Some("Edit Photo Album…"));
s.execute("edit.undo", &json!({})).unwrap();
assert_eq!(n_slides(&s), 3);
assert_eq!(pictures(&s, 1).len(), 1);
assert_eq!(pictures(&s, 2).len(), 1);
}
#[test]
fn picture_options_and_theme_are_one_step() {
let mut s = Session::with_new();
s.execute(
"insert.photoAlbum",
&json!({"pictures": [{"name": "hill.png", "data": base64_encode(&png()), "rot": 90, "brightness": 0.2, "contrast": -0.1}], "grayscale": true, "theme": "Harbor"}),
)
.unwrap();
let adj = adjust(&s, 1);
assert!(adj.grayscale);
assert!((adj.brightness - 0.2).abs() < 1e-6);
assert!((adj.contrast + 0.1).abs() < 1e-6);
assert!((pictures(&s, 1)[0].xfrm.unwrap().rot - 90.0).abs() < 0.01);
assert_eq!(theme_name(&s), "Harbor");
let mark = s.active().unwrap().doc.slides[1].raw_ext.clone().unwrap_or_default();
assert!(mark.contains("gray=1"), "{mark}");
assert!(mark.contains("theme=Harbor"), "{mark}");
assert_eq!(s.active().unwrap().doc.slides[1].name, "Photo Album");
s.execute("edit.undo", &json!({})).unwrap();
assert_eq!(theme_name(&s), "DeckCraft");
assert_eq!(n_slides(&s), 1);
}
#[test]
fn unknown_theme_and_a_plain_slide_change_nothing() {
let mut s = Session::with_new();
let before = n_slides(&s);
let err = s.execute("insert.photoAlbum", &json!({"pictures": [pic("a.png")], "theme": "NotATheme"})).unwrap_err();
assert!(err.to_string().contains("unknown theme"), "{err}");
assert!(s.execute("insert.photoAlbum", &json!({"pictures": [pic("a.png"), {"brightness": "high"}]})).is_err());
assert!(s.execute("insert.photoAlbum", &json!({"pictures": [{"name": "a.png", "data": base64_encode(&png()), "brightness": 2}]})).is_err());
let err = s.execute("insert.photoAlbumEdit", &json!({})).unwrap_err();
assert!(err.to_string().contains("not part of a photo album"), "{err}");
s.execute("insert.photoAlbum", &json!({"pictures": [pic("a.png")]})).unwrap();
s.execute("slide.go", &json!({"index": 0})).unwrap();
assert!(s.execute("insert.photoAlbumEdit", &json!({"pictures": [pic("b.png")], "layout": "2"})).is_err());
assert_eq!(n_slides(&s), before + 1);
assert_eq!(pictures(&s, 1).len(), 1);
assert_eq!(theme_name(&s), "DeckCraft");
}
#[test]
fn two_albums_stay_independent() {
let mut s = Session::with_new();
s.execute("insert.photoAlbum", &json!({"pictures": [pic("a.png"), pic("b.png")], "layout": "1"})).unwrap();
let last = n_slides(&s) - 1;
s.execute("slide.go", &json!({"index": last})).unwrap();
s.execute("insert.photoAlbum", &json!({"pictures": [pic("c.png"), pic("d.png")], "layout": "1"})).unwrap();
assert_eq!(n_slides(&s), 5);
s.execute("insert.photoAlbumEdit", &json!({"pictures": [pic("c.png"), pic("d.png")], "layout": "2"})).unwrap();
assert_eq!(n_slides(&s), 4);
assert_eq!(pictures(&s, 1).len(), 1);
assert_eq!(pictures(&s, 2).len(), 1);
assert_eq!(pictures(&s, 3).len(), 2);
let first = s.active().unwrap().doc.slides[1].raw_ext.clone().unwrap_or_default();
let second = s.active().unwrap().doc.slides[3].raw_ext.clone().unwrap_or_default();
assert!(first.starts_with("deckcraft-album:"));
assert!(second.starts_with("deckcraft-album:"));
assert_ne!(first.split(';').next(), second.split(';').next());
}
#[test]
fn a_named_run_without_a_marker_still_reflows() {
let mut s = Session::with_new();
s.execute("insert.photoAlbum", &json!({"pictures": [pic("a.png"), pic("b.png")], "layout": "1"})).unwrap();
{
let doc = std::sync::Arc::make_mut(&mut s.active_mut().unwrap().doc);
for sl in doc.slides.iter_mut().skip(1) {
std::sync::Arc::make_mut(sl).raw_ext = None;
}
}
s.execute("insert.photoAlbumEdit", &json!({"pictures": [pic("a.png"), pic("b.png")], "layout": "2"})).unwrap();
assert_eq!(n_slides(&s), 2);
assert_eq!(pictures(&s, 1).len(), 2);
let mark = s.active().unwrap().doc.slides[1].raw_ext.clone().unwrap_or_default();
assert!(mark.starts_with("deckcraft-album:"), "{mark}");
}