Some checks failed
repo hygiene / hygiene (push) Has been cancelled
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-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
nigig-build (CAD) / cad-widget-coverage (push) Has been cancelled
The CAD dashboard's CadDashboard node never realized as a widget, so tapping 'CAD Workspace' opened to a blank/editor-only sheet. Empirical device dumps showed the first child after the root realizes reliably while a sibling placed after the editor AdaptiveView does not, and a root ':=' widget child alone never realizes here. - Place dashboard_layer as the FIRST direct child of CadWorkspaceBase, wrapping mod.widgets.CadDashboard so it is a reliably-realizable, togglable View layer (matching the working doc workspace pattern). - Wrap the editor AdaptiveView in editor_layer (a plain View) and toggle editor_layer/dashboard_layer visibility from apply_dashboard_visibility instead of toggling the AdaptiveView variants, which always draw their active variant regardless of visible. - Add use mod.widgets.* to the CadDashboard script_mod (matches the CadEditorSheet registration that realizes inside this workspace). - Add regression test candlands_on_dashboard asserting the dashboard title and + New Project button land when opening the workspace.
779 lines
27 KiB
Rust
779 lines
27 KiB
Rust
use makepad_test::{makepad_test, Selector, TestApp, WidgetSnapshot};
|
||
|
||
/// Skip test when NIGIG_TEST_MODE is not set (used for tests that need
|
||
/// the home screen to be visible, bypassing login).
|
||
fn require_test_mode() -> bool {
|
||
std::env::var("NIGIG_TEST_MODE").is_ok()
|
||
}
|
||
|
||
/// Navigate from home → work → construction_grid → m_workspace_docs_btn
|
||
/// → (dashboard) new_document_btn → crdt_editor (the CRDT doc workspace).
|
||
fn navigate_to_doc_workspace(app: &TestApp) {
|
||
if !require_test_mode() {
|
||
return;
|
||
}
|
||
app.locator(Selector::id("work_button"))
|
||
.wait_visible()
|
||
.click();
|
||
// The work screen opens with its bottom pull-up sheet collapsed, hiding
|
||
// the section grid buttons. Drag the sheet's pull handle upward to expand
|
||
// it so the additional nav buttons become visible.
|
||
app.locator(Selector::id("pull_handle"))
|
||
.wait_visible()
|
||
.drag_by(0.0, -600.0);
|
||
// The construction grid button opens nigig-build's BuildProjectsPage
|
||
// (mobile variant), whose Workspace list is the entry to the docs.
|
||
app.locator(Selector::id("construction_grid_button"))
|
||
.wait_visible()
|
||
.click();
|
||
app.locator(Selector::id("m_workspace_docs_btn"))
|
||
.wait_visible()
|
||
.click();
|
||
// The CRDT doc workspace opens on its dashboard; "New Document" flips it
|
||
// to the editor (the saved-doc cards are hit-tested rects, not widgets).
|
||
app.locator(Selector::id("new_document_btn"))
|
||
.wait_visible()
|
||
.click();
|
||
app.locator(Selector::id("crdt_editor")).wait_visible();
|
||
}
|
||
|
||
/// Poll the editor's snapshot until its height grows past `before.height`
|
||
/// (the insert_table grid needs a rendered frame to show up; a single
|
||
/// snapshot right after the button click can race the layout refresh).
|
||
/// Returns the grown snapshot, panicking if it never appears.
|
||
fn wait_editor_grows(app: &TestApp, before: &WidgetSnapshot) -> WidgetSnapshot {
|
||
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(10);
|
||
loop {
|
||
let snap = app.locator(Selector::id("crdt_editor")).snapshot();
|
||
if snap.height > before.height {
|
||
return snap;
|
||
}
|
||
if std::time::Instant::now() >= deadline {
|
||
panic!(
|
||
"editor never grew past {} (stayed at {}): insert_table produced no visible grid",
|
||
before.height, snap.height
|
||
);
|
||
}
|
||
std::thread::sleep(std::time::Duration::from_millis(150));
|
||
}
|
||
}
|
||
|
||
#[makepad_test]
|
||
fn app_launches_and_shows_login_screen(app: TestApp) {
|
||
if require_test_mode() {
|
||
return;
|
||
}
|
||
app.locator(Selector::id("login_button"))
|
||
.wait_visible();
|
||
app.locator(Selector::id("user_id_input"))
|
||
.wait_visible();
|
||
app.locator(Selector::id("password_input"))
|
||
.wait_visible();
|
||
app.locator(Selector::id("homeserver_input"))
|
||
.wait_visible();
|
||
app.locator(Selector::id("signup_button"))
|
||
.wait_visible();
|
||
app.locator(Selector::all().text_exact("Login to Robrix"))
|
||
.wait_visible();
|
||
}
|
||
|
||
#[makepad_test]
|
||
fn login_form_accepts_input(app: TestApp) {
|
||
if require_test_mode() {
|
||
return;
|
||
}
|
||
app.locator(Selector::id("user_id_input"))
|
||
.wait_visible()
|
||
.fill("@testuser:matrix.org")
|
||
.wait_value("@testuser:matrix.org");
|
||
app.locator(Selector::id("password_input"))
|
||
.wait_visible()
|
||
.fill("my_password")
|
||
.wait_value("my_password");
|
||
app.locator(Selector::id("homeserver_input"))
|
||
.wait_visible()
|
||
.fill("matrix.example.com")
|
||
.wait_value("matrix.example.com");
|
||
}
|
||
|
||
#[makepad_test]
|
||
fn show_password_toggle_works(app: TestApp) {
|
||
if require_test_mode() {
|
||
return;
|
||
}
|
||
let _pw = app.locator(Selector::id("password_input")).wait_visible();
|
||
app.locator(Selector::id("show_password_button"))
|
||
.wait_visible()
|
||
.click();
|
||
let hide_btn = app.locator(Selector::id("hide_password_button")).wait_visible();
|
||
hide_btn.click();
|
||
app.locator(Selector::id("show_password_button")).wait_visible();
|
||
}
|
||
|
||
#[makepad_test]
|
||
fn login_with_empty_fields_shows_modal(app: TestApp) {
|
||
if require_test_mode() {
|
||
return;
|
||
}
|
||
app.locator(Selector::id("login_button"))
|
||
.wait_visible()
|
||
.click();
|
||
app.locator(Selector::all().text_exact("Missing User ID"))
|
||
.wait_visible();
|
||
app.locator(Selector::id("login_status_modal_inner"))
|
||
.wait_visible();
|
||
app.locator(Selector::all().text_exact("Please enter a valid User ID."))
|
||
.wait_visible();
|
||
}
|
||
|
||
#[makepad_test]
|
||
fn login_with_password_only_shows_missing_user_id(app: TestApp) {
|
||
if require_test_mode() {
|
||
return;
|
||
}
|
||
app.locator(Selector::id("password_input"))
|
||
.wait_visible()
|
||
.fill("some_password");
|
||
app.locator(Selector::id("login_button"))
|
||
.wait_visible()
|
||
.click();
|
||
app.locator(Selector::all().text_exact("Missing User ID"))
|
||
.wait_visible();
|
||
}
|
||
|
||
#[makepad_test]
|
||
fn login_with_user_id_only_shows_missing_password(app: TestApp) {
|
||
if require_test_mode() {
|
||
return;
|
||
}
|
||
app.locator(Selector::id("user_id_input"))
|
||
.wait_visible()
|
||
.fill("@test:matrix.org");
|
||
app.locator(Selector::id("login_button"))
|
||
.wait_visible()
|
||
.click();
|
||
app.locator(Selector::all().text_exact("Missing Password"))
|
||
.wait_visible();
|
||
}
|
||
|
||
#[makepad_test]
|
||
fn login_status_modal_can_be_closed(app: TestApp) {
|
||
if require_test_mode() {
|
||
return;
|
||
}
|
||
app.locator(Selector::id("login_button")).wait_visible().click();
|
||
let modal = app.locator(Selector::id("login_status_modal_inner")).wait_visible();
|
||
let close_btn = app.locator(Selector::widget_type("Button").text_exact("Okay")).wait_visible();
|
||
close_btn.click();
|
||
modal.try_wait_hidden().unwrap();
|
||
}
|
||
|
||
#[makepad_test]
|
||
fn signup_button_fires_correctly(app: TestApp) {
|
||
if require_test_mode() {
|
||
return;
|
||
}
|
||
app.locator(Selector::id("signup_button"))
|
||
.wait_visible()
|
||
.click();
|
||
}
|
||
|
||
#[makepad_test]
|
||
fn sso_buttons_are_present(app: TestApp) {
|
||
if require_test_mode() {
|
||
return;
|
||
}
|
||
let sso_providers = [
|
||
"apple_button",
|
||
"facebook_button",
|
||
"github_button",
|
||
"gitlab_button",
|
||
"google_button",
|
||
"twitter_button",
|
||
];
|
||
for provider_id in &sso_providers {
|
||
app.locator(Selector::id(*provider_id)).wait_visible();
|
||
}
|
||
}
|
||
|
||
// ── Home screen tab tests (require NIGIG_TEST_MODE=1) ──
|
||
|
||
#[makepad_test]
|
||
fn home_screen_tab_buttons_visible_in_test_mode(app: TestApp) {
|
||
if !require_test_mode() {
|
||
return;
|
||
}
|
||
// Desktop nav rail buttons
|
||
app.locator(Selector::id("home_button")).wait_visible();
|
||
app.locator(Selector::id("chats_button")).wait_visible();
|
||
app.locator(Selector::id("shop_button")).wait_visible();
|
||
app.locator(Selector::id("mobility_button")).wait_visible();
|
||
app.locator(Selector::id("work_button")).wait_visible();
|
||
}
|
||
|
||
#[makepad_test]
|
||
fn home_screen_home_tab_active_by_default(app: TestApp) {
|
||
if !require_test_mode() {
|
||
return;
|
||
}
|
||
// Home tab should be visible and active
|
||
app.locator(Selector::id("home_button")).wait_visible();
|
||
}
|
||
|
||
#[makepad_test]
|
||
fn home_tab_switches_to_chats(app: TestApp) {
|
||
if !require_test_mode() {
|
||
return;
|
||
}
|
||
app.locator(Selector::id("chats_button"))
|
||
.wait_visible()
|
||
.click();
|
||
// After clicking Chats, the chats button should still be visible
|
||
app.locator(Selector::id("chats_button")).wait_visible();
|
||
}
|
||
|
||
#[makepad_test]
|
||
fn home_tab_switches_to_shop(app: TestApp) {
|
||
if !require_test_mode() {
|
||
return;
|
||
}
|
||
app.locator(Selector::id("shop_button"))
|
||
.wait_visible()
|
||
.click();
|
||
app.locator(Selector::id("shop_button")).wait_visible();
|
||
}
|
||
|
||
#[makepad_test]
|
||
fn home_tab_switches_to_mobility(app: TestApp) {
|
||
if !require_test_mode() {
|
||
return;
|
||
}
|
||
app.locator(Selector::id("mobility_button"))
|
||
.wait_visible()
|
||
.click();
|
||
app.locator(Selector::id("mobility_button")).wait_visible();
|
||
}
|
||
|
||
#[makepad_test]
|
||
fn home_tab_switches_to_work(app: TestApp) {
|
||
if !require_test_mode() {
|
||
return;
|
||
}
|
||
app.locator(Selector::id("work_button"))
|
||
.wait_visible()
|
||
.click();
|
||
app.locator(Selector::id("work_button")).wait_visible();
|
||
}
|
||
|
||
#[makepad_test]
|
||
fn home_sub_tabs_visible_in_test_mode(app: TestApp) {
|
||
if !require_test_mode() {
|
||
return;
|
||
}
|
||
// Mobile pull-up sheet sub-tabs
|
||
app.locator(Selector::id("home_content_tab_btn")).wait_visible();
|
||
app.locator(Selector::id("home_profile_tab_btn")).wait_visible();
|
||
app.locator(Selector::id("home_preferences_tab_btn")).wait_visible();
|
||
}
|
||
|
||
#[makepad_test]
|
||
fn home_sub_tab_profile_click(app: TestApp) {
|
||
if !require_test_mode() {
|
||
return;
|
||
}
|
||
app.locator(Selector::id("home_profile_tab_btn"))
|
||
.wait_visible()
|
||
.click();
|
||
app.locator(Selector::id("home_profile_tab_btn")).wait_visible();
|
||
}
|
||
|
||
// ── Device verification: doc workspace sections 1–5 ──
|
||
// Execute against the CRDT workspace (CrdtDocWorkspace).
|
||
// Full runbook: DEVICE_VERIFICATION.md (extracted from git history).
|
||
|
||
/// Section 1.1–1.4: Interaction mode View ↔ Edit.
|
||
///
|
||
/// Cold-launch in View mode; the Edit button switches to Edit mode;
|
||
/// Done switches back. Touch in View is passive (no keyboard).
|
||
#[makepad_test]
|
||
fn doc_interaction_mode_view_and_edit(app: TestApp) {
|
||
if !require_test_mode() {
|
||
return;
|
||
}
|
||
navigate_to_doc_workspace(&app);
|
||
|
||
// 1.1: Cold-launch — first tap should NOT open keyboard (View mode).
|
||
// We verify that Edit button is visible (the AdaptiveView Mobile variant).
|
||
app.locator(Selector::id("edit_mode_btn")).wait_visible();
|
||
|
||
// 1.3: Tap Edit → toolbar switches to Done.
|
||
app.locator(Selector::id("edit_mode_btn"))
|
||
.wait_visible()
|
||
.click();
|
||
app.locator(Selector::id("edit_mode_btn"))
|
||
.wait_visible()
|
||
.assert_text("Done");
|
||
|
||
// 1.4: Tap Done → back to Edit label.
|
||
app.locator(Selector::id("edit_mode_btn"))
|
||
.wait_visible()
|
||
.click();
|
||
app.locator(Selector::id("edit_mode_btn"))
|
||
.wait_visible()
|
||
.assert_text("Edit");
|
||
}
|
||
|
||
/// Section 1.2: View mode scroll — touch-move inside the document area
|
||
/// should scroll, not select text.
|
||
#[makepad_test]
|
||
fn doc_view_mode_scroll_by_touch(app: TestApp) {
|
||
if !require_test_mode() {
|
||
return;
|
||
}
|
||
navigate_to_doc_workspace(&app);
|
||
|
||
// In View mode, touch-drag should scroll the page.
|
||
let editor = app.locator(Selector::id("crdt_editor")).wait_visible();
|
||
let snap = editor.snapshot();
|
||
|
||
// Touch down at center, move down (scroll up), release.
|
||
let (cx, cy) = (snap.x as f64 + snap.width as f64 / 2.0, snap.y as f64 + snap.height as f64 / 2.0);
|
||
app.touch_down(cx, cy);
|
||
app.touch_move(cx, cy - 80.0);
|
||
app.touch_up(cx, cy - 80.0);
|
||
|
||
// Editor should still be visible (page scrolled, not crashed).
|
||
app.locator(Selector::id("crdt_editor")).wait_visible();
|
||
}
|
||
|
||
/// Section 2.1: IME text input in Edit mode.
|
||
///
|
||
/// Enter Edit mode, type text, verify the widget receives it.
|
||
#[makepad_test]
|
||
fn doc_ime_text_input_in_edit_mode(app: TestApp) {
|
||
if !require_test_mode() {
|
||
return;
|
||
}
|
||
navigate_to_doc_workspace(&app);
|
||
|
||
// Switch to Edit mode.
|
||
app.locator(Selector::id("edit_mode_btn"))
|
||
.wait_visible()
|
||
.click();
|
||
app.locator(Selector::id("edit_mode_btn"))
|
||
.wait_visible()
|
||
.assert_text("Done");
|
||
|
||
// Tap on the editor to focus, then type.
|
||
let editor = app.locator(Selector::id("crdt_editor")).wait_visible();
|
||
editor.click();
|
||
app.type_text("Hello from device verification");
|
||
|
||
// Status bar should still show the engine is ready.
|
||
app.locator(Selector::id("status_left")).wait_visible();
|
||
}
|
||
|
||
/// Section 2.2: IME composition in a table cell.
|
||
///
|
||
/// Tap inside the document (which may contain a table), verify IME
|
||
/// composition text can be sent.
|
||
#[makepad_test]
|
||
fn doc_ime_composition_sends_text(app: TestApp) {
|
||
if !require_test_mode() {
|
||
return;
|
||
}
|
||
navigate_to_doc_workspace(&app);
|
||
|
||
app.locator(Selector::id("edit_mode_btn"))
|
||
.wait_visible()
|
||
.click();
|
||
|
||
let editor = app.locator(Selector::id("crdt_editor")).wait_visible();
|
||
editor.click();
|
||
|
||
// Send an IME composition — this simulates composing text before commit.
|
||
app.ime_composition("composed text");
|
||
|
||
// The document should still be alive.
|
||
app.locator(Selector::id("crdt_editor")).wait_visible();
|
||
}
|
||
|
||
/// Section 5.1–5.2: Scroll handoff to parent ScrollYView.
|
||
///
|
||
/// In View mode, a vertical drag should scroll; in Edit mode, a quick
|
||
/// drag should also scroll (before long-press arms).
|
||
#[makepad_test]
|
||
fn doc_scroll_handoff_view_and_edit(app: TestApp) {
|
||
if !require_test_mode() {
|
||
return;
|
||
}
|
||
navigate_to_doc_workspace(&app);
|
||
|
||
// 5.1: View mode — drag should scroll.
|
||
let editor = app.locator(Selector::id("crdt_editor")).wait_visible();
|
||
let snap = editor.snapshot();
|
||
let (cx, cy) = (snap.x as f64 + snap.width as f64 / 2.0, snap.y as f64 + snap.height as f64 / 2.0);
|
||
|
||
app.touch_down(cx, cy);
|
||
// Quick drag (short distance, well under the long-press threshold).
|
||
app.touch_move(cx, cy - 20.0);
|
||
app.touch_up(cx, cy - 20.0);
|
||
// Editor still visible — page scrolled, not selected.
|
||
app.locator(Selector::id("crdt_editor")).wait_visible();
|
||
|
||
// 5.2: Switch to Edit mode — quick drag should still scroll.
|
||
app.locator(Selector::id("edit_mode_btn"))
|
||
.wait_visible()
|
||
.click();
|
||
|
||
let editor = app.locator(Selector::id("crdt_editor")).wait_visible();
|
||
let snap = editor.snapshot();
|
||
let (cx, cy) = (snap.x as f64 + snap.width as f64 / 2.0, snap.y as f64 + snap.height as f64 / 2.0);
|
||
|
||
app.touch_down(cx, cy);
|
||
app.touch_move(cx, cy - 20.0);
|
||
app.touch_up(cx, cy - 20.0);
|
||
app.locator(Selector::id("crdt_editor")).wait_visible();
|
||
}
|
||
|
||
/// Section 3.1: Long-press triggers selection.
|
||
///
|
||
/// In Edit mode, a long-press on text should arm word selection.
|
||
/// We verify the mechanism by sending a long-press event and confirming
|
||
/// the document is still responsive.
|
||
#[makepad_test]
|
||
fn doc_long_press_arms_selection(app: TestApp) {
|
||
if !require_test_mode() {
|
||
return;
|
||
}
|
||
navigate_to_doc_workspace(&app);
|
||
|
||
app.locator(Selector::id("edit_mode_btn"))
|
||
.wait_visible()
|
||
.click();
|
||
|
||
let editor = app.locator(Selector::id("crdt_editor")).wait_visible();
|
||
let snap = editor.snapshot();
|
||
let (cx, cy) = (snap.x as f64 + snap.width as f64 / 2.0, snap.y as f64 + snap.height as f64 / 2.0);
|
||
|
||
// Long-press at center for 500ms (the default threshold).
|
||
app.long_press(cx, cy, 500.0);
|
||
|
||
// Document should still be alive after the long-press.
|
||
app.locator(Selector::id("crdt_editor")).wait_visible();
|
||
}
|
||
|
||
#[makepad_test]
|
||
fn doc_debug_nav_walk(app: TestApp) {
|
||
if !require_test_mode() {
|
||
return;
|
||
}
|
||
app.locator(Selector::id("work_button"))
|
||
.wait_visible()
|
||
.click();
|
||
app.locator(Selector::id("pull_handle"))
|
||
.wait_visible()
|
||
.drag_by(0.0, -600.0);
|
||
app.locator(Selector::id("construction_grid_button"))
|
||
.wait_visible()
|
||
.click();
|
||
app.locator(Selector::id("m_workspace_docs_btn"))
|
||
.wait_visible()
|
||
.click();
|
||
app.locator(Selector::id("new_document_btn"))
|
||
.wait_visible()
|
||
.click();
|
||
app.locator(Selector::id("crdt_editor")).wait_visible();
|
||
}
|
||
|
||
/// Section 9.0: Boot content — the CRDT workspace renders the editor after
|
||
/// navigating to a new blank document; the status bar reflects the blank
|
||
/// state without switching to the loaded-document engine string.
|
||
#[makepad_test]
|
||
fn doc_boot_content_renders(app: TestApp) {
|
||
if !require_test_mode() {
|
||
return;
|
||
}
|
||
navigate_to_doc_workspace(&app);
|
||
// A freshly created blank document reports its status here before the
|
||
// engine switches to a loaded-document status string.
|
||
app.locator(Selector::id("status_left"))
|
||
.wait_visible()
|
||
.assert_text("New blank document");
|
||
|
||
// The CRDT editor is visible (document rendered).
|
||
app.locator(Selector::id("crdt_editor")).wait_visible();
|
||
}
|
||
|
||
/// Section 10.1: Typing actually inserts document content. After switching
|
||
/// to Edit and focusing the editor, IME text lands in the document: the
|
||
/// stats bar (refreshed by a toolbar click) reports the typed words/chars.
|
||
#[makepad_test]
|
||
fn doc_type_text_inserts_document(app: TestApp) {
|
||
if !require_test_mode() {
|
||
return;
|
||
}
|
||
navigate_to_doc_workspace(&app);
|
||
let snap_of = |id: &str| app.locator(Selector::id(id)).snapshot();
|
||
|
||
app.locator(Selector::id("edit_mode_btn")).wait_visible().click();
|
||
let editor = app.locator(Selector::id("crdt_editor")).wait_visible();
|
||
editor.click();
|
||
app.type_text("hello world");
|
||
|
||
// A toolbar click drives handle_actions, which refreshes stats_label
|
||
// from the projected document (typing alone doesn't refresh it).
|
||
app.locator(Selector::id("bold_btn")).click();
|
||
app.locator(Selector::id("stats_label"))
|
||
.wait_visible()
|
||
.assert_text("Page 1 of 1 | 2 words | 11 chars");
|
||
assert_eq!(snap_of("stats_label").text, Some("Page 1 of 1 | 2 words | 11 chars".into()));
|
||
}
|
||
|
||
/// Section 10.2: The bold/italic/underline toolbar buttons drive real
|
||
/// document styling through the editor without corrupting content. Each
|
||
/// toggle keeps the typed text intact (word/char stats unchanged after a
|
||
/// refresh), and toggling a style back off leaves the doc stable. Actual
|
||
/// style semantics (run spans on the selection) are asserted by the
|
||
/// doc-ui runtime unit suite (e.g. runtime_ctrl_b_applies_bold...).
|
||
#[makepad_test]
|
||
fn doc_bold_italic_underline_ops(app: TestApp) {
|
||
if !require_test_mode() {
|
||
return;
|
||
}
|
||
navigate_to_doc_workspace(&app);
|
||
let snap_of = |id: &str| app.locator(Selector::id(id)).snapshot();
|
||
|
||
app.locator(Selector::id("edit_mode_btn")).wait_visible().click();
|
||
let editor = app.locator(Selector::id("crdt_editor")).wait_visible();
|
||
editor.click();
|
||
app.type_text("hello world");
|
||
|
||
for id in ["bold_btn", "italic_btn", "underline_btn"] {
|
||
app.locator(Selector::id(id)).wait_visible().click();
|
||
app.locator(Selector::id(id)).wait_visible().click();
|
||
}
|
||
|
||
// Refresh stats; styling must not alter the text.
|
||
app.locator(Selector::id("bold_btn")).click();
|
||
assert_eq!(snap_of("stats_label").text, Some("Page 1 of 1 | 2 words | 11 chars".into()));
|
||
app.locator(Selector::id("crdt_editor")).wait_visible();
|
||
}
|
||
|
||
/// Section 10.3: Inserting a table produces a visible, usable grid and text
|
||
/// typed into a cell reaches the cell. The editor's height grows when the
|
||
/// seeded 2x2 grid appears (a bare table block renders at zero size), and
|
||
/// after a short tap on a visible cell + IME text the stats bar (which
|
||
/// counts table-cell words, refreshed by a toolbar click) reports the cell
|
||
/// content alongside the block text.
|
||
#[makepad_test]
|
||
fn doc_insert_table_and_cell_text(app: TestApp) {
|
||
if !require_test_mode() {
|
||
return;
|
||
}
|
||
navigate_to_doc_workspace(&app);
|
||
let snap_of = |id: &str| app.locator(Selector::id(id)).snapshot();
|
||
|
||
app.locator(Selector::id("edit_mode_btn")).wait_visible().click();
|
||
let editor = app.locator(Selector::id("crdt_editor")).wait_visible();
|
||
editor.click();
|
||
app.type_text("hello world");
|
||
app.locator(Selector::id("bold_btn")).click();
|
||
|
||
let before = app.locator(Selector::id("crdt_editor")).snapshot();
|
||
app.locator(Selector::id("insert_table_btn")).click();
|
||
let after = wait_editor_grows(&app, &before);
|
||
// column (col1) is tappable. A short tap focuses the cell and shows the
|
||
// IME (Edit mode), so the subsequent type_text lands in the cell.
|
||
let raw_left = after.x as f64 + (after.width as f64 - 612.0) / 2.0;
|
||
let table_top = after.y as f64 + before.height as f64;
|
||
let col0_mid = raw_left + 80.0;
|
||
let row0 = table_top + 14.0;
|
||
let col1_mid = raw_left + 240.0;
|
||
app.touch_down(col1_mid, row0);
|
||
app.touch_up(col1_mid, row0);
|
||
app.type_text("alpha beta");
|
||
app.locator(Selector::id("bold_btn")).click();
|
||
|
||
// block "hello world" (2 words / 11 chars) + cell "alpha beta"
|
||
// (2 words / 10 chars) = 4 words / 21 chars.
|
||
assert_eq!(
|
||
snap_of("stats_label").text,
|
||
Some("Page 1 of 1 | 4 words | 21 chars".into()),
|
||
"cell text must join the document stats: {:?}",
|
||
snap_of("stats_label").text
|
||
);
|
||
let _ = col0_mid;
|
||
}
|
||
|
||
/// Section 10.4: Merge and split actually operate on the table's cells. A
|
||
/// touch-hold-then-drag on the visible right column arms a cell range
|
||
/// (Shift+Arrow on desktop); the merge toolbar button then merges the two
|
||
/// cells and reports "Merged selected cells", and split re-splits it and
|
||
/// reports "Split merged cell".
|
||
#[makepad_test]
|
||
fn doc_merge_split_cell_ops(app: TestApp) {
|
||
if !require_test_mode() {
|
||
return;
|
||
}
|
||
navigate_to_doc_workspace(&app);
|
||
let snap_of = |id: &str| app.locator(Selector::id(id)).snapshot();
|
||
|
||
app.locator(Selector::id("edit_mode_btn")).wait_visible().click();
|
||
let editor = app.locator(Selector::id("crdt_editor")).wait_visible();
|
||
editor.click();
|
||
app.type_text("hello world");
|
||
app.locator(Selector::id("bold_btn")).click();
|
||
|
||
let before = app.locator(Selector::id("crdt_editor")).snapshot();
|
||
app.locator(Selector::id("insert_table_btn")).click();
|
||
let after = wait_editor_grows(&app, &before);
|
||
|
||
// Reachable right column (col1), rows 0-1 of the seeded 2x2 grid.
|
||
let raw_left = after.x as f64 + (after.width as f64 - 612.0) / 2.0;
|
||
let table_top = after.y as f64 + before.height as f64;
|
||
let col1 = raw_left + 240.0;
|
||
let row0 = table_top + 14.0;
|
||
let row1 = table_top + 42.0;
|
||
|
||
// Touch-hold (long-press arm) then drag down one row to span the range.
|
||
app.touch_down(col1, row0);
|
||
std::thread::sleep(std::time::Duration::from_millis(700));
|
||
app.touch_move(col1, row1);
|
||
app.touch_up(col1, row1);
|
||
|
||
app.locator(Selector::id("merge_cells_btn")).click();
|
||
assert_eq!(
|
||
snap_of("status_left").text,
|
||
Some("Merged selected cells".into()),
|
||
"merge feedback: {:?}",
|
||
snap_of("status_left").text
|
||
);
|
||
|
||
app.locator(Selector::id("split_cell_btn")).click();
|
||
assert_eq!(
|
||
snap_of("status_left").text,
|
||
Some("Split merged cell".into()),
|
||
"split feedback: {:?}",
|
||
snap_of("status_left").text
|
||
);
|
||
}
|
||
|
||
/// Section 3.4: A long-press on empty space between content must NOT arm a
|
||
/// cell range or start a word selection — the document stays inert. Proven
|
||
/// by long-pressing well below the only content, then clicking Merge: with
|
||
/// no range armed it must report the "span a range" guidance, not merge.
|
||
#[makepad_test]
|
||
fn doc_long_press_empty_space_does_not_arm(app: TestApp) {
|
||
if !require_test_mode() {
|
||
return;
|
||
}
|
||
navigate_to_doc_workspace(&app);
|
||
let snap_of = |id: &str| app.locator(Selector::id(id)).snapshot();
|
||
|
||
app.locator(Selector::id("edit_mode_btn")).wait_visible().click();
|
||
let editor = app.locator(Selector::id("crdt_editor")).wait_visible();
|
||
editor.click();
|
||
app.type_text("hello world");
|
||
app.locator(Selector::id("bold_btn")).click();
|
||
|
||
// Long-press well below the text block on empty space.
|
||
let snap = app.locator(Selector::id("crdt_editor")).snapshot();
|
||
let empty_y = snap.y as f64 + snap.height as f64 - 20.0;
|
||
let empty_x = snap.x as f64 + snap.width as f64 / 2.0;
|
||
app.long_press(empty_x, empty_y, 500.0);
|
||
|
||
// Nothing armed: Merge must report the guidance, not a merge.
|
||
app.locator(Selector::id("merge_cells_btn")).click();
|
||
assert_eq!(
|
||
snap_of("status_left").text,
|
||
Some("Span a cell range with Shift+Arrow first".into()),
|
||
"long-press on empty space must NOT arm a cell range: {:?}",
|
||
snap_of("status_left").text
|
||
);
|
||
}
|
||
|
||
/// Section 4.1: A touch-hold on the left column's visible sliver dragged
|
||
/// diagonally to the bottom-right cell spans a 2x2 cell range, which Merge
|
||
/// consumes (anchor keeps its content). Mirrors the proven 1x2 path but
|
||
/// crosses both axes. Coordinates are clamped to the on-screen editor so
|
||
/// they hold on either the 411 dp (A60) or 384 dp (A16) screen.
|
||
#[makepad_test]
|
||
fn doc_diagonal_cell_range_merges(app: TestApp) {
|
||
if !require_test_mode() {
|
||
return;
|
||
}
|
||
navigate_to_doc_workspace(&app);
|
||
let snap_of = |id: &str| app.locator(Selector::id(id)).snapshot();
|
||
|
||
app.locator(Selector::id("edit_mode_btn")).wait_visible().click();
|
||
let editor = app.locator(Selector::id("crdt_editor")).wait_visible();
|
||
editor.click();
|
||
app.type_text("hello world");
|
||
app.locator(Selector::id("bold_btn")).click();
|
||
|
||
let before = app.locator(Selector::id("crdt_editor")).snapshot();
|
||
app.locator(Selector::id("insert_table_btn")).click();
|
||
let after = wait_editor_grows(&app, &before);
|
||
|
||
// On-screen geometry (clamped so col0 and col1 stay reachable).
|
||
let raw_left = after.x as f64 + (after.width as f64 - 612.0) / 2.0;
|
||
let table_top = after.y as f64 + before.height as f64;
|
||
let row0 = table_top + 14.0;
|
||
let row1 = table_top + 42.0;
|
||
// col0's center can sit off the left edge; tap a point just inside the
|
||
// editor's visible left region, which still falls in col0.
|
||
let col0_tap = if raw_left + 80.0 > after.x as f64 + 8.0 {
|
||
raw_left + 80.0
|
||
} else {
|
||
after.x as f64 + 12.0
|
||
};
|
||
let col1 = (raw_left + 240.0).min(after.x as f64 + after.width as f64 - 20.0);
|
||
|
||
// Touch-hold in (col0,row0), drag diagonally to (col1,row1): spans 2x2.
|
||
app.touch_down(col0_tap, row0);
|
||
std::thread::sleep(std::time::Duration::from_millis(700));
|
||
app.touch_move(col1, row1);
|
||
app.touch_up(col1, row1);
|
||
|
||
app.locator(Selector::id("merge_cells_btn")).click();
|
||
assert_eq!(
|
||
snap_of("status_left").text,
|
||
Some("Merged selected cells".into()),
|
||
"diagonal 2x2 range must merge: {:?}",
|
||
snap_of("status_left").text
|
||
);
|
||
|
||
app.locator(Selector::id("split_cell_btn")).click();
|
||
assert_eq!(
|
||
snap_of("status_left").text,
|
||
Some("Split merged cell".into()),
|
||
"split back: {:?}",
|
||
snap_of("status_left").text
|
||
);
|
||
}
|
||
|
||
/// Navigate from home → work → construction_grid → m_workspace_cad_btn and
|
||
/// assert the CAD workspace lands on the project dashboard (project grid +
|
||
/// "+ New Project"), not a blank/editor-only sheet. Regression test for the
|
||
/// white-screen bug: the `dashboard_layer` must realize as the first child
|
||
/// of CadWorkspace with its own `dashboard`/`dashboard_title`/`new_project_btn`
|
||
/// visible.
|
||
#[makepad_test]
|
||
fn cad_lands_on_dashboard(app: TestApp) {
|
||
if !require_test_mode() {
|
||
return;
|
||
}
|
||
app.locator(Selector::id("work_button")).wait_visible().click();
|
||
app.locator(Selector::id("pull_handle")).wait_visible().drag_by(0.0, -600.0);
|
||
app.locator(Selector::id("construction_grid_button")).wait_visible().click();
|
||
app.locator(Selector::id("m_workspace_cad_btn")).wait_visible().click();
|
||
|
||
// Landing on the dashboard grid: the dashboard header/title and the
|
||
// "+ New Project" button must be visible. `wait_visible` panics on
|
||
// timeout, so this fails the test if the dashboard never realizes.
|
||
app.locator(Selector::id("dashboard_layer")).wait_visible();
|
||
app.locator(Selector::id("dashboard")).wait_visible();
|
||
app.locator(Selector::id("dashboard_title")).wait_visible();
|
||
app.locator(Selector::id("new_project_btn")).wait_visible();
|
||
}
|