mirror of
https://github.com/storytold/figcraft.git
synced 2026-11-13 13:52:34 +00:00
Text: tests for the layer-level wrap style, the Type settings control, old files and inspect (#2094 review)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Model-Effort: Opus 5.5 drove; Opus 5.5 briefed; Sonnet 5.5 wrote at medium
This commit is contained in:
1 parent
071cc86bdf
commit
d14c637de8
3 files changed
+162
-1
No files matched your search
@@ -1168,6 +1168,49 @@ mod case_tests {
|
||||
assert!(t.paragraphs.is_empty());
|
||||
assert_eq!((t.para_wrap_style(0), t.para_wrap_style(1)), (Pretty, Pretty));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrap_style_loads_from_old_paragraph_styles_and_texts() {
|
||||
use WrapStyle::Auto;
|
||||
// A paragraph style saved before it carried a wrap style.
|
||||
let old = r#"{"paragraph_spacing":4.0,"list":"bulleted"}"#;
|
||||
let ps: ParagraphStyle = serde_json::from_str(old).unwrap();
|
||||
assert_eq!((ps.wrap_style, ps.paragraph_spacing, ps.list), (Auto, 4.0, ListStyle::Bulleted));
|
||||
assert!(!serde_json::to_string(&ParagraphStyle::default()).unwrap().contains("wrap_style"), "Auto is not saved");
|
||||
// A text saved before its paragraphs carried a wrap style.
|
||||
let mut t = TextData::new("a\nb");
|
||||
t.set_para_lists(0, 0, |p| p.list = ListStyle::Bulleted);
|
||||
let mut v = serde_json::to_value(&t).unwrap();
|
||||
for p in v["paragraphs"].as_array_mut().unwrap() {
|
||||
assert!(p.as_object_mut().unwrap().remove("wrap").is_none(), "Auto is not saved");
|
||||
}
|
||||
v.as_object_mut().unwrap().remove("wrap_style");
|
||||
let old: TextData = serde_json::from_value(v).unwrap();
|
||||
assert_eq!(old.wrap_style, Auto);
|
||||
assert_eq!(old.paragraphs.len(), 2);
|
||||
assert!(old.paragraphs.iter().all(|p| p.wrap.is_none()));
|
||||
assert_eq!((old.para_wrap_style(0), old.para_wrap_style(1)), (Auto, Auto));
|
||||
assert_eq!(old, t);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_wrap_style_clears_an_override_on_the_empty_last_paragraph() {
|
||||
use WrapStyle::{Auto, Balance, Pretty};
|
||||
let mut t = TextData::new("ab\n");
|
||||
assert_eq!(t.paragraph_count(), 2);
|
||||
// The empty paragraph after the final newline: a caret there reaches it.
|
||||
t.set_para_wrap_styles(3, 3, Balance);
|
||||
assert_eq!((t.para_wrap_style(0), t.para_wrap_style(1)), (Auto, Balance));
|
||||
t.set_wrap_style(Pretty);
|
||||
assert!(t.paragraphs.is_empty(), "{:?}", t.paragraphs);
|
||||
assert_eq!((t.para_wrap_style(0), t.para_wrap_style(1)), (Pretty, Pretty));
|
||||
// Back to the layer's own style: no override is left on either paragraph.
|
||||
t.set_para_wrap_styles(3, 3, Auto);
|
||||
assert_eq!(t.para_wrap_style(1), Auto);
|
||||
t.set_wrap_style(Auto);
|
||||
assert!(t.paragraphs.is_empty());
|
||||
assert_eq!(t.common_wrap_style(0, t.char_count()), Some(Auto));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -541,6 +541,46 @@ fn wrap_style_per_paragraph_overrides_the_layers() {
|
||||
assert!(s.execute("node.set", &json!({"id": t, "props": {"wrapStyle": "zigzag", "start": 0, "end": 0}})).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn text_wrap_style_without_editing_sets_the_layer_and_folds_paragraph_overrides() {
|
||||
let mut s = Session::new();
|
||||
let t = text(&mut s, "one\ntwo\nthree", json!({}));
|
||||
let inspect = |s: &Session| crate::inspect::node_json(s.doc().unwrap(), t, 0);
|
||||
let td = |s: &Session| s.doc().unwrap().node(t).unwrap().kind.text().unwrap().clone();
|
||||
s.execute("node.set", &json!({"id": t, "props": {"wrapStyle": "balance", "start": 5, "end": 5}})).unwrap();
|
||||
s.execute("node.set", &json!({"id": t, "props": {"wrapStyle": "pretty", "start": 9, "end": 9}})).unwrap();
|
||||
assert_eq!(inspect(&s)["lineWrapStyles"], json!(["auto", "balance", "pretty"]));
|
||||
assert!(s.ds().unwrap().text_edit.is_none(), "no edit session");
|
||||
s.execute("text.wrapStyle", &json!({"ids": [t], "style": "balance"})).unwrap();
|
||||
let after = td(&s);
|
||||
assert!(after.paragraphs.iter().all(|p| p.wrap.is_none()), "{:?}", after.paragraphs);
|
||||
assert_eq!(after.wrap_style, figcraft_doc::WrapStyle::Balance);
|
||||
assert!((0..3).all(|p| after.para_wrap_style(p) == figcraft_doc::WrapStyle::Balance));
|
||||
assert_eq!(inspect(&s)["wrapStyle"], json!("balance"));
|
||||
assert!(inspect(&s).get("lineWrapStyles").is_none());
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
assert_eq!(inspect(&s)["lineWrapStyles"], json!(["auto", "balance", "pretty"]), "undo restores the overrides");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inspect_reports_wrap_style_only_when_not_auto_and_counts_a_trailing_empty_paragraph() {
|
||||
let mut s = Session::new();
|
||||
let t = text(&mut s, "one\ntwo\n", json!({}));
|
||||
let inspect = |s: &Session| crate::inspect::node_json(s.doc().unwrap(), t, 0);
|
||||
let quiet = |s: &Session| inspect(s).get("wrapStyle").is_none() && inspect(s).get("lineWrapStyles").is_none();
|
||||
assert!(quiet(&s), "uniform Auto says nothing");
|
||||
// An override on the empty last paragraph (a caret after the final newline) is listed with the others.
|
||||
s.execute("node.set", &json!({"id": t, "props": {"wrapStyle": "balance", "start": 8, "end": 8}})).unwrap();
|
||||
assert_eq!(inspect(&s)["lineWrapStyles"], json!(["auto", "auto", "balance"]));
|
||||
assert!(inspect(&s).get("wrapStyle").is_none());
|
||||
// Every paragraph the same non-Auto style: the one key; back to Auto: neither.
|
||||
s.execute("node.set", &json!({"id": t, "props": {"wrapStyle": "balance"}})).unwrap();
|
||||
assert_eq!(inspect(&s)["wrapStyle"], json!("balance"));
|
||||
assert!(inspect(&s).get("lineWrapStyles").is_none());
|
||||
s.execute("node.set", &json!({"id": t, "props": {"wrapStyle": "auto"}})).unwrap();
|
||||
assert!(quiet(&s));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn max_lines_on_auto_width_text_keeps_auto_width_and_hugs_that_many_lines() {
|
||||
// "Max lines is available only if truncate text is enabled, and: Text resizing is set to
|
||||
|
||||
@@ -210,6 +210,7 @@ pub(crate) fn field_row<R>(ui: &mut Ui, label: &str, add: impl FnOnce(&mut Ui, f
|
||||
#[cfg(test)]
|
||||
mod variable_tests {
|
||||
use egui::{Event, Modifiers, PointerButton, Pos2, RawInput};
|
||||
use figcraft_doc::WrapStyle;
|
||||
use figcraft_engine::Session;
|
||||
|
||||
use super::*;
|
||||
@@ -223,6 +224,8 @@ mod variable_tests {
|
||||
ctx: egui::Context,
|
||||
time: f64,
|
||||
text: NodeId,
|
||||
/// The AccessKit nodes of the last frame.
|
||||
nodes: Vec<egui::accesskit::Node>,
|
||||
}
|
||||
|
||||
impl Panel {
|
||||
@@ -236,7 +239,8 @@ mod variable_tests {
|
||||
s.execute("variable.create", &json!({"collection": "Type", "name": name, "type": ty, "values": {"Base": value}})).unwrap();
|
||||
}
|
||||
s.execute("select.set", &json!({ "ids": [text] })).unwrap();
|
||||
let mut p = Self { app: FigApp::new(s, Box::new(NoServices)), ctx: egui::Context::default(), time: 0.0, text };
|
||||
let mut p = Self { app: FigApp::new(s, Box::new(NoServices)), ctx: egui::Context::default(), time: 0.0, text, nodes: Vec::new() };
|
||||
p.ctx.enable_accesskit();
|
||||
crate::theme::install_fonts(&p.ctx, false, 0);
|
||||
crate::theme::apply(&p.ctx, Tokens::light());
|
||||
for _ in 0..3 {
|
||||
@@ -269,6 +273,9 @@ mod variable_tests {
|
||||
crate::variable_ui::picker(&mut self.app, ui.ctx());
|
||||
});
|
||||
out.textures_delta.clear();
|
||||
if let Some(update) = out.platform_output.accesskit_update.take() {
|
||||
self.nodes = update.nodes.into_iter().map(|(_, n)| n).collect();
|
||||
}
|
||||
let mut texts = Vec::new();
|
||||
for c in out.shapes {
|
||||
walk(&c.shape, &mut texts);
|
||||
@@ -351,4 +358,75 @@ mod variable_tests {
|
||||
assert!(shows(&texts, "Inter") && shows(&texts, "Bold"), "the dropdowns show the last values");
|
||||
assert!(p.app.ui.last_error.is_none(), "{:?}", p.app.ui.last_error);
|
||||
}
|
||||
|
||||
/// The centre of the AccessKit node labelled `label` (a button, say).
|
||||
fn node_center(p: &Panel, label: &str) -> Pos2 {
|
||||
let n = p.nodes.iter().find(|n| n.label() == Some(label)).unwrap_or_else(|| panic!("no node {label:?}"));
|
||||
let r = n.bounds().expect("bounds");
|
||||
Pos2::new(((r.x0 + r.x1) / 2.0) as f32, ((r.y0 + r.y1) / 2.0) as f32)
|
||||
}
|
||||
|
||||
const WRAP: [&str; 3] = [
|
||||
"Auto: as many words as fit on each line",
|
||||
"Balance: every line close to the same length",
|
||||
"Pretty: full lines, no one word alone on the last",
|
||||
];
|
||||
|
||||
/// The Wrap style buttons that are on, by short name.
|
||||
fn wrap_on(p: &Panel) -> Vec<&'static str> {
|
||||
let on = |label: &str| p.nodes.iter().find(|n| n.label() == Some(label)).unwrap_or_else(|| panic!("no node {label:?}")).toggled();
|
||||
["Auto", "Balance", "Pretty"].into_iter().zip(WRAP).filter(|(_, l)| on(l) == Some(egui::accesskit::Toggled::True)).map(|(n, _)| n).collect()
|
||||
}
|
||||
|
||||
/// The panel with the Type settings popover open, over two paragraphs.
|
||||
fn settings_panel() -> Panel {
|
||||
let mut p = Panel::new();
|
||||
p.app.session.execute("node.set", &json!({"id": p.text, "props": {"characters": "one\ntwo"}})).unwrap();
|
||||
p.frame(vec![]);
|
||||
let at = node_center(&p, "Type settings");
|
||||
p.click(at);
|
||||
p.frame(vec![]);
|
||||
p
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrap_style_control_follows_the_caret_paragraph() {
|
||||
// #1827: with the caret in one paragraph the control shows and sets that paragraph's style.
|
||||
let mut p = settings_panel();
|
||||
assert_eq!(wrap_on(&p), ["Auto"], "the layer's style when not editing");
|
||||
figcraft_engine::textedit::begin(&mut p.app.session, p.text, Some(5), false).unwrap();
|
||||
p.frame(vec![]);
|
||||
assert_eq!(wrap_on(&p), ["Auto"]);
|
||||
let at = node_center(&p, WRAP[1]);
|
||||
p.click(at);
|
||||
let td = |p: &Panel| p.app.session.doc().unwrap().node(p.text).unwrap().kind.text().unwrap().clone();
|
||||
assert_eq!((td(&p).para_wrap_style(0), td(&p).para_wrap_style(1)), (WrapStyle::Auto, WrapStyle::Balance), "only the caret's paragraph");
|
||||
assert_eq!(td(&p).wrap_style, WrapStyle::Auto);
|
||||
assert_eq!(wrap_on(&p), ["Balance"], "the control shows the paragraph's value");
|
||||
figcraft_engine::textedit::set_caret(&mut p.app.session, 1, false).unwrap();
|
||||
p.frame(vec![]);
|
||||
assert_eq!(wrap_on(&p), ["Auto"], "another paragraph shows its own");
|
||||
assert!(p.app.ui.last_error.is_none(), "{:?}", p.app.ui.last_error);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrap_style_control_has_nothing_active_for_mixed_paragraphs() {
|
||||
let mut p = settings_panel();
|
||||
figcraft_engine::textedit::begin(&mut p.app.session, p.text, Some(5), false).unwrap();
|
||||
p.frame(vec![]);
|
||||
let at = node_center(&p, WRAP[2]);
|
||||
p.click(at);
|
||||
assert_eq!(wrap_on(&p), ["Pretty"]);
|
||||
// A selection across both paragraphs, which differ: none is on.
|
||||
figcraft_engine::textedit::set_caret(&mut p.app.session, 1, false).unwrap();
|
||||
figcraft_engine::textedit::set_caret(&mut p.app.session, 6, true).unwrap();
|
||||
p.frame(vec![]);
|
||||
assert!(wrap_on(&p).is_empty(), "{:?}", wrap_on(&p));
|
||||
// Choosing one sets both paragraphs.
|
||||
let at = node_center(&p, WRAP[1]);
|
||||
p.click(at);
|
||||
assert_eq!(wrap_on(&p), ["Balance"]);
|
||||
let t = p.app.session.doc().unwrap().node(p.text).unwrap().kind.text().unwrap().clone();
|
||||
assert_eq!((t.para_wrap_style(0), t.para_wrap_style(1)), (WrapStyle::Balance, WrapStyle::Balance));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user