mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-29 12:46:53 -06:00
refactor(ui-egui): move the font_menu_ui_tests module of panels.rs to panels/font_menu_ui_tests.rs
This commit is contained in:
@@ -540,317 +540,7 @@ mod font_menu_tests {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod font_menu_ui_tests {
|
||||
use egui::vec2;
|
||||
use egui_kittest::Harness;
|
||||
use egui_kittest::kittest::Queryable;
|
||||
use serde_json::json;
|
||||
|
||||
use crate::test_window::{self, Window, click_at};
|
||||
|
||||
/// The Type tool's selection over a new text frame's text, with the Control bar on. Returns the
|
||||
/// story.
|
||||
fn typing() -> (Harness<'static, Window>, u64) {
|
||||
typing_in("Advanced")
|
||||
}
|
||||
|
||||
/// [`typing`] in the workspace `name`.
|
||||
fn typing_in(name: &str) -> (Harness<'static, Window>, u64) {
|
||||
typing_on(name, vec2(1440.0, 900.0), false)
|
||||
}
|
||||
|
||||
/// [`typing_in`] a window of `size`, with the macOS window chrome (the native menu bar and the
|
||||
/// title bar inside the window) when `mac`.
|
||||
fn typing_on(name: &str, size: egui::Vec2, mac: bool) -> (Harness<'static, Window>, u64) {
|
||||
let mut app = crate::DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());
|
||||
app.run("window.workspace", json!({ "name": name })).unwrap();
|
||||
app.native_menu = mac;
|
||||
app.integrated_titlebar = mac;
|
||||
let mut h = test_window::open(app, size);
|
||||
let app = &mut h.state_mut().app;
|
||||
let r = app.run("frame.create", json!({"rect": [72, 72, 300, 200], "content": "text", "text": "Hello", "caret": true})).unwrap();
|
||||
app.run("tool.select", json!({"tool": "type"})).unwrap();
|
||||
app.run("text.select", json!({"story": r["story"], "anchor": 0, "focus": 5})).unwrap();
|
||||
h.run_steps(4);
|
||||
(h, r["story"].as_u64().unwrap())
|
||||
}
|
||||
|
||||
fn story_text(h: &mut Harness<'static, Window>, story: u64) -> String {
|
||||
h.state_mut().app.run("story.get", json!({"story": story})).unwrap()["text"].as_str().unwrap().to_string()
|
||||
}
|
||||
|
||||
/// The font fields showing `family`, top to bottom.
|
||||
fn fields(h: &Harness<'static, Window>, family: &str) -> Vec<egui::Rect> {
|
||||
let mut r: Vec<egui::Rect> =
|
||||
h.query_all_by(|n| n.role() == egui::accesskit::Role::ComboBox && n.value().as_deref() == Some(family)).map(|n| n.rect()).collect();
|
||||
r.sort_by(|a, b| a.min.y.total_cmp(&b.min.y).then(a.min.x.total_cmp(&b.min.x)));
|
||||
r
|
||||
}
|
||||
|
||||
/// The Control bar's font field (the topmost), showing `family`.
|
||||
fn field(h: &Harness<'static, Window>, family: &str) -> egui::Rect {
|
||||
*fields(h, family).first().unwrap_or_else(|| panic!("a font field shows {family}"))
|
||||
}
|
||||
|
||||
/// The text in the focused search field.
|
||||
fn search(h: &Harness<'static, Window>) -> String {
|
||||
h.get_by(|n| n.role() == egui::accesskit::Role::TextInput && n.is_focused()).value().unwrap_or_default()
|
||||
}
|
||||
|
||||
fn family(h: &mut Harness<'static, Window>) -> String {
|
||||
h.state_mut().app.run("type.selectionAttrs", json!({})).unwrap()["chars"]["fontFamily"].as_str().unwrap().to_string()
|
||||
}
|
||||
|
||||
/// Run the menu until the filters' font kinds are read (it says Loading fonts… meanwhile).
|
||||
fn settle(h: &mut Harness<'static, Window>) {
|
||||
for _ in 0..3000 {
|
||||
if h.query_by_label("Loading fonts…").is_none() {
|
||||
return;
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
h.run_steps(1);
|
||||
}
|
||||
panic!("the font kinds were never read");
|
||||
}
|
||||
|
||||
/// Open the Control bar's font menu (showing `family`) and search for `query`.
|
||||
fn open_menu(h: &mut Harness<'static, Window>, family: &str, query: &str) {
|
||||
let at = field(h, family);
|
||||
click_at(h, at.center());
|
||||
if !query.is_empty() {
|
||||
h.event(egui::Event::Text(query.into()));
|
||||
}
|
||||
h.run_steps(4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_family_opens_to_its_styles_and_a_style_is_picked() {
|
||||
let (mut h, _) = typing();
|
||||
let serif = designcraft_fonts::DEFAULT_FAMILY;
|
||||
open_menu(&mut h, serif, "serif 4");
|
||||
// The arrow before the name opens the family; the menu stays open.
|
||||
{
|
||||
let at = h.get_by_label("Show Styles").rect().center();
|
||||
click_at(&mut h, at);
|
||||
}
|
||||
assert!(h.query_by_label("Bold Italic").is_some(), "its styles are listed");
|
||||
{
|
||||
let at = h.get_by_label("Bold Italic").rect().center();
|
||||
click_at(&mut h, at);
|
||||
}
|
||||
let attrs = h.state_mut().app.run("type.selectionAttrs", json!({})).unwrap();
|
||||
assert_eq!((attrs["chars"]["fontFamily"].as_str(), attrs["chars"]["fontStyle"].as_str()), (Some(serif), Some("Bold Italic")));
|
||||
assert!(!h.ctx.text_edit_focused(), "picking closes the menu");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn picked_fonts_come_first_as_recently_used() {
|
||||
let (mut h, _) = typing();
|
||||
open_menu(&mut h, designcraft_fonts::DEFAULT_FAMILY, "source sans");
|
||||
{
|
||||
let at = h.get_by_label("Source Sans 3").rect().center();
|
||||
click_at(&mut h, at);
|
||||
}
|
||||
assert_eq!(h.state().app.session.prefs.recent_fonts, ["Source Sans 3"]);
|
||||
// Reopened without a search: Source Sans 3 is listed at the top, and again in its place (on
|
||||
// screen too when few fonts are installed).
|
||||
open_menu(&mut h, "Source Sans 3", "");
|
||||
let recent =
|
||||
h.query_all_by_label("Source Sans 3").map(|n| n.rect()).min_by(|a, b| a.min.y.total_cmp(&b.min.y)).expect("Source Sans 3 is listed");
|
||||
let search = h.get_by(|n| n.role() == egui::accesskit::Role::TextInput && n.is_focused()).rect();
|
||||
assert!(recent.min.y > search.max.y && recent.min.y - search.max.y < 80.0, "the first row, under the filters: {recent:?} vs {search:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_class_filter_and_similar_fonts_narrow_the_list() {
|
||||
let (mut h, _) = typing();
|
||||
open_menu(&mut h, designcraft_fonts::DEFAULT_FAMILY, "");
|
||||
{
|
||||
let at = h.get_by(|n| n.label().is_some_and(|l| l.starts_with("All Classes"))).rect().center();
|
||||
click_at(&mut h, at);
|
||||
}
|
||||
{
|
||||
let at = h.get_by_label("Monospaced").rect().center();
|
||||
click_at(&mut h, at);
|
||||
}
|
||||
settle(&mut h);
|
||||
assert!(h.query_by_label("JetBrains Mono").is_some(), "a monospaced family is listed");
|
||||
assert!(h.query_by_label(designcraft_fonts::DEFAULT_FAMILY).is_none(), "a serif isn't");
|
||||
// Back to all classes, then fonts like the serif: it leads, other serifs follow, no sans.
|
||||
{
|
||||
let at = h.get_by(|n| n.label().is_some_and(|l| l.starts_with("Monospaced ▾"))).rect().center();
|
||||
click_at(&mut h, at);
|
||||
}
|
||||
{
|
||||
let at = h.get_by_label("All Classes").rect().center();
|
||||
click_at(&mut h, at);
|
||||
}
|
||||
h.event(egui::Event::Text("serif 4".into()));
|
||||
h.run_steps(4);
|
||||
let row = h.get_by_label(designcraft_fonts::DEFAULT_FAMILY).rect();
|
||||
h.hover_at(row.center());
|
||||
h.run_steps(2);
|
||||
{
|
||||
let at = h.get_by_label("Show Similar Fonts").rect().center();
|
||||
click_at(&mut h, at);
|
||||
}
|
||||
assert!(h.query_by_label(&format!("Similar to {}", designcraft_fonts::DEFAULT_FAMILY)).is_some());
|
||||
assert!(h.query_by_label("Source Sans 3").is_none() && h.query_by_label("JetBrains Mono").is_none(), "other classes aren't similar");
|
||||
assert!(h.query_by_label(designcraft_fonts::DEFAULT_FAMILY).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_font_field_reports_as_a_combo_box_showing_the_family() {
|
||||
let (h, _) = typing();
|
||||
let shown = fields(&h, designcraft_fonts::DEFAULT_FAMILY);
|
||||
// The Control bar's, and the hand-drawn ones (Properties, the Contextual Task Bar).
|
||||
assert!(shown.len() >= 2, "{shown:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typing_in_the_font_menu_searches_and_never_reaches_the_story() {
|
||||
let (mut h, story) = typing();
|
||||
let before = story_text(&mut h, story);
|
||||
let at = field(&h, designcraft_fonts::DEFAULT_FAMILY);
|
||||
click_at(&mut h, at.center());
|
||||
assert!(h.ctx.text_edit_focused(), "the search field takes the keyboard when the menu opens");
|
||||
h.event(egui::Event::Text("source sans".into()));
|
||||
h.run_steps(4);
|
||||
assert_eq!(search(&h), "source sans");
|
||||
assert_eq!(story_text(&mut h, story), before, "the typing went to the search, not the story");
|
||||
assert!(h.query_by_label("Source Sans 3").is_some(), "a match is listed");
|
||||
assert!(h.query_by_label(designcraft_fonts::DEFAULT_FAMILY).is_none(), "families that don't match aren't");
|
||||
// ↓ and Return pick the first match and close the menu.
|
||||
h.key_press(egui::Key::ArrowDown);
|
||||
h.run_steps(2);
|
||||
h.key_press(egui::Key::Enter);
|
||||
h.run_steps(4);
|
||||
assert_eq!(family(&mut h), "Source Sans 3");
|
||||
assert_eq!(story_text(&mut h, story), before);
|
||||
assert!(!h.ctx.text_edit_focused(), "the menu closed");
|
||||
// Reopened, the search starts empty.
|
||||
let at = field(&h, "Source Sans 3");
|
||||
click_at(&mut h, at.center());
|
||||
assert_eq!(search(&h), "", "the search starts empty");
|
||||
// A click on a family picks it.
|
||||
h.event(egui::Event::Text("serif 4".into()));
|
||||
h.run_steps(4);
|
||||
let row = h.get_by_label(designcraft_fonts::DEFAULT_FAMILY).rect();
|
||||
click_at(&mut h, row.center());
|
||||
assert_eq!(family(&mut h), designcraft_fonts::DEFAULT_FAMILY);
|
||||
// Escape closes the menu and leaves the text as it was.
|
||||
let at = field(&h, designcraft_fonts::DEFAULT_FAMILY);
|
||||
click_at(&mut h, at.center());
|
||||
// A star marks a favourite and keeps the menu open.
|
||||
h.event(egui::Event::Text("serif 4".into()));
|
||||
h.run_steps(4);
|
||||
let row = h.get_by_label(designcraft_fonts::DEFAULT_FAMILY).rect();
|
||||
click_at(&mut h, row.min + vec2(10.0, 12.0));
|
||||
assert!(h.state().app.session.prefs.favorite_fonts.iter().any(|f| f == designcraft_fonts::DEFAULT_FAMILY));
|
||||
assert_eq!(search(&h), "serif 4", "the menu stays open");
|
||||
h.key_press(egui::Key::Escape);
|
||||
h.run_steps(4);
|
||||
assert!(!h.ctx.text_edit_focused(), "the menu closed");
|
||||
assert_eq!(story_text(&mut h, story), before);
|
||||
assert!(h.state().app.session.active().unwrap().selection.text.is_some(), "the text is still selected");
|
||||
}
|
||||
|
||||
/// Opens the font menu of the field at `at`, clicks its search field, types, checks the menu
|
||||
/// stays open and lists only the matches, then picks the first with ↓ and Return.
|
||||
fn search_in_menu(h: &mut Harness<'static, Window>, at: egui::Rect, what: &str) {
|
||||
click_at(h, at.center());
|
||||
let field = h.query_by(|n| n.role() == egui::accesskit::Role::TextInput && n.is_focused()).map(|n| n.rect());
|
||||
let field = field.unwrap_or_else(|| panic!("{what}: the menu opens with the search field focused"));
|
||||
click_at(h, field.center());
|
||||
assert!(h.ctx.text_edit_focused(), "{what}: a click on the search field keeps the menu open");
|
||||
h.event(egui::Event::Text("SOURCE SA".into()));
|
||||
h.run_steps(4);
|
||||
assert_eq!(search(h), "SOURCE SA", "{what}: the typing went to the search");
|
||||
assert!(h.query_by_label("Source Sans 3").is_some(), "{what}: a match is listed");
|
||||
assert!(h.query_by_label(designcraft_fonts::DEFAULT_FAMILY).is_none(), "{what}: families that don't match aren't");
|
||||
h.key_press(egui::Key::ArrowDown);
|
||||
h.run_steps(2);
|
||||
h.key_press(egui::Key::Enter);
|
||||
h.run_steps(4);
|
||||
assert!(!h.ctx.text_edit_focused(), "{what}: the menu closed");
|
||||
}
|
||||
|
||||
/// Every font field over selected text (Control bar, Contextual Task Bar, Properties ›
|
||||
/// Character, Character panel) searches, in the workspaces that show them and in a small
|
||||
/// window with the macOS window chrome.
|
||||
#[test]
|
||||
fn every_font_field_searches_in_every_workspace() {
|
||||
for (name, size, mac, panel) in [
|
||||
("Advanced", vec2(1440.0, 900.0), false, None),
|
||||
("Printing and Proofing", vec2(1440.0, 900.0), false, None),
|
||||
("Essentials", vec2(1440.0, 900.0), false, Some("character")),
|
||||
("Printing and Proofing", vec2(900.0, 560.0), true, None),
|
||||
] {
|
||||
let (mut h, story) = typing_on(name, size, mac);
|
||||
if panel.is_some() {
|
||||
h.state_mut().app.ui.open_panel = panel.map(str::to_string);
|
||||
h.run_steps(4);
|
||||
}
|
||||
let name = &format!("{name} {size:?} {panel:?}");
|
||||
let before = story_text(&mut h, story);
|
||||
let count = fields(&h, designcraft_fonts::DEFAULT_FAMILY).len();
|
||||
assert_eq!(count, 3, "{name}: the font fields are shown");
|
||||
for k in 0..count {
|
||||
h.state_mut().app.run("type.char", json!({"attrs": {"fontFamily": designcraft_fonts::DEFAULT_FAMILY}})).unwrap();
|
||||
h.run_steps(4);
|
||||
// A short Properties group (below it, the workspace's open panel) scrolls to the field.
|
||||
let mut nodes: Vec<_> = h
|
||||
.query_all_by(|n| n.role() == egui::accesskit::Role::ComboBox && n.value().as_deref() == Some(designcraft_fonts::DEFAULT_FAMILY))
|
||||
.collect();
|
||||
nodes.sort_by(|a, b| a.rect().min.y.total_cmp(&b.rect().min.y).then(a.rect().min.x.total_cmp(&b.rect().min.x)));
|
||||
if let Some(node) = nodes.get(k) {
|
||||
node.scroll_to_me();
|
||||
}
|
||||
h.run_steps(4);
|
||||
let at = fields(&h, designcraft_fonts::DEFAULT_FAMILY)[k];
|
||||
search_in_menu(&mut h, at, &format!("{name}, field {k}"));
|
||||
assert_eq!(family(&mut h), "Source Sans 3", "{name}, field {k}");
|
||||
assert_eq!(story_text(&mut h, story), before, "{name}, field {k}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The font fields away from the text: the Glyphs panel, Paragraph Style Options › Basic
|
||||
/// Character Formats and Find/Replace Font's Replace With.
|
||||
#[test]
|
||||
fn font_fields_in_panels_and_dialogs_search() {
|
||||
let paragraph = json!({"name": designcraft_doc::BASIC_PARAGRAPH, "section": "chars"});
|
||||
for (what, panel, dialog, shown) in [
|
||||
("Glyphs", Some("glyphs"), None, designcraft_fonts::DEFAULT_FAMILY),
|
||||
("Paragraph Style Options", None, Some(("paragraphStyleOptions", paragraph)), designcraft_fonts::DEFAULT_FAMILY),
|
||||
(
|
||||
"Find/Replace Font",
|
||||
None,
|
||||
Some(("findFont", json!({"toFamily": designcraft_fonts::DEFAULT_FAMILY}))),
|
||||
designcraft_fonts::DEFAULT_FAMILY,
|
||||
),
|
||||
] {
|
||||
let app = crate::DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());
|
||||
let mut h = test_window::open(app, vec2(1440.0, 900.0));
|
||||
let dialog_id = dialog.as_ref().map(|(id, _)| id.to_string());
|
||||
h.state_mut().app.ui.open_panel = panel.map(str::to_string);
|
||||
h.state_mut().app.ui.dialog = dialog.map(|(id, fields)| crate::dialogs::Dialog::new(id, fields));
|
||||
h.run_steps(4);
|
||||
let at = *fields(&h, shown).first().unwrap_or_else(|| panic!("{what}: the font field is shown"));
|
||||
search_in_menu(&mut h, at, what);
|
||||
assert_eq!(h.state().app.ui.dialog.as_ref().map(|d| d.id.clone()), dialog_id, "{what}: Return in the menu leaves the dialog open");
|
||||
assert!(!fields(&h, "Source Sans 3").is_empty(), "{what}: the field shows the family picked");
|
||||
// Escape closes the menu only.
|
||||
let at = fields(&h, "Source Sans 3")[0];
|
||||
click_at(&mut h, at.center());
|
||||
assert!(h.ctx.text_edit_focused(), "{what}: the menu opened");
|
||||
h.key_press(egui::Key::Escape);
|
||||
h.run_steps(4);
|
||||
assert!(!h.ctx.text_edit_focused(), "{what}: Escape closed the menu");
|
||||
assert_eq!(h.state().app.ui.dialog.as_ref().map(|d| d.id.clone()), dialog_id, "{what}: Escape in the menu leaves the dialog open");
|
||||
}
|
||||
}
|
||||
}
|
||||
mod font_menu_ui_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
mod swatch_list_tests {
|
||||
|
||||
@@ -0,0 +1,303 @@
|
||||
use egui::vec2;
|
||||
use egui_kittest::Harness;
|
||||
use egui_kittest::kittest::Queryable;
|
||||
use serde_json::json;
|
||||
|
||||
use crate::test_window::{self, Window, click_at};
|
||||
|
||||
/// The Type tool's selection over a new text frame's text, with the Control bar on. Returns the
|
||||
/// story.
|
||||
fn typing() -> (Harness<'static, Window>, u64) {
|
||||
typing_in("Advanced")
|
||||
}
|
||||
|
||||
/// [`typing`] in the workspace `name`.
|
||||
fn typing_in(name: &str) -> (Harness<'static, Window>, u64) {
|
||||
typing_on(name, vec2(1440.0, 900.0), false)
|
||||
}
|
||||
|
||||
/// [`typing_in`] a window of `size`, with the macOS window chrome (the native menu bar and the
|
||||
/// title bar inside the window) when `mac`.
|
||||
fn typing_on(name: &str, size: egui::Vec2, mac: bool) -> (Harness<'static, Window>, u64) {
|
||||
let mut app = crate::DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());
|
||||
app.run("window.workspace", json!({ "name": name })).unwrap();
|
||||
app.native_menu = mac;
|
||||
app.integrated_titlebar = mac;
|
||||
let mut h = test_window::open(app, size);
|
||||
let app = &mut h.state_mut().app;
|
||||
let r = app.run("frame.create", json!({"rect": [72, 72, 300, 200], "content": "text", "text": "Hello", "caret": true})).unwrap();
|
||||
app.run("tool.select", json!({"tool": "type"})).unwrap();
|
||||
app.run("text.select", json!({"story": r["story"], "anchor": 0, "focus": 5})).unwrap();
|
||||
h.run_steps(4);
|
||||
(h, r["story"].as_u64().unwrap())
|
||||
}
|
||||
|
||||
fn story_text(h: &mut Harness<'static, Window>, story: u64) -> String {
|
||||
h.state_mut().app.run("story.get", json!({"story": story})).unwrap()["text"].as_str().unwrap().to_string()
|
||||
}
|
||||
|
||||
/// The font fields showing `family`, top to bottom.
|
||||
fn fields(h: &Harness<'static, Window>, family: &str) -> Vec<egui::Rect> {
|
||||
let mut r: Vec<egui::Rect> =
|
||||
h.query_all_by(|n| n.role() == egui::accesskit::Role::ComboBox && n.value().as_deref() == Some(family)).map(|n| n.rect()).collect();
|
||||
r.sort_by(|a, b| a.min.y.total_cmp(&b.min.y).then(a.min.x.total_cmp(&b.min.x)));
|
||||
r
|
||||
}
|
||||
|
||||
/// The Control bar's font field (the topmost), showing `family`.
|
||||
fn field(h: &Harness<'static, Window>, family: &str) -> egui::Rect {
|
||||
*fields(h, family).first().unwrap_or_else(|| panic!("a font field shows {family}"))
|
||||
}
|
||||
|
||||
/// The text in the focused search field.
|
||||
fn search(h: &Harness<'static, Window>) -> String {
|
||||
h.get_by(|n| n.role() == egui::accesskit::Role::TextInput && n.is_focused()).value().unwrap_or_default()
|
||||
}
|
||||
|
||||
fn family(h: &mut Harness<'static, Window>) -> String {
|
||||
h.state_mut().app.run("type.selectionAttrs", json!({})).unwrap()["chars"]["fontFamily"].as_str().unwrap().to_string()
|
||||
}
|
||||
|
||||
/// Run the menu until the filters' font kinds are read (it says Loading fonts… meanwhile).
|
||||
fn settle(h: &mut Harness<'static, Window>) {
|
||||
for _ in 0..3000 {
|
||||
if h.query_by_label("Loading fonts…").is_none() {
|
||||
return;
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
h.run_steps(1);
|
||||
}
|
||||
panic!("the font kinds were never read");
|
||||
}
|
||||
|
||||
/// Open the Control bar's font menu (showing `family`) and search for `query`.
|
||||
fn open_menu(h: &mut Harness<'static, Window>, family: &str, query: &str) {
|
||||
let at = field(h, family);
|
||||
click_at(h, at.center());
|
||||
if !query.is_empty() {
|
||||
h.event(egui::Event::Text(query.into()));
|
||||
}
|
||||
h.run_steps(4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_family_opens_to_its_styles_and_a_style_is_picked() {
|
||||
let (mut h, _) = typing();
|
||||
let serif = designcraft_fonts::DEFAULT_FAMILY;
|
||||
open_menu(&mut h, serif, "serif 4");
|
||||
// The arrow before the name opens the family; the menu stays open.
|
||||
{
|
||||
let at = h.get_by_label("Show Styles").rect().center();
|
||||
click_at(&mut h, at);
|
||||
}
|
||||
assert!(h.query_by_label("Bold Italic").is_some(), "its styles are listed");
|
||||
{
|
||||
let at = h.get_by_label("Bold Italic").rect().center();
|
||||
click_at(&mut h, at);
|
||||
}
|
||||
let attrs = h.state_mut().app.run("type.selectionAttrs", json!({})).unwrap();
|
||||
assert_eq!((attrs["chars"]["fontFamily"].as_str(), attrs["chars"]["fontStyle"].as_str()), (Some(serif), Some("Bold Italic")));
|
||||
assert!(!h.ctx.text_edit_focused(), "picking closes the menu");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn picked_fonts_come_first_as_recently_used() {
|
||||
let (mut h, _) = typing();
|
||||
open_menu(&mut h, designcraft_fonts::DEFAULT_FAMILY, "source sans");
|
||||
{
|
||||
let at = h.get_by_label("Source Sans 3").rect().center();
|
||||
click_at(&mut h, at);
|
||||
}
|
||||
assert_eq!(h.state().app.session.prefs.recent_fonts, ["Source Sans 3"]);
|
||||
// Reopened without a search: Source Sans 3 is listed at the top, and again in its place (on
|
||||
// screen too when few fonts are installed).
|
||||
open_menu(&mut h, "Source Sans 3", "");
|
||||
let recent = h.query_all_by_label("Source Sans 3").map(|n| n.rect()).min_by(|a, b| a.min.y.total_cmp(&b.min.y)).expect("Source Sans 3 is listed");
|
||||
let search = h.get_by(|n| n.role() == egui::accesskit::Role::TextInput && n.is_focused()).rect();
|
||||
assert!(recent.min.y > search.max.y && recent.min.y - search.max.y < 80.0, "the first row, under the filters: {recent:?} vs {search:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_class_filter_and_similar_fonts_narrow_the_list() {
|
||||
let (mut h, _) = typing();
|
||||
open_menu(&mut h, designcraft_fonts::DEFAULT_FAMILY, "");
|
||||
{
|
||||
let at = h.get_by(|n| n.label().is_some_and(|l| l.starts_with("All Classes"))).rect().center();
|
||||
click_at(&mut h, at);
|
||||
}
|
||||
{
|
||||
let at = h.get_by_label("Monospaced").rect().center();
|
||||
click_at(&mut h, at);
|
||||
}
|
||||
settle(&mut h);
|
||||
assert!(h.query_by_label("JetBrains Mono").is_some(), "a monospaced family is listed");
|
||||
assert!(h.query_by_label(designcraft_fonts::DEFAULT_FAMILY).is_none(), "a serif isn't");
|
||||
// Back to all classes, then fonts like the serif: it leads, other serifs follow, no sans.
|
||||
{
|
||||
let at = h.get_by(|n| n.label().is_some_and(|l| l.starts_with("Monospaced ▾"))).rect().center();
|
||||
click_at(&mut h, at);
|
||||
}
|
||||
{
|
||||
let at = h.get_by_label("All Classes").rect().center();
|
||||
click_at(&mut h, at);
|
||||
}
|
||||
h.event(egui::Event::Text("serif 4".into()));
|
||||
h.run_steps(4);
|
||||
let row = h.get_by_label(designcraft_fonts::DEFAULT_FAMILY).rect();
|
||||
h.hover_at(row.center());
|
||||
h.run_steps(2);
|
||||
{
|
||||
let at = h.get_by_label("Show Similar Fonts").rect().center();
|
||||
click_at(&mut h, at);
|
||||
}
|
||||
assert!(h.query_by_label(&format!("Similar to {}", designcraft_fonts::DEFAULT_FAMILY)).is_some());
|
||||
assert!(h.query_by_label("Source Sans 3").is_none() && h.query_by_label("JetBrains Mono").is_none(), "other classes aren't similar");
|
||||
assert!(h.query_by_label(designcraft_fonts::DEFAULT_FAMILY).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_font_field_reports_as_a_combo_box_showing_the_family() {
|
||||
let (h, _) = typing();
|
||||
let shown = fields(&h, designcraft_fonts::DEFAULT_FAMILY);
|
||||
// The Control bar's, and the hand-drawn ones (Properties, the Contextual Task Bar).
|
||||
assert!(shown.len() >= 2, "{shown:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typing_in_the_font_menu_searches_and_never_reaches_the_story() {
|
||||
let (mut h, story) = typing();
|
||||
let before = story_text(&mut h, story);
|
||||
let at = field(&h, designcraft_fonts::DEFAULT_FAMILY);
|
||||
click_at(&mut h, at.center());
|
||||
assert!(h.ctx.text_edit_focused(), "the search field takes the keyboard when the menu opens");
|
||||
h.event(egui::Event::Text("source sans".into()));
|
||||
h.run_steps(4);
|
||||
assert_eq!(search(&h), "source sans");
|
||||
assert_eq!(story_text(&mut h, story), before, "the typing went to the search, not the story");
|
||||
assert!(h.query_by_label("Source Sans 3").is_some(), "a match is listed");
|
||||
assert!(h.query_by_label(designcraft_fonts::DEFAULT_FAMILY).is_none(), "families that don't match aren't");
|
||||
// ↓ and Return pick the first match and close the menu.
|
||||
h.key_press(egui::Key::ArrowDown);
|
||||
h.run_steps(2);
|
||||
h.key_press(egui::Key::Enter);
|
||||
h.run_steps(4);
|
||||
assert_eq!(family(&mut h), "Source Sans 3");
|
||||
assert_eq!(story_text(&mut h, story), before);
|
||||
assert!(!h.ctx.text_edit_focused(), "the menu closed");
|
||||
// Reopened, the search starts empty.
|
||||
let at = field(&h, "Source Sans 3");
|
||||
click_at(&mut h, at.center());
|
||||
assert_eq!(search(&h), "", "the search starts empty");
|
||||
// A click on a family picks it.
|
||||
h.event(egui::Event::Text("serif 4".into()));
|
||||
h.run_steps(4);
|
||||
let row = h.get_by_label(designcraft_fonts::DEFAULT_FAMILY).rect();
|
||||
click_at(&mut h, row.center());
|
||||
assert_eq!(family(&mut h), designcraft_fonts::DEFAULT_FAMILY);
|
||||
// Escape closes the menu and leaves the text as it was.
|
||||
let at = field(&h, designcraft_fonts::DEFAULT_FAMILY);
|
||||
click_at(&mut h, at.center());
|
||||
// A star marks a favourite and keeps the menu open.
|
||||
h.event(egui::Event::Text("serif 4".into()));
|
||||
h.run_steps(4);
|
||||
let row = h.get_by_label(designcraft_fonts::DEFAULT_FAMILY).rect();
|
||||
click_at(&mut h, row.min + vec2(10.0, 12.0));
|
||||
assert!(h.state().app.session.prefs.favorite_fonts.iter().any(|f| f == designcraft_fonts::DEFAULT_FAMILY));
|
||||
assert_eq!(search(&h), "serif 4", "the menu stays open");
|
||||
h.key_press(egui::Key::Escape);
|
||||
h.run_steps(4);
|
||||
assert!(!h.ctx.text_edit_focused(), "the menu closed");
|
||||
assert_eq!(story_text(&mut h, story), before);
|
||||
assert!(h.state().app.session.active().unwrap().selection.text.is_some(), "the text is still selected");
|
||||
}
|
||||
|
||||
/// Opens the font menu of the field at `at`, clicks its search field, types, checks the menu
|
||||
/// stays open and lists only the matches, then picks the first with ↓ and Return.
|
||||
fn search_in_menu(h: &mut Harness<'static, Window>, at: egui::Rect, what: &str) {
|
||||
click_at(h, at.center());
|
||||
let field = h.query_by(|n| n.role() == egui::accesskit::Role::TextInput && n.is_focused()).map(|n| n.rect());
|
||||
let field = field.unwrap_or_else(|| panic!("{what}: the menu opens with the search field focused"));
|
||||
click_at(h, field.center());
|
||||
assert!(h.ctx.text_edit_focused(), "{what}: a click on the search field keeps the menu open");
|
||||
h.event(egui::Event::Text("SOURCE SA".into()));
|
||||
h.run_steps(4);
|
||||
assert_eq!(search(h), "SOURCE SA", "{what}: the typing went to the search");
|
||||
assert!(h.query_by_label("Source Sans 3").is_some(), "{what}: a match is listed");
|
||||
assert!(h.query_by_label(designcraft_fonts::DEFAULT_FAMILY).is_none(), "{what}: families that don't match aren't");
|
||||
h.key_press(egui::Key::ArrowDown);
|
||||
h.run_steps(2);
|
||||
h.key_press(egui::Key::Enter);
|
||||
h.run_steps(4);
|
||||
assert!(!h.ctx.text_edit_focused(), "{what}: the menu closed");
|
||||
}
|
||||
|
||||
/// Every font field over selected text (Control bar, Contextual Task Bar, Properties ›
|
||||
/// Character, Character panel) searches, in the workspaces that show them and in a small
|
||||
/// window with the macOS window chrome.
|
||||
#[test]
|
||||
fn every_font_field_searches_in_every_workspace() {
|
||||
for (name, size, mac, panel) in [
|
||||
("Advanced", vec2(1440.0, 900.0), false, None),
|
||||
("Printing and Proofing", vec2(1440.0, 900.0), false, None),
|
||||
("Essentials", vec2(1440.0, 900.0), false, Some("character")),
|
||||
("Printing and Proofing", vec2(900.0, 560.0), true, None),
|
||||
] {
|
||||
let (mut h, story) = typing_on(name, size, mac);
|
||||
if panel.is_some() {
|
||||
h.state_mut().app.ui.open_panel = panel.map(str::to_string);
|
||||
h.run_steps(4);
|
||||
}
|
||||
let name = &format!("{name} {size:?} {panel:?}");
|
||||
let before = story_text(&mut h, story);
|
||||
let count = fields(&h, designcraft_fonts::DEFAULT_FAMILY).len();
|
||||
assert_eq!(count, 3, "{name}: the font fields are shown");
|
||||
for k in 0..count {
|
||||
h.state_mut().app.run("type.char", json!({"attrs": {"fontFamily": designcraft_fonts::DEFAULT_FAMILY}})).unwrap();
|
||||
h.run_steps(4);
|
||||
// A short Properties group (below it, the workspace's open panel) scrolls to the field.
|
||||
let mut nodes: Vec<_> = h
|
||||
.query_all_by(|n| n.role() == egui::accesskit::Role::ComboBox && n.value().as_deref() == Some(designcraft_fonts::DEFAULT_FAMILY))
|
||||
.collect();
|
||||
nodes.sort_by(|a, b| a.rect().min.y.total_cmp(&b.rect().min.y).then(a.rect().min.x.total_cmp(&b.rect().min.x)));
|
||||
if let Some(node) = nodes.get(k) {
|
||||
node.scroll_to_me();
|
||||
}
|
||||
h.run_steps(4);
|
||||
let at = fields(&h, designcraft_fonts::DEFAULT_FAMILY)[k];
|
||||
search_in_menu(&mut h, at, &format!("{name}, field {k}"));
|
||||
assert_eq!(family(&mut h), "Source Sans 3", "{name}, field {k}");
|
||||
assert_eq!(story_text(&mut h, story), before, "{name}, field {k}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The font fields away from the text: the Glyphs panel, Paragraph Style Options › Basic
|
||||
/// Character Formats and Find/Replace Font's Replace With.
|
||||
#[test]
|
||||
fn font_fields_in_panels_and_dialogs_search() {
|
||||
let paragraph = json!({"name": designcraft_doc::BASIC_PARAGRAPH, "section": "chars"});
|
||||
for (what, panel, dialog, shown) in [
|
||||
("Glyphs", Some("glyphs"), None, designcraft_fonts::DEFAULT_FAMILY),
|
||||
("Paragraph Style Options", None, Some(("paragraphStyleOptions", paragraph)), designcraft_fonts::DEFAULT_FAMILY),
|
||||
("Find/Replace Font", None, Some(("findFont", json!({"toFamily": designcraft_fonts::DEFAULT_FAMILY}))), designcraft_fonts::DEFAULT_FAMILY),
|
||||
] {
|
||||
let app = crate::DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());
|
||||
let mut h = test_window::open(app, vec2(1440.0, 900.0));
|
||||
let dialog_id = dialog.as_ref().map(|(id, _)| id.to_string());
|
||||
h.state_mut().app.ui.open_panel = panel.map(str::to_string);
|
||||
h.state_mut().app.ui.dialog = dialog.map(|(id, fields)| crate::dialogs::Dialog::new(id, fields));
|
||||
h.run_steps(4);
|
||||
let at = *fields(&h, shown).first().unwrap_or_else(|| panic!("{what}: the font field is shown"));
|
||||
search_in_menu(&mut h, at, what);
|
||||
assert_eq!(h.state().app.ui.dialog.as_ref().map(|d| d.id.clone()), dialog_id, "{what}: Return in the menu leaves the dialog open");
|
||||
assert!(!fields(&h, "Source Sans 3").is_empty(), "{what}: the field shows the family picked");
|
||||
// Escape closes the menu only.
|
||||
let at = fields(&h, "Source Sans 3")[0];
|
||||
click_at(&mut h, at.center());
|
||||
assert!(h.ctx.text_edit_focused(), "{what}: the menu opened");
|
||||
h.key_press(egui::Key::Escape);
|
||||
h.run_steps(4);
|
||||
assert!(!h.ctx.text_edit_focused(), "{what}: Escape closed the menu");
|
||||
assert_eq!(h.state().app.ui.dialog.as_ref().map(|d| d.id.clone()), dialog_id, "{what}: Escape in the menu leaves the dialog open");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user