mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-27 03:56:50 -06:00
* egui037-20261022: migrate DeckCraft to egui 0.37 Preserve current-main document and PDF behavior alongside the reviewed input, capture, font, ribbon and notice-packaging migration. Validation: cargo xtask ci passed all eight stages, with 1,877 workspace tests and three doctests passed, six tests ignored, strict native lint and 23 WASM crates checked. Native GUI, platform and performance qualification remains separate. * E21: restore Latin coverage and enforce required font inputs * Fix migration review findings in font validation and UI controls * M-egui037: document UI lifecycle and regression contracts
96 lines
4.0 KiB
Rust
96 lines
4.0 KiB
Rust
//! Home › Replace Fonts…: the dialog lists the deck's fonts and replaces one, as one undo step.
|
||
|
||
use deckcraft_engine::Session;
|
||
use egui_kittest::Harness;
|
||
use egui_kittest::kittest::Queryable;
|
||
use serde_json::json;
|
||
|
||
use crate::{Services, SlideApp};
|
||
|
||
/// Build the standard Replace Fonts UI harness with a fresh session.
|
||
fn harness() -> Harness<'static, SlideApp> {
|
||
let mut s = Session::with_new();
|
||
s.execute("slide.new", &json!({"layout": "blank"})).unwrap();
|
||
s.execute("insert.textBox", &json!({"rect": [40, 40, 400, 80], "text": "Hello fonts"})).unwrap();
|
||
s.execute("text.exit", &json!({})).unwrap();
|
||
s.execute("format.font", &json!({"family": "Comic Neue"})).unwrap();
|
||
s.execute("format.size", &json!({"size": 40})).unwrap();
|
||
let app = SlideApp::new(s, Services::default());
|
||
// The product's default window. The Home ribbon overflows here with the bundled interface
|
||
// fonts; the ribbon's own edge chevron must make its Editing group reachable instead of the
|
||
// regression widening the fixture past the width the app actually opens at.
|
||
let mut h = Harness::builder().with_size(egui::vec2(1440.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(3);
|
||
h
|
||
}
|
||
|
||
fn run_font(h: &Harness<'_, SlideApp>) -> Option<String> {
|
||
let st = h.state().session.active().unwrap();
|
||
st.current_slide()
|
||
.unwrap()
|
||
.shapes
|
||
.iter()
|
||
.find_map(|s| s.text.as_ref())
|
||
.and_then(|b| b.paragraphs.first())
|
||
.and_then(|p| p.runs.first())
|
||
.and_then(|r| r.props.font.clone())
|
||
}
|
||
|
||
/// Scroll the ribbon to its right edge through its own visible overflow control.
|
||
fn reveal_ribbon_end(h: &mut Harness<'_, SlideApp>) {
|
||
for _ in 0..16 {
|
||
let Some(cue) = h.query_by_label("Scroll ribbon right") else { break };
|
||
cue.click();
|
||
h.run_steps(1);
|
||
}
|
||
}
|
||
|
||
/// The ribbon command opens Replace Fonts; its Replace button updates the document and leaves
|
||
/// the dialog open with a confirmation.
|
||
#[test]
|
||
fn ribbon_button_opens_the_dialog_and_replaces() {
|
||
let mut h = harness();
|
||
reveal_ribbon_end(&mut h);
|
||
let button = h.get_by_label("Replace Fonts…");
|
||
assert!(button.rect().max.x <= 1440.0 && button.rect().min.x >= 0.0, "Replace Fonts… is on screen at the default width: {:?}", button.rect());
|
||
if let Some(left) = h.query_by_label("Scroll ribbon left") {
|
||
let cue = left.rect();
|
||
assert!(!button.rect().intersects(cue), "the left overflow cue {cue:?} does not cover Replace Fonts… {:?}", button.rect());
|
||
assert!(button.rect().min.x >= cue.max.x, "ribbon content starts after the reserved left chevron gutter");
|
||
}
|
||
button.click();
|
||
h.run_steps(3);
|
||
assert_eq!(h.state().dialog.as_ref().map(|d| d.id.as_str()), Some("replaceFonts"));
|
||
// Pick the font to replace and type the new one, the way a person would.
|
||
if let Some(d) = h.state_mut().dialog.as_mut() {
|
||
d.fields.insert("from".into(), "Comic Neue".into());
|
||
d.fields.insert("to".into(), "Lato".into());
|
||
}
|
||
h.run_steps(2);
|
||
// The ribbon has a Replace button too (Find and Replace); the dialog's is drawn last.
|
||
h.query_all_by_label("Replace").last().expect("the dialog's Replace button").click();
|
||
h.run_steps(3);
|
||
assert_eq!(run_font(&h).as_deref(), Some("Lato"));
|
||
// The dialog stays open with a confirmation, like PowerPoint's.
|
||
assert!(h.query_by_label_contains("Replaced “Comic Neue”").is_some());
|
||
h.state_mut().session.execute("edit.undo", &json!({})).unwrap();
|
||
assert_eq!(run_font(&h).as_deref(), Some("Comic Neue"));
|
||
}
|
||
|
||
#[test]
|
||
#[ignore = "writes a screenshot for review"]
|
||
fn screenshot() {
|
||
let _gpu = crate::gpu_lock::gpu_lock();
|
||
let mut h = harness();
|
||
h.render().unwrap().save("/tmp/replacefonts0.png").unwrap();
|
||
h.state_mut().dialog = Some(crate::dialogs::Dialog::new("replaceFonts"));
|
||
h.run_steps(4);
|
||
h.render().unwrap().save("/tmp/replacefonts.png").unwrap();
|
||
}
|