mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-31 07:38:16 +00:00
review.compare {path | name, data} reads a revised presentation (native or
PPTX) and lists slide-level revisions against the open one: inserted,
deleted and changed slides, with what changed (text, text formatting,
moved, resized, rotated, formatting, picture, objects inserted/deleted,
layout, notes, transition, animations, background, hidden). Slides match by
id, or by position when the decks share none (fixed for the session so
accepting doesn't switch the matching half way). Shapes match by id, then
name.
review.accept {index | all} applies revisions as one undo step: a revised
slide's media is copied (remapped on an id clash), its shape ids kept unless
another slide uses them (animations follow), its layout matched by id, then
name. review.revisions and review.endCompare complete the set.
Review tab: Compare (file picker), Accept All, End Review; the Revisions pane
lists changes with Go-to and Accept. Parity row: Compare presentations M → P.
Co-authored-by: Cursor <cursoragent@cursor.com>
62 lines
2.5 KiB
Rust
62 lines
2.5 KiB
Rust
//! Review › Compare: the Revisions pane lists a revised copy's changes and accepts them.
|
||
|
||
use deckcraft_engine::Session;
|
||
use egui_kittest::Harness;
|
||
use egui_kittest::kittest::Queryable;
|
||
use serde_json::json;
|
||
|
||
use crate::{Services, SlideApp};
|
||
|
||
/// The sample deck compared with a copy whose slide 2 is gone and that has a new last slide.
|
||
fn harness() -> (Harness<'static, SlideApp>, usize) {
|
||
let mut s = Session::new();
|
||
deckcraft_engine::sample::open_sample(&mut s).unwrap();
|
||
let n = s.doc().unwrap().doc.slides.len();
|
||
let data = s.execute("file.saveBytes", &json!({})).unwrap()["data"].as_str().unwrap().to_string();
|
||
s.execute("file.openBytes", &json!({"name": "rev.deckcraft", "data": data})).unwrap();
|
||
s.execute("slide.delete", &json!({"index": 1})).unwrap();
|
||
s.execute("slide.go", &json!({"index": n - 2})).unwrap();
|
||
s.execute("slide.new", &json!({"layout": "titleAndContent", "title": "From the reviewer", "body": "x"})).unwrap();
|
||
let rev = s.execute("file.saveBytes", &json!({})).unwrap()["data"].as_str().unwrap().to_string();
|
||
s.close_document(1);
|
||
s.set_active(0);
|
||
s.execute("review.compare", &json!({"name": "Q3 review.deckcraft", "data": rev})).unwrap();
|
||
let app = SlideApp::new(s, Services::default());
|
||
let mut h = Harness::builder().with_size(egui::vec2(1400.0, 900.0)).with_step_dt(1.0 / 60.0).build_ui_state(
|
||
|ui, app: &mut SlideApp| {
|
||
app.logic(ui.ctx());
|
||
app.ui(ui);
|
||
},
|
||
app,
|
||
);
|
||
h.run_steps(4);
|
||
(h, n)
|
||
}
|
||
|
||
#[test]
|
||
fn pane_opens_lists_changes_and_accept_all_applies_them() {
|
||
let (mut h, n) = harness();
|
||
assert_eq!(h.state().ui.pane.as_deref(), Some("revisions"), "compare opens the Revisions pane");
|
||
assert!(h.query_by_label("Compared with Q3 review.deckcraft").is_some());
|
||
assert!(h.query_by_label("Slide 2 deleted").is_some());
|
||
assert!(h.query_by_label_contains("Slide inserted after").is_some());
|
||
h.get_by_label_contains("Accept All").click();
|
||
h.run_steps(3);
|
||
let st = h.state().session.active().unwrap();
|
||
assert_eq!(st.doc.slides.len(), n, "one slide gone, one slide added");
|
||
assert!(h.query_by_label("No differences left.").is_some());
|
||
h.get_by_label("End Review").click();
|
||
h.run_steps(3);
|
||
assert!(h.state().session.active().unwrap().compare.is_none());
|
||
}
|
||
|
||
#[test]
|
||
#[ignore = "writes a screenshot for review"]
|
||
fn screenshot() {
|
||
let (mut h, _) = harness();
|
||
// Review tab.
|
||
h.state_mut().ui.tab = "Review".into();
|
||
h.run_steps(4);
|
||
h.render().unwrap().save("/tmp/compare.png").unwrap();
|
||
}
|