//! UI tests driving the PDF widget through real Makepad event delivery. //! //! This closes the gap carried since Phase 4: the interaction logic behind //! `handle_event` was unit-tested, but nothing proved a click actually //! reaches it through `event.hits()`. These tests go through the Studio //! protocol against the test host in `src/bin/nigig-pdf-makepad.rs`, so the //! whole path — event delivery, hit testing, action emission — is exercised. //! //! Headless by default. `MAKEPAD_TEST_VISIBLE=1` runs against a visible //! Studio for debugging. //! //! Run with `--test-threads=1`: each test starts its own app instance, and //! several competing for the same port produces spurious failures rather //! than real ones. //! //! # Why these are `#[ignore]` //! //! They are ignored by default because the Studio hub cannot start an app in //! this sandbox: the harness launches the binary with `--stdin-loop`, which //! Makepad refuses without a Studio websocket, and the build exits 101 //! before startup. This is not specific to the PDF widget — upstream's own //! `spreadsheet-ui` and `map` UI suites fail identically here with the same //! error, so it is an environment limitation rather than a defect in this //! code. //! //! They are checked in, and compiled by `cargo test`, so they cannot rot. //! Run them where a Studio hub is available: //! //! ```bash //! cargo test -p nigig-pdf-makepad --test ui -- --ignored --test-threads=1 //! ``` //! //! The framework's `click()` targets a widget's centre, which is no use for //! hitting a specific annotation, so coordinate clicks are sent as raw //! Studio mouse events through `forward()`. use makepad_widgets::makepad_test::{makepad_test, Selector, StudioToApp, TestApp}; // The Remote* payload structs are not re-exported by makepad_test, so they // come from the protocol crate the widget crate already depends on. use makepad_widgets::makepad_platform::studio::{ RemoteKeyModifiers, RemoteMouseDown, RemoteMouseUp, }; /// Window geometry declared by the test host. const WINDOW_WIDTH: f64 = 800.0; /// The fixture page is 792pt tall and the widget fills the window below the /// action label, so PDF y maps to screen y by this offset. const LABEL_HEIGHT: f64 = 24.0; const PAGE_HEIGHT: f64 = 792.0; /// Convert a PDF-space point on the fixture page into a window coordinate. /// /// PDF y grows upward from the bottom-left; the window grows downward from /// the top-left, and the widget starts below the action label. fn pdf_to_window(x: f64, y: f64) -> (f64, f64) { (x, LABEL_HEIGHT + (PAGE_HEIGHT - y)) } /// Send a press-and-release at a window coordinate. fn click_at(app: &TestApp, x: f64, y: f64) { let time = 0.0; app.forward(vec![ StudioToApp::MouseDown(RemoteMouseDown { button_raw_bits: 0, x, y, time, modifiers: RemoteKeyModifiers::default(), }), StudioToApp::MouseUp(RemoteMouseUp { button_raw_bits: 0, x, y, time, modifiers: RemoteKeyModifiers::default(), }), ]); } /// Click a point given in the fixture's PDF coordinates. fn click_pdf_point(app: &TestApp, x: f64, y: f64) { let (wx, wy) = pdf_to_window(x, y); click_at(app, wx, wy); } /// The widget mounts and draws without panicking. /// /// The weakest possible assertion, and still worth making: it is the first /// thing proving the widget survives a real draw pass with a parsed /// document, which no unit test can show. #[makepad_test] #[ignore = "needs a Makepad Studio hub; see the module docs"] fn the_pdf_widget_mounts_and_draws(app: TestApp) { app.locator(Selector::id("pdf_page")).wait_visible(); } /// The host starts with no action reported, so a later assertion that an /// action appeared cannot pass vacuously. #[makepad_test] #[ignore = "needs a Makepad Studio hub; see the module docs"] fn no_action_is_reported_before_any_input(app: TestApp) { app.locator(Selector::id("pdf_page")).wait_visible(); app.locator(Selector::id("action_label")) .wait_visible() .assert_text("no action"); } /// The Phase 4 exit criterion, now through real event delivery rather than /// a direct call into `InteractionState`. /// /// The fixture's link occupies PDF-space 100,500 to 200,520 on page index 1. /// Clicking it must surface `OpenUri` on the host, which proves the action /// travelled the whole path instead of stopping inside the widget. #[makepad_test] #[ignore = "needs a Makepad Studio hub; see the module docs"] fn clicking_a_link_delivers_open_uri_to_the_host(app: TestApp) { app.locator(Selector::id("pdf_page")).wait_visible(); click_pdf_point(&app, 150.0, 510.0); app.locator(Selector::id("action_label")) .wait_text("OpenUri: https://example.org/"); } /// Clicking empty space must not emit anything, so the link assertion above /// is not merely "any click produces an action". #[makepad_test] #[ignore = "needs a Makepad Studio hub; see the module docs"] fn clicking_empty_space_emits_nothing(app: TestApp) { app.locator(Selector::id("pdf_page")).wait_visible(); // Far from every annotation and widget on the fixture page. click_pdf_point(&app, 500.0, 300.0); app.locator(Selector::id("action_label")) .assert_text("no action"); } /// Typing into a focused field must change the value through the real /// keyboard path, not a synthesised `KeyInput`. #[makepad_test] #[ignore = "needs a Makepad Studio hub; see the module docs"] fn typing_into_a_form_field_reaches_the_widget(app: TestApp) { app.locator(Selector::id("pdf_page")).wait_visible(); // The fixture's text field spans PDF 100,600 to 300,620 on page 1. click_pdf_point(&app, 200.0, 610.0); app.type_text("Z"); // Committing emits FieldChanged, which the host mirrors into the label. app.press_return(); app.locator(Selector::id("action_label")) .wait_text("FieldChanged"); } /// A checkbox toggles on click, which is a different code path from the /// buffered text-field edit. #[makepad_test] #[ignore = "needs a Makepad Studio hub; see the module docs"] fn clicking_the_checkbox_emits_a_field_change(app: TestApp) { app.locator(Selector::id("pdf_page")).wait_visible(); // The fixture's checkbox is PDF 100,560 to 114,574. click_pdf_point(&app, 107.0, 567.0); app.locator(Selector::id("action_label")) .wait_text("FieldChanged"); } #[allow(unused)] const _: () = { // The window width is referenced only for documentation of the layout // the coordinates assume; keep it compiling. let _ = WINDOW_WIDTH; };