GraphEditor Unit Tests (#3352)

[ci no changelog needed]

This PR adds a few simple unit tests for GraphEditor, that can be used as an example of native Unit Tests.

Covered:
1. Creating nodes
- By internal API
- By using a TAB shortcut
- By using (+) button
- By dropping edge
2. Connecting two nodes with an edge

Some APIs were extended to allow their testing.

Usage of `glyph::System` in `text/component/area` was disabled by conditional compilation, as this code can't be used in native code due to JS dependencies.
This commit is contained in:
Ilya Bogdanov 2022-03-30 15:49:07 +03:00 committed by GitHub
parent fbd80ad4a3
commit e9f3b2327e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 381 additions and 46 deletions

View File

@ -969,3 +969,53 @@ impl display::Object for Node {
&self.model.display_object
}
}
// ==================
// === Test Utils ===
// ==================
/// Test-specific API.
pub mod test_utils {
use super::*;
/// Addional [`NodeModel`] API for tests.
pub trait NodeModelExt {
/// Return the `SinglePortView` of the first output port of the node.
///
/// Returns `None`:
/// 1. If there are no output ports.
/// 2. If the port does not have a `PortShapeView`. Some port models does not initialize
/// the `PortShapeView`, see [`output::port::Model::init_shape`].
/// 3. If the output port is [`MultiPortView`].
fn output_port_shape(&self) -> Option<output::port::SinglePortView>;
/// Return the `Shape` of the first input port of the node.
///
/// Returns `None`:
/// 1. If there are no input ports.
/// 2. If the port does not have a `Shape`. Some port models does not initialize the
/// `Shape`, see [`input::port::Model::init_shape`].
fn input_port_shape(&self) -> Option<input::port::Shape>;
}
impl NodeModelExt for NodeModel {
fn output_port_shape(&self) -> Option<output::port::SinglePortView> {
let ports = self.output.model.ports();
let port = ports.first()?;
let shape = port.shape.as_ref()?;
use output::port::PortShapeView::Single;
match shape {
Single(shape) => Some(shape.clone_ref()),
_ => None,
}
}
fn input_port_shape(&self) -> Option<input::port::Shape> {
let ports = self.input.model.ports();
let port = ports.first()?;
port.shape.as_ref().map(CloneRef::clone_ref)
}
}
}

View File

@ -286,6 +286,15 @@ impl Model {
self
}
/// Return a list of Node's input ports.
pub fn ports(&self) -> Vec<port::Model> {
let expression = self.expression.borrow();
let mut ports = Vec::new();
expression.span_tree.root_ref().dfs(|n| ports.push(n.payload.clone()));
ports
}
fn set_label_layer(&self, layer: &display::scene::Layer) {
self.label.add_to_scene_layer(layer);
}
@ -334,8 +343,8 @@ fn select_color(styles: &StyleWatch, tp: Option<&Type>) -> color::Lcha {
#[derive(Clone, CloneRef, Debug)]
pub struct Area {
#[allow(missing_docs)]
pub frp: Frp,
model: Rc<Model>,
pub frp: Frp,
pub(crate) model: Rc<Model>,
}
impl Deref for Area {

View File

@ -133,8 +133,6 @@ ensogl::define_endpoints! {
/// `set_expression` instead. In case the usage type is set to None, ports still may be
/// colored if the definition type was present.
set_expression_usage_type (Crumbs,Option<Type>),
/// Trigger `on_port_hover` output for testing purposes.
test_port_hover (),
}
Output {
@ -218,6 +216,18 @@ impl Model {
self
}
/// Return a list of Node's output ports.
pub fn ports(&self) -> Vec<port::Model> {
let port_count = self.port_count.get();
let mut ports = Vec::with_capacity(port_count);
self.traverse_borrowed_expression(|is_a_port, node, _| {
if is_a_port {
ports.push(node.payload.clone());
}
});
ports
}
fn set_label_layer(&self, layer: &display::scene::Layer) {
self.label.add_to_scene_layer(layer);
}
@ -351,8 +361,6 @@ impl Model {
frp::extend! { port_network
self.frp.source.on_port_hover <+ port_frp.on_hover.map
(f!([crumbs](t) Switch::new(crumbs.clone(),*t)));
self.frp.source.on_port_hover <+ self.frp.test_port_hover.map
(f_!([crumbs] Switch::new(crumbs.clone(),true)));
self.frp.source.on_port_press <+ port_frp.on_press.constant(crumbs.clone());
port_frp.set_size_multiplier <+ self.frp.port_size_multiplier;
@ -425,10 +433,10 @@ impl Model {
/// Please note that the origin of the node is on its left side, centered vertically. To learn more
/// about this design decision, please read the docs for the [`node::Node`].
#[derive(Clone, CloneRef, Debug)]
#[allow(missing_docs)]
pub struct Area {
#[allow(missing_docs)]
pub frp: Frp,
model: Rc<Model>,
pub frp: Frp,
pub model: Rc<Model>,
}
impl Deref for Area {

View File

@ -2393,6 +2393,18 @@ pub struct GraphEditor {
pub frp: Frp,
}
impl GraphEditor {
/// Graph editor nodes.
pub fn nodes(&self) -> &Nodes {
&self.model.nodes
}
/// Graph editor edges.
pub fn edges(&self) -> &Edges {
&self.model.edges
}
}
impl Deref for GraphEditor {
type Target = Frp;
fn deref(&self) -> &Self::Target {
@ -2735,9 +2747,6 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
create_edge_from_output <- node_output_touch.down.gate_not(&has_detached_edge_on_output_down);
create_edge_from_input <- node_input_touch.down.map(|value| value.clone());
// === Edge creation ===
on_new_edge <- any(&output_down,&input_down);
let selection_mode = selection::get_mode(network,inputs);
keep_selection <- selection_mode.map(|t| *t != selection::Mode::Normal);
@ -3635,3 +3644,172 @@ impl display::Object for GraphEditor {
self.model.display_object()
}
}
// =============
// === Tests ===
// =============
#[cfg(test)]
mod graph_editor_tests {
use super::*;
use application::test_utils::ApplicationExt;
use ensogl::control::io::mouse::PrimaryButton;
use ensogl::display::scene::test_utils::MouseExt;
use node::test_utils::NodeModelExt;
#[test]
fn test_adding_node_by_internal_api() {
let (_, graph_editor) = init();
assert_eq!(graph_editor.nodes().len(), 0);
graph_editor.add_node();
assert_eq!(graph_editor.nodes().len(), 1);
graph_editor.assert(Case { node_source: None, should_edit: false });
}
#[test]
fn test_adding_node_by_shortcut() {
let add_node = |editor: &GraphEditor| editor.start_node_creation();
test_adding_node(add_node);
}
#[test]
fn test_adding_node_by_adding_node_button() {
let add_node = |editor: &GraphEditor| {
let adding_node_button = &editor.model.add_node_button;
adding_node_button.click();
};
test_adding_node(add_node);
}
fn test_adding_node(add_node: impl Fn(&GraphEditor)) {
let (app, graph_editor) = init();
assert_eq!(graph_editor.nodes().len(), 0);
// Adding first node.
let (node_1_id, node_1) = graph_editor.add_node_by(&add_node);
graph_editor.assert(Case { node_source: None, should_edit: true });
graph_editor.stop_editing();
assert_eq!(graph_editor.nodes().len(), 1);
// First node is created in the center of the screen.
let node_1_pos = node_1.position();
let screen_center = app.display.default_scene.screen_to_scene_coordinates(Vector3::zeros());
assert_eq!(node_1_pos.xy(), screen_center.xy());
// Adding second node with the first node selected.
graph_editor.nodes().select(node_1_id);
let (_, node_2) = graph_editor.add_node_by(&add_node);
graph_editor.assert(Case { node_source: Some(node_1_id), should_edit: true });
assert_eq!(graph_editor.nodes().len(), 2);
// Second node is below the first and left-aligned to it.
let node_2_pos = node_2.position();
assert!(node_2_pos.y < node_1_pos.y);
assert_eq!(node_2_pos.x, node_1_pos.x);
}
#[test]
fn test_adding_node_by_dropping_edge() {
let (app, graph_editor) = init();
assert_eq!(graph_editor.nodes().len(), 0);
// Adding a new node.
let (node_1_id, node_1) = graph_editor.add_node_by_api();
graph_editor.stop_editing();
// Creating edge.
let port = node_1.model.output_port_shape().expect("No output port.");
port.events.mouse_down.emit(PrimaryButton);
port.events.mouse_up.emit(PrimaryButton);
assert_eq!(graph_editor.edges().len(), 1);
// Dropping edge.
let mouse = &app.display.default_scene.mouse;
let click_pos = Vector2(300.0, 300.0);
mouse.frp.position.emit(click_pos);
let click_on_background = |_: &GraphEditor| mouse.click_on_background();
let (_, node_2) = graph_editor.add_node_by(&click_on_background);
graph_editor.assert(Case { node_source: Some(node_1_id), should_edit: true });
let node_pos = node_2.position();
assert_eq!(node_pos.xy(), click_pos);
}
#[test]
fn test_connecting_two_nodes() {
let (_, ref graph_editor) = init();
let edges = graph_editor.edges();
assert!(graph_editor.nodes().is_empty());
assert!(edges.is_empty());
// Adding two nodes.
let (node_id_1, node_1) = graph_editor.add_node_by_api();
graph_editor.stop_editing();
let (node_id_2, node_2) = graph_editor.add_node_by_api();
graph_editor.stop_editing();
// Creating edge.
let port = node_1.model.output_port_shape().expect("No output port.");
port.events.mouse_down.emit(PrimaryButton);
port.events.mouse_up.emit(PrimaryButton);
let edge_id = graph_editor.on_edge_add.value();
let edge = edges.get_cloned_ref(&edge_id).expect("Edge was not added.");
assert_eq!(edge.source().map(|e| e.node_id), Some(node_id_1));
assert_eq!(edge.target().map(|e| e.node_id), None);
assert_eq!(edges.len(), 1);
// Connecting edge.
// We need to enable ports. Normally it is done by hovering the node.
node_2.model.input.frp.set_ports_active(true, None);
let port = node_2.model.input_port_shape().expect("No input port.");
port.hover.events.mouse_down.emit(PrimaryButton);
port.hover.events.mouse_up.emit(PrimaryButton);
assert_eq!(edge.source().map(|e| e.node_id), Some(node_id_1));
assert_eq!(edge.target().map(|e| e.node_id), Some(node_id_2));
}
// === Test utilities ===
/// An assertion case used when adding new nodes. See [`GraphEditor::assert`] below.
struct Case {
/// A source node of the added node.
node_source: Option<NodeId>,
/// Should we start the node editing immediately after adding it?
should_edit: bool,
}
impl GraphEditor {
fn add_node_by<F: Fn(&GraphEditor)>(&self, add_node: &F) -> (NodeId, Node) {
add_node(self);
let (node_id, ..) = self.node_added.value();
let node = self.nodes().get_cloned_ref(&node_id).expect("Node was not added.");
node.set_expression(node::Expression::new_plain("some_not_empty_expression"));
(node_id, node)
}
fn add_node_by_api(&self) -> (NodeId, Node) {
let add_node = |editor: &GraphEditor| editor.add_node();
self.add_node_by(&add_node)
}
fn assert(&self, case: Case) {
let (added_node, node_source, should_edit) = self.node_added.value();
let node_being_edited = self.node_being_edited.value();
assert_eq!(
should_edit, case.should_edit,
"Node editing state does not match expected."
);
assert_eq!(should_edit, node_being_edited.is_some());
if let Some(node_being_edited) = node_being_edited {
assert_eq!(node_being_edited, added_node, "Edited node does not match added one.");
}
let node_source = node_source.map(|source| source.node);
assert_eq!(node_source, case.node_source, "Source node does not match expected.");
}
}
fn init() -> (Application, GraphEditor) {
let app = Application::new("root");
app.set_screen_size_for_tests();
let graph_editor = new_graph_editor(&app);
let mouse = &app.display.default_scene.mouse;
mouse.frp.position.emit(Vector2::zeros());
(app, graph_editor)
}
}

View File

@ -224,7 +224,7 @@ impl Model {
}
fn searcher_left_top_position_when_under_node(&self, node_id: NodeId) -> Vector2<f32> {
if let Some(node) = self.graph_editor.model.nodes.get_cloned_ref(&node_id) {
if let Some(node) = self.graph_editor.nodes().get_cloned_ref(&node_id) {
Self::searcher_left_top_position_when_under_node_at(node.position().xy())
} else {
error!(self.logger, "Trying to show searcher under nonexisting node");
@ -252,7 +252,7 @@ impl Model {
}
fn show_fullscreen_visualization(&self, node_id: NodeId) {
let node = self.graph_editor.model.model.nodes.all.get_cloned_ref(&node_id);
let node = self.graph_editor.nodes().get_cloned_ref(&node_id);
if let Some(node) = node {
let visualization =
node.view.model.visualization.fullscreen_visualization().clone_ref();

View File

@ -26,7 +26,7 @@ use enso_gui::executor::web::EventLoopExecutor;
use enso_gui::initializer::setup_global_executor;
use enso_gui::Ide;
use enso_web::HtmlDivElement;
use ensogl::application::Application;
use ensogl::application::test_utils::ApplicationExt;
@ -57,8 +57,6 @@ pub struct IntegrationTest {
}
impl IntegrationTest {
const SCREEN_SIZE: (f32, f32) = (1920.0, 1000.0);
/// Initializes the executor and `Ide` structure and returns new Fixture.
pub async fn setup() -> Self {
let executor = setup_global_executor();
@ -69,16 +67,9 @@ impl IntegrationTest {
let initializer = enso_gui::ide::Initializer::new(default());
let ide = initializer.start().await.expect("Failed to initialize the application.");
Self::set_screen_size(&ide.ensogl_app);
ide.ensogl_app.set_screen_size_for_tests();
Self { executor, ide, root_div }
}
fn set_screen_size(app: &Application) {
let (screen_width, screen_height) = Self::SCREEN_SIZE;
app.display.default_scene.layers.iter_sublayers_and_masks_nested(|layer| {
layer.camera().set_screen(screen_width, screen_height)
});
}
}
impl Drop for IntegrationTest {

View File

@ -5,6 +5,7 @@
use enso_integration_test::prelude::*;
use approx::assert_abs_diff_eq;
use enso_gui::view::graph_editor::component::node::test_utils::NodeModelExt;
use enso_gui::view::graph_editor::component::node::Expression;
use enso_gui::view::graph_editor::GraphEditor;
use enso_gui::view::graph_editor::Node;
@ -24,15 +25,15 @@ async fn create_new_project_and_add_nodes() {
let test = IntegrationTestOnNewProject::setup().await;
let graph_editor = test.graph_editor();
assert_eq!(graph_editor.model.nodes.all.len(), 2);
assert_eq!(graph_editor.nodes().all.len(), 2);
let expect_node_added = graph_editor.node_added.next_event();
graph_editor.add_node();
let (added_node_id, source_node, _) = expect_node_added.expect();
assert_eq!(source_node, None);
assert_eq!(graph_editor.model.nodes.all.len(), 3);
assert_eq!(graph_editor.nodes().all.len(), 3);
let added_node =
graph_editor.model.nodes.get_cloned_ref(&added_node_id).expect("Added node is not added");
graph_editor.nodes().get_cloned_ref(&added_node_id).expect("Added node is not added");
assert_eq!(added_node.view.expression.value().to_string(), "");
}
@ -118,7 +119,7 @@ async fn adding_node_with_add_node_button() {
let graph_editor = test.graph_editor();
let scene = &test.ide.ensogl_app.display.default_scene;
let nodes = graph_editor.model.nodes.all.keys();
let nodes = graph_editor.nodes().all.keys();
let nodes_positions = nodes.into_iter().flat_map(|id| graph_editor.model.get_node_position(id));
let mut sorted_positions = nodes_positions.sorted_by_key(|pos| OrderedFloat(pos.y));
let bottom_most_pos =
@ -127,7 +128,7 @@ async fn adding_node_with_add_node_button() {
// Node is created below the bottom-most one.
let (first_node_id, node_source, _) = add_node_with_add_node_button(&graph_editor, "1 + 1");
assert!(node_source.is_none());
assert_eq!(graph_editor.model.nodes.all.len(), INITIAL_NODE_COUNT + 1);
assert_eq!(graph_editor.nodes().all.len(), INITIAL_NODE_COUNT + 1);
let node_position =
graph_editor.model.get_node_position(first_node_id).expect("Node was not added");
assert!(
@ -136,21 +137,21 @@ async fn adding_node_with_add_node_button() {
);
// Selected node is used as a `source` node.
graph_editor.model.nodes.deselect_all();
graph_editor.model.nodes.select(first_node_id);
graph_editor.nodes().deselect_all();
graph_editor.nodes().select(first_node_id);
let (_, node_source, _) = add_node_with_add_node_button(&graph_editor, "+ 1");
assert_eq!(node_source, Some(NodeSource { node: first_node_id }));
assert_eq!(graph_editor.model.nodes.all.len(), INITIAL_NODE_COUNT + 2);
assert_eq!(graph_editor.nodes().all.len(), INITIAL_NODE_COUNT + 2);
// If there is a free space, the new node is created in the center of screen.
let camera = scene.layers.main.camera();
camera.mod_position_xy(|pos| pos + Vector2(1000.0, 1000.0));
let wait_for_update = Duration::from_millis(500);
sleep(wait_for_update).await;
graph_editor.model.nodes.deselect_all();
graph_editor.nodes().deselect_all();
let (node_id, node_source, _) = add_node_with_add_node_button(&graph_editor, "1");
assert!(node_source.is_none());
assert_eq!(graph_editor.model.nodes.all.len(), INITIAL_NODE_COUNT + 3);
assert_eq!(graph_editor.nodes().all.len(), INITIAL_NODE_COUNT + 3);
let node_position = graph_editor.model.get_node_position(node_id).expect("Node was not added");
let center_of_screen = scene.screen_to_scene_coordinates(Vector3::zeros());
assert_abs_diff_eq!(node_position.x, center_of_screen.x, epsilon = 10.0);
@ -163,9 +164,9 @@ async fn adding_node_by_clicking_on_the_output_port() {
let graph_editor = test.graph_editor();
let (node_1_id, _, node_1) = add_node_with_internal_api(&graph_editor, "1 + 1");
let output = &node_1.model.output;
let method = |editor: &GraphEditor| {
output.test_port_hover();
let port = node_1.model.output_port_shape().expect("No output port");
port.events.mouse_over.emit(());
editor.start_node_creation_from_port();
};
let (_, source, node_2) = add_node(&graph_editor, "+ 1", method);
@ -182,7 +183,7 @@ fn add_node(
let node_added = graph_editor.node_added.next_event();
method(graph_editor);
let (node_id, source_node, _) = node_added.expect();
let node = graph_editor.model.nodes.get_cloned_ref(&node_id).expect("Node was not added");
let node = graph_editor.nodes().get_cloned_ref(&node_id).expect("Node was not added");
node.set_expression(Expression::new_plain(expression));
graph_editor.stop_editing();
(node_id, source_node, node)

View File

@ -11,9 +11,12 @@ use crate::buffer::Text;
use crate::buffer::Transform;
use crate::component::selection;
use crate::component::Selection;
#[cfg_attr(not(target_arch = "wasm32"), allow(unused_imports))]
use crate::typeface;
#[cfg_attr(not(target_arch = "wasm32"), allow(unused_imports))]
use crate::typeface::glyph;
use crate::typeface::glyph::Glyph;
#[cfg_attr(not(target_arch = "wasm32"), allow(unused_imports))]
use crate::typeface::pen;
use enso_frp as frp;
@ -47,6 +50,7 @@ const LINE_VERTICAL_OFFSET: f32 = 4.0; // Set manually. May depend on font. To b
/// Mapping between selection id, `Selection`, and text location.
#[derive(Clone, Debug, Default)]
#[cfg_attr(not(target_arch = "wasm32"), allow(dead_code))]
pub struct SelectionMap {
id_map: HashMap<usize, Selection>,
location_map: HashMap<usize, HashMap<Column, usize>>,
@ -64,6 +68,7 @@ pub struct SelectionMap {
/// The `divs` and `centers` are kept as vectors for performance reasons. Especially, when clicking
/// inside of the text area, it allows us to binary search the place of the mouse pointer.
#[derive(Debug)]
#[cfg_attr(not(target_arch = "wasm32"), allow(dead_code))]
pub struct Line {
display_object: display::object::Instance,
glyphs: Vec<Glyph>,
@ -82,6 +87,7 @@ impl Line {
}
/// Set the division points (offsets between letters). Also updates center points.
#[cfg_attr(not(target_arch = "wasm32"), allow(dead_code))]
fn set_divs(&mut self, divs: Vec<f32>) {
let div_iter = divs.iter();
let div_iter_skipped = divs.iter().skip(1);
@ -93,11 +99,13 @@ impl Line {
self.centers.binary_search_by(|t| t.partial_cmp(&offset).unwrap()).unwrap_both()
}
#[cfg_attr(not(target_arch = "wasm32"), allow(dead_code))]
fn div_by_column(&self, column: Column) -> f32 {
let ix = column.as_usize().min(self.divs.len() - 1);
self.divs[ix]
}
#[cfg_attr(not(target_arch = "wasm32"), allow(dead_code))]
fn resize_with(&mut self, size: usize, cons: impl Fn() -> Glyph) {
let display_object = self.display_object().clone_ref();
self.glyphs.resize_with(size, move || {
@ -530,6 +538,12 @@ impl Area {
}
}
#[cfg(not(target_arch = "wasm32"))]
fn symbols(&self) -> SmallVec<[display::Symbol; 1]> {
default()
}
#[cfg(target_arch = "wasm32")]
fn symbols(&self) -> SmallVec<[display::Symbol; 1]> {
let text_symbol = self.data.glyph_system.sprite_system().symbol.clone_ref();
let shapes = &self.data.app.display.default_scene.shapes;
@ -558,6 +572,7 @@ pub struct AreaModel {
frp_endpoints: FrpEndpoints,
buffer: buffer::View,
display_object: display::object::Instance,
#[cfg(target_arch = "wasm32")]
glyph_system: glyph::System,
lines: Lines,
single_line: Rc<Cell<bool>>,
@ -571,15 +586,19 @@ impl AreaModel {
let scene = &app.display.default_scene;
let logger = Logger::new("text_area");
let selection_map = default();
let fonts = scene.extension::<typeface::font::Registry>();
let font = fonts.load("DejaVuSansMono");
let glyph_system = typeface::glyph::System::new(&scene, font);
#[cfg(target_arch = "wasm32")]
let glyph_system = {
let fonts = scene.extension::<typeface::font::Registry>();
let font = fonts.load("DejaVuSansMono");
typeface::glyph::System::new(&scene, font)
};
let display_object = display::object::Instance::new(&logger);
let buffer = default();
let lines = default();
let single_line = default();
let camera = Rc::new(CloneRefCell::new(scene.camera().clone_ref()));
#[cfg(target_arch = "wasm32")]
display_object.add_child(&glyph_system);
// FIXME[WD]: These settings should be managed wiser. They should be set up during
@ -603,6 +622,7 @@ impl AreaModel {
frp_endpoints,
buffer,
display_object,
#[cfg(target_arch = "wasm32")]
glyph_system,
lines,
single_line,
@ -611,6 +631,10 @@ impl AreaModel {
.init()
}
#[cfg(not(target_arch = "wasm32"))]
fn on_modified_selection(&self, _: &buffer::selection::Group, _: f32, _: bool) {}
#[cfg(target_arch = "wasm32")]
fn on_modified_selection(
&self,
selections: &buffer::selection::Group,
@ -750,6 +774,12 @@ impl AreaModel {
self.lines.len() as f32 * LINE_HEIGHT
}
#[cfg(not(target_arch = "wasm32"))]
fn redraw_line(&self, _: usize, _: String) -> f32 {
0.0
}
#[cfg(target_arch = "wasm32")]
fn redraw_line(&self, view_line_index: usize, content: String) -> f32 {
let cursor_map = self
.selection_map
@ -881,6 +911,7 @@ impl AreaModel {
}
/// Constrain the selection to values fitting inside of the current text buffer.
#[cfg_attr(not(target_arch = "wasm32"), allow(dead_code))]
fn snap_selection(
&self,
selection: buffer::selection::Selection,

View File

@ -18,6 +18,7 @@ use ensogl_core::DEPRECATED_Animation;
const CURSOR_PADDING: f32 = 4.0;
const CURSOR_WIDTH: f32 = 2.0;
const CURSOR_ALPHA: f32 = 0.8;
#[cfg_attr(not(target_arch = "wasm32"), allow(dead_code))]
const CURSORS_SPACING: f32 = 1.0;
const SELECTION_ALPHA: f32 = 0.3;
const SELECTION_CORNER_RADIUS: f32 = 2.0;
@ -130,6 +131,7 @@ impl Deref for Selection {
impl Selection {
/// Constructor.
#[cfg_attr(not(target_arch = "wasm32"), allow(dead_code))]
pub fn new(logger: impl AnyLogger, edit_mode: bool) -> Self {
let logger = Logger::new_sub(logger, "selection");
let display_object = display::object::Instance::new(&logger);
@ -160,6 +162,7 @@ impl Selection {
.init()
}
#[cfg_attr(not(target_arch = "wasm32"), allow(dead_code))]
fn init(self) -> Self {
let network = &self.network;
let view = &self.shape_view;
@ -189,6 +192,7 @@ impl Selection {
self
}
#[cfg_attr(not(target_arch = "wasm32"), allow(dead_code))]
pub fn flip_sides(&self) {
let width = self.width.target_value();
self.position.set_value(self.position.value() + Vector2(width, 0.0));

View File

@ -83,6 +83,39 @@ impl AsRef<theme::Manager> for Application {
}
// ==================
// === Test Utils ===
// ==================
/// Test-specific API.
pub mod test_utils {
use super::*;
/// Screen size for unit and integration tests.
const TEST_SCREEN_SIZE: (f32, f32) = (1920.0, 1080.0);
/// Extended API for tests.
pub trait ApplicationExt {
/// Set "fake" screen dimensions for unit and integration tests. This is important for a lot
/// of position and screen size related computations in the IDE.
fn set_screen_size_for_tests(&self);
}
impl ApplicationExt for Application {
fn set_screen_size_for_tests(&self) {
let (screen_width, screen_height) = TEST_SCREEN_SIZE;
let scene = &self.display.default_scene;
scene.layers.iter_sublayers_and_masks_nested(|layer| {
let camera = layer.camera();
camera.set_screen(screen_width, screen_height);
camera.reset_zoom();
camera.update(scene);
});
}
}
}
// =============
// === Tests ===
// =============

View File

@ -1145,3 +1145,28 @@ impl<'t> DomPath for &'t str {
web::document.get_html_element_by_id(self)
}
}
// ==================
// === Test Utils ===
// ==================
/// Extended API for tests.
pub mod test_utils {
use super::*;
pub trait MouseExt {
/// Emulate click on background for testing purposes.
fn click_on_background(&self);
}
impl MouseExt for Mouse {
fn click_on_background(&self) {
self.target.set(PointerTargetId::Background);
let left_mouse_button = frp::io::mouse::Button::Button0;
self.frp.down.emit(left_mouse_button);
self.frp.up.emit(left_mouse_button);
}
}
}

View File

@ -1,6 +1,3 @@
// === Standard Linter Configuration ===
#![deny(non_ascii_idents)]
#![warn(unsafe_code)]
//! Interface to profile data.
//!
//! # Overview
@ -91,21 +88,29 @@
//! store_and_retrieve_metadata();
//! ```
// === Features ===
#![feature(test)]
// === Standard Linter Configuration ===
#![deny(non_ascii_idents)]
#![warn(unsafe_code)]
// === Non-Standard Linter Configuration ===
#![deny(unconditional_recursion)]
#![warn(missing_copy_implementations)]
#![warn(missing_debug_implementations)]
#![warn(missing_docs)]
#![warn(trivial_casts)]
#![warn(trivial_numeric_casts)]
#![warn(unsafe_code)]
#![warn(unused_import_braces)]
use enso_profiler as profiler;
use std::error;
use std::fmt;
// ==============
// === Export ===
// ==============
pub mod parse;