1
//! Accessibility types for screen reader support.
2
//!
3
//! Key types:
4
//! - [`AccessibilityInfo`] — full accessibility metadata for a UI element
5
//! - [`SmallAriaInfo`] — lightweight alternative for common cases (label + role + description)
6
//! - [`AccessibilityRole`] — element purpose (button, link, checkbox, etc.)
7
//! - [`AccessibilityState`] — dynamic state (focused, checked, expanded, etc.)
8
//! - [`AccessibilityAction`] — actions performable on an element (click, scroll, etc.)
9
//!
10
//! These types are consumed by `layout/src/managers/a11y.rs` and mapped to
11
//! platform accessibility backends in `dll/src/desktop/shell2/`.
12

            
13
use alloc::vec::Vec;
14
use azul_css::{
15
    AzString, OptionF32, OptionString,
16
    props::basic::length::FloatValue,
17
};
18
use crate::{
19
    dom::OptionDomNodeId,
20
    geom::LogicalPosition,
21
    window::OptionVirtualKeyCodeCombo,
22
};
23

            
24
/// Holds information about a UI element for accessibility purposes (e.g., screen readers).
25
/// This is a wrapper for platform-specific accessibility APIs like MSAA.
26
#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
27
#[repr(C)]
28
pub struct AccessibilityInfo {
29
    /// Get the "name" of the `IAccessible`, for example the
30
    /// name of a button, checkbox or menu item. Try to use unique names
31
    /// for each item in a dialog so that voice dictation software doesn't
32
    /// have to deal with extra ambiguity.
33
    pub accessibility_name: OptionString,
34
    /// Get the "value" of the `IAccessible`, for example a number in a slider,
35
    /// a URL for a link, the text a user entered in a field.
36
    pub accessibility_value: OptionString,
37
    /// Optional text description providing additional context about the element.
38
    /// Maps to `aria-description` / accesskit's `set_description()`.
39
    pub description: OptionString,
40
    /// Optional keyboard accelerator.
41
    pub accelerator: OptionVirtualKeyCodeCombo,
42
    /// Optional "default action" description. Only used when there is at least
43
    /// one `ComponentEventFilter::DefaultAction` callback present on this node.
44
    pub default_action: OptionString,
45
    /// Possible on/off states, such as focused, focusable, selected, selectable,
46
    /// visible, protected (for passwords), checked, etc.
47
    pub states: AccessibilityStateVec,
48
    /// A list of actions the user can perform on this element.
49
    /// Maps to accesskit's Action enum.
50
    pub supported_actions: AccessibilityActionVec,
51
    /// ID of another node that labels this one (for `aria-labelledby`).
52
    pub labelled_by: OptionDomNodeId,
53
    /// ID of another node that describes this one (for `aria-describedby`).
54
    pub described_by: OptionDomNodeId,
55
    /// Get an enumerated value representing what this `IAccessible` is used for,
56
    /// for example is it a link, static text, editable text, a checkbox, or a table cell, etc.
57
    pub role: AccessibilityRole,
58
    /// For live regions that update automatically (e.g., chat messages, timers).
59
    /// Maps to accesskit's `Live` property.
60
    pub is_live_region: bool,
61
}
62

            
63
/// Actions that can be performed on an accessible element.
64
/// This is a simplified version of `accesskit::Action` to avoid direct dependency in core.
65
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
66
#[repr(C, u8)]
67
pub enum AccessibilityAction {
68
    /// The default action for the element (usually a click).
69
    Default,
70
    /// Set focus to this element.
71
    Focus,
72
    /// Remove focus from this element.
73
    Blur,
74
    /// Collapse an expandable element (e.g., tree node, accordion).
75
    Collapse,
76
    /// Expand a collapsible element (e.g., tree node, accordion).
77
    Expand,
78
    /// Scroll this element into view.
79
    ScrollIntoView,
80
    /// Increment a numeric value (e.g., slider, spinner).
81
    Increment,
82
    /// Decrement a numeric value (e.g., slider, spinner).
83
    Decrement,
84
    /// Show a context menu.
85
    ShowContextMenu,
86
    /// Hide a tooltip.
87
    HideTooltip,
88
    /// Show a tooltip.
89
    ShowTooltip,
90
    /// Scroll up.
91
    ScrollUp,
92
    /// Scroll down.
93
    ScrollDown,
94
    /// Scroll left.
95
    ScrollLeft,
96
    /// Scroll right.
97
    ScrollRight,
98
    /// Replace selected text with new text.
99
    ReplaceSelectedText(AzString),
100
    /// Scroll to a specific point.
101
    ScrollToPoint(LogicalPosition),
102
    /// Set scroll offset.
103
    SetScrollOffset(LogicalPosition),
104
    /// Set text selection.
105
    SetTextSelection(TextSelectionStartEnd),
106
    /// Set sequential focus navigation starting point.
107
    SetSequentialFocusNavigationStartingPoint,
108
    /// Set the value of a control.
109
    SetValue(AzString),
110
    /// Set numeric value of a control.
111
    SetNumericValue(FloatValue),
112
    /// Custom action with ID.
113
    CustomAction(i32),
114
}
115

            
116
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
117
#[repr(C)]
118
pub struct TextSelectionStartEnd {
119
    pub selection_start: usize,
120
    pub selection_end: usize,
121
}
122

            
123
impl_vec!(AccessibilityAction, AccessibilityActionVec, AccessibilityActionVecDestructor, AccessibilityActionVecDestructorType, AccessibilityActionVecSlice, OptionAccessibilityAction);
124
impl_vec_debug!(AccessibilityAction, AccessibilityActionVec);
125
impl_vec_clone!(
126
    AccessibilityAction,
127
    AccessibilityActionVec,
128
    AccessibilityActionVecDestructor
129
);
130
impl_vec_partialeq!(AccessibilityAction, AccessibilityActionVec);
131
impl_vec_eq!(AccessibilityAction, AccessibilityActionVec);
132
impl_vec_partialord!(AccessibilityAction, AccessibilityActionVec);
133
impl_vec_ord!(AccessibilityAction, AccessibilityActionVec);
134
impl_vec_hash!(AccessibilityAction, AccessibilityActionVec);
135

            
136
impl_option![
137
    AccessibilityAction,
138
    OptionAccessibilityAction,
139
    copy = false,
140
    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
141
];
142

            
143
impl_option!(
144
    AccessibilityInfo,
145
    OptionAccessibilityInfo,
146
    copy = false,
147
    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
148
);
149

            
150
/// Defines the element's purpose for accessibility APIs, informing assistive technologies
151
/// like screen readers about the function of a UI element.
152
///
153
/// Each variant corresponds to a
154
/// standard control type or UI structure.
155
///
156
/// For more details, see the [MSDN Role Constants page](https://docs.microsoft.com/en-us/windows/winauto/object-roles).
157
#[repr(C)]
158
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
159
pub enum AccessibilityRole {
160
    /// Represents the title or caption bar of a window.
161
    /// - **Purpose**: To identify the title bar containing the window title and system commands.
162
    /// - **When to use**: This role is typically inserted by the operating system for standard
163
    ///   windows.
164
    /// - **Example**: The bar at the top of an application window displaying its name and the
165
    ///   minimize, maximize, and close buttons.
166
    TitleBar,
167

            
168
    /// Represents a menu bar at the top of a window.
169
    /// - **Purpose**: To contain a set of top-level menus for an application.
170
    /// - **When to use**: For the main menu bar of an application, such as one containing "File,"
171
    ///   "Edit," and "View."
172
    /// - **Example**: The "File", "Edit", "View" menu bar at the top of a text editor.
173
    MenuBar,
174

            
175
    /// Represents a vertical or horizontal scroll bar.
176
    /// - **Purpose**: To enable scrolling through content that is larger than the visible area.
177
    /// - **When to use**: For any scrollable region of content.
178
    /// - **Example**: The bar on the side of a web page that allows the user to scroll up and
179
    ///   down.
180
    ScrollBar,
181

            
182
    /// Represents a handle or grip used for moving or resizing.
183
    /// - **Purpose**: To provide a user interface element for manipulating another element's size
184
    ///   or position.
185
    /// - **When to use**: For handles that allow resizing of windows, panes, or other objects.
186
    /// - **Example**: The small textured area in the bottom-right corner of a window that can be
187
    ///   dragged to resize it.
188
    Grip,
189

            
190
    /// Represents a system sound indicating an event.
191
    /// - **Purpose**: To associate a sound with a UI event, providing an auditory cue.
192
    /// - **When to use**: When a sound is the primary representation of an event.
193
    /// - **Example**: A system notification sound that plays when a new message arrives.
194
    Sound,
195

            
196
    /// Represents the system's mouse pointer or other pointing device.
197
    /// - **Purpose**: To indicate the screen position of the user's pointing device.
198
    /// - **When to use**: This role is managed by the operating system.
199
    /// - **Example**: The arrow that moves on the screen as you move the mouse.
200
    Cursor,
201

            
202
    /// Represents the text insertion point indicator.
203
    /// - **Purpose**: To show the current text entry or editing position.
204
    /// - **When to use**: This role is typically managed by the operating system for text input
205
    ///   fields.
206
    /// - **Example**: The blinking vertical line in a text box that shows where the next character
207
    ///   will be typed.
208
    Caret,
209

            
210
    /// Represents an alert or notification.
211
    /// - **Purpose**: To convey an important, non-modal message to the user.
212
    /// - **When to use**: For non-intrusive notifications that do not require immediate user
213
    ///   interaction.
214
    /// - **Example**: A small, temporary "toast" notification that appears to confirm an action,
215
    ///   like "Email sent."
216
    Alert,
217

            
218
    /// Represents a window frame.
219
    /// - **Purpose**: To serve as the container for other objects like a title bar and client
220
    ///   area.
221
    /// - **When to use**: This is a fundamental role, typically managed by the windowing system.
222
    /// - **Example**: The main window of any application, which contains all other UI elements.
223
    Window,
224

            
225
    /// Represents a window's client area, where the main content is displayed.
226
    /// - **Purpose**: To define the primary content area of a window.
227
    /// - **When to use**: For the main content region of a window. It's often the default role for
228
    ///   a custom control container.
229
    /// - **Example**: The area of a web browser where the web page content is rendered.
230
    Client,
231

            
232
    /// Represents a pop-up menu.
233
    /// - **Purpose**: To display a list of `MenuItem` objects that appears when a user performs an
234
    ///   action.
235
    /// - **When to use**: For context menus (right-click menus) or drop-down menus.
236
    /// - **Example**: The menu that appears when you right-click on a file in a file explorer.
237
    MenuPopup,
238

            
239
    /// Represents an individual item within a menu.
240
    /// - **Purpose**: To represent a single command, option, or separator within a menu.
241
    /// - **When to use**: For individual options inside a `MenuBar` or `MenuPopup`.
242
    /// - **Example**: The "Save" option within the "File" menu.
243
    MenuItem,
244

            
245
    /// Represents a small pop-up window that provides information.
246
    /// - **Purpose**: To offer brief, contextual help or information about a UI element.
247
    /// - **When to use**: For informational pop-ups that appear on mouse hover.
248
    /// - **Example**: The small box of text that appears when you hover over a button in a
249
    ///   toolbar.
250
    Tooltip,
251

            
252
    /// Represents the main window of an application.
253
    /// - **Purpose**: To identify the top-level window of an application.
254
    /// - **When to use**: For the primary window that represents the application itself.
255
    /// - **Example**: The main window of a calculator or notepad application.
256
    Application,
257

            
258
    /// Represents a document window within an application.
259
    /// - **Purpose**: To represent a contained document, typically in a Multiple Document
260
    ///   Interface (MDI) application.
261
    /// - **When to use**: For individual document windows inside a larger application shell.
262
    /// - **Example**: In a photo editor that allows multiple images to be open in separate
263
    ///   windows, each image window would be a `Document`.
264
    Document,
265

            
266
    /// Represents a pane or a distinct section of a window.
267
    /// - **Purpose**: To divide a window into visually and functionally distinct areas.
268
    /// - **When to use**: For sub-regions of a window, like a navigation pane, preview pane, or
269
    ///   sidebar.
270
    /// - **Example**: The preview pane in an email client that shows the content of the selected
271
    ///   email.
272
    Pane,
273

            
274
    /// Represents a graphical chart or graph.
275
    /// - **Purpose**: To display data visually in a chart format.
276
    /// - **When to use**: For any type of chart, such as a bar chart, line chart, or pie chart.
277
    /// - **Example**: A bar chart displaying monthly sales figures.
278
    Chart,
279

            
280
    /// Represents a dialog box or message box.
281
    /// - **Purpose**: To create a secondary window that requires user interaction before returning
282
    ///   to the main application.
283
    /// - **When to use**: For modal or non-modal windows that prompt the user for information or a
284
    ///   response.
285
    /// - **Example**: The "Open File" or "Print" dialog in most applications.
286
    Dialog,
287

            
288
    /// Represents a window's border.
289
    /// - **Purpose**: To identify the border of a window, which is often used for resizing.
290
    /// - **When to use**: This role is typically managed by the windowing system.
291
    /// - **Example**: The decorative and functional frame around a window.
292
    Border,
293

            
294
    /// Represents a group of related controls.
295
    /// - **Purpose**: To logically group other objects that share a common purpose.
296
    /// - **When to use**: For grouping controls like a set of radio buttons or a fieldset with a
297
    ///   legend.
298
    /// - **Example**: A "Settings" group box in a dialog that contains several related checkboxes.
299
    Grouping,
300

            
301
    /// Represents a visual separator.
302
    /// - **Purpose**: To visually divide a space or a group of controls.
303
    /// - **When to use**: For visual separators in menus, toolbars, or between panes.
304
    /// - **Example**: The horizontal line in a menu that separates groups of related menu items.
305
    Separator,
306

            
307
    /// Represents a toolbar containing a group of controls.
308
    /// - **Purpose**: To group controls, typically buttons, for quick access to frequently used
309
    ///   functions.
310
    /// - **When to use**: For a bar of buttons or other controls, usually at the top of a window
311
    ///   or pane.
312
    /// - **Example**: The toolbar at the top of a word processor with buttons for "Bold,"
313
    ///   "Italic," and "Underline."
314
    Toolbar,
315

            
316
    /// Represents a status bar for displaying information.
317
    /// - **Purpose**: To display status information about the current state of the application.
318
    /// - **When to use**: For a bar, typically at the bottom of a window, that displays messages.
319
    /// - **Example**: The bar at the bottom of a web browser that shows the loading status of a
320
    ///   page.
321
    StatusBar,
322

            
323
    /// Represents a data table.
324
    /// - **Purpose**: To present data in a two-dimensional grid of rows and columns.
325
    /// - **When to use**: For grid-like data presentation.
326
    /// - **Example**: A spreadsheet or a table of data in a database application.
327
    Table,
328

            
329
    /// Represents a column header in a table.
330
    /// - **Purpose**: To provide a label for a column of data.
331
    /// - **When to use**: For the headers of columns in a `Table`.
332
    /// - **Example**: The header row in a spreadsheet with labels like "Name," "Date," and
333
    ///   "Amount."
334
    ColumnHeader,
335

            
336
    /// Represents a row header in a table.
337
    /// - **Purpose**: To provide a label for a row of data.
338
    /// - **When to use**: For the headers of rows in a `Table`.
339
    /// - **Example**: The numbered rows on the left side of a spreadsheet.
340
    RowHeader,
341

            
342
    /// Represents a full column of cells in a table.
343
    /// - **Purpose**: To represent an entire column as a single accessible object.
344
    /// - **When to use**: When it is useful to interact with a column as a whole.
345
    /// - **Example**: The "Amount" column in a financial data table.
346
    Column,
347

            
348
    /// Represents a full row of cells in a table.
349
    /// - **Purpose**: To represent an entire row as a single accessible object.
350
    /// - **When to use**: When it is useful to interact with a row as a whole.
351
    /// - **Example**: A row representing a single customer's information in a customer list.
352
    Row,
353

            
354
    /// Represents a single cell within a table.
355
    /// - **Purpose**: To represent a single data point or control within a `Table`.
356
    /// - **When to use**: For individual cells in a grid or table.
357
    /// - **Example**: A single cell in a spreadsheet containing a specific value.
358
    Cell,
359

            
360
    /// Represents a hyperlink to a resource.
361
    /// - **Purpose**: To provide a navigational link to another document or location.
362
    /// - **When to use**: For text or images that, when clicked, navigate to another resource.
363
    /// - **Example**: A clickable link on a web page.
364
    Link,
365

            
366
    /// Represents a help balloon or pop-up.
367
    /// - **Purpose**: To provide more detailed help information than a standard tooltip.
368
    /// - **When to use**: For a pop-up that offers extended help text, often initiated by a help
369
    ///   button.
370
    /// - **Example**: A pop-up balloon with a paragraph of help text that appears when a user
371
    ///   clicks a help icon.
372
    HelpBalloon,
373

            
374
    /// Represents an animated, character-like graphic object.
375
    /// - **Purpose**: To provide an animated agent for user assistance or entertainment.
376
    /// - **When to use**: For animated characters or avatars that provide help or guidance.
377
    /// - **Example**: An animated paperclip that offers tips in a word processor (e.g.,
378
    ///   Microsoft's Clippy).
379
    Character,
380

            
381
    /// Represents a list of items.
382
    /// - **Purpose**: To contain a set of `ListItem` objects.
383
    /// - **When to use**: For list boxes or similar controls that present a list of selectable
384
    ///   items.
385
    /// - **Example**: The list of files in a file selection dialog.
386
    List,
387

            
388
    /// Represents an individual item within a list.
389
    /// - **Purpose**: To represent a single, selectable item within a `List`.
390
    /// - **When to use**: For each individual item in a list box or combo box.
391
    /// - **Example**: A single file name in a list of files.
392
    ListItem,
393

            
394
    /// Represents an outline or tree structure.
395
    /// - **Purpose**: To display a hierarchical view of data.
396
    /// - **When to use**: For tree-view controls that show nested items.
397
    /// - **Example**: A file explorer's folder tree view.
398
    Outline,
399

            
400
    /// Represents an individual item within an outline or tree.
401
    /// - **Purpose**: To represent a single node (which can be a leaf or a branch) in an
402
    ///   `Outline`.
403
    /// - **When to use**: For each node in a tree view.
404
    /// - **Example**: A single folder in a file explorer's tree view.
405
    OutlineItem,
406

            
407
    /// Represents a single tab in a tabbed interface.
408
    /// - **Purpose**: To provide a control for switching between different `PropertyPage` views.
409
    /// - **When to use**: For the individual tabs that the user can click to switch pages.
410
    /// - **Example**: The "General" and "Security" tabs in a file properties dialog.
411
    PageTab,
412

            
413
    /// Represents the content of a page in a property sheet.
414
    /// - **Purpose**: To serve as a container for the controls displayed when a `PageTab` is
415
    ///   selected.
416
    /// - **When to use**: For the content area associated with a specific tab.
417
    /// - **Example**: The set of options displayed when the "Security" tab is active.
418
    PropertyPage,
419

            
420
    /// Represents a visual indicator, like a slider thumb.
421
    /// - **Purpose**: To visually indicate the current value or position of another control.
422
    /// - **When to use**: For a sub-element that indicates status, like the thumb of a scrollbar.
423
    /// - **Example**: The draggable thumb of a scrollbar that indicates the current scroll
424
    ///   position.
425
    Indicator,
426

            
427
    /// Represents a picture or graphical image.
428
    /// - **Purpose**: To display a non-interactive image.
429
    /// - **When to use**: For images and icons that are purely decorative or informational.
430
    /// - **Example**: A company logo displayed in an application's "About" dialog.
431
    Graphic,
432

            
433
    /// Represents read-only text.
434
    /// - **Purpose**: To provide a non-editable text label for another control or for displaying
435
    ///   information.
436
    /// - **When to use**: For text that the user cannot edit.
437
    /// - **Example**: The label "Username:" next to a text input field.
438
    StaticText,
439

            
440
    /// Represents editable text or a text area.
441
    /// - **Purpose**: To allow for user text input or selection.
442
    /// - **When to use**: For text input fields where the user can type.
443
    /// - **Example**: A text box for entering a username or password.
444
    Text,
445

            
446
    /// Represents a standard push button.
447
    /// - **Purpose**: To initiate an immediate action.
448
    /// - **When to use**: For standard buttons that perform an action when clicked.
449
    /// - **Example**: An "OK" or "Cancel" button in a dialog.
450
    PushButton,
451

            
452
    /// Represents a check box control.
453
    /// - **Purpose**: To allow the user to make a binary choice (checked or unchecked).
454
    /// - **When to use**: For options that can be toggled on or off independently.
455
    /// - **Example**: A "Remember me" checkbox on a login form.
456
    CheckButton,
457

            
458
    /// Represents a radio button.
459
    /// - **Purpose**: To allow the user to select one option from a mutually exclusive group.
460
    /// - **When to use**: For a choice where only one option from a `Grouping` can be selected.
461
    /// - **Example**: "Male" and "Female" radio buttons for selecting gender.
462
    RadioButton,
463

            
464
    /// Represents a combination of a text field and a drop-down list.
465
    /// - **Purpose**: To allow the user to either type a value or select one from a list.
466
    /// - **When to use**: For controls that offer a list of suggestions but also allow custom
467
    ///   input.
468
    /// - **Example**: A font selector that allows you to type a font name or choose one from a
469
    ///   list.
470
    ComboBox,
471

            
472
    /// Represents a drop-down list box.
473
    /// - **Purpose**: To allow the user to select an item from a non-editable list that drops
474
    ///   down.
475
    /// - **When to use**: For selecting a single item from a predefined list of options.
476
    /// - **Example**: A country selection drop-down menu.
477
    DropList,
478

            
479
    /// Represents a progress bar.
480
    /// - **Purpose**: To indicate the progress of a lengthy operation.
481
    /// - **When to use**: To provide feedback for tasks like file downloads or installations.
482
    /// - **Example**: The bar that fills up to show the progress of a file copy operation.
483
    ProgressBar,
484

            
485
    /// Represents a dial or knob.
486
    /// - **Purpose**: To allow selecting a value from a continuous or discrete range, often
487
    ///   circularly.
488
    /// - **When to use**: For controls that resemble real-world dials, like a volume knob.
489
    /// - **Example**: A volume control knob in a media player application.
490
    Dial,
491

            
492
    /// Represents a control for entering a keyboard shortcut.
493
    /// - **Purpose**: To capture a key combination from the user.
494
    /// - **When to use**: In settings where users can define their own keyboard shortcuts.
495
    /// - **Example**: A text field in a settings dialog where a user can press a key combination
496
    ///   to assign it to a command.
497
    HotkeyField,
498

            
499
    /// Represents a slider for selecting a value within a range.
500
    /// - **Purpose**: To allow the user to adjust a setting along a continuous or discrete range.
501
    /// - **When to use**: For adjusting values like volume, brightness, or zoom level.
502
    /// - **Example**: A slider to control the volume of a video.
503
    Slider,
504

            
505
    /// Represents a spin button (up/down arrows) for incrementing or decrementing a value.
506
    /// - **Purpose**: To provide fine-tuned adjustment of a value, typically numeric.
507
    /// - **When to use**: For controls that allow stepping through a range of values.
508
    /// - **Example**: The up and down arrows next to a number input for setting the font size.
509
    SpinButton,
510

            
511
    /// Represents a diagram or flowchart.
512
    /// - **Purpose**: To represent data or relationships in a schematic form.
513
    /// - **When to use**: For visual representations of structures that are not charts, like a
514
    ///   database schema diagram.
515
    /// - **Example**: A flowchart illustrating a business process.
516
    Diagram,
517

            
518
    /// Represents an animation control.
519
    /// - **Purpose**: To display a sequence of images or indicate an ongoing process.
520
    /// - **When to use**: For animations that show that an operation is in progress.
521
    /// - **Example**: The animation that plays while files are being copied.
522
    Animation,
523

            
524
    /// Represents a mathematical equation.
525
    /// - **Purpose**: To display a mathematical formula in the correct format.
526
    /// - **When to use**: For displaying mathematical equations.
527
    /// - **Example**: A rendered mathematical equation in a scientific document editor.
528
    Equation,
529

            
530
    /// Represents a button that drops down a list of items.
531
    /// - **Purpose**: To combine a default action button with a list of alternative actions.
532
    /// - **When to use**: For buttons that have a primary action and a secondary list of options.
533
    /// - **Example**: A "Send" button with a dropdown arrow that reveals "Send and Archive."
534
    ButtonDropdown,
535

            
536
    /// Represents a button that drops down a full menu.
537
    /// - **Purpose**: To provide a button that opens a menu of choices rather than performing a
538
    ///   single action.
539
    /// - **When to use**: When a button's primary purpose is to reveal a menu.
540
    /// - **Example**: A "Tools" button that opens a menu with various tool options.
541
    ButtonMenu,
542

            
543
    /// Represents a button that drops down a grid for selection.
544
    /// - **Purpose**: To allow selection from a two-dimensional grid of options.
545
    /// - **When to use**: For buttons that open a grid-based selection UI.
546
    /// - **Example**: A color picker button that opens a grid of color swatches.
547
    ButtonDropdownGrid,
548

            
549
    /// Represents blank space between other objects.
550
    /// - **Purpose**: To represent significant empty areas in a UI that are part of the layout.
551
    /// - **When to use**: Sparingly, to signify that a large area is intentionally blank.
552
    /// - **Example**: A large empty panel in a complex layout might use this role.
553
    Whitespace,
554

            
555
    /// Represents the container for a set of tabs.
556
    /// - **Purpose**: To group a set of `PageTab` elements.
557
    /// - **When to use**: To act as the parent container for a row or column of tabs.
558
    /// - **Example**: The entire row of tabs at the top of a properties dialog.
559
    PageTabList,
560

            
561
    /// Represents a clock control.
562
    /// - **Purpose**: To display the current time.
563
    /// - **When to use**: For any UI element that displays time.
564
    /// - **Example**: The clock in the system tray of the operating system.
565
    Clock,
566

            
567
    /// Represents a button with two parts: a default action and a dropdown.
568
    /// - **Purpose**: To combine a frequently used action with a set of related, less-used
569
    ///   actions.
570
    /// - **When to use**: When a button has a default action and other related actions available
571
    ///   in a dropdown.
572
    /// - **Example**: A "Save" split button where the primary part saves, and the dropdown offers
573
    ///   "Save As."
574
    SplitButton,
575

            
576
    /// Represents a control for entering an IP address.
577
    /// - **Purpose**: To provide a specialized input field for IP addresses, often with formatting
578
    ///   and validation.
579
    /// - **When to use**: For dedicated IP address input fields.
580
    /// - **Example**: A network configuration dialog with a field for entering a static IP
581
    ///   address.
582
    IpAddress,
583

            
584
    /// Represents an element with no specific role.
585
    /// - **Purpose**: To indicate an element that has no semantic meaning for accessibility.
586
    /// - **When to use**: Should be used sparingly for purely decorative elements that should be
587
    ///   ignored by assistive technologies.
588
    /// - **Example**: A decorative graphical flourish that has no function or information to
589
    ///   convey.
590
    Nothing,
591

            
592
    /// Unknown or unspecified role.
593
    /// - **Purpose**: Default fallback when no specific role is assigned.
594
    /// - **When to use**: As a default value or when role information is unavailable.
595
    Unknown,
596
}
597

            
598
impl_option!(
599
    AccessibilityRole,
600
    OptionAccessibilityRole,
601
    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
602
);
603

            
604
/// Defines the current state of an element for accessibility APIs (e.g., focused, checked).
605
/// These states provide dynamic information to assistive technologies about the element's
606
/// condition.
607
///
608
/// See the [MSDN State Constants page](https://docs.microsoft.com/en-us/windows/win32/winauto/object-state-constants) for more details.
609
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
610
#[repr(C)]
611
pub enum AccessibilityState {
612
    /// The element is unavailable and cannot be interacted with.
613
    /// - **Purpose**: To indicate that a control is disabled or grayed out.
614
    /// - **When to use**: For disabled buttons, non-interactive menu items, or any control that is
615
    ///   temporarily non-functional.
616
    /// - **Example**: A "Save" button that is disabled until the user makes changes to a document.
617
    Unavailable,
618

            
619
    /// The element is selected.
620
    /// - **Purpose**: To indicate that an item is currently chosen or highlighted. This is
621
    ///   distinct from having focus.
622
    /// - **When to use**: For selected items in a list, highlighted text, or the currently active
623
    ///   tab in a tab list.
624
    /// - **Example**: A file highlighted in a file explorer, or multiple selected emails in an
625
    ///   inbox.
626
    Selected,
627

            
628
    /// The element has the keyboard focus.
629
    /// - **Purpose**: To identify the single element that will receive keyboard input.
630
    /// - **When to use**: For the control that is currently active and ready to be manipulated by
631
    ///   the keyboard.
632
    /// - **Example**: A text box with a blinking cursor, or a button with a dotted outline around
633
    ///   it.
634
    Focused,
635

            
636
    /// The element is checked, toggled, or in an "on" state.
637
    /// - **Purpose**: To represent checked checkboxes, selected radio buttons, and active toggles.
638
    /// - **Example**: A checked "I agree" checkbox, a selected "Yes" radio button.
639
    CheckedTrue,
640
    /// The element is unchecked, untoggled, or in an "off" state.
641
    /// - **Purpose**: To explicitly represent an unchecked checkbox or unselected radio button.
642
    /// - **Example**: An unchecked checkbox that the user has not yet ticked.
643
    CheckedFalse,
644

            
645
    /// The element's content cannot be edited by the user.
646
    /// - **Purpose**: To indicate that the element's value can be viewed and copied, but not
647
    ///   modified.
648
    /// - **When to use**: For display-only text fields or documents.
649
    /// - **Example**: A text box displaying a license agreement that the user can scroll through
650
    ///   but cannot edit.
651
    Readonly,
652

            
653
    /// The element is the default action in a dialog or form.
654
    /// - **Purpose**: To identify the button that will be activated if the user presses the Enter
655
    ///   key.
656
    /// - **When to use**: For the primary confirmation button in a dialog.
657
    /// - **Example**: The "OK" button in a dialog box, which often has a thicker or colored
658
    ///   border.
659
    Default,
660

            
661
    /// The element is expanded, showing its child items.
662
    /// - **Purpose**: To indicate that a collapsible element is currently open and its contents
663
    ///   are visible.
664
    /// - **When to use**: For tree view nodes, combo boxes with their lists open, or expanded
665
    ///   accordion panels.
666
    /// - **Example**: A folder in a file explorer's tree view that has been clicked to show its
667
    ///   subfolders.
668
    Expanded,
669

            
670
    /// The element is collapsed, hiding its child items.
671
    /// - **Purpose**: To indicate that a collapsible element is closed and its contents are
672
    ///   hidden.
673
    /// - **When to use**: The counterpart to `Expanded` for any collapsible UI element.
674
    /// - **Example**: A closed folder in a file explorer's tree view, hiding its contents.
675
    Collapsed,
676

            
677
    /// The element is busy and cannot respond to user interaction.
678
    /// - **Purpose**: To indicate that the element or application is performing an operation and
679
    ///   is temporarily unresponsive.
680
    /// - **When to use**: When an application is loading, processing data, or otherwise occupied.
681
    /// - **Example**: A window that is grayed out and shows a spinning cursor while saving a large
682
    ///   file.
683
    Busy,
684

            
685
    /// The element is not currently visible on the screen.
686
    /// - **Purpose**: To indicate that an element exists but is currently scrolled out of the
687
    ///   visible area.
688
    /// - **When to use**: For items in a long list or a large document that are not within the
689
    ///   current viewport.
690
    /// - **Example**: A list item in a long dropdown that you would have to scroll down to see.
691
    Offscreen,
692

            
693
    /// The element can accept keyboard focus.
694
    /// - **Purpose**: To indicate that the user can navigate to this element using the keyboard
695
    ///   (e.g., with the Tab key).
696
    /// - **When to use**: On all interactive elements like buttons, links, and input fields,
697
    ///   whether they currently have focus or not.
698
    /// - **Example**: A button that can receive focus, even if it is not the currently focused
699
    ///   element.
700
    Focusable,
701

            
702
    /// The element is a container whose children can be selected.
703
    /// - **Purpose**: To indicate that the element contains items that can be chosen.
704
    /// - **When to use**: On container controls like list boxes, tree views, or text spans where
705
    ///   text can be highlighted.
706
    /// - **Example**: A list box control is `Selectable`, while its individual list items have the
707
    ///   `Selected` state when chosen.
708
    Selectable,
709

            
710
    /// The element is a hyperlink.
711
    /// - **Purpose**: To identify an object that navigates to another resource or location when
712
    ///   activated.
713
    /// - **When to use**: On any object that functions as a hyperlink.
714
    /// - **Example**: Text or an image that, when clicked, opens a web page.
715
    Linked,
716

            
717
    /// The element is a hyperlink that has been visited.
718
    /// - **Purpose**: To indicate that a hyperlink has already been followed by the user.
719
    /// - **When to use**: On a `Linked` object that the user has previously activated.
720
    /// - **Example**: A hyperlink on a web page that has changed color to show it has been
721
    ///   visited.
722
    Traversed,
723

            
724
    /// The element allows multiple of its children to be selected at once.
725
    /// - **Purpose**: To indicate that a container control supports multi-selection.
726
    /// - **When to use**: On container controls like list boxes or file explorers that support
727
    ///   multiple selections (e.g., with Ctrl-click).
728
    /// - **Example**: A file list that allows the user to select several files at once for a copy
729
    ///   operation.
730
    Multiselectable,
731

            
732
    /// The element contains protected content that should not be read aloud.
733
    /// - **Purpose**: To prevent assistive technologies from speaking the content of a sensitive
734
    ///   field.
735
    /// - **When to use**: Primarily for password input fields.
736
    /// - **Example**: A password text box where typed characters are masked with asterisks or
737
    ///   dots.
738
    Protected,
739
}
740

            
741
impl_option!(
742
    AccessibilityState,
743
    OptionAccessibilityState,
744
    [Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash]
745
);
746

            
747
impl_vec!(AccessibilityState, AccessibilityStateVec, AccessibilityStateVecDestructor, AccessibilityStateVecDestructorType, AccessibilityStateVecSlice, OptionAccessibilityState);
748
impl_vec_clone!(
749
    AccessibilityState,
750
    AccessibilityStateVec,
751
    AccessibilityStateVecDestructor
752
);
753
impl_vec_debug!(AccessibilityState, AccessibilityStateVec);
754
impl_vec_partialeq!(AccessibilityState, AccessibilityStateVec);
755
impl_vec_partialord!(AccessibilityState, AccessibilityStateVec);
756
impl_vec_eq!(AccessibilityState, AccessibilityStateVec);
757
impl_vec_ord!(AccessibilityState, AccessibilityStateVec);
758
impl_vec_hash!(AccessibilityState, AccessibilityStateVec);
759

            
760
/// Compact accessibility information for common use cases.
761
///
762
/// This is a lighter-weight alternative to `AccessibilityInfo` for cases where
763
/// only basic accessibility properties are needed. Developers must explicitly
764
/// pass `None` if they choose not to provide accessibility information.
765
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
766
#[repr(C)]
767
pub struct SmallAriaInfo {
768
    /// Accessible label/name
769
    pub label: OptionString,
770
    /// Element's role (button, link, etc.)
771
    pub role: OptionAccessibilityRole,
772
    /// Additional description
773
    pub description: OptionString,
774
}
775

            
776
impl_option!(
777
    SmallAriaInfo,
778
    OptionSmallAriaInfo,
779
    copy = false,
780
    [Debug, Clone, PartialEq, Eq, Hash]
781
);
782

            
783
impl SmallAriaInfo {
784
107
    pub fn label<S: Into<AzString>>(text: S) -> Self {
785
107
        Self {
786
107
            label: OptionString::Some(text.into()),
787
107
            role: OptionAccessibilityRole::None,
788
107
            description: OptionString::None,
789
107
        }
790
107
    }
791

            
792
66
    #[must_use] pub const fn with_role(mut self, role: AccessibilityRole) -> Self {
793
66
        self.role = OptionAccessibilityRole::Some(role);
794
66
        self
795
66
    }
796

            
797
13
    #[must_use] pub fn with_description<S: Into<AzString>>(mut self, desc: S) -> Self {
798
13
        self.description = OptionString::Some(desc.into());
799
13
        self
800
13
    }
801

            
802
    /// Convert to full `AccessibilityInfo`
803
849
    #[must_use] pub fn to_full_info(&self) -> AccessibilityInfo {
804
        AccessibilityInfo {
805
849
            accessibility_name: self.label.clone(),
806
849
            accessibility_value: OptionString::None,
807
849
            description: self.description.clone(),
808
849
            role: match self.role {
809
557
                OptionAccessibilityRole::Some(r) => r,
810
292
                OptionAccessibilityRole::None => AccessibilityRole::Unknown,
811
            },
812
849
            states: Vec::new().into(),
813
849
            accelerator: OptionVirtualKeyCodeCombo::None,
814
849
            default_action: OptionString::None,
815
849
            supported_actions: Vec::new().into(),
816
            is_live_region: false,
817
849
            labelled_by: OptionDomNodeId::None,
818
849
            described_by: OptionDomNodeId::None,
819
        }
820
849
    }
821
}
822

            
823
/// Accessibility information for a `<progress>` indicator.
824
///
825
/// Mirrors HTML's `<progress value max>` plus an `indeterminate` flag for
826
/// progress bars whose end is unknown. Maps to `AccessibilityRole::ProgressBar`.
827
#[derive(Debug, Clone, PartialEq, Eq)]
828
#[repr(C)]
829
pub struct ProgressAriaInfo {
830
    /// Accessible label describing the task being measured.
831
    pub label: OptionString,
832
    /// Current progress value. `None` for indeterminate progress.
833
    pub current_value: OptionF32,
834
    /// Maximum value the progress bar can reach. `None` falls back to `1.0`.
835
    pub max: OptionF32,
836
    /// `true` for spinners / progress with no known endpoint. Overrides `current_value`.
837
    pub indeterminate: bool,
838
    /// Optional extended description (`aria-describedby` equivalent).
839
    pub description: OptionString,
840
}
841

            
842
impl_option!(
843
    ProgressAriaInfo,
844
    OptionProgressAriaInfo,
845
    copy = false,
846
    [Debug, Clone, PartialEq, Eq]
847
);
848

            
849
impl ProgressAriaInfo {
850
    /// Creates a `ProgressAriaInfo` with only an accessible label.
851
82
    #[must_use] pub const fn create(label: AzString) -> Self {
852
82
        Self {
853
82
            label: OptionString::Some(label),
854
82
            current_value: OptionF32::None,
855
82
            max: OptionF32::None,
856
82
            indeterminate: false,
857
82
            description: OptionString::None,
858
82
        }
859
82
    }
860

            
861
    /// Returns a copy with the given current value.
862
46
    #[must_use] pub const fn with_current_value(mut self, value: f32) -> Self {
863
46
        self.current_value = OptionF32::Some(value);
864
46
        self
865
46
    }
866

            
867
    /// Returns a copy with the given maximum value.
868
25
    #[must_use] pub const fn with_max(mut self, max: f32) -> Self {
869
25
        self.max = OptionF32::Some(max);
870
25
        self
871
25
    }
872

            
873
    /// Returns a copy with the indeterminate flag set.
874
5
    #[must_use] pub const fn with_indeterminate(mut self, indeterminate: bool) -> Self {
875
5
        self.indeterminate = indeterminate;
876
5
        self
877
5
    }
878

            
879
    /// Returns a copy with the given description.
880
13
    #[must_use] pub fn with_description(mut self, desc: AzString) -> Self {
881
13
        self.description = OptionString::Some(desc);
882
13
        self
883
13
    }
884

            
885
    /// Convert to full `AccessibilityInfo` so the value can be installed on a node.
886
46
    #[must_use] pub fn to_full_info(&self) -> AccessibilityInfo {
887
46
        let value_string = if self.indeterminate {
888
1
            OptionString::None
889
        } else {
890
45
            match self.current_value {
891
44
                OptionF32::Some(v) => OptionString::Some(format!("{v}").into()),
892
1
                OptionF32::None => OptionString::None,
893
            }
894
        };
895
46
        AccessibilityInfo {
896
46
            accessibility_name: self.label.clone(),
897
46
            accessibility_value: value_string,
898
46
            description: self.description.clone(),
899
46
            role: AccessibilityRole::ProgressBar,
900
46
            states: Vec::new().into(),
901
46
            accelerator: OptionVirtualKeyCodeCombo::None,
902
46
            default_action: OptionString::None,
903
46
            supported_actions: Vec::new().into(),
904
46
            is_live_region: false,
905
46
            labelled_by: OptionDomNodeId::None,
906
46
            described_by: OptionDomNodeId::None,
907
46
        }
908
46
    }
909
}
910

            
911
/// Accessibility information for a `<meter>` gauge.
912
///
913
/// Unlike `<progress>`, `<meter>` always carries a known `value`/`min`/`max`
914
/// triple, so those fields are required at construction time. Maps to
915
/// `AccessibilityRole::Indicator`.
916
#[derive(Debug, Clone, PartialEq)]
917
#[repr(C)]
918
pub struct MeterAriaInfo {
919
    /// Accessible label describing what the meter measures.
920
    pub label: OptionString,
921
    /// Current value of the meter (within `[min, max]`).
922
    pub current_value: f32,
923
    /// Lower bound of the measurement range.
924
    pub min: f32,
925
    /// Upper bound of the measurement range.
926
    pub max: f32,
927
    /// Optional "low" threshold (values below this are considered low).
928
    pub low: OptionF32,
929
    /// Optional "high" threshold (values above this are considered high).
930
    pub high: OptionF32,
931
    /// Optional optimum value within the range.
932
    pub optimum: OptionF32,
933
    /// Optional extended description.
934
    pub description: OptionString,
935
}
936

            
937
impl_option!(
938
    MeterAriaInfo,
939
    OptionMeterAriaInfo,
940
    copy = false,
941
    [Debug, Clone, PartialEq]
942
);
943

            
944
impl MeterAriaInfo {
945
    /// Creates a `MeterAriaInfo` with the required label and value/range triple.
946
53
    #[must_use] pub const fn create(label: AzString, current_value: f32, min: f32, max: f32) -> Self {
947
53
        Self {
948
53
            label: OptionString::Some(label),
949
53
            current_value,
950
53
            min,
951
53
            max,
952
53
            low: OptionF32::None,
953
53
            high: OptionF32::None,
954
53
            optimum: OptionF32::None,
955
53
            description: OptionString::None,
956
53
        }
957
53
    }
958

            
959
    /// Returns a copy with the given low threshold.
960
18
    #[must_use] pub const fn with_low(mut self, low: f32) -> Self {
961
18
        self.low = OptionF32::Some(low);
962
18
        self
963
18
    }
964

            
965
    /// Returns a copy with the given high threshold.
966
15
    #[must_use] pub const fn with_high(mut self, high: f32) -> Self {
967
15
        self.high = OptionF32::Some(high);
968
15
        self
969
15
    }
970

            
971
    /// Returns a copy with the given optimum value.
972
15
    #[must_use] pub const fn with_optimum(mut self, optimum: f32) -> Self {
973
15
        self.optimum = OptionF32::Some(optimum);
974
15
        self
975
15
    }
976

            
977
    /// Returns a copy with the given description.
978
12
    #[must_use] pub fn with_description(mut self, desc: AzString) -> Self {
979
12
        self.description = OptionString::Some(desc);
980
12
        self
981
12
    }
982

            
983
    /// Convert to full `AccessibilityInfo` so the value can be installed on a node.
984
28
    #[must_use] pub fn to_full_info(&self) -> AccessibilityInfo {
985
28
        AccessibilityInfo {
986
28
            accessibility_name: self.label.clone(),
987
28
            accessibility_value: OptionString::Some(format!("{}", self.current_value).into()),
988
28
            description: self.description.clone(),
989
28
            role: AccessibilityRole::Indicator,
990
28
            states: Vec::new().into(),
991
28
            accelerator: OptionVirtualKeyCodeCombo::None,
992
28
            default_action: OptionString::None,
993
28
            supported_actions: Vec::new().into(),
994
28
            is_live_region: false,
995
28
            labelled_by: OptionDomNodeId::None,
996
28
            described_by: OptionDomNodeId::None,
997
28
        }
998
28
    }
999
}
/// Accessibility information for a `<dialog>` element.
///
/// Captures the modal/non-modal distinction and a reference to a separate
/// node that describes the dialog (`aria-describedby`). The `role` defaults
/// to `AccessibilityRole::Dialog` but can be overridden (e.g., for alert
/// dialogs).
#[derive(Debug, Clone, PartialEq, Eq)]
#[repr(C)]
pub struct DialogAriaInfo {
    /// Accessible label / title for the dialog.
    pub label: OptionString,
    /// Optional ID of another node that describes the dialog content.
    pub described_by: OptionString,
    /// Optional inline description.
    pub description: OptionString,
    /// Role for the dialog. Defaults to `Dialog`; use `Alert` for urgent dialogs.
    pub role: AccessibilityRole,
    /// `true` if the dialog is modal (focus trapped, background inert).
    pub modal: bool,
}
impl_option!(
    DialogAriaInfo,
    OptionDialogAriaInfo,
    copy = false,
    [Debug, Clone, PartialEq, Eq]
);
impl DialogAriaInfo {
    /// Creates a `DialogAriaInfo` with the given accessible label. Defaults
    /// to a non-modal dialog with role `Dialog`.
48
    #[must_use] pub const fn create(label: AzString) -> Self {
48
        Self {
48
            label: OptionString::Some(label),
48
            modal: false,
48
            described_by: OptionString::None,
48
            role: AccessibilityRole::Dialog,
48
            description: OptionString::None,
48
        }
48
    }
    /// Returns a copy with the given modality flag.
7
    #[must_use] pub const fn with_modal(mut self, modal: bool) -> Self {
7
        self.modal = modal;
7
        self
7
    }
    /// Returns a copy with `aria-describedby` pointing at the given node ID.
14
    #[must_use] pub fn with_described_by(mut self, described_by: AzString) -> Self {
14
        self.described_by = OptionString::Some(described_by);
14
        self
14
    }
    /// Returns a copy with the given role (defaults to `Dialog`).
11
    #[must_use] pub const fn with_role(mut self, role: AccessibilityRole) -> Self {
11
        self.role = role;
11
        self
11
    }
    /// Returns a copy with the given inline description.
12
    #[must_use] pub fn with_description(mut self, desc: AzString) -> Self {
12
        self.description = OptionString::Some(desc);
12
        self
12
    }
    /// Convert to full `AccessibilityInfo` so the value can be installed on a node.
5
    #[must_use] pub fn to_full_info(&self) -> AccessibilityInfo {
5
        AccessibilityInfo {
5
            accessibility_name: self.label.clone(),
5
            accessibility_value: OptionString::None,
5
            description: self.description.clone(),
5
            role: self.role,
5
            states: Vec::new().into(),
5
            accelerator: OptionVirtualKeyCodeCombo::None,
5
            default_action: OptionString::None,
5
            supported_actions: Vec::new().into(),
5
            is_live_region: false,
5
            labelled_by: OptionDomNodeId::None,
5
            described_by: OptionDomNodeId::None,
5
        }
5
    }
}
#[cfg(test)]
mod autotest_generated {
    use super::*;
    use alloc::string::String;
    // ---- small helpers to reach into the FFI-style option wrappers ----
    fn name_str(o: &OptionString) -> Option<&str> {
        o.as_ref().map(|s| s.as_str())
    }
    fn f32_of(o: &OptionF32) -> Option<f32> {
        o.as_ref().copied()
    }
    /// A battery of adversarial strings: empty, embedded NUL, control chars,
    /// combining unicode, emoji, RTL, and a very large allocation.
    fn adversarial_strings() -> Vec<String> {
        vec![
            String::new(),
            String::from(" "),
            String::from("\0"),
            String::from("a\0b\0c"),
            String::from("\t\r\n\x1b[0m"),
            String::from("日本語のテキスト"),
            String::from("🎉👨‍👩‍👧‍👦🇺🇳"),
            String::from("e\u{0301}\u{0301}\u{0301}"), // combining accents
            String::from("\u{202e}reversed\u{202d}"),  // RTL override
            String::from("\u{FFFD}\u{10FFFF}"),        // replacement + max scalar
            "x".repeat(100_000),                        // huge
        ]
    }
    /// Numeric edge values for f32 fields.
    fn adversarial_f32() -> Vec<f32> {
        vec![
            0.0,
            -0.0,
            1.0,
            -1.0,
            f32::MIN,
            f32::MAX,
            f32::MIN_POSITIVE,
            -f32::MIN_POSITIVE,
            f32::EPSILON,
            f32::NAN,
            f32::INFINITY,
            f32::NEG_INFINITY,
        ]
    }
    // =====================================================================
    // 1. SmallAriaInfo::label — no_panic_smoke
    // =====================================================================
    #[test]
    fn small_label_no_panic_smoke() {
        for s in adversarial_strings() {
            let expected = s.clone();
            let info = SmallAriaInfo::label(s);
            // The label must round-trip verbatim and the other fields default to None.
            assert_eq!(name_str(&info.label), Some(expected.as_str()));
            assert!(info.role.is_none());
            assert!(info.description.is_none());
            // to_full_info must not panic even for pathological labels.
            let full = info.to_full_info();
            assert_eq!(name_str(&full.accessibility_name), Some(expected.as_str()));
        }
        // `&str` input path as well.
        let info = SmallAriaInfo::label("hello");
        assert_eq!(name_str(&info.label), Some("hello"));
    }
    // =====================================================================
    // 2. SmallAriaInfo::with_role — no_panic + invariants
    // =====================================================================
    fn representative_roles() -> Vec<AccessibilityRole> {
        vec![
            AccessibilityRole::TitleBar, // first variant
            AccessibilityRole::PushButton,
            AccessibilityRole::CheckButton,
            AccessibilityRole::Slider,
            AccessibilityRole::Link,
            AccessibilityRole::Nothing,
            AccessibilityRole::Unknown, // last variant
        ]
    }
    #[test]
    fn small_with_role_invariants() {
        for role in representative_roles() {
            let info = SmallAriaInfo::label("base").with_role(role);
            // Only the role field changes; label preserved, description untouched.
            assert_eq!(info.role, OptionAccessibilityRole::Some(role));
            assert_eq!(name_str(&info.label), Some("base"));
            assert!(info.description.is_none());
        }
        // Last-write-wins when applied twice.
        let info = SmallAriaInfo::label("x")
            .with_role(AccessibilityRole::Link)
            .with_role(AccessibilityRole::Slider);
        assert_eq!(info.role, OptionAccessibilityRole::Some(AccessibilityRole::Slider));
    }
    // =====================================================================
    // 3. SmallAriaInfo::with_description — no_panic + invariants
    // =====================================================================
    #[test]
    fn small_with_description_invariants() {
        for s in adversarial_strings() {
            let expected = s.clone();
            let info = SmallAriaInfo::label("base").with_description(s);
            assert_eq!(name_str(&info.description), Some(expected.as_str()));
            // label untouched, role still None.
            assert_eq!(name_str(&info.label), Some("base"));
            assert!(info.role.is_none());
        }
    }
    // =====================================================================
    // 4. SmallAriaInfo::to_full_info — basic + edge
    // =====================================================================
    #[test]
    fn small_to_full_info_basic() {
        let info = SmallAriaInfo::label("Submit")
            .with_role(AccessibilityRole::PushButton)
            .with_description("primary action")
            .to_full_info();
        assert_eq!(name_str(&info.accessibility_name), Some("Submit"));
        assert_eq!(info.role, AccessibilityRole::PushButton);
        assert_eq!(name_str(&info.description), Some("primary action"));
        assert!(info.accessibility_value.is_none());
        assert_eq!(info.states.len(), 0);
        assert_eq!(info.supported_actions.len(), 0);
        assert!(!info.is_live_region);
        assert!(info.labelled_by.is_none());
        assert!(info.described_by.is_none());
    }
    #[test]
    fn small_to_full_info_edge_missing_role_maps_to_unknown() {
        // No role set => full info must fall back to `Unknown`, never panic.
        let info = SmallAriaInfo::label("").to_full_info();
        assert_eq!(info.role, AccessibilityRole::Unknown);
        assert_eq!(name_str(&info.accessibility_name), Some(""));
        assert!(info.description.is_none());
    }
    // =====================================================================
    // 5. ProgressAriaInfo::create — no_panic_smoke
    // =====================================================================
    #[test]
    fn progress_create_no_panic_smoke() {
        for s in adversarial_strings() {
            let expected = s.clone();
            let p = ProgressAriaInfo::create(s.into());
            assert_eq!(name_str(&p.label), Some(expected.as_str()));
            // Documented defaults.
            assert!(p.current_value.is_none());
            assert!(p.max.is_none());
            assert!(!p.indeterminate);
            assert!(p.description.is_none());
        }
    }
    // =====================================================================
    // 6. ProgressAriaInfo::with_current_value — no_panic + invariants (numeric)
    // =====================================================================
    #[test]
    fn progress_with_current_value_numeric() {
        for v in adversarial_f32() {
            let p = ProgressAriaInfo::create("p".into()).with_current_value(v);
            match f32_of(&p.current_value) {
                Some(got) if v.is_nan() => assert!(got.is_nan()),
                Some(got) => assert_eq!(got, v),
                None => panic!("current_value should be Some after with_current_value"),
            }
            // to_full_info must not panic for any float, and (since determinate)
            // must emit a value string.
            let full = p.to_full_info();
            assert!(full.accessibility_value.is_some());
        }
    }
    // =====================================================================
    // 7. ProgressAriaInfo::with_max — no_panic + invariants (numeric)
    // =====================================================================
    #[test]
    fn progress_with_max_numeric() {
        for v in adversarial_f32() {
            let p = ProgressAriaInfo::create("p".into()).with_max(v);
            match f32_of(&p.max) {
                Some(got) if v.is_nan() => assert!(got.is_nan()),
                Some(got) => assert_eq!(got, v),
                None => panic!("max should be Some after with_max"),
            }
            // max does not influence the value string; current_value stays None.
            assert!(p.current_value.is_none());
        }
    }
    // =====================================================================
    // 8. ProgressAriaInfo::with_indeterminate — no_panic + invariants
    // =====================================================================
    #[test]
    fn progress_with_indeterminate_invariants() {
        for flag in [true, false] {
            let p = ProgressAriaInfo::create("p".into()).with_indeterminate(flag);
            assert_eq!(p.indeterminate, flag);
        }
        // indeterminate must override a present current_value in to_full_info.
        let p = ProgressAriaInfo::create("p".into())
            .with_current_value(0.5)
            .with_indeterminate(true);
        assert!(p.to_full_info().accessibility_value.is_none());
    }
    // =====================================================================
    // 9. ProgressAriaInfo::with_description — no_panic + invariants
    // =====================================================================
    #[test]
    fn progress_with_description_invariants() {
        for s in adversarial_strings() {
            let expected = s.clone();
            let p = ProgressAriaInfo::create("p".into()).with_description(s.into());
            assert_eq!(name_str(&p.description), Some(expected.as_str()));
            assert_eq!(name_str(&p.label), Some("p"));
        }
    }
    // =====================================================================
    // 10. ProgressAriaInfo::to_full_info — basic + edge
    // =====================================================================
    #[test]
    fn progress_to_full_info_basic() {
        let info = ProgressAriaInfo::create("Loading".into())
            .with_current_value(0.5)
            .to_full_info();
        assert_eq!(name_str(&info.accessibility_name), Some("Loading"));
        assert_eq!(info.role, AccessibilityRole::ProgressBar);
        assert_eq!(name_str(&info.accessibility_value), Some("0.5"));
        assert_eq!(info.states.len(), 0);
        assert_eq!(info.supported_actions.len(), 0);
    }
    #[test]
    fn progress_to_full_info_edge() {
        // No current value => value string is None.
        let info = ProgressAriaInfo::create("x".into()).to_full_info();
        assert!(info.accessibility_value.is_none());
        assert_eq!(info.role, AccessibilityRole::ProgressBar);
        // NaN / inf current values format to defined strings, no panic.
        assert_eq!(
            name_str(
                &ProgressAriaInfo::create("x".into())
                    .with_current_value(f32::NAN)
                    .to_full_info()
                    .accessibility_value
            ),
            Some("NaN")
        );
        assert_eq!(
            name_str(
                &ProgressAriaInfo::create("x".into())
                    .with_current_value(f32::INFINITY)
                    .to_full_info()
                    .accessibility_value
            ),
            Some("inf")
        );
        assert_eq!(
            name_str(
                &ProgressAriaInfo::create("x".into())
                    .with_current_value(f32::NEG_INFINITY)
                    .to_full_info()
                    .accessibility_value
            ),
            Some("-inf")
        );
    }
    // =====================================================================
    // 11. MeterAriaInfo::create — numeric (zero / min_max / negative / nan_inf)
    // =====================================================================
    #[test]
    fn meter_create_zero() {
        let m = MeterAriaInfo::create("z".into(), 0.0, 0.0, 0.0);
        assert_eq!(m.current_value, 0.0);
        assert_eq!(m.min, 0.0);
        assert_eq!(m.max, 0.0);
        assert_eq!(name_str(&m.to_full_info().accessibility_value), Some("0"));
    }
    #[test]
    fn meter_create_min_max() {
        let m = MeterAriaInfo::create("mm".into(), f32::MAX, f32::MIN, f32::MAX);
        assert_eq!(m.current_value, f32::MAX);
        assert_eq!(m.min, f32::MIN);
        assert_eq!(m.max, f32::MAX);
        // Formatting an extreme (but finite) float must not panic.
        assert!(m.to_full_info().accessibility_value.is_some());
    }
    #[test]
    fn meter_create_negative() {
        let m = MeterAriaInfo::create("neg".into(), -5.0, -10.0, -1.0);
        assert_eq!(m.current_value, -5.0);
        assert_eq!(m.min, -10.0);
        assert_eq!(m.max, -1.0);
        assert_eq!(name_str(&m.to_full_info().accessibility_value), Some("-5"));
        // Inverted range (min > max) is accepted verbatim; no panic, no clamping.
        let inv = MeterAriaInfo::create("inv".into(), 5.0, 100.0, 0.0);
        assert_eq!(inv.min, 100.0);
        assert_eq!(inv.max, 0.0);
        assert!(inv.to_full_info().accessibility_value.is_some());
    }
    #[test]
    fn meter_create_overflow_saturates_to_inf() {
        // f32 arithmetic saturates rather than panicking; feed the saturated
        // result straight in and confirm formatting stays defined.
        let over = f32::MAX * 2.0; // == +inf
        assert!(over.is_infinite());
        let m = MeterAriaInfo::create("o".into(), over, -over, over);
        assert_eq!(name_str(&m.to_full_info().accessibility_value), Some("inf"));
    }
    #[test]
    fn meter_create_nan_inf() {
        // NaN preserved as NaN, no panic constructing or formatting.
        let m = MeterAriaInfo::create("n".into(), f32::NAN, 0.0, 1.0);
        assert!(m.current_value.is_nan());
        assert_eq!(name_str(&m.to_full_info().accessibility_value), Some("NaN"));
        let pos = MeterAriaInfo::create("n".into(), f32::INFINITY, 0.0, 1.0);
        assert_eq!(name_str(&pos.to_full_info().accessibility_value), Some("inf"));
        let neg = MeterAriaInfo::create("n".into(), f32::NEG_INFINITY, 0.0, 1.0);
        assert_eq!(name_str(&neg.to_full_info().accessibility_value), Some("-inf"));
        // Non-finite bounds must not panic either.
        let bounds = MeterAriaInfo::create("n".into(), 0.5, f32::NEG_INFINITY, f32::INFINITY);
        assert!(bounds.min.is_infinite());
        assert!(bounds.max.is_infinite());
        assert!(bounds.to_full_info().accessibility_value.is_some());
    }
    // =====================================================================
    // 12-14. MeterAriaInfo::with_low / with_high / with_optimum — numeric invariants
    // =====================================================================
    #[test]
    fn meter_with_low_high_optimum_numeric() {
        for v in adversarial_f32() {
            let m = MeterAriaInfo::create("m".into(), 0.5, 0.0, 1.0)
                .with_low(v)
                .with_high(v)
                .with_optimum(v);
            for opt in [&m.low, &m.high, &m.optimum] {
                match f32_of(opt) {
                    Some(got) if v.is_nan() => assert!(got.is_nan()),
                    Some(got) => assert_eq!(got, v),
                    None => panic!("threshold should be Some after builder"),
                }
            }
            // Core value/range untouched by the threshold builders.
            assert_eq!(m.current_value, 0.5);
            assert_eq!(m.min, 0.0);
            assert_eq!(m.max, 1.0);
        }
    }
    // =====================================================================
    // 15. MeterAriaInfo::with_description — no_panic + invariants
    // =====================================================================
    #[test]
    fn meter_with_description_invariants() {
        for s in adversarial_strings() {
            let expected = s.clone();
            let m = MeterAriaInfo::create("m".into(), 1.0, 0.0, 2.0).with_description(s.into());
            assert_eq!(name_str(&m.description), Some(expected.as_str()));
            assert_eq!(m.current_value, 1.0);
        }
    }
    // =====================================================================
    // 16. MeterAriaInfo::to_full_info — basic + edge
    // =====================================================================
    #[test]
    fn meter_to_full_info_basic() {
        let info = MeterAriaInfo::create("Disk".into(), 42.0, 0.0, 100.0)
            .with_description("usage".into())
            .to_full_info();
        assert_eq!(name_str(&info.accessibility_name), Some("Disk"));
        assert_eq!(info.role, AccessibilityRole::Indicator);
        assert_eq!(name_str(&info.accessibility_value), Some("42"));
        assert_eq!(name_str(&info.description), Some("usage"));
        assert_eq!(info.states.len(), 0);
        assert_eq!(info.supported_actions.len(), 0);
    }
    #[test]
    fn meter_to_full_info_edge() {
        // Meter always emits a value string (unlike progress). Even for an
        // extreme/empty-label instance it must not panic.
        let info = MeterAriaInfo::create("".into(), f32::MIN, f32::MIN, f32::MAX).to_full_info();
        assert!(info.accessibility_value.is_some());
        assert_eq!(info.role, AccessibilityRole::Indicator);
        assert_eq!(name_str(&info.accessibility_name), Some(""));
    }
    // =====================================================================
    // 17. DialogAriaInfo::create — no_panic_smoke
    // =====================================================================
    #[test]
    fn dialog_create_no_panic_smoke() {
        for s in adversarial_strings() {
            let expected = s.clone();
            let d = DialogAriaInfo::create(s.into());
            assert_eq!(name_str(&d.label), Some(expected.as_str()));
            // Documented defaults: non-modal, role Dialog, no describers.
            assert!(!d.modal);
            assert_eq!(d.role, AccessibilityRole::Dialog);
            assert!(d.described_by.is_none());
            assert!(d.description.is_none());
        }
    }
    // =====================================================================
    // 18. DialogAriaInfo::with_modal — no_panic + invariants
    // =====================================================================
    #[test]
    fn dialog_with_modal_invariants() {
        for flag in [true, false] {
            let d = DialogAriaInfo::create("t".into()).with_modal(flag);
            assert_eq!(d.modal, flag);
            // Unrelated fields keep their defaults.
            assert_eq!(d.role, AccessibilityRole::Dialog);
            assert_eq!(name_str(&d.label), Some("t"));
        }
    }
    // =====================================================================
    // 19. DialogAriaInfo::with_described_by — no_panic + invariants
    // =====================================================================
    #[test]
    fn dialog_with_described_by_invariants() {
        for s in adversarial_strings() {
            let expected = s.clone();
            let d = DialogAriaInfo::create("t".into()).with_described_by(s.into());
            assert_eq!(name_str(&d.described_by), Some(expected.as_str()));
            assert_eq!(name_str(&d.label), Some("t"));
        }
    }
    // =====================================================================
    // 20. DialogAriaInfo::with_role — no_panic + invariants
    // =====================================================================
    #[test]
    fn dialog_with_role_invariants() {
        for role in representative_roles() {
            let d = DialogAriaInfo::create("t".into()).with_role(role);
            assert_eq!(d.role, role);
            assert!(!d.modal);
        }
        // to_full_info propagates the overridden role verbatim.
        let info = DialogAriaInfo::create("t".into())
            .with_role(AccessibilityRole::Alert)
            .to_full_info();
        assert_eq!(info.role, AccessibilityRole::Alert);
    }
    // =====================================================================
    // 21. DialogAriaInfo::with_description — no_panic + invariants
    // =====================================================================
    #[test]
    fn dialog_with_description_invariants() {
        for s in adversarial_strings() {
            let expected = s.clone();
            let d = DialogAriaInfo::create("t".into()).with_description(s.into());
            assert_eq!(name_str(&d.description), Some(expected.as_str()));
            assert_eq!(name_str(&d.label), Some("t"));
        }
    }
    // =====================================================================
    // 22. DialogAriaInfo::to_full_info — basic + edge
    // =====================================================================
    #[test]
    fn dialog_to_full_info_basic() {
        let info = DialogAriaInfo::create("Confirm".into())
            .with_modal(true)
            .with_role(AccessibilityRole::Alert)
            .with_described_by("body-node".into())
            .with_description("Are you sure?".into())
            .to_full_info();
        assert_eq!(name_str(&info.accessibility_name), Some("Confirm"));
        assert_eq!(info.role, AccessibilityRole::Alert);
        assert_eq!(name_str(&info.description), Some("Are you sure?"));
        // The string `described_by` node-ref is NOT propagated into the DomNodeId field.
        assert!(info.described_by.is_none());
        assert!(info.labelled_by.is_none());
        assert!(info.accessibility_value.is_none());
        assert_eq!(info.states.len(), 0);
        assert_eq!(info.supported_actions.len(), 0);
    }
    #[test]
    fn dialog_to_full_info_edge() {
        // Default (non-modal, empty) instance must convert without panic.
        let info = DialogAriaInfo::create("".into()).to_full_info();
        assert_eq!(info.role, AccessibilityRole::Dialog);
        assert_eq!(name_str(&info.accessibility_name), Some(""));
        assert!(info.description.is_none());
    }
    // #####################################################################
    // Appended: round-trip, total-order and FFI-vec coverage.
    //
    // The block above exercises the 22 listed builder/getter fns. What it
    // does NOT cover is the machinery those fns feed into: the FFI vec/option
    // wrappers, and the `Eq`/`Ord`/`Hash` impls that `AccessibilityInfo`
    // derives *through* f32-carrying payloads (`LogicalPosition`,
    // `FloatValue`). Those derives are where a total-order contract can
    // silently break, so they get the adversarial treatment here.
    // #####################################################################
    use core::hash::{Hash, Hasher};
    use crate::{
        dom::{DomId, DomNodeId},
        styled_dom::NodeHierarchyItemId,
    };
    /// FNV-1a. Hand-rolled rather than `DefaultHasher` so these tests still
    /// build when azul-core is compiled `--no-default-features` (i.e. `no_std`,
    /// where `std::collections::hash_map` does not exist).
    struct Fnv(u64);
    impl Default for Fnv {
        fn default() -> Self {
            Self(0xcbf2_9ce4_8422_2325) // offset basis
        }
    }
    impl Hasher for Fnv {
        fn finish(&self) -> u64 {
            self.0
        }
        fn write(&mut self, bytes: &[u8]) {
            for b in bytes {
                self.0 ^= u64::from(*b);
                self.0 = self.0.wrapping_mul(0x0000_0100_0000_01b3); // FNV prime
            }
        }
    }
    fn hash_of<T: Hash>(t: &T) -> u64 {
        let mut h = Fnv::default();
        t.hash(&mut h);
        h.finish()
    }
    /// Every `AccessibilityRole`, in declaration order.
    ///
    /// `Ord` is derived, so declaration order *is* the sort order — the tests
    /// below pin that. Kept in sync with the enum by `role_exhaustiveness_canary`.
    fn all_roles() -> Vec<AccessibilityRole> {
        use AccessibilityRole::*;
        vec![
            TitleBar, MenuBar, ScrollBar, Grip, Sound, Cursor, Caret, Alert, Window, Client,
            MenuPopup, MenuItem, Tooltip, Application, Document, Pane, Chart, Dialog, Border,
            Grouping, Separator, Toolbar, StatusBar, Table, ColumnHeader, RowHeader, Column, Row,
            Cell, Link, HelpBalloon, Character, List, ListItem, Outline, OutlineItem, PageTab,
            PropertyPage, Indicator, Graphic, StaticText, Text, PushButton, CheckButton,
            RadioButton, ComboBox, DropList, ProgressBar, Dial, HotkeyField, Slider, SpinButton,
            Diagram, Animation, Equation, ButtonDropdown, ButtonMenu, ButtonDropdownGrid,
            Whitespace, PageTabList, Clock, SplitButton, IpAddress, Nothing, Unknown,
        ]
    }
    /// Every `AccessibilityState`, in declaration order.
    fn all_states() -> Vec<AccessibilityState> {
        use AccessibilityState::*;
        vec![
            Unavailable, Selected, Focused, CheckedTrue, CheckedFalse, Readonly, Default, Expanded,
            Collapsed, Busy, Offscreen, Focusable, Selectable, Linked, Traversed, Multiselectable,
            Protected,
        ]
    }
    /// Exhaustive `match`es: if a variant is added upstream without being added
    /// to `all_roles()` / `all_states()`, this stops compiling. That is the
    /// point — it keeps the ordering tests below honest instead of letting them
    /// silently degrade into partial coverage.
    #[test]
    fn role_exhaustiveness_canary() {
        use AccessibilityRole::*;
        for r in all_roles() {
            let known = match r {
                TitleBar | MenuBar | ScrollBar | Grip | Sound | Cursor | Caret | Alert | Window
                | Client | MenuPopup | MenuItem | Tooltip | Application | Document | Pane | Chart
                | Dialog | Border | Grouping | Separator | Toolbar | StatusBar | Table
                | ColumnHeader | RowHeader | Column | Row | Cell | Link | HelpBalloon | Character
                | List | ListItem | Outline | OutlineItem | PageTab | PropertyPage | Indicator
                | Graphic | StaticText | Text | PushButton | CheckButton | RadioButton | ComboBox
                | DropList | ProgressBar | Dial | HotkeyField | Slider | SpinButton | Diagram
                | Animation | Equation | ButtonDropdown | ButtonMenu | ButtonDropdownGrid
                | Whitespace | PageTabList | Clock | SplitButton | IpAddress | Nothing | Unknown => true,
            };
            assert!(known);
        }
        use AccessibilityState::*;
        for s in all_states() {
            let known = match s {
                Unavailable | Selected | Focused | CheckedTrue | CheckedFalse | Readonly
                | Default | Expanded | Collapsed | Busy | Offscreen | Focusable | Selectable
                | Linked | Traversed | Multiselectable | Protected => true,
            };
            assert!(known);
        }
    }
    // =====================================================================
    // Total-order / Eq / Hash contracts on the plain C-like enums
    // =====================================================================
    #[test]
    fn role_ord_is_strict_declaration_order() {
        let roles = all_roles();
        // Strictly increasing => derived Ord follows declaration order AND the
        // list has no duplicates.
        for pair in roles.windows(2) {
            assert!(
                pair[0] < pair[1],
                "roles must sort in declaration order: {:?} !< {:?}",
                pair[0],
                pair[1]
            );
        }
        // Trichotomy: for every ordered pair exactly one of <, ==, > holds.
        for a in &roles {
            for b in &roles {
                let lt = a < b;
                let eq = a == b;
                let gt = a > b;
                assert_eq!(
                    u8::from(lt) + u8::from(eq) + u8::from(gt),
                    1,
                    "trichotomy violated for {a:?} vs {b:?}"
                );
            }
        }
        // Reflexivity + the documented endpoints.
        assert_eq!(roles[0], AccessibilityRole::TitleBar);
        assert_eq!(*roles.last().unwrap(), AccessibilityRole::Unknown);
        assert!(AccessibilityRole::TitleBar < AccessibilityRole::Unknown);
    }
    #[test]
    fn state_ord_is_strict_declaration_order() {
        let states = all_states();
        for pair in states.windows(2) {
            assert!(pair[0] < pair[1], "{:?} !< {:?}", pair[0], pair[1]);
        }
        // CheckedTrue / CheckedFalse are adjacent but must never compare equal —
        // aliasing them would make a checked and unchecked box indistinguishable.
        assert_ne!(AccessibilityState::CheckedTrue, AccessibilityState::CheckedFalse);
        assert_ne!(
            hash_of(&AccessibilityState::CheckedTrue),
            hash_of(&AccessibilityState::CheckedFalse)
        );
    }
    #[test]
    fn role_and_state_hash_agrees_with_eq() {
        // Eq => equal hashes (the direction the Hash contract actually requires),
        // and Hash is deterministic across calls.
        for r in all_roles() {
            let copy = r;
            assert_eq!(hash_of(&r), hash_of(&copy));
        }
        for s in all_states() {
            let copy = s;
            assert_eq!(hash_of(&s), hash_of(&copy));
        }
        // Stronger: no two distinct variants may collide. A collision here would
        // let two different roles/states alias as the same HashMap key. The
        // derive hashes the (necessarily distinct) discriminant, and FNV-1a's
        // multiply step is invertible mod 2^64, so distinctness is guaranteed —
        // this pins that no variant is ever given a duplicate discriminant.
        let role_hashes: Vec<u64> = all_roles().iter().map(hash_of).collect();
        for (i, a) in role_hashes.iter().enumerate() {
            for (j, b) in role_hashes.iter().enumerate() {
                assert_eq!(i == j, a == b, "role hash collision at {i}/{j}");
            }
        }
        let state_hashes: Vec<u64> = all_states().iter().map(hash_of).collect();
        for (i, a) in state_hashes.iter().enumerate() {
            for (j, b) in state_hashes.iter().enumerate() {
                assert_eq!(i == j, a == b, "state hash collision at {i}/{j}");
            }
        }
    }
    // =====================================================================
    // AccessibilityStateVec — FFI vec round-trip
    // =====================================================================
    #[test]
    fn state_vec_round_trips_through_ffi_wrapper() {
        let cases: Vec<Vec<AccessibilityState>> = vec![
            Vec::new(),
            vec![AccessibilityState::Focused],
            all_states(),
            // duplicates must survive verbatim (this is a Vec, not a Set)
            vec![
                AccessibilityState::Busy,
                AccessibilityState::Busy,
                AccessibilityState::Busy,
            ],
            // large allocation: the FFI wrapper owns the buffer, so this is the
            // shape most likely to trip a bad len/cap or double-free.
            std::iter::repeat_n(AccessibilityState::Selected, 10_000).collect(),
        ];
        for original in cases {
            let wrapped: AccessibilityStateVec = original.clone().into();
            // len / is_empty stay consistent with the source Vec.
            assert_eq!(wrapped.len(), original.len());
            assert_eq!(wrapped.is_empty(), original.is_empty());
            assert_eq!(wrapped.as_slice(), original.as_slice());
            assert_eq!(wrapped.iter().count(), original.len());
            // Clone must deep-copy: equal content, and dropping the clone must
            // not invalidate the original (both are dropped at end of scope).
            let cloned = wrapped.clone();
            assert_eq!(cloned.as_slice(), original.as_slice());
            assert_eq!(cloned, wrapped);
            assert_eq!(hash_of(&cloned), hash_of(&wrapped));
            drop(cloned);
            assert_eq!(wrapped.as_slice(), original.as_slice());
            // Round-trip back out: decode(encode(x)) == x.
            let back = wrapped.into_library_owned_vec();
            assert_eq!(back, original);
        }
    }
    #[test]
    fn state_vec_indexing_is_bounds_safe() {
        let v: AccessibilityStateVec = all_states().into();
        let len = v.len();
        for (i, expected) in all_states().into_iter().enumerate() {
            assert_eq!(v.get(i), Some(&expected));
        }
        // One-past-the-end and the pathological index must return None, not panic.
        assert_eq!(v.get(len), None);
        assert_eq!(v.get(len + 1), None);
        assert_eq!(v.get(usize::MAX), None);
        assert!(v.c_get(usize::MAX).is_none());
        assert!(v.c_get(len).is_none());
        assert!(v.c_get(0).is_some());
        // The empty vec has no valid index at all.
        let empty = AccessibilityStateVec::new();
        assert!(empty.is_empty());
        assert_eq!(empty.get(0), None);
        assert_eq!(empty.get(usize::MAX), None);
        assert!(empty.c_get(0).is_none());
    }
    #[test]
    fn state_vec_from_vec_preserves_order_len_and_lookup() {
        // The C-ABI vec is built from a Rust Vec and is then read-only — it has
        // no push/pop. Assert the round-trip is lossless and lookups agree.
        let empty = AccessibilityStateVec::new();
        assert_eq!(empty.len(), 0);
        assert!(empty.is_empty());
        assert_eq!(empty.get(0), None);
        let states = all_states();
        let v = AccessibilityStateVec::from_vec(states.clone());
        assert_eq!(v.len(), states.len());
        assert!(!v.is_empty());
        assert!(v.capacity() >= v.len(), "capacity must never trail len");
        // Order is preserved and every index is reachable.
        assert_eq!(v.as_slice(), states.as_slice());
        for (i, s) in states.iter().enumerate() {
            assert_eq!(v.get(i), Some(s));
        }
        assert_eq!(
            v.get(states.len()),
            None,
            "out-of-bounds must be None, not a panic"
        );
        assert!(v.iter().eq(states.iter()));
    }
    // =====================================================================
    // AccessibilityAction — payload-carrying variants
    // =====================================================================
    /// One instance of every `AccessibilityAction` variant, in declaration order.
    fn all_actions() -> Vec<AccessibilityAction> {
        use AccessibilityAction::*;
        vec![
            Default,
            Focus,
            Blur,
            Collapse,
            Expand,
            ScrollIntoView,
            Increment,
            Decrement,
            ShowContextMenu,
            HideTooltip,
            ShowTooltip,
            ScrollUp,
            ScrollDown,
            ScrollLeft,
            ScrollRight,
            ReplaceSelectedText("replacement".into()),
            ScrollToPoint(LogicalPosition::new(1.0, 2.0)),
            SetScrollOffset(LogicalPosition::new(-3.0, 4.0)),
            SetTextSelection(TextSelectionStartEnd {
                selection_start: 0,
                selection_end: 5,
            }),
            SetSequentialFocusNavigationStartingPoint,
            SetValue("value".into()),
            SetNumericValue(FloatValue::new(1.5)),
            CustomAction(42),
        ]
    }
    #[test]
    fn action_vec_round_trips_with_payloads() {
        let original = all_actions();
        let wrapped: AccessibilityActionVec = original.clone().into();
        assert_eq!(wrapped.len(), original.len());
        assert_eq!(wrapped.as_slice(), original.as_slice());
        // The payload variants own heap data (AzString). Cloning must deep-copy;
        // dropping the clone must leave the original intact (no double-free).
        let cloned = wrapped.clone();
        assert_eq!(cloned, wrapped);
        assert_eq!(hash_of(&cloned), hash_of(&wrapped));
        drop(cloned);
        assert_eq!(wrapped.as_slice(), original.as_slice());
        let back = wrapped.into_library_owned_vec();
        assert_eq!(back, original);
        // Variant order dominates payload in the derived Ord.
        for pair in original.windows(2) {
            assert!(pair[0] < pair[1], "{:?} !< {:?}", pair[0], pair[1]);
        }
    }
    #[test]
    fn action_string_payloads_survive_adversarial_strings() {
        for s in adversarial_strings() {
            let expected = s.clone();
            let replace = AccessibilityAction::ReplaceSelectedText(s.clone().into());
            let set = AccessibilityAction::SetValue(s.into());
            // Payload preserved verbatim — including interior NUL and lone
            // combining marks, which a C-string round-trip would truncate.
            match &replace {
                AccessibilityAction::ReplaceSelectedText(got) => {
                    assert_eq!(got.as_str(), expected.as_str());
                    assert_eq!(got.as_str().len(), expected.len());
                }
                other => panic!("wrong variant: {other:?}"),
            }
            match &set {
                AccessibilityAction::SetValue(got) => assert_eq!(got.as_str(), expected.as_str()),
                other => panic!("wrong variant: {other:?}"),
            }
            // Clone/Eq/Hash agree even for the pathological payloads.
            assert_eq!(replace.clone(), replace);
            assert_eq!(hash_of(&replace.clone()), hash_of(&replace));
            // Different variants with the *same* payload must never alias.
            assert_ne!(replace, set);
        }
    }
    #[test]
    fn action_custom_action_i32_limits() {
        let min = AccessibilityAction::CustomAction(i32::MIN);
        let zero = AccessibilityAction::CustomAction(0);
        let max = AccessibilityAction::CustomAction(i32::MAX);
        // Signed ordering, not a bit-pattern/unsigned ordering.
        assert!(min < zero, "i32::MIN must sort below 0");
        assert!(zero < max);
        assert!(min < max);
        assert_eq!(min, AccessibilityAction::CustomAction(i32::MIN));
        assert_ne!(min, max);
        assert_eq!(hash_of(&min), hash_of(&AccessibilityAction::CustomAction(i32::MIN)));
        // -1 must not alias u32::MAX-style onto anything.
        assert_ne!(
            AccessibilityAction::CustomAction(-1),
            AccessibilityAction::CustomAction(i32::MAX)
        );
    }
    #[test]
    fn text_selection_start_end_limits() {
        // usize::MAX bounds: constructing and comparing must not overflow.
        let huge = TextSelectionStartEnd {
            selection_start: usize::MAX,
            selection_end: usize::MAX,
        };
        assert_eq!(huge.selection_start, usize::MAX);
        assert_eq!(huge.selection_end, usize::MAX);
        assert_eq!(huge, huge);
        // Inverted range (start > end) is accepted verbatim — the type does not
        // normalise or clamp, so downstream consumers must not assume start<=end.
        let inverted = TextSelectionStartEnd {
            selection_start: 10,
            selection_end: 0,
        };
        assert_eq!(inverted.selection_start, 10);
        assert_eq!(inverted.selection_end, 0);
        assert_ne!(
            inverted,
            TextSelectionStartEnd {
                selection_start: 0,
                selection_end: 10,
            }
        );
        // Collapsed (zero-length) selection is distinct from an empty-at-zero one.
        let collapsed = TextSelectionStartEnd {
            selection_start: 7,
            selection_end: 7,
        };
        assert_ne!(
            collapsed,
            TextSelectionStartEnd {
                selection_start: 0,
                selection_end: 0,
            }
        );
        // Ord is lexicographic (start, then end).
        let a = TextSelectionStartEnd {
            selection_start: 1,
            selection_end: 99,
        };
        let b = TextSelectionStartEnd {
            selection_start: 2,
            selection_end: 0,
        };
        assert!(a < b, "selection_start must dominate the ordering");
        // Wrapped in the action, the same invariants hold.
        let action = AccessibilityAction::SetTextSelection(huge);
        assert_eq!(action.clone(), action);
        assert_eq!(hash_of(&action.clone()), hash_of(&action));
    }
    // =====================================================================
    // f32-carrying payloads: the Eq/Ord/Hash total-order contract
    //
    // `AccessibilityAction` *derives* Eq + Ord + Hash while carrying
    // `LogicalPosition` (two f32s) and `FloatValue`. f32 is not Eq/Ord, so
    // those inner types must supply total impls. These tests pin the actual
    // behaviour at NaN / inf / overflow, where a naive impl breaks the
    // reflexivity (a == a) that HashMap and BTreeMap rely on.
    // =====================================================================
    #[test]
    fn scroll_to_point_nan_is_reflexive_and_totally_ordered() {
        let nan = AccessibilityAction::ScrollToPoint(LogicalPosition::new(f32::NAN, f32::NAN));
        let origin = AccessibilityAction::ScrollToPoint(LogicalPosition::new(0.0, 0.0));
        // Reflexivity: `Eq` promises a == a. Raw f32 PartialEq would return
        // false here and quietly corrupt any HashMap keyed on this action.
        assert_eq!(nan, nan.clone());
        assert_eq!(hash_of(&nan), hash_of(&nan.clone()));
        assert_eq!(nan.cmp(&nan.clone()), core::cmp::Ordering::Equal);
        // NaN must NOT alias onto the origin (LogicalPosition::quantize maps NaN
        // to a dedicated i64::MIN sentinel precisely to avoid that collision).
        assert_ne!(nan, origin);
        assert_ne!(hash_of(&nan), hash_of(&origin));
        assert!(nan < origin, "NaN sorts below every real coordinate");
        // Ord is total: every pair of these is comparable and antisymmetric.
        let neg = AccessibilityAction::ScrollToPoint(LogicalPosition::new(-1.0, -1.0));
        let pos = AccessibilityAction::ScrollToPoint(LogicalPosition::new(1.0, 1.0));
        let mut sorted = vec![pos.clone(), origin.clone(), nan.clone(), neg.clone()];
        sorted.sort();
        assert_eq!(sorted, vec![nan, neg, origin, pos]);
    }
    #[test]
    fn scroll_to_point_infinite_coords_saturate_without_panic() {
        let inf = AccessibilityAction::SetScrollOffset(LogicalPosition::new(
            f32::INFINITY,
            f32::NEG_INFINITY,
        ));
        let finite = AccessibilityAction::SetScrollOffset(LogicalPosition::new(1.0, 1.0));
        // Defined, reflexive, no panic on the fixed-point conversion.
        assert_eq!(inf, inf.clone());
        assert_eq!(hash_of(&inf), hash_of(&inf.clone()));
        assert!(inf > finite, "+inf x-coordinate must sort above a finite one");
        // Documented saturation: the fixed-point quantisation clamps, so
        // f32::MAX and +inf land in the same bucket. Asserted so a future
        // change to the quantiser has to consciously break this.
        let max = AccessibilityAction::SetScrollOffset(LogicalPosition::new(f32::MAX, f32::MAX));
        let plus_inf =
            AccessibilityAction::SetScrollOffset(LogicalPosition::new(f32::INFINITY, f32::INFINITY));
        assert_eq!(
            max, plus_inf,
            "f32::MAX and +inf both saturate to the same quantised coordinate"
        );
    }
    #[test]
    fn set_numeric_value_float_edges_are_defined() {
        // Representable-under-quantisation values round-trip exactly
        // (FloatValue is fixed-point with a 1/1000 quantum).
        for v in [0.0_f32, 1.5, -1.5, 2.25, -3.75, 1000.0] {
            let f = FloatValue::new(v);
            assert_eq!(f.get(), v, "FloatValue must round-trip {v}");
            let action = AccessibilityAction::SetNumericValue(f);
            assert_eq!(action.clone(), action);
        }
        // Non-finite input must not panic. `as isize` saturates, so:
        assert_eq!(FloatValue::new(f32::INFINITY).number(), isize::MAX);
        assert_eq!(FloatValue::new(f32::NEG_INFINITY).number(), isize::MIN);
        // NOTE (reported, not a weakened assertion): FloatValue::new maps NaN to
        // 0 via a raw `as isize` cast, so a NaN numeric value is INDISTINGUISHABLE
        // from 0.0. LogicalPosition::quantize explicitly fixed this same aliasing
        // (NaN -> i64::MIN sentinel); FloatValue still has it. Pinning the current
        // behaviour so the aliasing is visible and a fix has to update this test.
        assert_eq!(FloatValue::new(f32::NAN).number(), 0);
        assert_eq!(
            AccessibilityAction::SetNumericValue(FloatValue::new(f32::NAN)),
            AccessibilityAction::SetNumericValue(FloatValue::new(0.0)),
            "KNOWN ALIASING: NaN numeric value collides with 0.0"
        );
        // Reflexivity still holds for the NaN case (it is Eq-safe, just aliased).
        let nan_action = AccessibilityAction::SetNumericValue(FloatValue::new(f32::NAN));
        assert_eq!(hash_of(&nan_action), hash_of(&nan_action.clone()));
        // f32::MAX overflows the fixed-point scale and saturates rather than wrapping.
        assert_eq!(FloatValue::new(f32::MAX).number(), isize::MAX);
        assert_eq!(FloatValue::new(f32::MIN).number(), isize::MIN);
    }
    // =====================================================================
    // Float -> value-string encoding: format/parse round-trip
    // =====================================================================
    #[test]
    fn progress_value_string_round_trips_through_parse() {
        for v in adversarial_f32() {
            let full = ProgressAriaInfo::create("p".into())
                .with_current_value(v)
                .to_full_info();
            let s = name_str(&full.accessibility_value).expect("determinate => Some");
            if v.is_nan() {
                assert_eq!(s, "NaN");
                assert!(s.parse::<f32>().unwrap().is_nan());
            } else if v.is_infinite() {
                assert_eq!(s, if v > 0.0 { "inf" } else { "-inf" });
            } else {
                // Display for f32 is shortest-round-trip: decode(encode(v)) == v.
                let parsed: f32 = s.parse().expect("emitted value string must re-parse");
                assert_eq!(parsed, v, "round-trip failed for {v} via {s:?}");
                if v != 0.0 {
                    // Bit-exact for everything except +0.0/-0.0, which compare
                    // equal under `==` by definition.
                    assert_eq!(parsed.to_bits(), v.to_bits(), "lossy round-trip for {v}");
                }
            }
        }
    }
    #[test]
    fn meter_value_string_round_trips_through_parse() {
        for v in adversarial_f32() {
            let full = MeterAriaInfo::create("m".into(), v, 0.0, 1.0).to_full_info();
            // Meter ALWAYS emits a value string (unlike progress).
            let s = name_str(&full.accessibility_value).expect("meter always emits a value");
            if v.is_nan() {
                assert_eq!(s, "NaN");
            } else if v.is_infinite() {
                assert_eq!(s, if v > 0.0 { "inf" } else { "-inf" });
            } else {
                let parsed: f32 = s.parse().expect("emitted value string must re-parse");
                assert_eq!(parsed, v);
                if v != 0.0 {
                    assert_eq!(parsed.to_bits(), v.to_bits());
                }
            }
        }
    }
    // =====================================================================
    // Builder algebra: purity, idempotence, last-write-wins, order-independence
    // =====================================================================
    #[test]
    fn to_full_info_is_pure_and_idempotent() {
        let small = SmallAriaInfo::label("s")
            .with_role(AccessibilityRole::Slider)
            .with_description("d");
        let progress = ProgressAriaInfo::create("p".into())
            .with_current_value(0.25)
            .with_max(10.0);
        let meter = MeterAriaInfo::create("m".into(), 1.0, 0.0, 2.0).with_low(0.5);
        let dialog = DialogAriaInfo::create("d".into()).with_modal(true);
        // &self getters must not mutate the receiver, and must be deterministic:
        // f(x) == f(x) for repeated calls.
        let (s0, p0, m0, d0) = (small.clone(), progress.clone(), meter.clone(), dialog.clone());
        assert_eq!(small.to_full_info(), small.to_full_info());
        assert_eq!(progress.to_full_info(), progress.to_full_info());
        assert_eq!(meter.to_full_info(), meter.to_full_info());
        assert_eq!(dialog.to_full_info(), dialog.to_full_info());
        assert_eq!(small, s0, "to_full_info must not mutate SmallAriaInfo");
        assert_eq!(progress, p0, "to_full_info must not mutate ProgressAriaInfo");
        assert_eq!(meter, m0, "to_full_info must not mutate MeterAriaInfo");
        assert_eq!(dialog, d0, "to_full_info must not mutate DialogAriaInfo");
        // Idempotent even when the value is NaN — the AccessibilityInfo carries a
        // *string* ("NaN"), which is Eq-comparable, so this holds where a raw f32
        // comparison would not.
        let nan_meter = MeterAriaInfo::create("m".into(), f32::NAN, 0.0, 1.0);
        assert_eq!(nan_meter.to_full_info(), nan_meter.to_full_info());
    }
    #[test]
    fn progress_max_is_never_surfaced_in_full_info() {
        // `max` has no representation in AccessibilityInfo, so setting it to
        // anything at all — including inf/NaN — must not perturb the conversion.
        let baseline = ProgressAriaInfo::create("p".into())
            .with_current_value(0.5)
            .to_full_info();
        for v in adversarial_f32() {
            let with_max = ProgressAriaInfo::create("p".into())
                .with_current_value(0.5)
                .with_max(v)
                .to_full_info();
            assert_eq!(with_max, baseline, "with_max({v}) leaked into to_full_info");
        }
    }
    #[test]
    fn meter_threshold_builders_are_order_independent_and_last_write_wins() {
        // Order-independence: the three threshold setters touch disjoint fields.
        let a = MeterAriaInfo::create("m".into(), 0.5, 0.0, 1.0)
            .with_low(0.1)
            .with_high(0.9)
            .with_optimum(0.7);
        let b = MeterAriaInfo::create("m".into(), 0.5, 0.0, 1.0)
            .with_optimum(0.7)
            .with_high(0.9)
            .with_low(0.1);
        assert_eq!(a, b);
        // Last-write-wins, including when the second write is a non-finite value.
        let m = MeterAriaInfo::create("m".into(), 0.5, 0.0, 1.0)
            .with_low(0.1)
            .with_low(f32::INFINITY);
        assert_eq!(f32_of(&m.low), Some(f32::INFINITY));
        // Nonsensical-but-accepted config: low > high, optimum outside [min,max].
        // The type performs no validation; assert it stores them verbatim rather
        // than silently clamping (downstream code must do its own validation).
        let weird = MeterAriaInfo::create("m".into(), 5.0, 0.0, 1.0)
            .with_low(100.0)
            .with_high(-100.0)
            .with_optimum(-1.0);
        assert_eq!(f32_of(&weird.low), Some(100.0));
        assert_eq!(f32_of(&weird.high), Some(-100.0));
        assert_eq!(f32_of(&weird.optimum), Some(-1.0));
        assert_eq!(weird.current_value, 5.0); // out of [min,max], not clamped
        assert!(weird.to_full_info().accessibility_value.is_some());
    }
    #[test]
    fn progress_and_dialog_builders_last_write_wins() {
        let p = ProgressAriaInfo::create("p".into())
            .with_current_value(1.0)
            .with_current_value(2.0)
            .with_indeterminate(true)
            .with_indeterminate(false)
            .with_description("a".into())
            .with_description("b".into());
        assert_eq!(f32_of(&p.current_value), Some(2.0));
        assert!(!p.indeterminate);
        assert_eq!(name_str(&p.description), Some("b"));
        // Not indeterminate => the (last) current value is surfaced.
        assert_eq!(name_str(&p.to_full_info().accessibility_value), Some("2"));
        let d = DialogAriaInfo::create("d".into())
            .with_modal(true)
            .with_modal(false)
            .with_role(AccessibilityRole::Alert)
            .with_role(AccessibilityRole::Dialog)
            .with_described_by("x".into())
            .with_described_by("y".into());
        assert!(!d.modal);
        assert_eq!(d.role, AccessibilityRole::Dialog);
        assert_eq!(name_str(&d.described_by), Some("y"));
    }
    // =====================================================================
    // AccessibilityInfo — the fully-populated aggregate
    // =====================================================================
    fn full_info_fixture() -> AccessibilityInfo {
        AccessibilityInfo {
            accessibility_name: OptionString::Some("name".into()),
            accessibility_value: OptionString::Some("value".into()),
            description: OptionString::Some("desc".into()),
            accelerator: OptionVirtualKeyCodeCombo::None,
            default_action: OptionString::Some("activate".into()),
            states: all_states().into(),
            supported_actions: all_actions().into(),
            labelled_by: OptionDomNodeId::Some(DomNodeId::ROOT),
            described_by: OptionDomNodeId::Some(DomNodeId {
                dom: DomId { inner: 3 },
                node: NodeHierarchyItemId::from_raw(7),
            }),
            role: AccessibilityRole::PushButton,
            is_live_region: true,
        }
    }
    #[test]
    fn full_info_clone_eq_hash_ord_are_consistent() {
        let a = full_info_fixture();
        let b = a.clone();
        // Deep clone: equal, equally hashed, mutually Equal under Ord.
        assert_eq!(a, b);
        assert_eq!(hash_of(&a), hash_of(&b));
        assert_eq!(a.cmp(&b), core::cmp::Ordering::Equal);
        // The clone owns its own heap buffers — dropping it must leave `a` intact.
        drop(b);
        assert_eq!(a.states.len(), all_states().len());
        assert_eq!(a.supported_actions.len(), all_actions().len());
        assert_eq!(name_str(&a.accessibility_name), Some("name"));
        // Perturbing any single field must break equality (no field is ignored
        // by the derived PartialEq — a field silently dropped from the derive
        // would let two different a11y nodes compare equal).
        let mut differs = a.clone();
        differs.is_live_region = false;
        assert_ne!(a, differs);
        let mut differs = a.clone();
        differs.role = AccessibilityRole::Unknown;
        assert_ne!(a, differs);
        let mut differs = a.clone();
        differs.labelled_by = OptionDomNodeId::None;
        assert_ne!(a, differs);
        let mut differs = a.clone();
        differs.states = Vec::new().into();
        assert_ne!(a, differs);
        let mut differs = a.clone();
        differs.supported_actions = Vec::new().into();
        assert_ne!(a, differs);
        let mut differs = a.clone();
        differs.default_action = OptionString::None;
        assert_ne!(a, differs);
    }
    // =====================================================================
    // Option<T> FFI wrappers — Some/None round-trip
    // =====================================================================
    #[test]
    fn option_wrappers_round_trip() {
        // Copy payloads.
        for r in all_roles() {
            let opt = OptionAccessibilityRole::Some(r);
            assert!(opt.is_some());
            assert!(!opt.is_none());
            assert_eq!(opt.as_ref(), Some(&r));
            assert_eq!(opt.into_option(), Some(r));
        }
        assert!(OptionAccessibilityRole::None.is_none());
        assert_eq!(OptionAccessibilityRole::None.into_option(), None);
        for s in all_states() {
            assert_eq!(OptionAccessibilityState::Some(s).into_option(), Some(s));
        }
        assert_eq!(OptionAccessibilityState::None.into_option(), None);
        // Non-Copy payloads (heap-owning) must round-trip without a double-free.
        for a in all_actions() {
            let opt = OptionAccessibilityAction::Some(a.clone());
            assert!(opt.is_some());
            assert_eq!(opt.into_option(), Some(a));
        }
        assert!(OptionAccessibilityAction::None.is_none());
        let small = SmallAriaInfo::label("s").with_role(AccessibilityRole::Link);
        assert_eq!(
            OptionSmallAriaInfo::Some(small.clone()).into_option(),
            Some(small)
        );
        assert!(OptionSmallAriaInfo::None.is_none());
        let progress = ProgressAriaInfo::create("p".into()).with_current_value(0.5);
        assert_eq!(
            OptionProgressAriaInfo::Some(progress.clone()).into_option(),
            Some(progress)
        );
        let meter = MeterAriaInfo::create("m".into(), 1.0, 0.0, 2.0);
        assert_eq!(
            OptionMeterAriaInfo::Some(meter.clone()).into_option(),
            Some(meter)
        );
        let dialog = DialogAriaInfo::create("d".into()).with_modal(true);
        assert_eq!(
            OptionDialogAriaInfo::Some(dialog.clone()).into_option(),
            Some(dialog)
        );
        // The big aggregate, which owns two FFI vecs.
        let info = full_info_fixture();
        assert_eq!(
            OptionAccessibilityInfo::Some(info.clone()).into_option(),
            Some(info)
        );
        assert!(OptionAccessibilityInfo::None.is_none());
    }
}