nigig-org/crates/apps/pdf/pdf-makepad/tests/ui.rs
andodeki 456cfa5a68
Some checks failed
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-map / test (push) Has been cancelled
PDF engine / engine (push) Has been cancelled
PDF engine / makepad-integration (push) Has been cancelled
PDF engine / fuzz (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
fix: use re-exported makepad-test from makepad-widgets
Remove direct dependencies on makepad-test and use the re-exported
version from makepad-widgets instead. This avoids path dependency
issues and follows the correct pattern for using Makepad crates.

Changes:
- Add 'test' feature to makepad-widgets dependencies
- Remove direct makepad-test dependencies
- Update imports to use makepad_widgets::makepad_test

Affected crates:
- crates/apps/map
- crates/apps/pdf/pdf-makepad
- crates/apps/spreadsheet/spreadsheet-ui
2026-07-31 19:49:54 +00:00

175 lines
6.5 KiB
Rust

//! 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;
};