nigig-org/crates/apps/pdf/pdf-makepad/tests/ui.rs
andodeki bd01604e65
Some checks failed
repo hygiene / hygiene (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
docs(pdf): Phase 1 status, and stop pdf-ui failing for an environmental reason
Phase 1 of NIGIG_PDF_FEATURE_PARITY_PLAN.md. Findings verified by running
them, written up in REVIEWS/PDF_PARITY_PHASE1_STATUS.md.

Three of the five exit criteria are met: the workspace is green on the new
rev (pdf 606 passing), both pins are already at 5efe6e24c, and the upstream
baseline is documented - libs/pdf_parse is 4,575 lines with no save/write
path at all, against nigig-pdf's 25,845 lines with writing, encryption,
signatures, structure tree and transparency. nigig-pdf supersedes both
libs/pdf_parse and widgets/src/pdf_view.rs; nothing in either is a
capability we lack.

The remaining two criteria need fork work this repo cannot do.

WHY THE UI SUITE CANNOT PASS YET

The six #[ignore] markers were removed from pdf-makepad/tests/ui.rs and the
docs now claim the suite runs without a Studio hub. The markers went but the
tests did not start passing - TEST_TARGET=pdf-ui was simply red. Three
layers, each found by fixing the one in front of it:

1. studio/hub/src/build_manager.rs:398 spawns the build with `sh -lc`. The
   -l makes it a LOGIN shell, which discards the inherited PATH and rebuilds
   it from /etc/profile, where ~/.cargo/bin does not appear. cargo is not
   found and the child exits 127 in 0.4s. This breaks any rustup-based CI,
   not just this sandbox. `sh -c`, or resolving cargo through the CARGO env
   var, would fix it.

2. Past that the build runs (88s) and the failure becomes 101.
   libs/makepad_test sets MAKEPAD=headless for the child, but
   platform/src/os/linux/windowing_backend.rs only knows X11 and Wayland -
   there is no headless backend and the env var is not consulted. The app
   selects X11, finds no display, and segfaults (139). This is the real
   Phase 1 fork task: "terminal/standalone mode" needs a backend, not just
   an env var the harness sets.

3. Under xvfb-run the app starts properly and OpenGL initialises, so the
   binary is fine - but the hub spawns its child outside that display.

The markers are restored, with a reason pointing at the status document.
A red suite everyone knows to disregard stops reporting the next real
regression, which is strictly worse than an explicit skip.

Also fixes a latent build break this exposed: the fork's app_main! macro
expands to #[cfg(native_activity)], a cfg this crate never declares, which
is a hard error under -D warnings. Declared as expected-but-unset via
[lints.rust] check-cfg rather than silencing unexpected_cfgs wholesale,
which would also hide our own typos.

One genuine improvement on this rev: pdf-makepad now builds in release
inside the workspace. That was previously blocked by a Makepad os::linux
feature-unification bug.

TEST_TARGET=pdf 606 passing, TEST_TARGET=pdf-ui 651 passing + 6 ignored.
2026-08-16 16:48:02 +00:00

173 lines
6.7 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 headless
//! `makepad_test` runtime against the test host in `src/bin/nigig-pdf-makepad.rs`,
//! so the whole path — event delivery, hit testing, action emission — is
//! exercised without a Studio hub.
//!
//! 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.
//!
//! **Currently `#[ignore]`d.** The fork's `makepad_test` sets
//! `MAKEPAD=headless` for the app it spawns, but
//! `platform/src/os/linux/windowing_backend.rs` still only knows `X11` and
//! `Wayland` — there is no headless backend, so the app selects X11, finds
//! no display and dies. The full three-layer diagnosis, including a
//! `sh -lc` bug in the hub that hides `cargo` from the build, is in
//! `REVIEWS/PDF_PARITY_PHASE1_STATUS.md`.
//!
//! These are ignored rather than deleted or left failing on purpose: a red
//! suite everyone knows to disregard stops reporting the next real
//! regression. Remove the markers when the fork grows a headless backend.
//!
//! ```bash
//! cargo test -p nigig-pdf-makepad --test ui
//! ```
//!
//! 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
//! 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. The label occupies
/// 28px (verified from the widget's live viewport origin during bring-up).
const LABEL_HEIGHT: f64 = 28.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 headless backend; see REVIEWS/PDF_PARITY_PHASE1_STATUS.md"]
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 headless backend; see REVIEWS/PDF_PARITY_PHASE1_STATUS.md"]
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 headless backend; see REVIEWS/PDF_PARITY_PHASE1_STATUS.md"]
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 headless backend; see REVIEWS/PDF_PARITY_PHASE1_STATUS.md"]
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 headless backend; see REVIEWS/PDF_PARITY_PHASE1_STATUS.md"]
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 headless backend; see REVIEWS/PDF_PARITY_PHASE1_STATUS.md"]
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;
};