mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 03:51:43 +03:00
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:
parent
ae9d51555f
commit
9d6f9373f9
10
Cargo.lock
generated
10
Cargo.lock
generated
@ -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",
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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::*;
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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`)
|
||||
|
@ -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"
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user