Add tests for debug mode and zoom restriction. (#3289)

This PR adds integration tests created during acceptance process of [181181159](https://www.pivotaltracker.com/story/show/181181159) and [181181203](https://www.pivotaltracker.com/story/show/181181203).

The PRs for those tasks were merged, because I hadn't realized they should not.

Additionally, as the `wasm-bindgen` version was bumped, I extended the timeout of integration tests and made them headless.
This commit is contained in:
Adam Obuchowicz 2022-02-22 17:43:37 +01:00 committed by GitHub
parent ae9d51555f
commit 9d6f9373f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 227 additions and 48 deletions

10
Cargo.lock generated
View File

@ -68,6 +68,15 @@ dependencies = [
"num-traits",
]
[[package]]
name = "approx"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
dependencies = [
"num-traits",
]
[[package]]
name = "assert_approx_eq"
version = "1.1.0"
@ -1016,6 +1025,7 @@ dependencies = [
name = "enso-integration-test"
version = "0.1.0"
dependencies = [
"approx 0.5.1",
"enso-frp",
"enso-gui",
"enso-prelude",

View File

@ -281,18 +281,16 @@ have prepared several scripts which maximally automate the process:
is in your `PATH`.
- **Integration Tests** The integration tests are gathered in `integration-test`
crate. One test suite can be run with
`node ./run integration-test -- --test <suite-name>`. This will spawn required
Engine process and then set up a server on localhost:8000 - open the page in
Chrome browser to see the tests running. The `<suite-name>` is a name of the
file in `integration-test/tests` directory without extension, for example
`graph_editor`.
crate. You can run them with `node ./run integration-test` command. The script
will spawn required Engine process.
- To run une test suite add `-- --test <suite-name>` at end of command
options. The `<suite-name>` is a name of the file in
`integration-test/tests` directory without extension, for example
`graph_editor`.
- The integration test can create and leave new Enso projects. **Keep it in
mind when running the script with your own backend (the `--no-backend`
option)**. The Engine spawned by the script will use a dedicated workspace
created in temporary directory, so the user workspace will not be affected.
- Note: in the future there will be possibility to run all tests suite
headlessly.
- **Linting** Please be sure to fix all errors reported by `node ./run lint`
before creating a pull request to this repository.

View File

@ -65,6 +65,7 @@ pub mod test;
pub mod transport;
pub use crate::ide::*;
pub use ide_view as view;
use ensogl::system::web;
use wasm_bindgen::prelude::*;

View File

@ -1454,6 +1454,7 @@ pub struct GraphEditorModel {
pub edges: Edges,
pub vis_registry: visualization::Registry,
pub drop_manager: ensogl_drop_manager::Manager,
pub navigator: Navigator,
// FIXME[MM]: The tooltip should live next to the cursor in `Application`. This does not
// currently work, however, because the `Application` lives in enso-core, and the tooltip
// requires enso-text, which in turn depends on enso-core, creating a cyclic dependency.
@ -1461,7 +1462,6 @@ pub struct GraphEditorModel {
touch_state: TouchState,
visualisations: Visualisations,
frp: FrpEndpoints,
navigator: Navigator,
profiling_statuses: profiling::Statuses,
profiling_button: component::profiling::Button,
styles_frp: StyleWatchFrp,

View File

@ -41,11 +41,12 @@ const LABEL_PADDING_TOP: f32 = 50.0;
/// Text label that disappears after a predefined delay.
#[derive(Debug, Clone, CloneRef)]
struct PopupLabel {
pub struct PopupLabel {
label: Label,
network: frp::Network,
delay_animation: DelayedAnimation,
show: frp::Source<String>,
/// Show the Popup with the given message.
pub show: frp::Source<String>,
}
impl display::Object for PopupLabel {
@ -189,6 +190,11 @@ impl View {
Self { frp, model }
}
/// Get the label of the popup.
pub fn label(&self) -> &PopupLabel {
&self.model.label
}
}
impl display::Object for View {

View File

@ -676,6 +676,11 @@ impl View {
pub fn open_dialog(&self) -> &OpenDialog {
&self.model.open_dialog
}
/// Debug Mode Popup
pub fn debug_mode_popup(&self) -> &debug_mode_popup::View {
&self.model.debug_mode_popup
}
}
impl display::Object for View {

View File

@ -268,7 +268,14 @@ commands['integration-test'].rust = async function (argv) {
}
try {
console.log(`Running Rust WASM test suite.`)
let args = ['test', '--chrome', 'integration-test', '--profile=integration-test']
process.env.WASM_BINDGEN_TEST_TIMEOUT = 120
let args = [
'test',
'--headless',
'--chrome',
'integration-test',
'--profile=integration-test',
]
await run_cargo('wasm-pack', args)
} finally {
console.log(`Shutting down Project Manager`)

View File

@ -4,12 +4,11 @@ version = "0.1.0"
edition = "2021"
[dependencies]
approx = "0.5.1"
ensogl = { path = "../lib/rust/ensogl" }
enso-frp = { path = "../lib/rust/frp" }
enso-prelude = { path = "../lib/rust/prelude" }
enso-gui = { path = "../app/gui" }
enso-web = { path = "../lib/rust/web" }
wasm-bindgen = { version = "0.2.58" }
[dev-dependencies]
wasm-bindgen = { version = "0.2.78" }
wasm-bindgen-test = "0.3.8"

View File

@ -15,16 +15,26 @@
#![warn(missing_copy_implementations)]
#![warn(missing_debug_implementations)]
pub use enso_prelude as prelude;
use enso_prelude::*;
use crate::prelude::*;
use enso_frp::future::EventOutputExt;
use enso_gui::executor::web::EventLoopExecutor;
use enso_gui::initializer::setup_global_executor;
use enso_gui::Ide;
use enso_web::HtmlDivElement;
use enso_web::NodeInserter;
use enso_web::StyleSetter;
use ensogl::application::Application;
/// Reexports of commonly-used structures, methods and traits.
pub mod prelude {
pub use crate::IntegrationTest;
pub use crate::IntegrationTestOnNewProject;
pub use enso_frp::future::EventOutputExt;
pub use enso_gui::prelude::*;
pub use wasm_bindgen_test::wasm_bindgen_test;
}
@ -43,7 +53,9 @@ pub struct IntegrationTest {
}
impl IntegrationTest {
/// Initializes the executor and `Ide` structure and returns new Fixture
const SCREEN_SIZE: (f32, f32) = (1920.0, 1000.0);
/// Initializes the executor and `Ide` structure and returns new Fixture.
pub async fn setup() -> Self {
enso_web::forward_panic_hook_to_error();
let executor = setup_global_executor();
@ -54,8 +66,16 @@ 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);
Self { executor, ide, root_div }
}
fn set_screen_size(app: &Application) {
let (screen_width, screen_height) = Self::SCREEN_SIZE;
app.display.scene().layers.iter_sublayers_and_masks_nested(|layer| {
layer.camera().set_screen(screen_width, screen_height)
});
}
}
impl Drop for IntegrationTest {
@ -63,3 +83,53 @@ impl Drop for IntegrationTest {
self.root_div.remove();
}
}
// ===================================
// === IntegrationTestOnNewProject ===
// ===================================
/// A fixture for IDE integration tests on created project. It is derived from [`IntegrationTest`].
/// During setup, the Ide initialization is performed, then new project is created, and we wait till
/// the prompt for user will be displayed (thus informing us, that the project is ready to work).
#[derive(Debug)]
pub struct IntegrationTestOnNewProject {
parent: IntegrationTest,
}
impl Deref for IntegrationTestOnNewProject {
type Target = IntegrationTest;
fn deref(&self) -> &Self::Target {
&self.parent
}
}
impl IntegrationTestOnNewProject {
/// Test initialization. After returning, the IDE is in state with new project opened and ready
/// to work (after libraries' compilation).
pub async fn setup() -> Self {
let parent = IntegrationTest::setup().await;
let ide = &parent.ide;
let project = ide.presenter.view().project();
let controller = ide.presenter.controller();
let project_management =
controller.manage_projects().expect("Cannot access Managing Project API");
let expect_prompt = project.show_prompt.next_event();
project_management.create_new_project(None).await.expect("Failed to create new project");
expect_prompt.await;
Self { parent }
}
/// Get the Project View.
pub fn project_view(&self) -> enso_gui::view::project::View {
self.ide.presenter.view().project()
}
/// Get the Graph Editor.
pub fn graph_editor(&self) -> enso_gui::view::graph_editor::GraphEditor {
self.project_view().graph().clone_ref()
}
}

View File

@ -1,23 +1,17 @@
use enso_frp::future::EventOutputExt;
use enso_integration_test::IntegrationTest;
use wasm_bindgen_test::wasm_bindgen_test;
use enso_integration_test::prelude::*;
use approx::assert_abs_diff_eq;
use enso_web::sleep;
use ensogl::display::navigation::navigator::ZoomEvent;
use std::time::Duration;
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
#[wasm_bindgen_test]
async fn create_new_project_and_add_nodes() {
let test = IntegrationTest::setup().await;
let ide = &test.ide;
let project = ide.presenter.view().project();
let graph_editor = project.graph();
let controller = ide.presenter.controller();
let project_management =
controller.manage_projects().expect("Cannot access Managing Project API");
let expect_prompt = project.show_prompt.next_event();
project_management.create_new_project(None).await.expect("Failed to create new project");
expect_prompt.await;
let test = IntegrationTestOnNewProject::setup().await;
let graph_editor = test.graph_editor();
assert_eq!(graph_editor.model.nodes.all.len(), 2);
let expect_node_added = graph_editor.node_added.next_event();
@ -29,3 +23,78 @@ async fn create_new_project_and_add_nodes() {
graph_editor.model.nodes.get_cloned_ref(&added_node_id).expect("Added node is not added");
assert_eq!(added_node.view.expression.value().to_string(), "");
}
#[wasm_bindgen_test]
async fn debug_mode() {
let test = IntegrationTestOnNewProject::setup().await;
let project = test.project_view();
let graph_editor = test.graph_editor();
assert!(!graph_editor.debug_mode.value());
// Turning On
let expect_mode = project.debug_mode.next_event();
let expect_popup_message = project.debug_mode_popup().label().show.next_event();
project.enable_debug_mode();
assert!(expect_mode.expect());
let message = expect_popup_message.expect();
assert!(
message.contains("Debug Mode enabled"),
"Message \"{}\" does not mention enabling Debug mode",
message
);
assert!(
message.contains(enso_gui::view::debug_mode_popup::DEBUG_MODE_SHORTCUT),
"Message \"{}\" does not inform about shortcut to turn mode off",
message
);
assert!(graph_editor.debug_mode.value());
// Turning Off
let expect_mode = project.debug_mode.next_event();
let expect_popup_message = project.debug_mode_popup().label().show.next_event();
project.disable_debug_mode();
assert!(!expect_mode.expect());
let message = expect_popup_message.expect();
assert!(
message.contains("Debug Mode disabled"),
"Message \"{}\" does not mention disabling of debug mode",
message
);
assert!(!graph_editor.debug_mode.value());
}
#[wasm_bindgen_test]
async fn zooming() {
let test = IntegrationTestOnNewProject::setup().await;
let project = test.project_view();
let graph_editor = test.graph_editor();
let camera = test.ide.ensogl_app.display.scene().layers.main.camera();
let navigator = &graph_editor.model.navigator;
let zoom_on_center = |amount: f32| ZoomEvent { focus: Vector2(0.0, 0.0), amount };
let zoom_duration_ms = Duration::from_millis(1000);
// Without debug mode
navigator.emit_zoom_event(zoom_on_center(-1.0));
sleep(zoom_duration_ms).await;
assert_abs_diff_eq!(camera.zoom(), 1.0, epsilon = 0.001);
navigator.emit_zoom_event(zoom_on_center(1.0));
sleep(zoom_duration_ms).await;
assert!(camera.zoom() < 1.0, "Camera zoom {} must be less than 1.0", camera.zoom());
navigator.emit_zoom_event(zoom_on_center(-2.0));
sleep(zoom_duration_ms).await;
assert_abs_diff_eq!(camera.zoom(), 1.0, epsilon = 0.001);
// With debug mode
project.enable_debug_mode();
navigator.emit_zoom_event(zoom_on_center(-1.0));
sleep(zoom_duration_ms).await;
assert!(camera.zoom() > 1.0, "Camera zoom {} must be greater than 1.0", camera.zoom());
navigator.emit_zoom_event(zoom_on_center(5.0));
sleep(zoom_duration_ms).await;
assert!(camera.zoom() < 1.0, "Camera zoom {} must be less than 1.0", camera.zoom());
navigator.emit_zoom_event(zoom_on_center(-5.0));
sleep(zoom_duration_ms).await;
assert!(camera.zoom() > 1.0, "Camera zoom {} must be greater than 1.0", camera.zoom());
}

View File

@ -1,17 +1,17 @@
mod events;
pub use crate::display::navigation::navigator::events::PanEvent;
pub use crate::display::navigation::navigator::events::ZoomEvent;
use crate::prelude::*;
use crate::animation::physics;
use crate::control::callback;
use crate::display::camera::Camera2d;
use crate::display::navigation::navigator::events::NavigatorEvents;
use crate::display::object::traits::*;
use crate::display::Scene;
use events::NavigatorEvents;
use events::PanEvent;
use events::ZoomEvent;
// =================
@ -33,7 +33,7 @@ const MIN_ZOOM: f32 = 0.001;
/// Navigator enables camera navigation with mouse interactions.
#[derive(Debug)]
pub struct NavigatorModel {
_events: NavigatorEvents,
events: NavigatorEvents,
simulator: physics::inertia::DynSimulator<Vector3>,
resize_callback: callback::Handle,
zoom_speed: SharedSwitch<f32>,
@ -50,7 +50,7 @@ impl NavigatorModel {
let pan_speed = Rc::new(Cell::new(Switch::On(1.0)));
let disable_events = Rc::new(Cell::new(true));
let max_zoom: Rc<Cell<_>> = default();
let (simulator, resize_callback, _events) = Self::start_navigator_events(
let (simulator, resize_callback, events) = Self::start_navigator_events(
scene,
camera,
zoom_speed.clone_ref(),
@ -58,15 +58,7 @@ impl NavigatorModel {
disable_events.clone_ref(),
max_zoom.clone_ref(),
);
Self {
_events,
simulator,
resize_callback,
zoom_speed,
pan_speed,
disable_events,
max_zoom,
}
Self { events, simulator, resize_callback, zoom_speed, pan_speed, disable_events, max_zoom }
}
fn create_simulator(camera: &Camera2d) -> physics::inertia::DynSimulator<Vector3> {
@ -180,6 +172,16 @@ impl NavigatorModel {
pub fn set_max_zoom(&self, value: Option<f32>) {
self.max_zoom.set(value);
}
/// Emit zoom event. This function could be used in the tests to simulate user interactions.
pub fn emit_zoom_event(&self, event: ZoomEvent) {
self.events.emit_zoom_event(event);
}
/// Emit pan event. This function could be used in the tests to simulate user interactions.
pub fn emit_pan_event(&self, event: PanEvent) {
self.events.emit_pan_event(event);
}
}

View File

@ -17,6 +17,7 @@ use nalgebra::Vector2;
pub trait FnZoomEvent = FnMut(ZoomEvent) + 'static;
/// A struct holding zoom event information, such as the focus point and the amount of zoom.
#[derive(Clone, Copy, Debug, Default)]
pub struct ZoomEvent {
pub focus: Vector2<f32>,
pub amount: f32,
@ -45,6 +46,7 @@ impl ZoomEvent {
pub trait FnPanEvent = FnMut(PanEvent) + 'static;
/// A struct holding pan event information.
#[derive(Clone, Copy, Debug, Default)]
pub struct PanEvent {
pub movement: Vector2<f32>,
}
@ -348,6 +350,16 @@ impl NavigatorEvents {
});
self.mouse_move = Some(listener);
}
/// Emit zoom event. This function could be used in the tests to simulate user interactions.
pub fn emit_zoom_event(&self, event: ZoomEvent) {
self.data.on_zoom(event);
}
/// Emit pan event. This function could be used in the tests to simulate user interactions.
pub fn emit_pan_event(&self, event: PanEvent) {
self.data.on_pan(event);
}
}
fn movement_to_zoom(v: Vector2<f32>) -> f32 {