Files
Ely-GPUI-Components/src/canvas/tests/boards.rs
T

379 lines
12 KiB
Rust

use gpui::{AnyElement, Entity, IntoElement, TestAppContext, VisualTestContext};
use super::{
Stage, said, say, settle, stage,
tools::{at, drag, layer, pair, press},
};
use crate::canvas::{
Board, Edge, Link, MindMap, Node, NodeGraph, Port, Tool, Topic, Viewport, Whiteboard,
};
fn tap(key: &str, cx: &mut VisualTestContext) {
cx.simulate_keystrokes(key);
cx.simulate_event(gpui::KeyUpEvent {
keystroke: gpui::Keystroke::parse(key).expect("a key"),
});
settle(cx);
}
#[gpui::test]
fn the_connector_links_the_shape_pressed_to_the_one_released_on(cx: &mut TestAppContext) {
let (host, cx) = stage(|_, owner| layer(Tool::Connector, &[], owner), cx);
drag((50.0, 50.0), (250.0, 60.0), cx);
drag((50.0, 50.0), (150.0, 200.0), cx);
assert_eq!(
said(&host, cx),
["link a b"],
"a release on nothing links nothing"
);
}
#[gpui::test]
fn a_double_press_writes_on_a_shape_and_enter_keeps_the_words(cx: &mut TestAppContext) {
let (host, cx) = stage(|_, owner| layer(Tool::Select, &["a"], owner), cx);
press(50.0, 50.0, 1, cx);
press(50.0, 50.0, 2, cx);
cx.simulate_input("Start");
tap("enter", cx);
assert_eq!(said(&host, cx), ["text a Start"]);
}
fn nodes() -> Vec<Node> {
vec![
Node::new("load", "Load", (0.0, 0.0)).output(Port::new("image", "Image", "image")),
Node::new("blur", "Blur", (300.0, 0.0))
.input(Port::new("image", "Image", "image"))
.output(Port::new("image", "Image", "image")),
]
}
fn graph(wired: bool, owner: Entity<Stage>) -> AnyElement {
let (joined, parted) = (owner.clone(), owner);
let edges = wired.then(|| Edge::new(("load", "image"), ("blur", "image")));
NodeGraph::new("graph", Viewport::new(0.0, 0.0, 1.0), nodes(), edges)
.on_connect(move |edge, _, cx| {
say(
&joined,
format!(
"connect {}.{} {}.{}",
edge.from.0, edge.from.1, edge.to.0, edge.to.1
),
cx,
)
})
.on_disconnect(move |edge, _, cx| {
say(
&parted,
format!("disconnect {}.{}", edge.to.0, edge.to.1),
cx,
)
})
.into_any_element()
}
/// Sockets sit on the node's sides a title and half a row down: 32 + 14.
#[gpui::test]
fn a_drag_from_an_output_wires_it_to_an_input(cx: &mut TestAppContext) {
let (host, cx) = stage(|_, owner| graph(false, owner), cx);
drag((200.0, 46.0), (301.0, 47.0), cx);
assert_eq!(said(&host, cx), ["connect load.image blur.image"]);
}
#[gpui::test]
fn a_press_on_a_wired_input_lifts_its_wire(cx: &mut TestAppContext) {
let (host, cx) = stage(|_, owner| graph(true, owner), cx);
drag((300.0, 46.0), (250.0, 250.0), cx);
assert_eq!(said(&host, cx), ["disconnect blur.image"]);
}
fn map(_: &Stage, owner: Entity<Stage>) -> AnyElement {
let root = Topic::new("root", "Launch")
.child(Topic::new("a", "Site"))
.child(Topic::new("b", "Press"))
.child(Topic::new("c", "Budget"));
let [picked, added, removed, renamed] = [(); 4].map(|_| owner.clone());
MindMap::new("map", root, Viewport::new(-60.0, -150.0, 1.0))
.selected(Some("a"))
.on_select(move |key, _, cx| say(&picked, format!("select {key:?}"), cx))
.on_add(move |key, _, cx| say(&added, format!("add under {key}"), cx))
.on_remove(move |key, _, cx| say(&removed, format!("remove {key}"), cx))
.on_rename(move |key, text, _, cx| say(&renamed, format!("rename {key} {text}"), cx))
.into_any_element()
}
#[gpui::test]
fn keys_on_a_mind_map_ask_for_children_siblings_and_removals(cx: &mut TestAppContext) {
let (host, cx) = stage(map, cx);
press(276.0, 126.0, 1, cx);
for key in ["tab", "enter", "down", "backspace"] {
tap(key, cx);
}
assert_eq!(
said(&host, cx),
[
"select Some(\"a\")",
"add under a",
"add under root",
"select Some(\"b\")",
"remove a",
],
"a press selects and focuses; the keys act on the owner's selection"
);
}
fn board(_: &Stage, owner: Entity<Stage>) -> AnyElement {
Whiteboard::new(
"board",
Board {
shapes: pair(),
links: vec![Link::new("ab", "a", "b")],
},
)
.on_change(move |board, _, cx| {
let keys: Vec<String> = board
.shapes
.iter()
.map(|shape| shape.key.to_string())
.collect();
say(
&owner,
format!("shapes {keys:?} links {}", board.links.len()),
cx,
)
})
.into_any_element()
}
#[gpui::test]
fn backspace_takes_the_selection_off_the_board(cx: &mut TestAppContext) {
let (host, cx) = stage(board, cx);
press(250.0, 50.0, 1, cx);
tap("backspace", cx);
assert_eq!(
said(&host, cx),
["shapes [\"a\"] links 0"],
"a link goes with its shape"
);
}
#[gpui::test]
fn tab_in_a_topic_being_rewritten_keeps_the_words_and_adds_nothing(cx: &mut TestAppContext) {
let (host, cx) = stage(map, cx);
press(276.0, 126.0, 1, cx);
tap("f2", cx);
cx.simulate_input("Web");
tap("tab", cx);
assert_eq!(said(&host, cx), ["select Some(\"a\")", "rename a Web"]);
}
fn three(_: &Stage, owner: Entity<Stage>) -> AnyElement {
let (joined, parted) = (owner.clone(), owner);
let gain = Node::new("gain", "Gain", (0.0, 150.0)).output(Port::new("image", "Image", "image"));
let wired = Edge::new(("load", "image"), ("blur", "image"));
let edge = |edge: &Edge| {
format!(
"{}.{} {}.{}",
edge.from.0, edge.from.1, edge.to.0, edge.to.1
)
};
NodeGraph::new(
"graph",
Viewport::new(0.0, 0.0, 1.0),
nodes().into_iter().chain([gain]),
[wired],
)
.on_connect(move |made, _, cx| say(&joined, format!("connect {}", edge(made)), cx))
.on_disconnect(move |gone, _, cx| say(&parted, format!("disconnect {}", edge(gone)), cx))
.into_any_element()
}
/// Gain's output sits on its right side, a title and half a row down: 150 + 32 + 14.
#[gpui::test]
fn a_new_wire_into_a_wired_input_replaces_the_old(cx: &mut TestAppContext) {
let (host, cx) = stage(three, cx);
drag((200.0, 196.0), (301.0, 47.0), cx);
assert_eq!(
said(&host, cx),
[
"disconnect load.image blur.image",
"connect gain.image blur.image"
]
);
}
#[gpui::test]
fn a_link_released_on_its_own_shape_links_nothing(cx: &mut TestAppContext) {
let (host, cx) = stage(|_, owner| layer(Tool::Connector, &[], owner), cx);
drag((50.0, 50.0), (60.0, 70.0), cx);
assert!(said(&host, cx).is_empty());
}
#[gpui::test]
fn enter_and_escape_in_a_topic_hand_the_keys_back_to_the_map(cx: &mut TestAppContext) {
let (host, cx) = stage(map, cx);
press(276.0, 126.0, 1, cx);
tap("f2", cx);
cx.simulate_input("Web");
tap("enter", cx);
tap("down", cx);
tap("f2", cx);
cx.simulate_input("Gone");
tap("escape", cx);
tap("down", cx);
assert_eq!(
said(&host, cx),
[
"select Some(\"a\")",
"rename a Web",
"select Some(\"b\")",
"select Some(\"b\")",
],
"the owner keeps a selected, so each Down steps from a"
);
}
#[gpui::test]
fn after_writing_on_a_shape_its_board_still_takes_backspace(cx: &mut TestAppContext) {
let (host, cx) = stage(board, cx);
press(250.0, 50.0, 1, cx);
press(250.0, 50.0, 2, cx);
cx.simulate_input("Idea");
tap("enter", cx);
tap("backspace", cx);
assert_eq!(
said(&host, cx),
["shapes [\"a\", \"b\"] links 1", "shapes [\"a\"] links 0"]
);
}
/// Presses at `from` and drags to `to` in four moves.
fn pull(from: (f32, f32), to: (f32, f32), cx: &mut VisualTestContext) {
use gpui::{Modifiers, MouseButton};
cx.simulate_mouse_move(at(from.0, from.1), None, Modifiers::none());
cx.simulate_mouse_down(at(from.0, from.1), MouseButton::Left, Modifiers::none());
for step in 1..=4 {
let share = step as f32 / 4.0;
let (x, y) = (
from.0 + (to.0 - from.0) * share,
from.1 + (to.1 - from.1) * share,
);
cx.simulate_mouse_move(at(x, y), Some(MouseButton::Left), Modifiers::none());
settle(cx);
}
}
fn release(to: (f32, f32), cx: &mut VisualTestContext) {
let (left, none) = (gpui::MouseButton::Left, gpui::Modifiers::none());
cx.simulate_mouse_up(at(to.0, to.1), left, none);
settle(cx);
}
/// The owner changes what it hands the part, mid-gesture.
fn change(host: &Entity<Stage>, cx: &mut VisualTestContext) {
host.update(cx, |stage, cx| {
stage.said.push("owner changed".into());
cx.notify();
});
settle(cx);
}
#[gpui::test]
fn f2_on_a_selected_topic_that_left_renames_nothing(cx: &mut TestAppContext) {
let (_, cx) = stage(
|_, _| {
let view = Viewport::new(-200.0, -150.0, 1.0);
MindMap::new("map", Topic::new("root", "Root"), view)
.selected(Some("gone"))
.into_any_element()
},
cx,
);
press(200.0, 150.0, 1, cx);
tap("f2", cx);
}
/// Two wireable nodes whose source leaves, or changes kind, once the owner changes.
fn shifting(stage: &Stage, owner: Entity<Stage>, retype: bool) -> AnyElement {
let mut current = nodes();
if !stage.said.is_empty() {
match retype {
true => current[0].outputs[0].kind = "number".into(),
false => drop(current.remove(0)),
}
}
NodeGraph::new("graph", Viewport::new(0.0, 0.0, 1.0), current, [])
.on_connect(move |edge, _, cx| {
let words = format!(
"connect {}.{} {}.{}",
edge.from.0, edge.from.1, edge.to.0, edge.to.1
);
say(&owner, words, cx)
})
.into_any_element()
}
#[gpui::test]
fn a_wire_whose_source_left_connects_nothing(cx: &mut TestAppContext) {
let (host, cx) = stage(|stage, owner| shifting(stage, owner, false), cx);
pull((200.0, 46.0), (301.0, 47.0), cx);
change(&host, cx);
release((301.0, 47.0), cx);
assert_eq!(said(&host, cx), ["owner changed"]);
}
#[gpui::test]
fn a_wire_whose_source_changed_kind_connects_nothing(cx: &mut TestAppContext) {
let (host, cx) = stage(|stage, owner| shifting(stage, owner, true), cx);
pull((200.0, 46.0), (301.0, 47.0), cx);
change(&host, cx);
release((301.0, 47.0), cx);
assert_eq!(said(&host, cx), ["owner changed"]);
}
#[gpui::test]
fn a_link_whose_shape_left_links_nothing(cx: &mut TestAppContext) {
let (host, cx) = stage(
|stage, owner| {
let mut shapes = pair();
if !stage.said.is_empty() {
shapes.remove(0);
}
let view = Viewport::new(0.0, 0.0, 1.0);
let tool =
crate::canvas::ToolLayer::new("layer", Tool::Connector, view, shapes.clone())
.on_link(move |from, to, _, cx| say(&owner, format!("link {from} {to}"), cx));
crate::canvas::InfiniteCanvas::new("plane", view)
.shapes(shapes)
.layer(tool)
.into_any_element()
},
cx,
);
pull((50.0, 50.0), (250.0, 60.0), cx);
change(&host, cx);
release((250.0, 60.0), cx);
assert_eq!(said(&host, cx), ["owner changed"]);
}
#[gpui::test]
fn a_layer_that_left_mid_drag_restacks_nothing(cx: &mut TestAppContext) {
let (host, cx) = stage(
|stage, owner| {
let mut shapes = pair();
if !stage.said.is_empty() {
shapes.remove(0);
}
crate::canvas::LayerPanel::new("layers", shapes)
.on_restack(move |keys, _, cx| say(&owner, format!("restack {keys:?}"), cx))
.into_any_element()
},
cx,
);
let a = cx.debug_bounds("tree-chevron-a").expect("a row").center();
let b = cx.debug_bounds("tree-chevron-b").expect("b row").center();
pull((100.0, f32::from(a.y)), (100.0, f32::from(b.y)), cx);
change(&host, cx);
release((100.0, f32::from(b.y)), cx);
assert_eq!(said(&host, cx), ["owner changed"]);
}