Compare commits
10 Commits
main
...
accessibil
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d5d9a3f1d | ||
| 3bce527500 | |||
| 3c631d29d4 | |||
| 5e35667794 | |||
| aa17b8b340 | |||
| fb88f2a9dc | |||
| 2371c8d694 | |||
| 623b974e56 | |||
| 6882b9a643 | |||
| c4e6ef142b |
@@ -1286,6 +1286,7 @@ Emacs and macOS / GNUstep
|
||||
* Mac / GNUstep Basics:: Basic Emacs usage under GNUstep or macOS.
|
||||
* Mac / GNUstep Customization:: Customizations under GNUstep or macOS.
|
||||
* Mac / GNUstep Events:: How window system events are handled.
|
||||
* Accessibility:: Screen reader and Zoom support on macOS.
|
||||
* GNUstep Support:: Details on status of GNUstep support.
|
||||
|
||||
Emacs and Haiku
|
||||
|
||||
@@ -36,6 +36,7 @@ Support}), but we hope to improve it in the future.
|
||||
* Mac / GNUstep Basics:: Basic Emacs usage under GNUstep or macOS.
|
||||
* Mac / GNUstep Customization:: Customizations under GNUstep or macOS.
|
||||
* Mac / GNUstep Events:: How window system events are handled.
|
||||
* Accessibility:: Screen reader and Zoom support on macOS.
|
||||
* GNUstep Support:: Details on status of GNUstep support.
|
||||
@end menu
|
||||
|
||||
@@ -272,6 +273,107 @@ and return the result as a string. You can also use the Lisp function
|
||||
services and receive the results back. Note that you may need to
|
||||
restart Emacs to access newly-available services.
|
||||
|
||||
@node Accessibility
|
||||
@section Accessibility (macOS)
|
||||
@cindex VoiceOver
|
||||
@cindex accessibility (macOS)
|
||||
@cindex screen reader (macOS)
|
||||
@cindex Zoom, cursor tracking (macOS)
|
||||
@cindex accessibility, Zoom (macOS)
|
||||
@cindex echo area, accessibility (macOS)
|
||||
@cindex completion announcements, accessibility (macOS)
|
||||
|
||||
When built with the Cocoa interface on macOS, Emacs exposes buffer
|
||||
content, cursor position, mode lines, and interactive elements to the
|
||||
macOS accessibility subsystem. This enables use with VoiceOver,
|
||||
Apple's built-in screen reader, and with other assistive technology
|
||||
such as macOS Zoom.
|
||||
|
||||
Toggle VoiceOver with @kbd{Cmd-F5} (or via System Settings,
|
||||
Accessibility, VoiceOver). When Emacs is focused, VoiceOver announces
|
||||
the buffer name and current line. Standard Emacs navigation produces
|
||||
speech feedback:
|
||||
|
||||
@itemize @bullet
|
||||
@item
|
||||
Arrow keys read individual characters (left/right) or full lines
|
||||
(up/down).
|
||||
@item
|
||||
@kbd{M-f} and @kbd{M-b} announce words.
|
||||
@item
|
||||
@kbd{C-n} and @kbd{C-p} read the destination line.
|
||||
@item
|
||||
Shift-modified movement announces selected or deselected text.
|
||||
@item
|
||||
@key{TAB} and @kbd{S-@key{TAB}} navigate interactive elements
|
||||
(buttons, completion candidates) within a buffer.
|
||||
@end itemize
|
||||
|
||||
The @file{*Completions*} buffer announces each completion candidate
|
||||
as you navigate, even while keyboard focus remains in the minibuffer.
|
||||
|
||||
Echo area messages are announced automatically. When a background
|
||||
operation completes and displays a message (e.g., @samp{Git finished},
|
||||
@samp{Wrote file}), VoiceOver reads it without requiring any action.
|
||||
Messages are suppressed while the minibuffer is active (i.e., while
|
||||
you are typing a command) to avoid interrupting prompt reading.
|
||||
During keyboard navigation, echo area messages are temporarily
|
||||
suppressed (for 0.5 seconds) to prevent them from interrupting the
|
||||
cursor-position announcement.
|
||||
|
||||
VoiceOver's rotor browse cursor stays synchronized with the Emacs
|
||||
cursor after large programmatic jumps (for example, heading navigation
|
||||
in Org mode, @code{xref-find-definitions}, or @code{imenu}).
|
||||
|
||||
@subheading Zoom Cursor Tracking
|
||||
|
||||
When macOS Zoom is enabled (System Settings, Accessibility, Zoom,
|
||||
with @samp{Follow keyboard focus} active), Emacs reports its cursor
|
||||
position to the Zoom subsystem after every redisplay cycle. The
|
||||
zoomed viewport automatically tracks the Emacs insertion point as you
|
||||
type or navigate. During completion, Zoom follows the selected
|
||||
candidate rather than the text cursor. This integration is always
|
||||
active when Zoom is enabled; no Emacs-side toggle is needed.
|
||||
|
||||
@vindex ns-accessibility-enabled
|
||||
@cindex disabling accessibility (macOS)
|
||||
To disable the VoiceOver accessibility interface (e.g., to
|
||||
eliminate overhead on systems where a screen reader is not in use),
|
||||
set @code{ns-accessibility-enabled} to @code{nil}. The default value
|
||||
is @code{nil}; Emacs sets it to @code{t} automatically at startup if
|
||||
it detects an active assistive technology. Zoom cursor tracking
|
||||
operates independently and is not affected by this variable.
|
||||
|
||||
@subheading Known Limitations
|
||||
|
||||
@itemize @bullet
|
||||
@item
|
||||
Very large buffers (tens of megabytes) may cause slow initial
|
||||
accessibility text extraction. Once cached, subsequent queries
|
||||
are fast.
|
||||
@item
|
||||
Mode-line text extraction handles only character glyphs. Mode lines
|
||||
using icon fonts (e.g., icon-based mode lines)
|
||||
produce incomplete accessibility text.
|
||||
@item
|
||||
Window configuration changes (splits, deletions, new buffers) trigger
|
||||
a full rebuild of the accessibility virtual element tree, which may
|
||||
cause a brief pause in VoiceOver announcements.
|
||||
@item
|
||||
Right-to-left (bidi) text is exposed correctly as buffer content,
|
||||
but hit-testing of screen positions to character ranges assumes
|
||||
left-to-right layout.
|
||||
@item
|
||||
Completion popup tracking (for Zoom and VoiceOver) is limited to
|
||||
buffers smaller than 10,000 characters. Larger buffers are assumed
|
||||
not to be completion popups and are skipped to avoid excessive work
|
||||
per redisplay cycle.
|
||||
@end itemize
|
||||
|
||||
This support is available only on the Cocoa build. GNUstep has a
|
||||
different accessibility model and is not yet supported.
|
||||
|
||||
|
||||
@node GNUstep Support
|
||||
@section GNUstep Support
|
||||
|
||||
|
||||
26
etc/NEWS
26
etc/NEWS
@@ -4544,6 +4544,24 @@ accessibility features. Enable Emacs via (System Settings, Privacy &
|
||||
Security, Accessibility...) and add the Emacs.app installed directory to
|
||||
the enabled application list.
|
||||
|
||||
+++
|
||||
** VoiceOver accessibility support on macOS.
|
||||
Emacs now exposes buffer content, cursor position, and interactive
|
||||
elements to the macOS accessibility subsystem (VoiceOver). Standard
|
||||
navigation keys produce speech feedback: arrow keys read characters
|
||||
and lines, 'M-f'/'M-b' announce words, and shift-modified movement
|
||||
reports selected text. Echo area messages (e.g., "Wrote file",
|
||||
"Compilation finished") are announced automatically without user
|
||||
interaction. The VoiceOver rotor cursor stays synchronized after
|
||||
large programmatic jumps such as xref, imenu, or Org heading
|
||||
navigation. Pressing 'TAB' navigates interactive spans (buttons,
|
||||
completion candidates) within a buffer. Completion
|
||||
frameworks that render via overlays or child frames announce the
|
||||
selected candidate.
|
||||
|
||||
Set 'ns-accessibility-enabled' to nil to disable the accessibility
|
||||
interface and eliminate the associated overhead.
|
||||
|
||||
---
|
||||
** Process execution has been optimized on Android.
|
||||
The run-time performance of subprocesses on recent Android releases,
|
||||
@@ -4679,11 +4697,3 @@ GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
Local variables:
|
||||
coding: utf-8
|
||||
mode: outline
|
||||
mode: emacs-news
|
||||
paragraph-separate: "[ ]"
|
||||
end:
|
||||
|
||||
177
src/nsterm.h
177
src/nsterm.h
@@ -453,6 +453,143 @@ enum ns_return_frame_mode
|
||||
@end
|
||||
|
||||
|
||||
/* ==========================================================================
|
||||
|
||||
Accessibility virtual elements (macOS / Cocoa only)
|
||||
|
||||
========================================================================== */
|
||||
|
||||
#ifdef NS_IMPL_COCOA
|
||||
@class EmacsView;
|
||||
|
||||
/* Base class for virtual accessibility elements attached to EmacsView. */
|
||||
@interface EmacsAccessibilityElement : NSAccessibilityElement
|
||||
@property (nonatomic, unsafe_unretained) EmacsView *emacsView;
|
||||
/* Lisp window object --- safe across GC cycles.
|
||||
GC safety: these Lisp_Objects are NOT visible to GC via staticpro
|
||||
or the specpdl stack. This is safe because:
|
||||
(1) Emacs GC runs only on the main thread, at well-defined safe
|
||||
points during Lisp evaluation --- never during redisplay.
|
||||
(2) Accessibility elements are owned by EmacsView which belongs to
|
||||
an active frame; windows referenced here are always reachable
|
||||
from the frame's window tree until rebuildAccessibilityTree
|
||||
updates them during the next redisplay cycle.
|
||||
(3) AX getters dispatch_sync to main before accessing Lisp state,
|
||||
so GC cannot run concurrently with any access to lispWindow.
|
||||
(4) validWindow checks WINDOW_LIVE_P before dereferencing.
|
||||
Note: this relies on non-moving GC. If Emacs adopts a moving
|
||||
collector (MPS), these fields must be registered as GC roots. */
|
||||
@property (nonatomic, assign) Lisp_Object lispWindow;
|
||||
- (struct window *)validWindow; /* Returns live window or NULL. */
|
||||
- (NSRect)screenRectFromEmacsX:(int)x y:(int)y width:(int)w height:(int)h;
|
||||
@end
|
||||
|
||||
/* A visible run: maps a contiguous range of accessibility indices
|
||||
to a contiguous range of buffer character positions. Invisible
|
||||
text is skipped, so ax_start values are consecutive across runs
|
||||
while charpos values may have gaps. */
|
||||
typedef struct ns_ax_visible_run
|
||||
{
|
||||
ptrdiff_t charpos; /* Buffer charpos where this visible run starts. */
|
||||
ptrdiff_t length; /* Number of visible Emacs characters in this run. */
|
||||
NSUInteger ax_start; /* Starting index in the accessibility string. */
|
||||
NSUInteger ax_length; /* Length in accessibility string (UTF-16 units). */
|
||||
} ns_ax_visible_run;
|
||||
|
||||
/* Virtual AXTextArea element --- one per visible Emacs window (buffer). */
|
||||
@interface EmacsAccessibilityBuffer
|
||||
: EmacsAccessibilityElement <NSAccessibility>
|
||||
{
|
||||
ns_ax_visible_run *visibleRuns;
|
||||
NSUInteger visibleRunCount;
|
||||
NSUInteger *lineStartOffsets; /* AX index for each line. */
|
||||
NSUInteger lineCount; /* Entries in lineStartOffsets. */
|
||||
NSMutableArray *cachedInteractiveSpans;
|
||||
BOOL interactiveSpansDirty;
|
||||
/* Deferred overlay visibility detection. The overlay notification
|
||||
branch invalidates the text cache and records the old visible text
|
||||
length here; ensureTextCache posts ValueChanged after rebuild if
|
||||
the length changed. This avoids running Lisp (ns_ax_buffer_text)
|
||||
synchronously in the notification dispatch path, which interferes
|
||||
with redisplay state. */
|
||||
BOOL pendingOverlayCheck;
|
||||
NSUInteger pendingOverlayTextLen;
|
||||
}
|
||||
@property (nonatomic, copy) NSString *cachedText;
|
||||
@property (nonatomic, assign) modiff_count cachedTextModiff;
|
||||
@property (nonatomic, assign) modiff_count cachedOverlayModiff;
|
||||
@property (nonatomic, assign) ptrdiff_t cachedTextStart;
|
||||
@property (nonatomic, assign) modiff_count cachedModiff;
|
||||
@property (nonatomic, assign) modiff_count cachedCharsModiff;
|
||||
@property (nonatomic, assign) ptrdiff_t cachedPoint;
|
||||
@property (nonatomic, assign) BOOL cachedMarkActive;
|
||||
@property (nonatomic, assign) struct buffer *cachedBuffer;
|
||||
@property (nonatomic, copy) NSString *cachedCompletionAnnouncement;
|
||||
@property (nonatomic, assign) ptrdiff_t cachedCompletionOverlayStart;
|
||||
@property (nonatomic, assign) ptrdiff_t cachedCompletionOverlayEnd;
|
||||
@property (nonatomic, assign) ptrdiff_t cachedCompletionPoint;
|
||||
@property (nonatomic, assign) BOOL pendingEditNotification;
|
||||
- (void)invalidateTextCache;
|
||||
- (NSInteger)lineForAXIndex:(NSUInteger)idx;
|
||||
- (NSRange)rangeForLine:(NSUInteger)line textLength:(NSUInteger)tlen;
|
||||
- (ptrdiff_t)charposForAccessibilityIndex:(NSUInteger)ax_idx;
|
||||
- (NSUInteger)accessibilityIndexForCharpos:(ptrdiff_t)charpos;
|
||||
@end
|
||||
|
||||
@interface EmacsAccessibilityBuffer (Notifications)
|
||||
- (void)postTextChangedNotification:(ptrdiff_t)point;
|
||||
- (void)postFocusedCursorNotification:(ptrdiff_t)point
|
||||
direction:(NSInteger)direction
|
||||
granularity:(NSInteger)granularity
|
||||
markActive:(BOOL)markActive
|
||||
oldMarkActive:(BOOL)oldMarkActive;
|
||||
- (void)postCompletionAnnouncementForBuffer:(struct buffer *)b
|
||||
point:(ptrdiff_t)point;
|
||||
- (void)postAccessibilityNotificationsForFrame:(struct frame *)f;
|
||||
@end
|
||||
|
||||
@interface EmacsAccessibilityBuffer (InteractiveSpans)
|
||||
- (void)invalidateInteractiveSpans;
|
||||
@end
|
||||
|
||||
/* Virtual AXStaticText element --- one per mode line. */
|
||||
@interface EmacsAccessibilityModeLine : EmacsAccessibilityElement
|
||||
@end
|
||||
|
||||
/* Span types for interactive AX child elements.
|
||||
Used to distinguish span types during scanning; all map to AXButton role at present. */
|
||||
typedef NS_ENUM(NSInteger, EmacsAXSpanType)
|
||||
{
|
||||
EmacsAXSpanTypeNone = -1,
|
||||
EmacsAXSpanTypeButton = 0,
|
||||
EmacsAXSpanTypeCompletionItem = 1,
|
||||
EmacsAXSpanTypeWidget = 2,
|
||||
};
|
||||
|
||||
/* A lightweight AX element representing one interactive text span
|
||||
(button, link, checkbox, completion candidate, etc.) within a buffer
|
||||
window. Exposed as AX child of EmacsAccessibilityBuffer so VoiceOver
|
||||
Tab navigation can reach individual interactive elements. */
|
||||
@interface EmacsAccessibilityInteractiveSpan : EmacsAccessibilityElement
|
||||
|
||||
@property (nonatomic, assign) ptrdiff_t charposStart;
|
||||
@property (nonatomic, assign) ptrdiff_t charposEnd;
|
||||
@property (nonatomic, assign) EmacsAXSpanType spanType;
|
||||
@property (nonatomic, copy) NSString *spanLabel;
|
||||
@property (nonatomic, unsafe_unretained)
|
||||
EmacsAccessibilityBuffer *parentBuffer;
|
||||
|
||||
- (NSAccessibilityRole) accessibilityRole;
|
||||
- (NSString *) accessibilityLabel;
|
||||
- (NSRect) accessibilityFrame;
|
||||
- (BOOL) isAccessibilityElement;
|
||||
- (BOOL) isAccessibilityFocused;
|
||||
- (void) setAccessibilityFocused: (BOOL) focused;
|
||||
|
||||
@end
|
||||
#endif /* NS_IMPL_COCOA */
|
||||
|
||||
|
||||
/* ==========================================================================
|
||||
|
||||
The main Emacs view
|
||||
@@ -471,6 +608,37 @@ enum ns_return_frame_mode
|
||||
#ifdef NS_IMPL_COCOA
|
||||
char *old_title;
|
||||
BOOL maximizing_resize;
|
||||
NSMutableArray *accessibilityElements;
|
||||
/* See GC safety comment on EmacsAccessibilityElement.lispWindow. */
|
||||
Lisp_Object lastSelectedWindow;
|
||||
Lisp_Object lastRootWindow;
|
||||
BOOL accessibilityTreeValid;
|
||||
int lastLeafCount;
|
||||
@public /* Accessed by ns_ax_post_updates_error (C function). */
|
||||
BOOL accessibilityUpdating;
|
||||
BOOL childFrameCompletionActive;
|
||||
char *childFrameLastCandidate;
|
||||
char *childFrameLastBufferName;
|
||||
modiff_count childFrameLastModiff;
|
||||
/* Last BUF_CHARS_MODIFF seen for echo_area_buffer[0]. Used by
|
||||
postEchoAreaAnnouncementIfNeeded to detect new echo area messages
|
||||
independently of the per-element notification cycle. */
|
||||
modiff_count lastEchoCharsModiff;
|
||||
/* Last known point of the selected window's buffer. Used to detect
|
||||
cursor movement and suppress echo area announcements during
|
||||
navigation (which would interrupt line reading). */
|
||||
ptrdiff_t lastSelectedBufferPoint;
|
||||
/* Timestamp of the most recent cursor movement. Echo area
|
||||
announcements are suppressed for NS_AX_ECHO_SUPPRESS_SECONDS
|
||||
after the last movement so that asynchronous updates (eldoc,
|
||||
which-function-mode) arriving in subsequent redisplay cycles
|
||||
do not interrupt VoiceOver line reading. */
|
||||
NSTimeInterval lastCursorMoveTimestamp;
|
||||
/* Timestamp of the most recent text edit (typing). Child frame
|
||||
completion announcements are suppressed for a brief window after
|
||||
text edits to prevent the completion candidate announcement from
|
||||
interrupting VoiceOver character echo. */
|
||||
NSTimeInterval lastTextEditTimestamp;
|
||||
#endif
|
||||
BOOL font_panel_active;
|
||||
NSFont *font_panel_result;
|
||||
@@ -528,6 +696,15 @@ enum ns_return_frame_mode
|
||||
- (void)windowWillExitFullScreen;
|
||||
- (void)windowDidExitFullScreen;
|
||||
- (void)windowDidBecomeKey;
|
||||
|
||||
#ifdef NS_IMPL_COCOA
|
||||
/* Accessibility support. */
|
||||
- (void)rebuildAccessibilityTree;
|
||||
- (void)invalidateAccessibilityTree;
|
||||
- (void)postAccessibilityUpdates;
|
||||
- (void)postEchoAreaAnnouncementIfNeeded;
|
||||
- (void)announceChildFrameCompletion;
|
||||
#endif
|
||||
@end
|
||||
|
||||
|
||||
|
||||
4832
src/nsterm.m
4832
src/nsterm.m
File diff suppressed because it is too large
Load Diff
257
test/src/nsterm-accessibility-tests.el
Normal file
257
test/src/nsterm-accessibility-tests.el
Normal file
@@ -0,0 +1,257 @@
|
||||
;;; nsterm-accessibility-tests.el --- ERT tests for NS accessibility -*- lexical-binding: t; -*-
|
||||
|
||||
;; Copyright (C) 2026 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Martin Sukany <martin@sukany.cz>
|
||||
;; Keywords: accessibility, tests
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; Tests for the NS (macOS Cocoa) accessibility interface.
|
||||
;;
|
||||
;; These tests verify:
|
||||
;; - The `ns-accessibility-enabled' variable exists and is configurable
|
||||
;; - DEFSYM symbols used by the accessibility code are correctly interned
|
||||
;; - The accessibility code does not break standard Emacs operations
|
||||
;;
|
||||
;; Running:
|
||||
;; make -C test TESTS=src/nsterm-accessibility-tests
|
||||
;; Or from a running Emacs:
|
||||
;; M-x ert RET t RET
|
||||
;;
|
||||
;; Note: GUI-dependent tests (VoiceOver interaction, Zoom tracking)
|
||||
;; cannot be automated and must be tested manually. See the
|
||||
;; ``Accessibility'' section in the macOS appendix of the Emacs manual.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'ert)
|
||||
|
||||
;; These tests only make sense on NS (Cocoa) builds.
|
||||
(defvar ns-accessibility-tests-skip
|
||||
(not (eq window-system 'ns))
|
||||
"Non-nil means skip NS-specific tests (not an NS build).")
|
||||
|
||||
;;; --- Variable existence and type tests ---
|
||||
|
||||
(ert-deftest ns-accessibility-enabled-exists ()
|
||||
"Test that `ns-accessibility-enabled' is bound."
|
||||
(skip-unless (not ns-accessibility-tests-skip))
|
||||
(should (boundp 'ns-accessibility-enabled)))
|
||||
|
||||
(ert-deftest ns-accessibility-enabled-is-boolean ()
|
||||
"Test that `ns-accessibility-enabled' holds a boolean value."
|
||||
(skip-unless (not ns-accessibility-tests-skip))
|
||||
(should (booleanp ns-accessibility-enabled)))
|
||||
|
||||
(ert-deftest ns-accessibility-enabled-settable ()
|
||||
"Test that `ns-accessibility-enabled' can be toggled."
|
||||
(skip-unless (not ns-accessibility-tests-skip))
|
||||
(let ((original ns-accessibility-enabled))
|
||||
(unwind-protect
|
||||
(progn
|
||||
(setq ns-accessibility-enabled t)
|
||||
(should (eq ns-accessibility-enabled t))
|
||||
(setq ns-accessibility-enabled nil)
|
||||
(should (eq ns-accessibility-enabled nil)))
|
||||
(setq ns-accessibility-enabled original))))
|
||||
|
||||
;;; --- DEFSYM symbol tests ---
|
||||
;; Verify that the accessibility DEFSYM symbols are correctly interned.
|
||||
;; These symbols are used for text property scanning in interactive
|
||||
;; span detection and completion candidate extraction.
|
||||
;; These symbols are always present in standard Emacs (from widget.el,
|
||||
;; button.el, etc.). The tests verify that DEFSYM did not use
|
||||
;; namespaced names like 'ns-ax-widget' which would be wrong.
|
||||
|
||||
(ert-deftest ns-ax-defsym-widget ()
|
||||
"Test that the `widget' symbol used by span scanning is interned."
|
||||
(should (intern-soft "widget")))
|
||||
|
||||
(ert-deftest ns-ax-defsym-button ()
|
||||
"Test that the `button' symbol used by span scanning is interned."
|
||||
(should (intern-soft "button")))
|
||||
|
||||
(ert-deftest ns-ax-defsym-completion ()
|
||||
"Test that the `completion' symbol is interned."
|
||||
(should (intern-soft "completion")))
|
||||
|
||||
(ert-deftest ns-ax-defsym-completion-list-mode ()
|
||||
"Test that `completion-list-mode' symbol is interned."
|
||||
(should (intern-soft "completion-list-mode")))
|
||||
|
||||
(ert-deftest ns-ax-defsym-backtab ()
|
||||
"Test that `backtab' symbol is interned."
|
||||
(should (intern-soft "backtab")))
|
||||
|
||||
;;; --- Buffer operation safety tests ---
|
||||
;; Ensure the accessibility code does not interfere with basic
|
||||
;; buffer operations.
|
||||
|
||||
(ert-deftest ns-accessibility-buffer-create-kill ()
|
||||
"Test that creating and killing buffers works with accessibility enabled."
|
||||
(skip-unless (not ns-accessibility-tests-skip))
|
||||
(let ((ns-accessibility-enabled t))
|
||||
(let ((buf (generate-new-buffer " *ns-ax-test*")))
|
||||
(should (buffer-live-p buf))
|
||||
(with-current-buffer buf
|
||||
(insert "Test content for accessibility.\n")
|
||||
(should (= (point-max) 33)))
|
||||
(kill-buffer buf)
|
||||
(should-not (buffer-live-p buf)))))
|
||||
|
||||
(ert-deftest ns-accessibility-large-buffer ()
|
||||
"Test that large buffers do not cause issues."
|
||||
(skip-unless (not ns-accessibility-tests-skip))
|
||||
(let ((ns-accessibility-enabled t))
|
||||
(let ((buf (generate-new-buffer " *ns-ax-large-test*")))
|
||||
(unwind-protect
|
||||
(with-current-buffer buf
|
||||
;; Insert ~100KB of text
|
||||
(dotimes (_ 1000)
|
||||
(insert (make-string 100 ?x) "\n"))
|
||||
(should (> (buffer-size) 100000))
|
||||
(goto-char (point-min))
|
||||
(should (= (point) 1))
|
||||
(goto-char (point-max))
|
||||
(should (= (point) (point-max))))
|
||||
(kill-buffer buf)))))
|
||||
|
||||
(ert-deftest ns-accessibility-multibyte-buffer ()
|
||||
"Test that multibyte content is handled correctly."
|
||||
(skip-unless (not ns-accessibility-tests-skip))
|
||||
(let ((ns-accessibility-enabled t))
|
||||
(let ((buf (generate-new-buffer " *ns-ax-mb-test*")))
|
||||
(unwind-protect
|
||||
(with-current-buffer buf
|
||||
(insert "Hello řeřicha 日本語 🎉\n")
|
||||
(should (multibyte-string-p (buffer-string)))
|
||||
(goto-char (point-min))
|
||||
(forward-word)
|
||||
(should (> (point) 1)))
|
||||
(kill-buffer buf)))))
|
||||
|
||||
(ert-deftest ns-accessibility-invisible-text ()
|
||||
"Test that invisible text does not break buffer operations."
|
||||
(skip-unless (not ns-accessibility-tests-skip))
|
||||
(let ((ns-accessibility-enabled t))
|
||||
(let ((buf (generate-new-buffer " *ns-ax-invis-test*")))
|
||||
(unwind-protect
|
||||
(with-current-buffer buf
|
||||
(insert "visible")
|
||||
(let ((start (point)))
|
||||
(insert "invisible")
|
||||
(put-text-property start (point) 'invisible t))
|
||||
(insert "visible again\n")
|
||||
;; Buffer should contain all text
|
||||
(should (string-match "invisible"
|
||||
(buffer-substring-no-properties
|
||||
(point-min) (point-max))))
|
||||
;; But invisible text should be invisible
|
||||
(goto-char (point-min))
|
||||
(should (not (get-text-property 1 'invisible)))
|
||||
(should (get-text-property 8 'invisible)))
|
||||
(kill-buffer buf)))))
|
||||
|
||||
;;; --- Widget/button text property tests ---
|
||||
;; Verify that text properties used by span scanning work correctly.
|
||||
|
||||
(ert-deftest ns-accessibility-widget-property ()
|
||||
"Test that `widget' text property can be set and read."
|
||||
(let ((buf (generate-new-buffer " *ns-ax-widget-test*")))
|
||||
(unwind-protect
|
||||
(with-current-buffer buf
|
||||
(insert "Click here")
|
||||
(put-text-property 1 11 'widget '(test-widget))
|
||||
(should (equal (get-text-property 1 'widget)
|
||||
'(test-widget)))
|
||||
(should (null (get-text-property 11 'widget))))
|
||||
(kill-buffer buf))))
|
||||
|
||||
(ert-deftest ns-accessibility-button-property ()
|
||||
"Test that `button' text property can be set and read."
|
||||
(let ((buf (generate-new-buffer " *ns-ax-button-test*")))
|
||||
(unwind-protect
|
||||
(with-current-buffer buf
|
||||
(insert "Press me")
|
||||
(put-text-property 1 9 'button '(test-button))
|
||||
(should (equal (get-text-property 1 'button)
|
||||
'(test-button))))
|
||||
(kill-buffer buf))))
|
||||
|
||||
;;; --- Window/frame safety tests ---
|
||||
|
||||
;; Note: this test only runs in interactive (GUI) sessions.
|
||||
;; It is always skipped under 'make check' (batch mode).
|
||||
(ert-deftest ns-accessibility-window-split-delete ()
|
||||
"Test that window operations work with accessibility enabled."
|
||||
(skip-unless (not noninteractive))
|
||||
(save-window-excursion
|
||||
(let ((ns-accessibility-enabled t))
|
||||
(delete-other-windows)
|
||||
(split-window)
|
||||
(should (= (length (window-list)) 2))
|
||||
(delete-other-windows)
|
||||
(should (= (length (window-list)) 1)))))
|
||||
|
||||
(ert-deftest ns-accessibility-minibuffer ()
|
||||
"Test that minibuffer is accessible."
|
||||
(should (minibuffer-window))
|
||||
(should (window-live-p (minibuffer-window))))
|
||||
|
||||
;;; --- Completion buffer tests ---
|
||||
|
||||
(ert-deftest ns-accessibility-completions-buffer ()
|
||||
"Test that *Completions* buffer can be created with completion content."
|
||||
(skip-unless (not ns-accessibility-tests-skip))
|
||||
(let ((ns-accessibility-enabled t))
|
||||
(let ((buf (get-buffer-create "*Completions*")))
|
||||
(unwind-protect
|
||||
(with-current-buffer buf
|
||||
(display-completion-list '("alpha" "beta" "gamma"))
|
||||
(should (string-match "alpha" (buffer-string)))
|
||||
(should (string-match "gamma" (buffer-string))))
|
||||
(kill-buffer buf)))))
|
||||
|
||||
;;; --- Batch-mode sanity tests ---
|
||||
;; These tests verify basic variable/symbol sanity on NS builds.
|
||||
|
||||
(ert-deftest ns-accessibility-batch-no-crash ()
|
||||
"Test that accessibility variable access does not crash in batch mode."
|
||||
(skip-unless (not ns-accessibility-tests-skip))
|
||||
(should (boundp 'ns-accessibility-enabled))
|
||||
;; In batch mode, should be nil (no window system)
|
||||
(when noninteractive
|
||||
(should (eq ns-accessibility-enabled nil))))
|
||||
|
||||
(ert-deftest ns-accessibility-symbols-consistent ()
|
||||
"Test that DEFSYM symbols match their expected Lisp names.
|
||||
The C code uses Qns_ax_widget for symbol `widget', etc.
|
||||
This test ensures the symbol names are standard Emacs names,
|
||||
not namespaced (e.g., not `ns-ax-widget')."
|
||||
;; These should be standard symbols that widget.el and button.el use
|
||||
(should (eq (intern "widget") 'widget))
|
||||
(should (eq (intern "button") 'button))
|
||||
(should (eq (intern "completion") 'completion))
|
||||
;; Verify namespaced versions are NOT the ones used
|
||||
;; (they would be wrong since no Lisp code sets them)
|
||||
(should-not (eq (intern "widget") (intern "ns-ax-widget"))))
|
||||
|
||||
(provide 'nsterm-accessibility-tests)
|
||||
|
||||
;;; nsterm-accessibility-tests.el ends here
|
||||
Reference in New Issue
Block a user