Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions crates/neomacs-gui-tests/fixtures/cursor-rtl.el
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
;;; cursor-rtl.el --- Coherent RTL replacement cursor geometry -*- lexical-binding: t; -*-

(defun neo-rtl-fail (err)
"Report ERR and terminate the isolated GUI process."
(princ (format "COMMIT-EDITING-FAIL %S\n" err) 'external-debugging-output)
(kill-emacs 1))

(defun neo-rtl-stats-size ()
"Require Neomacs telemetry, returning nil only for the GNU reference."
(when (fboundp 'neomacs--frame-snapshot)
(let* ((path (getenv "NEOMACS_LAYOUT_STATS_FILE"))
(attributes (and path (file-attributes path))))
(unless attributes (error "Missing layout telemetry: %S" path))
(file-attribute-size attributes))))

(run-at-time 10 nil (lambda () (neo-rtl-fail "watchdog expired")))
(switch-to-buffer (get-buffer-create "*rtl-cursor*"))
(fundamental-mode)
(blink-cursor-mode -1)
(insert "AAA\nnext\n")
;; Logical index zero is tall but appears after the small glyphs visually.
(put-text-property 1 4 'display
(concat (propertize "א" 'face '(:height 2.0))
(propertize "בג" 'face '(:height 0.5))))
(goto-char 1)
(run-at-time
0.5 nil
(lambda ()
(condition-case err
(progn
(redisplay t)
(let* ((cold (window-cursor-info))
(height (- (cdr (posn-x-y (posn-at-point 5))) (aref cold 2))))
(unless (= (aref cold 4) height)
(error "RTL cursor must use logical first glyph's full height: %S/%S" cold height))
(goto-char 4)
(redisplay t)
(let ((before (neo-rtl-stats-size)))
(goto-char 1)
(redisplay t)
(unless (equal (window-cursor-info) cold)
(error "RTL cursor changed under replay: %S/%S" cold (window-cursor-info)))
(if before
(princ (format "CURSOR-REPLAY-STATS %d %d\n" before
(neo-rtl-stats-size))
'external-debugging-output)
(princ "CURSOR-REPLAY-REFERENCE-GEOMETRY-ONLY\n" 'external-debugging-output)))
(princ "COMMIT-EDITING-PASS\n" 'external-debugging-output)
(kill-emacs 0)))
(error (neo-rtl-fail err)))))
55 changes: 55 additions & 0 deletions crates/neomacs-gui-tests/fixtures/cursor-stretch-policy.el
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
;;; cursor-stretch-policy.el --- Fresh/replay width policy -*- lexical-binding: t; -*-
(defun neo-stretch-fail (error-data)
"Report probe failure and terminate, including asynchronous timer errors."
(princ (format "COMMIT-EDITING-FAIL %S\n" error-data) 'external-debugging-output)
(kill-emacs 1))
(defun neo-stretch-stats-size ()
"Require telemetry on Neomacs; GNU is explicitly geometry-only."
(when (fboundp 'neomacs--frame-snapshot)
(let* ((path (getenv "NEOMACS_LAYOUT_STATS_FILE"))
(attributes (and path (file-attributes path))))
(unless attributes (error "Missing layout telemetry"))
(file-attribute-size attributes))))
(run-at-time 10 nil (lambda () (neo-stretch-fail "watchdog expired")))
(condition-case error-data
(let* ((probe (or (getenv "NEOMACS_COMMIT_PROBE") ""))
(narrow (string-match-p "narrow" probe))
(enabled (string-match-p "enabled" probe))
(width (if narrow 0.25 5)))
(setq warning-minimum-level :emergency x-stretch-cursor (and enabled t))
(blink-cursor-mode -1)
(set-frame-parameter nil 'cursor-type 'box)
(switch-to-buffer (get-buffer-create "*stretch-cursor-policy*"))
(fundamental-mode)
(insert "אxב\n")
(put-text-property 2 3 'display (list 'space :width width))
(goto-char 2)
(run-at-time
0.5 nil
(lambda ()
(condition-case error-data
(progn
(redisplay t)
(let ((cold (window-cursor-info)))
(goto-char 1)
(redisplay t)
(let ((before (neo-stretch-stats-size)))
(goto-char 2)
(redisplay t)
(let* ((after (neo-stretch-stats-size))
(replayed (window-cursor-info))
(actual (aref replayed 3))
(column (frame-char-width)))
(unless (= (aref cold 3) actual)
(error "Stretch width changed on replay: cold=%S replay=%S" cold replayed))
(unless (and (> actual 0)
(if narrow (< actual column)
(if enabled (> actual column) (<= actual column))))
(error "Invalid stretch-width policy: enabled=%S narrow=%S width=%S column=%S" enabled narrow actual column))
(if before
(princ (format "CURSOR-REPLAY-STATS %d %d\n" before after) 'external-debugging-output)
(princ "CURSOR-REPLAY-REFERENCE-GEOMETRY-ONLY\n" 'external-debugging-output))
(princ "COMMIT-EDITING-PASS\n" 'external-debugging-output)
(kill-emacs 0)))))
(error (neo-stretch-fail error-data))))))
(error (neo-stretch-fail error-data)))
51 changes: 51 additions & 0 deletions crates/neomacs-gui-tests/fixtures/org-table-whitespace.el
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
;;; org-table-whitespace.el --- Synthetic table probe -*- lexical-binding: t; -*-
;;; No third-party package, SQL, or user file is loaded.
(defun neo-org-probe-fail (error-data)
(princ (format "COMMIT-EDITING-FAIL %S\n" error-data) 'external-debugging-output)
(kill-emacs 1))
(run-at-time 10 nil (lambda () (neo-org-probe-fail "watchdog expired")))
(condition-case error-data
(progn
(setq warning-minimum-level :emergency)
(require 'org)
(switch-to-buffer (get-buffer-create "*org-table-whitespace-probe*"))
(insert "| label |\n| alpha |\n| longer |\n\ncontrol \n")
(let ((enable-dir-local-variables nil)) (org-mode))
(setq-local show-trailing-whitespace t)
(font-lock-ensure)
;; org-modern--table uses exactly this display/face for vertical bars.
(goto-char (point-min))
(while (search-forward "|" nil t)
(add-text-properties (1- (point)) (point)
'(display (space :width (3))
face (:inherit org-table :inverse-video t))))
(goto-char (point-min))
(run-at-time
1 nil
(lambda ()
(condition-case error-data
(progn
(redisplay t)
(if (fboundp 'neomacs--frame-snapshot)
(let ((snapshot (neomacs--frame-snapshot nil 'text-faces))
row highlighted)
(princ snapshot 'external-debugging-output)
(dolist (line (split-string snapshot "\n"))
(when (string-match "\\` *\\([0-9]+\\)|" line)
(setq row (string-to-number (match-string 1 line))))
(when (string-match-p "trailing-whitespace" line)
(push row highlighted)))
(when (seq-some (lambda (row) (memq row '(0 1 2))) highlighted)
(error "Org table border/padding was classified as trailing whitespace: %S" highlighted))
(unless (memq 4 highlighted)
(error "Genuine trailing spaces lost their highlight: %S" highlighted))
(princ "ORG-TABLE-RENDERED-FACES-PASS\n" 'external-debugging-output))
;; This is explicitly source-only, not a GNU face-color oracle.
(progn
(unless (equal (get-text-property 1 'display) '(space :width (3)))
(error "Missing reference table-bar display property"))
(princ "ORG-TABLE-REFERENCE-SOURCE-ONLY\n" 'external-debugging-output)))
(princ "COMMIT-EDITING-PASS\n" 'external-debugging-output)
(kill-emacs 0))
(error (neo-org-probe-fail error-data))))))
(error (neo-org-probe-fail error-data)))
81 changes: 76 additions & 5 deletions crates/neomacs-gui-tests/tests/commit_editing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ fn run_fixture_probe(probe: &str, fixture: &str) {
let program = std::env::var_os("NEOMACS_GUI_TEST_BINARY")
.map(PathBuf::from)
.unwrap_or_else(|| root.join("target/release/neomacs"));
let missing_telemetry = probe == "rtl-missing-telemetry";
let stats_path = if missing_telemetry {
artifacts
.join(format!("missing-{}", std::process::id()))
.join("layout-stats.log")
} else {
artifacts.join("layout-stats.log")
};
let mut plan = GuiTestPlan::new(
backend,
&root,
Expand All @@ -50,12 +58,24 @@ fn run_fixture_probe(probe: &str, fixture: &str) {
.with_env("NEOMACS_COMMIT_PROBE", probe)
.with_env(
"NEOMACS_LAYOUT_STATS_FILE",
artifacts
.join("layout-stats.log")
.to_string_lossy()
.into_owned(),
stats_path.to_string_lossy().into_owned(),
)
.with_env("RUST_LOG", "warn");
if missing_telemetry {
assert!(!stats_path.exists());
// Exercise Neomacs's mandatory-telemetry contract on either executable.
plan = plan.with_args([
"-Q".to_string(),
"--eval".to_string(),
"(unless (fboundp 'neomacs--frame-snapshot) (fset 'neomacs--frame-snapshot #'ignore))"
.to_string(),
"-l".to_string(),
root.join("crates/neomacs-gui-tests/fixtures")
.join(fixture)
.to_string_lossy()
.into_owned(),
]);
}
for (key, value) in session.env() {
plan = plan.with_env(key, value);
}
Expand All @@ -67,9 +87,31 @@ fn run_fixture_probe(probe: &str, fixture: &str) {
.unwrap();
let stderr = fs::read_to_string(&result.artifacts.stderr).unwrap_or_default();
assert!(!result.timed_out, "{result:#?}\n{stderr}");
if missing_telemetry {
assert_eq!(result.exit_code, Some(1), "{result:#?}\n{stderr}");
assert!(stderr.contains("Missing layout telemetry"), "{stderr}");
assert!(
!stderr.contains("CURSOR-REPLAY-REFERENCE-GEOMETRY-ONLY"),
"{stderr}"
);
return;
}
assert_eq!(result.exit_code, Some(0), "{result:#?}\n{stderr}");
assert!(stderr.contains("COMMIT-EDITING-PASS"), "{stderr}");
if probe.ends_with("newline-cursor")
if probe == "org-table-whitespace" {
let rendered = stderr.contains("ORG-TABLE-RENDERED-FACES-PASS");
let source_only = stderr.contains("ORG-TABLE-REFERENCE-SOURCE-ONLY");
assert_ne!(
rendered, source_only,
"missing or ambiguous Org evidence: {stderr}"
);
if source_only {
eprintln!("Org reference control: source setup only; rendered faces were not asserted");
}
}
if (probe.ends_with("newline-cursor")
|| probe == "rtl-replacement-cursor"
|| probe.starts_with("stretch-policy-"))
&& !stderr.contains("CURSOR-REPLAY-REFERENCE-GEOMETRY-ONLY")
{
let marker = stderr
Expand Down Expand Up @@ -149,6 +191,25 @@ fn commit_probe_rejects_unknown_backend() {
commit_probe_backend(Some("unknown"));
}

/// Neomacs verifies rendered faces; GNU explicitly reports a source-only control.
#[test]
fn org_table_probe_reports_rendered_faces_or_reference_source() {
run_fixture_probe("org-table-whitespace", "org-table-whitespace.el");
}

/// Fresh and actual cursor-only replay obey narrow/wide stretch policy in RTL.
#[test]
fn stretch_cursor_replay_obeys_width_policy() {
for probe in [
"stretch-policy-narrow",
"stretch-policy-wide",
"stretch-policy-narrow-enabled",
"stretch-policy-wide-enabled",
] {
run_fixture_probe(probe, "cursor-stretch-policy.el");
}
}

#[test]
fn first_commit_character_uses_the_asynchronously_selected_buffer() {
run_commit_probe("async-map");
Expand Down Expand Up @@ -196,6 +257,16 @@ fn mixed_newline_cursor_matches_gnu_row_geometry() {
}
}

#[test]
fn rtl_missing_telemetry_is_an_explicit_failure() {
run_fixture_probe("rtl-missing-telemetry", "cursor-rtl.el");
}

#[test]
fn rtl_replacement_cursor_uses_one_glyph_for_placement_and_metrics() {
run_fixture_probe("rtl-replacement-cursor", "cursor-rtl.el");
}

#[test]
fn raised_text_keeps_the_original_descent() {
run_fixture_probe("raised-text", "newline-geometry.el");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pub(super) fn bounded_string(value: Value, max_chars: usize) -> bool {
if string.schars() > max_chars || string.as_bytes().len() > max_chars.saturating_mul(4) {
return false;
}
let props = neovm_core::emacs_core::value::get_string_text_properties_table_for_value(value);
let props = crate::cursor_source_dependencies::string_properties(value);
let Some(props) = props else {
return true;
};
Expand Down
Loading