From e9f3b2327e1cc16a2c55783af63441c667ed7d0a Mon Sep 17 00:00:00 2001 From: Ilya Bogdanov Date: Wed, 30 Mar 2022 15:49:07 +0300 Subject: [PATCH] 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. --- .../view/graph-editor/src/component/node.rs | 50 +++++ .../src/component/node/input/area.rs | 13 +- .../src/component/node/output/area.rs | 22 ++- app/gui/view/graph-editor/src/lib.rs | 184 +++++++++++++++++- app/gui/view/src/project.rs | 4 +- integration-test/src/lib.rs | 13 +- integration-test/tests/graph_editor.rs | 27 +-- .../component/text/src/component/area.rs | 37 +++- .../component/text/src/component/selection.rs | 4 + lib/rust/ensogl/core/src/application.rs | 33 ++++ lib/rust/ensogl/core/src/display/scene.rs | 25 +++ lib/rust/profiler/data/src/lib.rs | 15 +- 12 files changed, 381 insertions(+), 46 deletions(-) diff --git a/app/gui/view/graph-editor/src/component/node.rs b/app/gui/view/graph-editor/src/component/node.rs index ad5338eec41..b883415cad1 100644 --- a/app/gui/view/graph-editor/src/component/node.rs +++ b/app/gui/view/graph-editor/src/component/node.rs @@ -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; + + /// 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; + } + + impl NodeModelExt for NodeModel { + fn output_port_shape(&self) -> Option { + 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 { + let ports = self.input.model.ports(); + let port = ports.first()?; + port.shape.as_ref().map(CloneRef::clone_ref) + } + } +} diff --git a/app/gui/view/graph-editor/src/component/node/input/area.rs b/app/gui/view/graph-editor/src/component/node/input/area.rs index 10a98cb12ad..45cf54e08db 100644 --- a/app/gui/view/graph-editor/src/component/node/input/area.rs +++ b/app/gui/view/graph-editor/src/component/node/input/area.rs @@ -286,6 +286,15 @@ impl Model { self } + /// Return a list of Node's input ports. + pub fn ports(&self) -> Vec { + 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, + pub frp: Frp, + pub(crate) model: Rc, } impl Deref for Area { diff --git a/app/gui/view/graph-editor/src/component/node/output/area.rs b/app/gui/view/graph-editor/src/component/node/output/area.rs index 88245b02050..2d1551477ec 100644 --- a/app/gui/view/graph-editor/src/component/node/output/area.rs +++ b/app/gui/view/graph-editor/src/component/node/output/area.rs @@ -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), - /// 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 { + 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, + pub frp: Frp, + pub model: Rc, } impl Deref for Area { diff --git a/app/gui/view/graph-editor/src/lib.rs b/app/gui/view/graph-editor/src/lib.rs index eece12c27d0..392fca4775d 100644 --- a/app/gui/view/graph-editor/src/lib.rs +++ b/app/gui/view/graph-editor/src/lib.rs @@ -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, + /// Should we start the node editing immediately after adding it? + should_edit: bool, + } + + impl GraphEditor { + fn add_node_by(&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) + } +} diff --git a/app/gui/view/src/project.rs b/app/gui/view/src/project.rs index 2d5747e4426..fda3ec322ef 100644 --- a/app/gui/view/src/project.rs +++ b/app/gui/view/src/project.rs @@ -224,7 +224,7 @@ impl Model { } fn searcher_left_top_position_when_under_node(&self, node_id: NodeId) -> Vector2 { - 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(); diff --git a/integration-test/src/lib.rs b/integration-test/src/lib.rs index 4ae1beedb4d..af2b6e9adba 100644 --- a/integration-test/src/lib.rs +++ b/integration-test/src/lib.rs @@ -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 { diff --git a/integration-test/tests/graph_editor.rs b/integration-test/tests/graph_editor.rs index a2a84012dc0..d071b25f98d 100644 --- a/integration-test/tests/graph_editor.rs +++ b/integration-test/tests/graph_editor.rs @@ -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) diff --git a/lib/rust/ensogl/component/text/src/component/area.rs b/lib/rust/ensogl/component/text/src/component/area.rs index fe077cf56bb..07dd11d3557 100644 --- a/lib/rust/ensogl/component/text/src/component/area.rs +++ b/lib/rust/ensogl/component/text/src/component/area.rs @@ -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, location_map: HashMap>, @@ -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, @@ -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) { 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>, @@ -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::(); - 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::(); + 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, diff --git a/lib/rust/ensogl/component/text/src/component/selection.rs b/lib/rust/ensogl/component/text/src/component/selection.rs index 6872c8e4f05..8ab10c8a1c4 100644 --- a/lib/rust/ensogl/component/text/src/component/selection.rs +++ b/lib/rust/ensogl/component/text/src/component/selection.rs @@ -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)); diff --git a/lib/rust/ensogl/core/src/application.rs b/lib/rust/ensogl/core/src/application.rs index 9d68a2ff7d2..8e6543cf04a 100644 --- a/lib/rust/ensogl/core/src/application.rs +++ b/lib/rust/ensogl/core/src/application.rs @@ -83,6 +83,39 @@ impl AsRef 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 === // ============= diff --git a/lib/rust/ensogl/core/src/display/scene.rs b/lib/rust/ensogl/core/src/display/scene.rs index 9fc1af98700..81b263a7eec 100644 --- a/lib/rust/ensogl/core/src/display/scene.rs +++ b/lib/rust/ensogl/core/src/display/scene.rs @@ -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); + } + } +} diff --git a/lib/rust/profiler/data/src/lib.rs b/lib/rust/profiler/data/src/lib.rs index 58d28accfcb..a9ae3c135f2 100644 --- a/lib/rust/profiler/data/src/lib.rs +++ b/lib/rust/profiler/data/src/lib.rs @@ -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;