mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-29 21:16:57 -06:00
font.replace {from, to} renames a font family everywhere it is named
explicitly: runs (latin, east Asian, complex script and symbol faces), end
marks, bullet fonts, list styles of shapes, layouts and masters, the master
text styles, the deck default style, notes, tables and the theme fonts, so
placeholders that follow the theme change too. Names compare
case-insensitively, theme references (+mn-lt) are left alone, a replace that
finds nothing adds no undo step, and missing fonts are recomputed.
font.used lists the families the slides draw in, for the dialog's Replace
list.
Home gets a Replace Fonts… button opening the dialog: Replace (fonts in the
deck), With (type or pick from installed fonts), and it stays open with a
confirmation like PowerPoint's. Ribbon small buttons now expose their label
to accessibility, which the UI test uses to click it.
Co-authored-by: Cursor <cursoragent@cursor.com>
71 lines
2.5 KiB
Rust
71 lines
2.5 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};
|
||
|
||
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());
|
||
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(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())
|
||
}
|
||
|
||
#[test]
|
||
fn ribbon_button_opens_the_dialog_and_replaces() {
|
||
let mut h = harness();
|
||
h.get_by_label("Replace Fonts…").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);
|
||
h.get_by_label("Replace").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 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();
|
||
}
|