# What a user does and sees (compat/apps.rs): each case after its tap.
# base-status-bar
tap id:case-base-status-bar
# platform is told to show: nsh.json's default under the layers of the
# pages and dialogs in the tree, the latest winning field by field.
expect Plain page
# nsh.json's default alone: the root page sets none.
statusbar foreground=dark hidden=true animated=false
# A page over it sets `foreground` only: `hidden` stays nsh.json's.
tap text:Push first
expect First page
statusbar foreground=light hidden=true animated=false
# The next sets `hidden` only: the page below's `foreground` stays.
tap text:Push second
expect Second page
statusbar foreground=light hidden=false animated=false
# A dialog over the pages, animated.
tap text:Open dialog
expect Dialog content
statusbar foreground=auto hidden=false animated=true
# Closing it takes its layer away.
tap text:Close dialog
absent Dialog content
statusbar foreground=light hidden=false animated=false
# Popping takes the pages' away, down to nsh.json's default.
tap text:Pop
expect Routes: plain > first
statusbar foreground=light hidden=true animated=false
tap text:Pop
expect Routes: plain
statusbar foreground=dark hidden=true animated=false
covers [props] statusBar
covers [nsh:rt] setup
# base-alert-dialog
tap id:case-base-alert-dialog
# headless: its promise stays pending.
expect Alert: not shown
tap text:Alert
expect Alert: pending
tap text:Sheet
expect Sheet: pending
tap text:Empty
expect Empty: rejected for actions
wait 300
expect Alert: pending
expect Sheet: pending
covers [nsh/base] AlertDialog
# base-button
tap id:case-base-button
# events the platform reports.
expect Presses: 0
prop id:plain role button
prop id:plain accessibilityState {}
style id:plain backgroundColor #336699ff
frame id:plain 0 20 390 40
listens id:plain onPress
listens id:plain onPressDown
listens id:plain onPressUp
tap text:Press me
expect Presses: 1
event text:"Press me" onPressDown
expect Downs: 1, ups: 0
event text:"Press me" onPressUp
expect Downs: 1, ups: 1
# A role, `selected`, and `selectedStyle` over `class` while selected.
prop id:tab role tab
prop id:tab accessibilityState {}
style id:tab backgroundColor #ccccccff
frame id:tab 0 106 390 30
tap text:Tab
prop id:tab accessibilityState selected=true
style id:tab backgroundColor #00ff00ff
tap text:Tab
prop id:tab accessibilityState {}
style id:tab backgroundColor #ccccccff
# `accessibilityLabel`: what screen readers say, and what a label finds.
prop id:icon accessibilityLabel Close the panel
tap label:Close the panel
expect Presses: 11
# `disabled`: screen readers say so, `disabledStyle` applies, presses do
# nothing (the platform gets no press to report).
prop id:blocked accessibilityState disabled=true
style id:blocked opacity 0.5
expect Blocked presses: 0
tap text:Toggle blocked
prop id:blocked accessibilityState {}
style id:blocked opacity 1
tap text:Blocked
expect Blocked presses: 1
prop id:off accessibilityState disabled=true
style id:off opacity 1
covers [nsh/base] Button
covers [props] role
covers [props] accessibilityLabel
covers [props] disabled
covers [props] disabledStyle
covers [events] onPressDown
covers [events] onPressUp
covers [events] onPress
covers [props] Button.selected
covers [props] Button.selectedStyle
# base-dialog
tap id:case-base-dialog
# (appearing, going away, Escape / back and swipes) are events here: the
# headless runtime presents nothing.
# Open by default, with the defaults.
expect Plain dialog
prop id:plain animation slide
prop id:plain presentation fullScreen
prop id:plain closeOnEscape true
prop id:plain closeOnBackdropPress true
prop id:plain closeOnSwipe true
noprop id:plain supportedOrientations
# Closed while `open` is false.
absent Synced dialog
absent Guarded dialog
tap text:Open synced
expect Synced dialog
prop id:synced animation fade
prop id:synced presentation pageSheet
listens id:synced onOpen
listens id:synced onCancel
# `style` styles the popup around the children.
frame id:synced-body 20 20 350 30
event text:"Synced dialog" onOpen
expect Log: synced opened
# Escape / back: onCancel, then onOpenChange(false, "cancel").
event text:"Synced dialog" onCancel reason=cancel
expect Log: synced opened, synced cancel, synced change false cancel
absent Synced dialog
tap text:Clear log
# Opens again when `open` goes from false to true.
tap text:Open synced
expect Synced dialog
event text:"Synced dialog" onClose
expect Log: synced closed
tap text:Close synced
absent Synced dialog
# `class` styles the popup; the iOS props.
expect Stubborn dialog
prop id:stubborn animation none
prop id:stubborn presentation formSheet
prop id:stubborn ios.supportedOrientations portrait, landscape-left
frame id:stubborn-body 12 12 366 30
# Closed by the user, it stays closed while `open` stays true.
tap text:Clear log
event text:"Stubborn dialog" onCancel reason=swipe
expect Log: stubborn change false swipe
absent Stubborn dialog
wait 100
absent Stubborn dialog
tap text:Stubborn off
absent Stubborn dialog
tap text:Stubborn on
expect Stubborn dialog
# A `closeOn*` flag set to false keeps it open: onCancel only.
tap text:Clear log
tap text:Open guarded
expect Guarded dialog
prop id:guarded closeOnEscape false
prop id:guarded closeOnBackdropPress false
prop id:guarded closeOnSwipe false
event text:"Guarded dialog" onCancel reason=cancel
expect Log: guarded cancel
event text:"Guarded dialog" onCancel reason=swipe
expect Log: guarded cancel, guarded cancel
expect Guarded dialog
tap text:Close guarded
absent Guarded dialog
# Each reason by its own flag: Escape kept, a swipe closes.
tap text:Clear log
tap text:Open swipes
expect Swipes dialog
event text:"Swipes dialog" onCancel reason=cancel
expect Swipes dialog
event text:"Swipes dialog" onCancel reason=swipe
expect Log: swipes change false swipe
absent Swipes dialog
covers [nsh/base] Dialog
covers [props] presentation
covers [props] animation
covers [props] closeOnEscape
covers [props] closeOnSwipe
covers [props] closeOnBackdropPress
covers [props] ios.supportedOrientations
covers [props] Dialog.open
covers [events] Dialog.onOpen
covers [events] Dialog.onClose
covers [events] Dialog.onCancel
covers [events] Dialog.onOpenChange
# base-dialog-backdrop
tap id:case-base-dialog-backdrop
expect Styled dialog
expect Classed dialog
# The backdrops, styled by `backdropStyle` and `backdropClass`.
style id:shade backgroundColor #00000080
style id:tinted backgroundColor #ff0000ff
frame id:shade 0 0 390 844
# A press on the backdrop is a close attempt.
tap id:shade
expect Log: styled cancel, styled change false backdropPress
absent Styled dialog
expect Classed dialog
covers [nsh/base] Dialog
covers [props] Dialog.backdropStyle
covers [props] Dialog.backdropClass
covers [props] Dialog.backdropTestID
# base-nav-page
tap id:case-base-nav-page
# pop and header are events and props here: the headless runtime has no
# system header.
expect First content
# A page's defaults, and `headerClass` as the header's colors.
prop id:first title First title
prop id:first headerVisible true
prop id:first headerLargeTitle false
prop id:first headerTransparent false
prop id:first headerBackVisible true
noprop id:first headerBackTitle
prop id:first popOnSwipe true
prop id:first popOnBack true
prop id:first animation default
prop id:first headerStyle backgroundColor=#112233ff color=#445566ff tintColor=#778899ff
# Props follow signals.
tap text:Rename
prop id:first title Renamed
# Every option set; `headerStyle` wins over `headerClass`.
tap text:Open second
expect Routes: first > second
expect Second content
prop id:second title Second title
prop id:second headerVisible false
prop id:second headerLargeTitle true
prop id:second headerTransparent true
prop id:second headerBackTitle Back to second
prop id:second headerBackVisible false
prop id:second popOnSwipe false
prop id:second popOnBack true
prop id:second animation fade
prop id:second headerStyle backgroundColor=#00ff00ff color=#ff0000ff tintColor=#0000ffff
frame id:second-content 10 10 370 40
# The header's slots.
expect Desktop header
expect Left item
expect Right item
expect Title item
tap text:Toggle right
absent Right item
expect Left item
tap text:Toggle right
expect Right item
# popOnBack follows its signal; a back press reaches the app either way and
# leaves the route to the platform.
tap text:Keep
prop id:second popOnBack false
listens id:second onBackPress
event text:"Second content" onBackPress
expect Backs: 1
expect Routes: first > second
tap text:Keep
prop id:second popOnBack true
event text:"Second content" onBackPress
expect Backs: 2
# The user popped it: its route is gone before the app hears.
event text:"Second content" onDismiss
expect Dismissed: second, routes first
expect Routes: first
absent Second content
absent Left item
# Each animation as written.
tap text:Open thirds
expect Third default
prop id:third-none animation none
# "slide" is the platform's push transition, as "default".
prop id:third-slide animation default
prop id:third-default animation default
prop id:third-none popOnBack false
covers [nsh/base] NavPage
covers [nsh/base] NavStack
covers [nsh/base] createNavStack
covers [props] title
covers [props] headerVisible
covers [props] headerLargeTitle
covers [props] headerTransparent
covers [props] headerBackTitle
covers [props] headerBackVisible
covers [props] popOnSwipe
covers [props] popOnBack
covers [props] animation
covers [props] NavPage.header
covers [props] NavPage.headerLeft
covers [props] NavPage.headerRight
covers [props] NavPage.headerTitle
covers [props] NavPage.headerStyle
covers [props] NavPage.headerClass
covers [events] NavPage.onBackPress
covers [events] NavPage.onDismiss
# base-nav-stack
tap id:case-base-nav-stack
# on screen headless (no platform hides the covered ones).
expect Routes: home
expect Depth: 1
expect Renders: 1
expect Page home at 0
frame id:stack 0 292 390 300
# A page keeps its state while covered.
tap text:Bump home
expect Bumps of home: 1
tap text:Push
expect Routes: home > p1
expect Page p1 at 1
expect Renders: 2
tap text:Push
expect Page p2 at 2
expect Routes: home > p1 > p2
expect Bumps of home: 1
# pop: one page.
tap text:Pop
expect Routes: home > p1
absent Page p2 at 2
expect Cleanups: p2
expect Renders: 3
# The same route twice is two pages; remove takes the topmost.
tap text:Push twice
expect Routes: home > p1 > twice > twice
expect Page twice at 2
expect Page twice at 3
expect Renders: 5
tap text:Remove twice
expect Routes: home > p1 > twice
expect Page twice at 2
absent Page twice at 3
expect Cleanups: p2 twice
# A route not in the stack: nothing changes.
tap text:Remove unknown
expect Depth: 3
# pop(count).
tap text:Pop two
expect Routes: home
absent Page p1 at 1
absent Page twice at 2
expect Cleanups: p1 p2 twice twice
# One page always stays.
tap text:Pop
expect Routes: home
expect Page home at 0
tap text:Push
tap text:Push
expect Routes: home > p3 > p4
tap text:Pop many
expect Routes: home
absent Page p3 at 1
expect Cleanups: p1 p2 p3 p4 twice twice
expect Page home at 0
expect Bumps of home: 1
# replace: the top page.
tap text:Push
expect Page p5 at 1
tap text:Replace
expect Routes: home > swapped
expect Page swapped at 1
absent Page p5 at 1
expect Cleanups: p1 p2 p3 p4 p5 twice twice
# setRoutes: all pages.
tap text:Set routes
expect Routes: home > a > b
expect Page a at 1
expect Page b at 2
absent Page swapped at 1
# remove below: the pages above follow with their index.
tap text:Remove second
expect Routes: home > b
expect Page b at 1
absent Page a at 1
# popToRoot.
tap text:Push
expect Depth: 3
tap text:Pop to root
expect Routes: home
absent Page b at 1
expect Page home at 0
# A route rendered without a NavPage is a page too.
tap text:Push plain
expect Plain page at 1
expect Routes: home > plain
tap text:Pop
absent Plain page at 1
covers [nsh/base] createNavStack
covers [nsh/base] NavStack
covers [nsh/base] NavPage
covers [nsh/base] Button
covers [props] NavStack.state
# base-safe-area-keyboard
tap id:case-base-safe-area-keyboard
# insets and no keyboard; a page's and a dialog's insets are what the
# platform reports (`onInsets`).
expect Safe area: 0 0 0 0
expect Root insets: 0 0 0 0
expect Keyboard: 0, duration a number
frame id:root-inner 2 2 386 36
# A page's top is its header's, 0 until the platform reports it.
expect Page insets: 0 0 0 0
frame id:page-all-inner 2 2 386 96
event text:"Page insets: 0 0 0 0" onInsets top=40 right=5 bottom=30 left=7
expect Page insets: 40 5 30 7
expect Root insets: 0 0 0 0
frame id:page-all-inner 9 42 374 26
frame id:page-vertical-inner 0 40 390 30
frame id:page-left-inner 7 0 383 20
# A dialog's.
expect Dialog insets: 0 0 0 0
event text:"Dialog insets: 0 0 0 0" onInsets top=10 right=11 bottom=12 left=13
expect Dialog insets: 10 11 12 13
frame id:dialog-inner 0 0 379 88
expect Page insets: 40 5 30 7
# No keyboard: no room made.
frame id:avoid-padding-inner 0 0 390 30
frame id:avoid-height-inner 0 0 390 30
frame id:avoid-off-inner 0 0 390 30
# A keyboard 5 points tall: `padding` pads the bottom by it and the
# `offset`, `height` makes a margin of as much below it, and a disabled
# one makes no room.
keyboard 5 0
expect Keyboard: 5, duration a number
frame id:avoid-padding-inner 0 0 390 5
frame id:avoid-height-inner 0 0 390 30
frame id:avoid-off-inner 0 0 390 30
frame id:avoid-off 0 210 390 30
# It hides: no room again.
keyboard 0 0
frame id:avoid-padding-inner 0 0 390 30
frame id:avoid-off 0 185 390 30
tap text:Dismiss the keyboard
expect Dismiss: called
covers [nsh] safeArea
covers [nsh] safeAreaInsets
covers [nsh] keyboardFrame
covers [nsh] Keyboard
# base-text-view
tap id:case-base-text-view
# Markdown: a heading, a link, a list, a table with the app's actions
# above it, code with the app's actions in its header.
expect Markdown title
style text:"Markdown title" fontWeight 600
prop text:"Markdown title" role header
expect Read the guide now.
expect First item
expect Second item
expect Name
expect Alpha
expect 12
expect Rows: 1, columns: 2
expect let a = 1;
expect Action run
tap label:Run code
expect Code action: run ts
tap label:Copy code
expect Code action: copy ts
# The code's scroll view without its indicator; the root's color
# passes to every part.
prop scroll:text:"let a = 1;" horizontal true
prop scroll:text:"let a = 1;" scrollbar false
# The table's scroll view without its indicator.
prop scroll:text:"Alpha" horizontal true
prop scroll:text:"Alpha" scrollbar false
style text:"First item" color #0000ffff
# A press on the link reports its URL; nothing opens it.
expect Link: none
tap span:the guide
expect Link: https://example.com/guide
# HTML.
expect From HTML
expect An hypertext paragraph.
style span:hypertext fontWeight 600
# The app's own blocks.
expect From a document
# Streaming: the `**` left open shows closed, the caret after the text.
style span:stream-bold fontWeight 600
expect CARET
# An extension of each kind, shown by `renderCustom`.
expect Mention maya
expect Chart 1 2 3
expect Callout tip
expect Note Heads up
# The other render functions.
expect Code js: const b = 2;
expect Math inline: E=mc^2
expect Math block: x^2
expect Image Logo: https://example.com/logo.png
tap label:Logo
expect Image pressed: Logo
# Parts styled by `classes` and `styles`; `selectable`.
style text:"Classed heading" color #ff0000ff
style text:"Classed heading" fontWeight 600
style text:"Styled paragraph." color #00ff00ff
prop text:"Styled paragraph." selectable true
# The static functions.
expect Plain: a b
expect Parsed: 1 paragraph
expect Extended: text,custom
expect Math off: Math $x$
expect From HTML: Hi there
covers [nsh/base] TextView
covers [props] TextView.markdown
covers [props] TextView.document
covers [props] TextView.options
covers [props] TextView.extensions
covers [props] TextView.classes
covers [props] TextView.styles
covers [props] selectable
covers [props] TextView.codeBlockScrollbar
covers [props] TextView.tableScrollbar
covers [props] TextView.streamingCaret
covers [props] TextView.renderCustom
covers [props] TextView.renderCodeBlock
covers [props] TextView.codeBlockActions
covers [props] TextView.renderCodeBlockAction
covers [props] TextView.renderTableActions
covers [props] TextView.renderMath
covers [props] TextView.renderImage
covers [events] TextView.onLinkPress
covers [events] TextView.onCodeBlockAction
covers [events] TextView.onImagePress
# base-selectable-view
tap id:case-base-selectable-view
# One area around a text, a TextView, a label kept out and an inner
# area; a selectable TextView an area of its own; a text outside.
expect Area first text
expect Area document
expect Area item
expect Area label
expect Inner text
expect Own selection
expect Outside text
prop id:area selectableView true
prop id:inner selectableView true
prop id:own selectableView true
noprop id:area-doc selectableView
# Texts join the area unless they say `selectable={false}`.
noprop text:"Area first text" selectable
noprop text:"Area item" selectable
prop text:"Area label" selectable false
noprop text:"Inner text" selectable
prop text:"Own selection" selectable true
noprop text:"Outside text" selectable
# base-popups
tap id:case-base-popups
# Shown at first: a controlled popover, one with `defaultOpen`, a tooltip
# with `defaultOpen`, a sheet and a menu with `defaultOpen`.
expect PopoverBody
expect OwnBody
expect ShownTip
expect StuckSheet
expect OpenAtStart
expect Life: popover open
# The popover: above its trigger (20, 85, 350 × 30), 6 points away, its end
# at the trigger's but kept 30 points from the edge; 80 wide; the app's
# class.
frame parent:text:PopoverBody 280 45 80 34
style parent:text:PopoverBody backgroundColor #123456ff
# Escape or back: heard, and with `closeOnEscape={false}` it stays.
event text:PopoverBody onCancel
expect Log: popover cancel
expect PopoverBody
# The trigger closes it.
tap text:PopTrigger
expect Log: popover false triggerPress
absent PopoverBody
expect Life: popover closed
covers [nsh/base] Popover
covers [props] Popover.open
covers [props] Popover.defaultOpen
covers [props] Popover.placement
covers [props] Popover.align
covers [props] Popover.offset
covers [props] Popover.margin
covers [props] Popover.popupClass
covers [props] Popover.popupStyle
covers [props] Popover.trigger
covers [events] Popover.onOpenChange
covers [events] Popover.onCancel
# The menu, a sheet on a phone: its items, a separator, submenus.
tap text:MenuTrigger
expect NewItem
expect Log: menu true triggerPress
expect Life: menu open
style parent:parent:text:NewItem backgroundColor #00ff00ff
frame parent:text:NewItem 4 4 382 17
style below:parent:parent:text:NewItem backgroundColor #000000ff
style below:parent:parent:text:NewItem opacity 0.5
frame id:separator 4 55 382 1
prop parent:text:OffItem accessibilityState disabled=true
prop parent:text:KeepItem accessibilityLabel Keep the menu
tap text:KeepItem
expect Log: chose Keep
expect KeepItem
# A submenu shows its items in place of the menu's, its back row first.
tap text:ShareItem
expect MailItem
expect BackRow
absent NewItem
tap text:BackRow
expect NewItem
absent MailItem
# A disabled one does not open.
tap text:LockedItem
absent HiddenItem
expect NewItem
# Choosing an item closes the menu.
tap text:NewItem
expect Log: menu false select
absent NewItem
expect Life: menu closed
# The app opens it; Escape or back is heard, and closes it.
tap text:ShowMenu
expect NewItem
event text:NewItem onCancel
expect Heard: menu cancel
expect Log: menu false cancel
absent NewItem
covers [nsh/base] Menu
covers [nsh/base] MenuItem
covers [nsh/base] MenuSeparator
covers [nsh/base] Submenu
covers [props] Menu.open
covers [props] Menu.defaultOpen
covers [props] Menu.popupClass
covers [props] Menu.popupStyle
covers [props] Menu.backdropClass
covers [props] Menu.backdropStyle
covers [props] Menu.trigger
covers [events] Menu.onOpenChange
covers [props] MenuItem.closeOnSelect
covers [events] MenuItem.onSelect
covers [props] Submenu.trigger
covers [props] Submenu.back
# The context menu: a finger held on its trigger.
touch start text:HoldArea 5 5
wait 600
touch end
expect CopyItem
expect Log: context true longPress
expect Life: context open
style parent:parent:text:CopyItem backgroundColor #ff0000ff
frame parent:text:CopyItem 2 2 386 17
style below:parent:parent:text:CopyItem backgroundColor #0000ffff
style below:parent:parent:text:CopyItem opacity 0.25
tap text:CopyItem
expect Log: context false select
absent CopyItem
expect Life: context closed
# Disabled, a held finger does nothing; the app still opens it.
tap text:ToggleContext
touch start text:HoldArea 5 5
wait 600
touch end
absent CopyItem
tap text:ShowContext
expect CopyItem
event text:CopyItem onCancel
expect Heard: context cancel
expect Log: context false cancel
absent CopyItem
covers [nsh/base] ContextMenu
covers [props] ContextMenu.open
covers [props] ContextMenu.popupClass
covers [props] ContextMenu.popupStyle
covers [props] ContextMenu.backdropClass
covers [props] ContextMenu.backdropStyle
covers [props] ContextMenu.trigger
covers [events] ContextMenu.onOpenChange
# The tooltips: a touch screen shows none of its own, nor an element's
# `tooltip`; one the app opens shows below its trigger (20, 287, 350 × 30),
# 4 points away, from its start but 30 points from the edge; 60 wide; the
# app's class.
touch start text:TipTrigger 5 5
wait 1000
touch end
absent PropTip
tap text:TipTrigger
expect TipText
frame parent:text:TipText 30 321 60 17
style parent:text:TipText backgroundColor #333333ff
tap text:TipTrigger
absent TipText
touch start text:PropTrigger 5 5
wait 1000
touch end
absent PropTip
# Disabled: never shown.
absent NeverTip
tap text:ShowTip
expect AskedTip
covers [nsh/base] Tooltip
covers [events] Popover.onOpen
covers [events] Popover.onClose
covers [events] Menu.onOpen
covers [events] Menu.onClose
covers [events] Menu.onCancel
covers [events] ContextMenu.onOpen
covers [events] ContextMenu.onClose
covers [events] ContextMenu.onCancel
covers [events] Sheet.onOpen
covers [events] Sheet.onClose
covers [props] Tooltip.open
covers [props] Tooltip.defaultOpen
covers [props] Tooltip.placement
covers [props] Tooltip.align
covers [props] Tooltip.offset
covers [props] Tooltip.margin
# The sheet: at the top, 50 tall, over a backdrop.
tap text:OpenSheet
expect SheetBody
expect Life: sheet open
frame parent:text:SheetBody 0 0 390 50
style parent:text:SheetBody backgroundColor #abcdefff
style below:parent:text:SheetBody backgroundColor #111111ff
style below:parent:text:SheetBody opacity 0.75
event text:SheetBody onCancel
expect Log: sheet cancel
expect SheetBody
tap below:parent:text:SheetBody
expect Log: sheet false backdropPress
absent SheetBody
expect Life: sheet closed
# Opt-in bottom dragging installs the native release listener.
tap text:OpenSwipeSheet
expect SwipeSheetBody
wait 400
frame parent:text:SwipeSheetBody 0 490 390 354
listens parent:text:SwipeSheetBody onPanEnd
tap below:parent:text:SwipeSheetBody
absent SwipeSheetBody
covers [props] closeOnSwipe
# `closeOnBackdropPress={false}`: its backdrop keeps it.
tap below:parent:text:StuckSheet
expect StuckSheet
covers [nsh/base] Sheet
covers [props] Sheet.open
covers [props] Sheet.defaultOpen
covers [props] Sheet.side
covers [props] Sheet.popupClass
covers [props] Sheet.popupStyle
covers [props] Sheet.backdropClass
covers [props] Sheet.backdropStyle
covers [events] Sheet.onOpenChange
covers [events] Sheet.onCancel
# base-switch
tap id:case-base-switch
# The switch role and its value for screen readers, on the switch alone;
# the track (2 + 20 + 2 wide, a margin of 8) puts the label at 32.
expect Changes: 0
prop id:wifi role switch
prop id:wifi accessibilityState checked=false
nostyle id:wifi backgroundColor
frame id:wifi 0 20 390 24
frame text:Wi-Fi 32 4 39 17
listens id:wifi onPress
# A press reports the next value; the app sets it: `checkedStyle`, and
# `checked:` of the track (12 on the left) and thumb (30 wide).
tap text:Wi-Fi
expect Changes: 1
prop id:wifi accessibilityState checked=true
style id:wifi backgroundColor #00ff00ff
frame text:Wi-Fi 52 4 39 17
tap id:wifi
expect Changes: 2
prop id:wifi accessibilityState checked=false
frame text:Wi-Fi 32 4 39 17
# The app owns the value: a switch it keeps on asks for off and stays on.
prop id:lights accessibilityLabel Lights
prop id:lights accessibilityState checked=true
tap label:Lights
expect Asked: false
prop id:lights accessibilityState checked=true
# `disabled`: dimmed for screen readers, `disabled:` on the thumb (10
# wide), presses do nothing (the platform gets no press to report).
prop id:locked accessibilityState disabled=true checked=false
frame text:Locked 22 4 47 17
expect Changes: 2
tap id:enable
prop id:locked accessibilityState checked=false
frame text:Locked 32 4 47 17
listens id:locked onPress
tap text:Locked
expect Changes: 12
covers [nsh/base] Switch
covers [props] Switch.checked
covers [props] disabled
covers [props] checkedStyle
covers [props] accessibilityLabel
covers [props] Switch.trackClass
covers [props] Switch.trackStyle
covers [props] Switch.thumbClass
covers [props] Switch.thumbStyle
covers [events] onChange
# base-checkbox
tap id:case-base-checkbox
# The checkbox role and its value for screen readers, on the checkbox
# alone, mixed while indeterminate: `indeterminateStyle`, and
# `indeterminate:` of the indicator (24 wide, a margin of 8) puts the
# label at 32; the app's mark is inside the indicator.
expect Changes: 0
expect Mark: some
prop id:all role checkbox
prop id:all accessibilityState checked=mixed
style id:all backgroundColor #ffff00ff
frame text:All 32 2 24 17
listens id:all onPress
# A press from indeterminate asks for checked; the app sets it:
# `checkedStyle`, `checked:` of the indicator (30 wide) and the mark.
tap text:All
expect Changes: 1
expect Mark: all
prop id:all accessibilityState checked=true
style id:all backgroundColor #00ff00ff
frame text:All 38 2 24 17
# From checked it asks for unchecked.
tap id:all
expect Changes: 2
expect Mark: none
prop id:all accessibilityState checked=false
frame text:All 28 2 24 17
# The app owns the value: a checkbox it keeps checked asks for
# unchecked and stays checked.
prop id:kept accessibilityLabel Kept
prop id:kept accessibilityState checked=true
tap label:Kept
expect Asked: false
prop id:kept accessibilityState checked=true
# `role` says what it is instead of a checkbox.
prop id:menu role menuitem
prop id:menu accessibilityState checked=false
# `disabled`: dimmed for screen readers, `disabled:` on the indicator
# (10 wide), presses do nothing (the platform gets no press to report).
prop id:locked accessibilityState disabled=true checked=false
frame text:Locked 18 2 47 17
expect Changes: 2
tap id:enable
prop id:locked accessibilityState checked=false
frame text:Locked 28 2 47 17
listens id:locked onPress
tap text:Locked
expect Changes: 12
covers [nsh/base] Checkbox
covers [props] Checkbox.checked
covers [props] Checkbox.indeterminate
covers [props] Checkbox.indicatorClass
covers [props] Checkbox.indicatorStyle
covers [props] Checkbox.indicator
covers [props] disabled
covers [props] checkedStyle
covers [props] indeterminateStyle
covers [props] accessibilityLabel
covers [props] role
covers [events] onChange
# base-radio
tap id:case-base-radio
# The group is a radio group for screen readers; each radio is a radio
# of its own, checked and selected while checked: `checkedStyle`, and
# `group-checked:` of the app's dot (30 wide, a margin of 8) puts the
# label at 38.
expect Plan: basic
expect Changes: 0
prop id:plans role radiogroup
prop id:basic role radio
prop id:basic accessibilityState selected=true checked=true
style id:basic backgroundColor #00ff00ff
frame text:Basic 38 2 39 17
prop id:pro role radio
prop id:pro accessibilityState checked=false
nostyle id:pro backgroundColor
frame text:Pro 28 2 24 17
listens id:pro onPress
# A press on an unchecked radio asks for `true`; the app checks it and
# unchecks the one that was.
tap text:Pro
expect Changes: 1
expect Plan: pro
prop id:pro accessibilityState selected=true checked=true
style id:pro backgroundColor #00ff00ff
frame text:Pro 38 2 24 17
prop id:basic accessibilityState checked=false
frame text:Basic 28 2 39 17
# A checked radio cannot uncheck itself: a press on it asks nothing.
tap id:pro
expect Changes: 1
expect Plan: pro
# `disabled`: dimmed for screen readers, `group-disabled:` on the dot
# (10 wide), presses do nothing; `accessibilityLabel` names it.
prop id:team accessibilityLabel Team plan
prop id:team accessibilityState disabled=true checked=false
frame text:Team 18 2 31 17
tap id:enable
prop id:team accessibilityState checked=false
frame text:Team 28 2 31 17
tap label:Team plan
expect Changes: 2
expect Plan: team
prop id:team accessibilityState selected=true checked=true
covers [nsh/base] Radio
covers [nsh/base] RadioGroup
covers [props] Radio.checked
covers [props] disabled
covers [props] checkedStyle
covers [props] accessibilityLabel
covers [events] onChange
# base-tabs
tap id:case-base-tabs
# The list is a tab list for screen readers; each tab is a tab of its
# own, selected while it is the app's: `selectedStyle`, and
# `group-selected:` of the app's marker (30 wide, a margin of 8) puts the
# label at 38.
expect Showing: overview
expect Presses: 0
prop id:sections role tablist
prop id:overview role tab
prop id:overview accessibilityState selected=true
style id:overview backgroundColor #00ff00ff
frame text:Overview 38 2 62 17
prop id:activity role tab
prop id:activity accessibilityState {}
nostyle id:activity backgroundColor
frame text:Activity 28 2 62 17
listens id:activity onPress
# A press reports `onPress`; the app selects the tab, deselects the one
# that was and shows its content.
tap text:Activity
expect Presses: 1
expect Showing: activity
prop id:activity accessibilityState selected=true
style id:activity backgroundColor #00ff00ff
frame text:Activity 38 2 62 17
prop id:overview accessibilityState {}
frame text:Overview 28 2 62 17
# The selected tab reports its presses too.
tap id:activity
expect Presses: 2
expect Showing: activity
# `disabled`: dimmed for screen readers, `group-disabled:` on the marker
# (10 wide), presses do nothing; `accessibilityLabel` names it.
prop id:billing accessibilityLabel Billing section
prop id:billing accessibilityState disabled=true
frame text:Billing 18 2 54 17
tap id:enable
prop id:billing accessibilityState {}
frame text:Billing 28 2 54 17
tap label:Billing section
expect Presses: 3
expect Showing: billing
prop id:billing accessibilityState selected=true
covers [nsh/base] Tab
covers [nsh/base] Tabs
covers [props] Tab.selected
covers [props] disabled
covers [props] Tab.selectedStyle
covers [props] accessibilityLabel
covers [events] onPress

# A collapsible: the summary always, the details only while the app's
# `open` holds. Collapsed, the region is the trigger's 20 points.
tap id:case-base-collapsible
expect Summary
absent Details 0
frame id:region 0 20 390 20
prop id:trigger accessibilityState expanded=false
# Open: the details after the summary, 4 apart (`class`).
tap id:trigger
expect Details 0
prop id:trigger accessibilityState expanded=true
frame id:region 0 20 390 54
frame id:details 0 0 390 30
# Collapsed again: out of the layout.
tap id:trigger
absent Details 0
frame id:region 0 20 390 20
# Kept while collapsed: it follows the app and shows the latest.
tap id:bump
tap id:trigger
expect Details 1
frame id:region 0 20 390 54
covers [nsh/base] Collapsible
covers [props] Collapsible.open
covers [props] Collapsible.content

# base-progress
tap id:case-base-progress
# The progress bar role, its label, range and value for screen readers;
# the indicator (30 high, 4 above it) in the track (3 around it, 6
# below it) makes the bar 46 high.
expect 40%
prop id:upload role progressbar
prop id:upload accessibilityLabel Uploading
prop id:upload accessibilityValue min=0 max=100 now=40
frame id:upload 0 20 390 46
# The app's value, held to the range.
tap id:more
expect 120%
prop id:upload accessibilityValue min=0 max=100 now=100
# Indeterminate: the range with no value.
tap id:wait
prop id:upload accessibilityValue min=0 max=100
frame id:upload 0 20 390 46
tap id:wait
prop id:upload accessibilityValue min=0 max=100 now=100
covers [nsh/base] Progress
covers [props] value
covers [props] Progress.indeterminate
covers [props] Progress.trackClass
covers [props] Progress.trackStyle
covers [props] Progress.indicatorClass
covers [props] Progress.indicatorStyle

# base-table
tap id:case-base-table
# The roles and the table's label for screen readers.
expect Recent invoices
prop id:table role table
prop id:table accessibilityLabel Invoices
prop id:head role rowgroup
prop id:body role rowgroup
prop id:foot role rowgroup
prop id:head-row role row
prop id:title role columnheader
prop id:cell role cell
# A row's cells side by side; the groups and the caption stacked.
frame id:head 0 20 390 30
frame id:title 0 0 100 30
frame id:amount 100 0 80 24
frame id:foot 0 74 390 24
frame id:caption 0 98 390 16
covers [nsh/base] Table
covers [nsh/base] TableHeader
covers [nsh/base] TableBody
covers [nsh/base] TableFooter
covers [nsh/base] TableRow
covers [nsh/base] TableHead
covers [nsh/base] TableCell
covers [nsh/base] TableCaption

tap id:case-base-plot
# One image for screen readers, with the app's label, at the app's size.
expect hovered null
prop id:plot role image
prop id:plot accessibilityLabel Price
frame id:plot 0 20 200 100
# The rows' x on a linear scale of 0 to 40: at 0, 50 and 100 points. The
# row nearest the pointer, reported when it changes, none once it leaves;
# the app's value for screen readers and its tooltip follow.
event id:plot onHoverMove position.x=45 position.y=10
expect hovered 1
expect tip 1
prop id:plot accessibilityValue text=row1
# The hover's dots, one per mark, each a box the size of its dot around
# the mark's point (the tooltip's sibling below it the last mark's): the
# plot's `dotClass` (8), a mark's own `hoverDotStyle` (10 by 10) or
# `hoverDotClass` (12) in its place, an area's class and style each in
# place of the plot's (4 wide, 6 high).
frame below:parent:parent:parent:id:tip 0 0 10 10
frame below:below:parent:parent:parent:id:tip 0 0 12 12
frame below:below:below:parent:parent:parent:id:tip 0 0 8 8
frame below:below:below:below:parent:parent:parent:id:tip 0 0 4 6
# The tooltip at the top, `tooltipOffset` below the top edge (the
# animator centers it on the cross line, inside the plot).
frame parent:parent:id:tip 0 6 40 16
# Beside the pointer: right of the cross line in the plot's left half,
# above the pointer in its lower half and below it in the upper one,
# `tooltipOffset` away.
tap id:pointer-tip
event id:plot onHoverMove position.x=45 position.y=80
frame parent:parent:id:tip 6 -22 40 16
event id:plot onHoverMove position.x=45 position.y=10
frame parent:parent:id:tip 6 6 40 16
event id:plot onHoverMove position.x=90 position.y=10
expect hovered 2
prop id:plot accessibilityValue text=row2
event id:plot onHoverOut
expect hovered null
# New rows: the plot reads them again.
tap id:more
expect rows 4
event id:plot onHoverMove position.x=60 position.y=10
expect hovered 1
event id:plot onHoverOut
expect hovered null
# A sparkline follows no pointer: it reports nothing, even once a press
# that comes after the pointer has been handled.
prop id:spark accessibilityLabel Spark
event id:spark onHoverMove position.x=90 position.y=10
tap id:more
expect rows 5
expect hovered null
covers [nsh/base/plot] Plot
covers [nsh/base/plot] Line
covers [nsh/base/plot] Area
covers [nsh/base/plot] Grid
covers [props] Plot.data
covers [props] Plot.x
covers [props] Plot.xScale
covers [props] Plot.tooltipContent
covers [props] Plot.tooltipPlacement
covers [props] Plot.tooltipOffset
covers [props] Line.hoverDotClass
covers [props] Line.hoverDotStyle
covers [props] Area.hoverDotClass
covers [props] Area.hoverDotStyle
covers [props] Plot.interactive
covers [events] Plot.onHoverChange

# The app's design tokens: shadcn's `neutral` with the app's theme over it.
tap id:case-base-token
expect token up #138a5aff
expect token primary #171717ff
expect token radius-card 14
style id:up backgroundColor #138a5aff
style id:primary backgroundColor #17171780
covers [nsh] token

# base-resizable
tap id:case-base-resizable
# The sidebar starts at its `size`, the main panel takes the rest; the
# hidden aside takes no space. One handle, on the boundary: a band 9
# thick, the line in its middle, `adjustable` and named by the app; its
# indicator is the app's (half opaque, red) and holds what `renderHandle`
# draws, told the handle's index, axis and state.
expect Grip 0 horizontal idle
expect Reported: none
frame id:sidebar 0 0 150 100
frame id:main 150 0 250 100
frame id:aside 0 0 0 0
prop label:Resize role adjustable
frame label:Resize 146 0 9 100
style parent:parent:parent:text:"Grip 0 horizontal idle" backgroundColor #ff0000ff
style parent:parent:parent:text:"Grip 0 horizontal idle" opacity 0.5
# A drag moves the boundary with the finger; nothing is reported until
# it ends, then the group's sizes and the sidebar's, which the app
# passes back.
touch start label:Resize 4 50
expect Grip 0 horizontal pressed
touch move 34 50
expect Grip 0 horizontal dragging
frame id:sidebar 0 0 180 100
frame id:main 180 0 220 100
expect Reported: none
touch end
expect Grip 0 horizontal idle
expect Reported: 180, 220
expect Sidebar: 180
frame label:Resize 176 0 9 100
# Past its `maxSize` the sidebar stops at 200, below its `minSize` at
# 120.
touch start label:Resize 4 50
touch move 64 50
touch end
expect Reported: 200, 200
expect Sidebar: 200
frame id:main 200 0 200 100
touch start label:Resize 4 50
touch move -146 50
touch end
expect Reported: 120, 280
expect Sidebar: 120
frame id:sidebar 0 0 120 100
# The aside shown (`visible`) takes its 100 from the main panel, which
# has no size of its own. A second handle comes.
tap id:aside-toggle
frame id:aside 300 0 100 100
frame id:main 120 0 180 100
expect Grip 1 horizontal idle
# The group 100 wider: the panels with a size keep it, the main panel
# takes the rest.
tap id:widen
frame id:sidebar 0 0 120 100
frame id:main 120 0 280 100
frame id:aside 400 0 100 100
# Too narrow for them all (300): the sidebar (120) and the main panel
# (100) are at their minimums, and the aside, `fixed`, does not shrink
# either (it would to 80). The minimums together (280) fit, so none
# yields.
tap id:narrow
frame id:sidebar 0 0 120 100
frame id:main 120 0 100 100
frame id:aside 220 0 100 100
# Hidden again, it gives its space back to the main panel.
tap id:aside-toggle
frame id:sidebar 0 0 120 100
frame id:main 120 0 180 100
absent Grip 1 horizontal idle
# None of that was a drag: nothing was reported.
expect Reported: 120, 280
# `disabled`: a drag does nothing.
tap id:lock
touch start label:Resize 4 50
touch move 54 50
touch end
frame id:sidebar 0 0 120 100
expect Reported: 120, 280
# A screen reader's increment does nothing while it is disabled either;
# once enabled, it moves the handle by 10, reported as a drag's end.
event label:Resize onAccessibilityAction actionName=increment
frame id:sidebar 0 0 120 100
tap id:lock
event label:Resize onAccessibilityAction actionName=increment
expect Reported: 130, 170
expect Sidebar: 130
frame id:sidebar 0 0 130 100
frame id:main 130 0 170 100
# A vertical group stacks its panels, sharing the height.
frame id:top 0 0 100 100
frame id:bottom 0 100 100 100
covers [nsh/base] ResizablePanelGroup
covers [nsh/base] ResizablePanel
covers [props] ResizablePanelGroup.axis
covers [props] disabled
covers [props] ResizablePanelGroup.handleClass
covers [props] ResizablePanelGroup.handleStyle
covers [props] ResizablePanelGroup.handleAccessibilityLabel
covers [props] ResizablePanelGroup.renderHandle
covers [events] ResizablePanelGroup.onResize
covers [props] ResizablePanel.size
covers [props] ResizablePanel.minSize
covers [props] ResizablePanel.maxSize
covers [props] ResizablePanel.fixed
covers [props] ResizablePanel.visible
covers [events] ResizablePanel.onResize
# base-dock
tap id:case-base-dock
# 390 wide: compact. One group at a time, its tab bar listing every
# center panel; the other group's panels are placed, hidden.
expect Tab a*
expect Tab b
expect Tab c
expect Panel a
# A tab of the other group shows that group.
tap text:Tab c
expect Tab c*
expect Panel c
expect Reported none
# The left dock is open in the layout, but nothing is presented at
# first (reported against the layout's `open`); the ref presents its
# drawer, and the layout stays as it is.
expect Docks: left false
tap id:open-files
expect Docks: left false, left true
expect Panel files
expect Reported none
style parent:parent:parent:parent:parent:parent:parent:parent:id:panel-files backgroundColor #ffffffff
style below:parent:parent:parent:parent:parent:parent:parent:parent:id:panel-files backgroundColor #00000066
# A press on its backdrop dismisses it.
tap below:parent:parent:parent:parent:parent:parent:parent:parent:id:panel-files
expect Docks: left false, left true, left false
expect Reported none
covers [props] DockArea.defaultLayout
covers [props] DockArea.drawerClass
covers [props] DockArea.backdropClass
covers [events] DockArea.onDockOpenChange

# base-toast
tap id:case-base-toast
expect Log: none
expect Count: 0 0 true false
# Pushed: starting (its first frame with the starting class), then
# present after the motion's 100 ms.
tap text:PushA
expect Log: pushed a replaced none
style parent:text:TitleA opacity 0.5
expect Log: presented a
style parent:text:TitleA backgroundColor #123456ff
nostyle parent:text:TitleA opacity
# Its timeout runs out: ending (its class), removed after the exit's 100 ms.
tap text:PushB
expect TitleB
expect Log: ending b
style parent:text:TitleB opacity 0.25
expect Log: removed b
absent TitleB
# A third with a limit of two; then the same id replaces the oldest and
# becomes the newest.
tap text:PushC
expect TitleC
tap text:ReplaceA
expect Log: pushed a replaced TitleA
expect TitleA2
wait 300
tap text:Inspect
expect Log: entries c=present,a=present get TitleA2
# Collapsed and anchored to the bottom: the stack is the front toast plus a
# peek (30 + 10), each toast's item ends at its bottom.
frame parent:parent:parent:text:TitleC 0 0 200 40
frame parent:parent:text:TitleC 0 10 200 30
frame parent:parent:text:TitleA2 0 10 200 30
# Anchored to the top, the newest stays at the top as the stack expands.
tap text:AnchorTop
frame parent:parent:text:TitleA2 0 0 200 30
# A finger on the stack expands it; a slow swipe of 45 points to the left
# (past 40, short of the default 50) asks to dismiss the toast, with the
# swiping class meanwhile.
touch start text:TitleA2 150 5
expect Count: 2 2 false true
touch move 143 5
touch move 136 5
style parent:text:TitleA2 backgroundColor #00ff00ff
touch move 129 5
touch move 122 5
touch move 115 5
touch move 108 5
touch move 105 5
touch end
expect Log: swiped a
expect Count: 1 1 false false
# dismissAll ends the rest.
tap text:DismissAll
expect Log: dismissed c
expect Count: 0 0 true false
covers [nsh/base] Toast
covers [nsh/base] ToastStack
covers [nsh/base] ToastManager
covers [props] Toast.transitionStatus
covers [props] Toast.startingClass
covers [props] Toast.endingClass
covers [props] Toast.swipingClass
covers [props] Toast.swipeDirection
covers [props] Toast.swipeThreshold
covers [events] Toast.onDismissRequest
covers [props] ToastStack.motion
covers [props] ToastStack.placement
covers [events] ToastStack.onExpandedChange

# base-editor
tap id:case-base-editor
# The text is the editor's own, drawn a row per line; nothing is
# reported before an edit.
expect let x = 1;
expect Value: none
# The mouse resting over `let` reports a hover there once its delay
# runs out: the character, its word and no diagnostic; leaving the text
# ends it.
event text:"let x = 1;" onHoverMove position.x=1 position.y=5
wait 200
expect Hover: 0:0 0-3 0
event text:"let x = 1;" onHoverOut
expect Hover: ended
# The ref puts the caret at the line's end, reported with every
# selection (the primary last).
tap id:to-end
expect Caret: 0:10
# Typed through the ref: the whole new text is reported, the caret
# after what was typed, and there is an edit to take back.
tap id:insert
expect Value: let x = 1; // ok
expect Caret: 0:16
expect History: Undo true Redo false
# Undo takes it back, redo does it again.
tap id:undo
expect Value: let x = 1;
expect History: Undo false Redo true
tap id:redo
expect Value: let x = 1; // ok
expect History: Undo true Redo false
# Edits by range, as a language server's: all in one step, which one
# undo takes back.
tap id:edit
expect Value: const x = 10; // ok
tap id:undo
expect Value: let x = 1; // ok
# A search finds its matches (case-sensitive: one `x`), and replacing
# them all is one edit reporting how many.
tap id:find
expect Found: 1
tap id:replace-all
expect Read: replaced 1
expect Value: let y = 1; // ok
expect Found: 0
# The ref reads the text and the selections back.
tap id:read
expect Read: let y = 1; // ok (1 selection)
# Where the caret is (a row tall), the lines in view, the scrolling, and
# offsets as UTF-16 code units of the text.
tap id:geometry
expect Read: caret 20 lines 0-0 scroll 0 offset 4 0:4
# The primary selection's text, and going to a line puts the caret at
# its start.
tap id:select-word
expect Read: selected y
expect Caret: 0:5
tap id:go-to-line
expect Caret: 0:0
# A command changes the case of the selection, which stays selected,
# and another reports it for the app's find bar.
tap id:upper
expect Value: LET y = 1; // ok
tap id:ask
expect Asked: LET
# The second editor's tab shows as an arrow, and the editor is as tall
# as its rows: one line takes the two rows of `minRows` (20 points
# each), five lines the three of `maxRows`, past which they scroll.
expect →
frame id:grow 0 0 300 40
tap id:grow-more
wait 100
frame id:grow 0 0 300 60
# The app's fold ranges (the outer one shorter than the indentation's):
# level 2 folds the inner block alone.
tap id:fold-level
expect Folded: 1-2
# Folding the outer block recursively keeps the inner one folded, so
# unfolding the outer block alone leaves it folded.
tap id:fold-recursive
expect Folded: 0-2 1-2
tap id:unfold
expect Folded: 1-2
tap id:fold-recursive
expect Folded: 0-2 1-2
tap id:unfold-recursive
expect Folded: -
# Any lines fold by hand, wherever folds start.
tap id:fold-range
expect Folded: 2-4
# The app sets the folds and reads them back.
tap id:set-folds
expect Folded: 0-4
expect Read: folds 0-4
# The secondary button over the text: where in the text, from a pointer
# (no touch selection).
expect Menu: none
event text:"LET y = 1; // ok" onContextMenu
expect Menu: 0:0 pointer
# The fourth editor completes. Typing a word asks the app once, and its
# answer shows as a list that typing on filters.
event label:Complete onFocus
type label:Complete pr
expect Requested: 1 p typing
expect private
type label:Complete in
absent private
expect print
# Pressing an item puts it in place of what was typed.
tap text:println
expect Completed: println
expect Accepted: println
# A trigger character asks with an empty query: every item shows.
type label:Complete .
expect Requested: 2 - character
tap text:private
expect Completed: println.private
expect Accepted: private
# A pause in typing asks for an inline completion, shown at the caret.
type label:Complete (
expect Inlined: 1
expect value
# The fifth editor's diagnostics: the unnecessary code in its part's
# style, the deprecated code (also a warning) struck through and underlined.
style span:unused color #00ff00ff
style span:legacy textDecorationLine underline line-through
# Resting over the unnecessary code reports its diagnostic with its tags,
# its code's page and the place related to it.
event text:"unused = legacy();" onHoverMove position.x=1 position.y=5
wait 200
expect Tagged: never read unnecessary https://eslint.org/docs/latest/rules/no-unused-vars declared here file:///lib.ts 2
# The sixth editor goes to definitions. Without Mod the pointer asks
# nothing; with Mod (Command here) held over `goto` it asks at once, and
# the app's answer underlines the word.
event text:"goto target();" onHoverMove position.x=1 position.y=5
wait 50
expect Defined: none
event text:"goto target();" onHoverMove position.x=1 position.y=5 modifiers.platform=true
expect Defined: 1 hover 0 0-4
# Moving inside the word asks nothing more; hiding the link and holding
# Mod over the word again asks again.
event text:"goto target();" onHoverMove position.x=2 position.y=6 modifiers.platform=true
wait 50
expect Defined: 1 hover 0 0-4
tap id:hide-link
event text:"goto target();" onHoverMove position.x=2 position.y=6 modifiers.platform=true
expect Defined: 2 hover 0 0-4
# The command asks at the caret, inside `target`.
tap id:go-to-definition
expect Defined: 3 key 7 5-11
# Code actions at the selection, start first, with the caret's box.
tap id:code-actions
expect Acted: 5-11 5 placed
covers [nsh/base] Editor
covers [events] Editor.onHover
covers [events] Editor.onHoverEnd
covers [props] Editor.search
covers [events] Editor.onSearchChange
covers [events] Editor.onFindWithSelection
covers [props] Editor.minRows
covers [props] Editor.maxRows
covers [props] Editor.showWhitespaces
covers [props] Editor.foldRanges
covers [events] Editor.onFoldChange
covers [props] Editor.completion
covers [props] Editor.completionTriggerCharacters
covers [events] Editor.onCompletionRequest
covers [events] Editor.onCompletionAccept
covers [events] Editor.onInlineCompletionRequest
covers [props] Editor.diagnostics
covers [props] Editor.styles
covers [events] Editor.onDefinitionRequest
covers [events] Editor.onCodeActionRequest

# base-diff
tap id:case-base-diff
# Two changes, one line each, with a line of context around them: the
# two unchanged lines between them collapse into the app's separator,
# told its gap (between changes 0 and 1) and how many lines it hides.
expect Hidden 1 2
expect Revert 0
expect Keep 1
tap id:inspect
expect Read: +2 -2 2 [0@1:1>1:1 1@6:1>6:1]
# The ref expands the gap: its lines show, the separator goes.
tap id:expand
absent Hidden 1 2
# The app's annotation shows under new line 1.
expect Note n1
# The ref goes to the first change below the top, then on from it.
tap id:next
expect Hunk: 0
tap id:next
expect Hunk: 1
tap id:current
expect Hunk: current 1
# Revert puts the change's old line back in the new text, reported with
# both texts, which the app sets: one change is left.
tap id:revert-0
expect New: a b c d e f G h
expect Old: a b c d e f g h
tap id:inspect
expect Read: +1 -1 1 [0@6:1>6:1]
# Keep makes the change's new line the old text's: nothing differs.
tap id:keep-0
expect Old: a b c d e f G h
tap id:inspect
expect Read: +0 -0 0 []
# A patch shows its hunk under its header.
expect Header fn main
# With softWrap the patch's long line wraps: no text holds all of it.
absent alpha beta gamma delta epsilon zeta eta theta iota kappa lambda mu nu xi omicron pi rho sigma tau upsilon phi chi psi omega
# A press on its line's number (42) selects the line and presses the
# number; the app's add control shows at the selection's end.
tap text:42
expect Selection: new 0-0
expect Pressed: new 0
expect Add new 0-0
# The ref clears and sets the selection (to the side's lines) and reads
# its text.
tap id:select
expect Selected: true new 0-0 alpha
# A patch of two files: each under the app's header, told the file.
expect File 0 a.ts modified +1 -1
expect File 1 b.ts added +1 -0
expect BETA
# The files, and their changes counted across them.
tap id:files
expect Files: a.ts@0 b.ts@1 hunks 0:0 1:1
# The app's header collapses its file through the ref, reported.
tap id:file-0
expect Toggled: a.ts true
expect File 0 a.ts modified +1 -1 collapsed
absent BETA
tap id:file-0
expect Toggled: a.ts false
expect BETA
covers [props] DiffView.softWrap
covers [props] DiffView.annotations
covers [props] DiffView.renderAnnotation
covers [props] DiffView.renderAddAnnotation
covers [events] DiffView.onLineSelectionChange
covers [events] DiffView.onLineNumberPress
covers [nsh/base] DiffView
covers [props] DiffView.oldValue
covers [props] DiffView.newValue
covers [props] DiffView.patch
covers [props] DiffView.contextLines
covers [props] DiffView.renderSeparator
covers [props] DiffView.renderHunkActions
covers [props] DiffView.renderFileHeader
covers [events] DiffView.onFileToggle

# base-data-table
tap id:case-base-data-table
# The app draws each cell with the text the table would show: rows 0, 3
# and 4 are not among the rows the app holds, rows 1 and 2 are its two.
expect Cell 1:name fig
expect Cell 2:name pear
expect Cell 2:qty 3
# The rows in view are reported: all five, the last two empty.
expect Visible: 0-5
# A press on a cell selects it, reported.
tap text:Cell 1:name fig
expect Selected: row 1 column 0
expect Pressed: 1:0 1
# Screen readers get a grid named by the app.
prop label:Fruits role grid
# The ref sets the selection without reporting it, and reads it with the
# rows in view.
tap id:select
expect Selected: row 1 column 0
tap id:read
expect Read: 2:1 0-5
tap id:scroll
# A sortable column's sort button reports the next sort, which the app
# sets (`sort`): from it, the next press goes on to descending, then
# clears it.
tap id:sort:name
expect Sorted: name ascending
tap id:sort:name
expect Sorted: name descending
tap id:sort:name
expect Sorted: cleared
covers [events] DataTable.onRowPress
covers [nsh/base] DataTable
covers [props] DataTable.columns
covers [props] DataTable.data
covers [props] DataTable.rowCount
covers [props] DataTable.dataStart
covers [props] DataTable.cellSelectable
covers [props] DataTable.sort
covers [props] DataTable.renderCell
covers [events] DataTable.onSortChange
covers [events] DataTable.onVisibleRowsChange
