mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-29 12:56:47 -06:00
* E21: require release font coverage and stabilize capture deadline tests * E21: separate capture, IME and field-save regression phases * M0: correct the migration qualification reference * M-egui037: document input lifecycle and UI regression contracts * M0: correct egui 0.37 status docs and stabilize join-code regression --------- Co-authored-by: Dylan M. Taylor <dylan@dylanmtaylor.com> Co-authored-by: Codebuff <noreply@codebuff.com>
163 lines
7.6 KiB
Rust
163 lines
7.6 KiB
Rust
//! Dev Mode in the UI, driven headless (the harness is `present_tests`'): the compare view opens
|
|
//! from `dev.compare {show}`, switches to the overlay and closes with Escape before the focus view
|
|
//! does; the focus view's Escape; the annotation filter; the playground's instance-swap menu.
|
|
|
|
use egui::accesskit::{Node, Role};
|
|
use egui::{Event, Key, MouseWheelUnit, Pos2, TouchPhase, pos2, vec2};
|
|
use figcraft_doc::NodeId;
|
|
use figcraft_engine::Session;
|
|
use serde_json::json;
|
|
|
|
use crate::dev_compare::Mode;
|
|
use crate::dev_mode::CategoryFilter;
|
|
use crate::present_tests::{Ui, id};
|
|
|
|
fn setup() -> (Ui, figcraft_doc::NodeId, figcraft_doc::NodeId) {
|
|
let mut s = Session::new();
|
|
let f = id(&s.execute("frame.create", &json!({"x": 0, "y": 0, "width": 300, "height": 400, "name": "Login"})).unwrap());
|
|
let r = id(&s
|
|
.execute("shape.rectangle", &json!({"x": 20, "y": 300, "width": 120, "height": 40, "parent": f, "props": {"fill": "#FF0000"}}))
|
|
.unwrap());
|
|
s.execute("node.set", &json!({"id": f, "props": {"devStatus": "readyForDev"}})).unwrap();
|
|
s.execute("node.set", &json!({"id": r, "props": {"fill": "#00AA00", "width": 160}})).unwrap();
|
|
let mut u = Ui::new(s);
|
|
u.app.ui.right_tab = crate::RightTab::Inspect;
|
|
u.frame(vec![]);
|
|
(u, f, r)
|
|
}
|
|
|
|
#[test]
|
|
fn compare_view_opens_switches_and_closes_before_the_focus_view() {
|
|
let (mut u, f, _) = setup();
|
|
u.run("dev.focus", json!({"id": f}));
|
|
u.run("dev.compare", json!({"id": f, "show": true}));
|
|
assert_eq!(u.app.inspect()["devCompare"]["id"], json!(f.0));
|
|
assert_eq!(u.app.inspect()["devFocus"], json!(f.0));
|
|
if let Some(c) = u.app.ui.dev.compare.as_mut() {
|
|
c.mode = Mode::Overlay;
|
|
c.split = 0.3;
|
|
}
|
|
u.frames(2, Vec::new);
|
|
assert_eq!(u.app.inspect()["devCompare"]["mode"], json!("overlay"));
|
|
// Escape closes the compare view only; the next one leaves the focus view.
|
|
let at = pos2(700.0, 450.0);
|
|
u.key(at, Key::Escape);
|
|
u.frame(vec![]);
|
|
assert!(u.app.ui.dev.compare.is_none());
|
|
assert_eq!(u.app.inspect()["devFocus"], json!(f.0));
|
|
u.key(at, Key::Escape);
|
|
u.frame(vec![]);
|
|
assert_eq!(u.app.inspect()["devFocus"], json!(null));
|
|
}
|
|
|
|
#[test]
|
|
fn compare_view_follows_edits_and_resolving() {
|
|
let (mut u, f, r) = setup();
|
|
u.run("dev.compare", json!({"id": f, "show": true}));
|
|
u.run("node.set", json!({"id": r, "props": {"height": 48}}));
|
|
u.frames(2, Vec::new);
|
|
assert!(u.app.ui.dev.compare.is_some(), "still open after an edit");
|
|
u.run("dev.resolveChange", json!({"ids": [f]}));
|
|
u.frames(2, Vec::new);
|
|
// Nothing changed since: the view says so instead of comparing.
|
|
assert!(u.app.ui.dev.compare.is_some());
|
|
u.run("edit.delete", json!({"ids": [f]}));
|
|
u.frames(2, Vec::new);
|
|
assert!(u.app.ui.dev.compare.is_none(), "closes when its frame is gone");
|
|
}
|
|
|
|
#[test]
|
|
fn annotation_filter_and_categories_panel() {
|
|
let (mut u, _, r) = setup();
|
|
u.run("annotation.add", json!({"id": r, "label": "Tap target", "category": "Accessibility"}));
|
|
u.run("annotation.add", json!({"id": r, "label": "Plain"}));
|
|
u.run("select.set", json!({"ids": []}));
|
|
for f in [CategoryFilter::All, CategoryFilter::Uncategorized, CategoryFilter::Only("accessibility".into())] {
|
|
u.app.ui.dev.categories = f.clone();
|
|
u.frames(2, Vec::new);
|
|
assert_eq!(u.app.inspect()["annotationFilter"], json!(f.id()));
|
|
}
|
|
let doc = u.app.session.doc().unwrap().clone();
|
|
assert_eq!(CategoryFilter::parse(&doc, "Accessibility"), Some(CategoryFilter::Only("accessibility".into())));
|
|
assert_eq!(CategoryFilter::parse(&doc, "none"), Some(CategoryFilter::Uncategorized));
|
|
assert_eq!(CategoryFilter::parse(&doc, "nope"), None);
|
|
}
|
|
|
|
/// A "Button" component whose nested "Heart" instance is its instance-swap property "Icon", a
|
|
/// second icon "Star", and an instance of the button selected in the Inspect tab. Returns (ui,
|
|
/// instance, heart, star).
|
|
fn swap_setup() -> (crate::layers_drag_tests::Ui, NodeId, NodeId, NodeId) {
|
|
let mut s = Session::new();
|
|
let icon = |s: &mut Session, name: &str, x: i32| {
|
|
let f = id(&s.execute("frame.create", &json!({"x": x, "y": 0, "width": 24, "height": 24, "name": name})).unwrap());
|
|
id(&s.execute("component.create", &json!({"ids": [f]})).unwrap())
|
|
};
|
|
let (heart, star) = (icon(&mut s, "Heart", 0), icon(&mut s, "Star", 40));
|
|
let f = id(&s.execute("frame.create", &json!({"x": 0, "y": 100, "width": 120, "height": 40, "name": "Button"})).unwrap());
|
|
let nested = id(&s.execute("instance.create", &json!({"component": heart, "x": 8, "y": 108, "parent": f})).unwrap());
|
|
let button = id(&s.execute("component.create", &json!({"ids": [f]})).unwrap());
|
|
s.execute("component.addProperty", &json!({"id": button, "name": "Icon", "type": "instanceSwap", "default": heart, "layer": nested})).unwrap();
|
|
let inst = id(&s.execute("instance.create", &json!({"component": button, "x": 0, "y": 300})).unwrap());
|
|
s.execute("select.set", &json!({"ids": [inst]})).unwrap();
|
|
let mut u = crate::layers_drag_tests::Ui::new(s);
|
|
u.app.ui.right_tab = crate::RightTab::Inspect;
|
|
u.ctx.enable_accesskit();
|
|
(u, inst, heart, star)
|
|
}
|
|
|
|
fn tree(u: &mut crate::layers_drag_tests::Ui) -> Vec<Node> {
|
|
u.frame_output(vec![]).platform_output.accesskit_update.unwrap().nodes.into_iter().map(|(_, n)| n).collect()
|
|
}
|
|
|
|
fn centre(n: &Node) -> Pos2 {
|
|
let b = n.bounds().unwrap();
|
|
pos2(((b.x0 + b.x1) / 2.0) as f32, ((b.y0 + b.y1) / 2.0) as f32)
|
|
}
|
|
|
|
fn click(u: &mut crate::layers_drag_tests::Ui, at: Pos2) {
|
|
u.button(at, true);
|
|
u.button(at, false);
|
|
}
|
|
|
|
/// The playground's dropdown for property `name`, scrolled into view.
|
|
fn playground_combo(u: &mut crate::layers_drag_tests::Ui, name: &str) -> Option<Node> {
|
|
for _ in 0..20 {
|
|
let combo = tree(u).into_iter().find(|n| n.role() == Role::ComboBox && n.label() == Some(name))?;
|
|
if centre(&combo).y < 860.0 {
|
|
return Some(combo);
|
|
}
|
|
let wheel = Event::MouseWheel {
|
|
unit: MouseWheelUnit::Point,
|
|
source: egui::MouseWheelSource::Unknown,
|
|
delta: vec2(0.0, -100.0),
|
|
phase: TouchPhase::Move,
|
|
modifiers: Default::default(),
|
|
};
|
|
u.frame(vec![Event::PointerMoved(pos2(1300.0, 500.0)), wheel]);
|
|
for _ in 0..30 {
|
|
u.frame(vec![]);
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
#[test]
|
|
fn the_playground_swaps_an_instance_in_the_preview_only() {
|
|
let (mut u, inst, heart, star) = swap_setup();
|
|
let (doc, undo) = (u.app.session.ds().unwrap().doc.clone(), u.app.session.undo_depth());
|
|
let combo = playground_combo(&mut u, "Icon").expect("the playground has a dropdown for the instance-swap property");
|
|
assert_eq!(combo.value(), Some("Heart"));
|
|
// Open it and pick Star: the item that wasn't there before the menu opened.
|
|
let before: Vec<Pos2> = tree(&mut u).iter().filter(|n| n.label() == Some("Star")).map(centre).collect();
|
|
click(&mut u, centre(&combo));
|
|
let item = tree(&mut u).into_iter().find(|n| n.label() == Some("Star") && !before.contains(¢re(n))).expect("Star in the menu");
|
|
click(&mut u, centre(&item));
|
|
let combo = playground_combo(&mut u, "Icon").unwrap();
|
|
assert_eq!(combo.value(), Some("Star"), "the playground shows the swapped icon");
|
|
// The file isn't touched: the instance still has the heart, nothing to undo.
|
|
assert!(std::sync::Arc::ptr_eq(&doc, &u.app.session.ds().unwrap().doc));
|
|
assert_eq!(u.app.session.undo_depth(), undo);
|
|
let props = u.app.session.execute("component.properties", &json!({"id": inst})).unwrap();
|
|
assert_eq!(props["properties"][0]["value"], json!(heart.0), "{star:?}");
|
|
}
|