@astryxdesign/core 0.7.6-canary.f52dbbe → 0.7.6-canary.f67d5fa

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (887) hide show
  1. package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
  2. package/dist/ComplexSelector/ComplexSelector.js +8 -2
  3. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  4. package/dist/DateRangeInput/DateRangeInput.js +8 -4
  5. package/dist/FieldStatus/FieldStatus.d.ts +3 -0
  6. package/dist/FieldStatus/FieldStatus.d.ts.map +1 -1
  7. package/dist/FieldStatus/FieldStatus.js +1 -0
  8. package/dist/FileInput/FileInput.d.ts +3 -0
  9. package/dist/FileInput/FileInput.d.ts.map +1 -1
  10. package/dist/FileInput/FileInput.js +27 -12
  11. package/dist/Link/Link.d.ts.map +1 -1
  12. package/dist/Link/Link.js +10 -1
  13. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  14. package/dist/MultiSelector/MultiSelector.js +19 -9
  15. package/dist/Selector/Selector.d.ts.map +1 -1
  16. package/dist/Selector/Selector.js +9 -6
  17. package/dist/Selector/hooks.js +2 -2
  18. package/dist/Token/Token.d.ts +1 -1
  19. package/dist/Token/Token.d.ts.map +1 -1
  20. package/dist/Token/Token.js +15 -0
  21. package/dist/Typeahead/Typeahead.d.ts.map +1 -1
  22. package/dist/Typeahead/Typeahead.js +20 -9
  23. package/dist/astryx.css +1 -1
  24. package/dist/i18n/useListFormatter.d.ts +9 -0
  25. package/dist/i18n/useListFormatter.d.ts.map +1 -0
  26. package/dist/i18n/useListFormatter.js +41 -0
  27. package/package.json +18 -5
  28. package/src/Chat/ChatMessage.spec.md +64 -65
  29. package/src/Citation/Citation.spec.md +60 -47
  30. package/src/ComplexSelector/ComplexSelector.tsx +19 -2
  31. package/src/DateRangeInput/DateRangeInput.tsx +18 -3
  32. package/src/FieldStatus/FieldStatus.tsx +7 -0
  33. package/src/FileInput/FileInput.tsx +30 -5
  34. package/src/Lightbox/Lightbox.spec.md +249 -61
  35. package/src/Link/Link.tsx +9 -2
  36. package/src/MultiSelector/MultiSelector.spec.md +121 -114
  37. package/src/MultiSelector/MultiSelector.tsx +19 -5
  38. package/src/ProgressBar/ProgressBar.spec.md +143 -119
  39. package/src/Selector/Selector.tsx +16 -6
  40. package/src/Selector/hooks.ts +2 -2
  41. package/src/Token/Token.spec.md +24 -23
  42. package/src/Token/Token.tsx +15 -0
  43. package/src/Typeahead/Typeahead.tsx +30 -9
  44. package/src/i18n/useListFormatter.ts +42 -0
  45. package/dist/Item/ItemMarkerColumns.a11y.chromium.spec.d.ts +0 -2
  46. package/dist/Item/ItemMarkerColumns.a11y.chromium.spec.js +0 -132
  47. package/dist/Lightbox/LightboxKeyboard.a11y.browser.spec.d.ts +0 -2
  48. package/dist/Lightbox/LightboxKeyboard.a11y.browser.spec.js +0 -78
  49. package/dist/Lightbox/LightboxViewport.a11y.browser.spec.d.ts +0 -2
  50. package/dist/Lightbox/LightboxViewport.a11y.browser.spec.js +0 -66
  51. package/dist/List/ListMarkers.a11y.chromium.spec.d.ts +0 -2
  52. package/dist/List/ListMarkers.a11y.chromium.spec.js +0 -677
  53. package/dist/Markdown/Markdown.fr23.bench.d.ts +0 -2
  54. package/dist/Markdown/Markdown.fr23.bench.d.ts.map +0 -1
  55. package/dist/Markdown/Markdown.fr23.bench.js +0 -152
  56. package/dist/Markdown/MarkdownTable.a11y.chromium.spec.d.ts +0 -2
  57. package/dist/Markdown/MarkdownTable.a11y.chromium.spec.js +0 -590
  58. package/dist/Markdown/plugins/headingLinks.a11y.chromium.spec.d.ts +0 -2
  59. package/dist/Markdown/plugins/headingLinks.a11y.chromium.spec.js +0 -657
  60. package/dist/MobileNav/MobileNavPinchZoom.a11y.chromium.spec.d.ts +0 -2
  61. package/dist/MobileNav/MobileNavPinchZoom.a11y.chromium.spec.js +0 -153
  62. package/dist/MultiSelector/CreateRow.a11y.chromium.spec.d.ts +0 -2
  63. package/dist/MultiSelector/CreateRow.a11y.chromium.spec.js +0 -117
  64. package/dist/MultiSelector/MultiSelector.disabledFocus.a11y.chromium.spec.d.ts +0 -2
  65. package/dist/MultiSelector/MultiSelector.disabledFocus.a11y.chromium.spec.js +0 -49
  66. package/dist/MultiSelector/OptionActions.a11y.chromium.spec.d.ts +0 -2
  67. package/dist/MultiSelector/OptionActions.a11y.chromium.spec.js +0 -273
  68. package/dist/Outline/OutlineFocus.a11y.chromium.spec.d.ts +0 -2
  69. package/dist/Outline/OutlineFocus.a11y.chromium.spec.js +0 -421
  70. package/dist/Typeahead/BaseTypeahead.emptyState.a11y.chromium.spec.d.ts +0 -2
  71. package/dist/Typeahead/BaseTypeahead.emptyState.a11y.chromium.spec.js +0 -118
  72. package/dist/reset.a11y.chromium.spec.d.ts +0 -2
  73. package/dist/reset.a11y.chromium.spec.js +0 -231
  74. package/dist/reset.until-found.a11y.browser.spec.d.ts +0 -2
  75. package/dist/reset.until-found.a11y.browser.spec.js +0 -72
  76. package/src/AlertDialog/AlertDialog.test.tsx +0 -519
  77. package/src/AlertDialog/__tests__/AlertDialog.a11y.chromium.spec.ts +0 -128
  78. package/src/AlertDialog/__tests__/AlertDialog.a11y.renders.tsx +0 -52
  79. package/src/AlertDialog/__tests__/AlertDialog.a11y.states.ts +0 -68
  80. package/src/AlertDialog/__tests__/AlertDialog.a11y.test.tsx +0 -144
  81. package/src/AlertDialog/useImperativeAlertDialog.test.tsx +0 -163
  82. package/src/AppShell/AppShell.test.tsx +0 -1310
  83. package/src/AppShell/__tests__/AppShellMainKeyboard.a11y.browser.spec.ts +0 -534
  84. package/src/AppShell/observeMainOverflow.test.ts +0 -173
  85. package/src/AspectRatio/AspectRatio.test.tsx +0 -398
  86. package/src/AspectRatio/__tests__/AspectRatioFitLayers.a11y.browser.spec.ts +0 -158
  87. package/src/Avatar/Avatar.test.tsx +0 -867
  88. package/src/Avatar/AvatarStatusDot.test.tsx +0 -306
  89. package/src/Avatar/__tests__/AvatarForcedColors.a11y.chromium.spec.ts +0 -126
  90. package/src/AvatarGroup/AvatarGroup.test.tsx +0 -313
  91. package/src/AvatarGroup/AvatarGroupOverflow.test.tsx +0 -263
  92. package/src/Badge/Badge.test.tsx +0 -125
  93. package/src/Badge/__tests__/BadgeLargerText.a11y.chromium.spec.ts +0 -260
  94. package/src/Banner/Banner.test.tsx +0 -879
  95. package/src/Blockquote/Blockquote.test.tsx +0 -121
  96. package/src/BottomSheet/BottomSheet.test.tsx +0 -3154
  97. package/src/BottomSheet/BottomSheetEdgeTint.test.tsx +0 -248
  98. package/src/BottomSheet/BottomSheetKeyboard.test.tsx +0 -195
  99. package/src/BottomSheet/BottomSheetPanel.test.tsx +0 -549
  100. package/src/BottomSheet/BottomSheetReplace.test.tsx +0 -1638
  101. package/src/BottomSheet/BottomSheetStacking.announce.test.tsx +0 -246
  102. package/src/BottomSheet/BottomSheetStacking.test.tsx +0 -3184
  103. package/src/BottomSheet/BottomSheetSwitcher.test.tsx +0 -2010
  104. package/src/BottomSheet/__tests__/BottomSheetDrag.a11y.chromium.spec.ts +0 -129
  105. package/src/BottomSheet/__tests__/BottomSheetFocusReturn.a11y.browser.spec.ts +0 -97
  106. package/src/BottomSheet/__tests__/BottomSheetKeyboard.a11y.browser.spec.ts +0 -415
  107. package/src/BottomSheet/__tests__/BottomSheetNativeModal.a11y.browser.spec.ts +0 -462
  108. package/src/BottomSheet/__tests__/BottomSheetPinchZoom.a11y.chromium.spec.ts +0 -247
  109. package/src/BottomSheet/__tests__/BottomSheetToastOutlet.a11y.chromium.spec.ts +0 -83
  110. package/src/BottomSheet/snapOffsets.test.ts +0 -254
  111. package/src/BottomSheet/useSheetGestures.test.ts +0 -1690
  112. package/src/Breadcrumbs/Breadcrumbs.test.tsx +0 -921
  113. package/src/Breadcrumbs/__tests__/Breadcrumbs.a11y.chromium.spec.ts +0 -132
  114. package/src/Breadcrumbs/__tests__/Breadcrumbs.a11y.renders.tsx +0 -45
  115. package/src/Breadcrumbs/__tests__/Breadcrumbs.a11y.states.ts +0 -81
  116. package/src/Breadcrumbs/__tests__/Breadcrumbs.a11y.test.tsx +0 -125
  117. package/src/Button/Button.test.tsx +0 -864
  118. package/src/Button/__tests__/Button.a11y.chromium.spec.ts +0 -355
  119. package/src/Button/__tests__/Button.a11y.harnesses.tsx +0 -47
  120. package/src/Button/__tests__/Button.a11y.known-failures.ts +0 -25
  121. package/src/Button/__tests__/Button.a11y.renders.tsx +0 -139
  122. package/src/Button/__tests__/Button.a11y.states.ts +0 -317
  123. package/src/Button/__tests__/Button.a11y.test.tsx +0 -187
  124. package/src/ButtonGroup/ButtonGroup.test.tsx +0 -1025
  125. package/src/Calendar/Calendar.test.tsx +0 -2186
  126. package/src/Calendar/__tests__/Calendar.a11y.chromium.spec.ts +0 -141
  127. package/src/Calendar/__tests__/Calendar.a11y.known-failures.ts +0 -12
  128. package/src/Calendar/__tests__/Calendar.a11y.renders.tsx +0 -57
  129. package/src/Calendar/__tests__/Calendar.a11y.states.ts +0 -139
  130. package/src/Calendar/__tests__/Calendar.a11y.test.tsx +0 -137
  131. package/src/Calendar/dayCellUtils.test.ts +0 -520
  132. package/src/Calendar/getInitialFocusDate.test.ts +0 -94
  133. package/src/Calendar/getStandaloneShortWeekdayNames.test.ts +0 -54
  134. package/src/Calendar/hooks/useCalendarConstraints.test.ts +0 -153
  135. package/src/Calendar/hooks/useCalendarDays.test.ts +0 -265
  136. package/src/Calendar/hooks/useCalendarNavigation.test.ts +0 -252
  137. package/src/Card/Card.test.tsx +0 -202
  138. package/src/Carousel/Carousel.test.tsx +0 -1036
  139. package/src/Carousel/__tests__/Carousel.a11y.chromium.spec.ts +0 -207
  140. package/src/Carousel/__tests__/Carousel.a11y.known-failures.ts +0 -12
  141. package/src/Carousel/__tests__/Carousel.a11y.renders.tsx +0 -39
  142. package/src/Carousel/__tests__/Carousel.a11y.states.ts +0 -139
  143. package/src/Carousel/__tests__/Carousel.a11y.test.tsx +0 -127
  144. package/src/Center/Center.test.tsx +0 -406
  145. package/src/Chat/ChatComposer.test.tsx +0 -364
  146. package/src/Chat/ChatComposerDrawer.test.tsx +0 -130
  147. package/src/Chat/ChatComposerInput.test.tsx +0 -2136
  148. package/src/Chat/ChatComposerTokenElement.test.tsx +0 -85
  149. package/src/Chat/ChatDictationButton.test.tsx +0 -108
  150. package/src/Chat/ChatLayout.test.tsx +0 -311
  151. package/src/Chat/ChatLayoutScrollButton.test.tsx +0 -99
  152. package/src/Chat/ChatMessage.test.tsx +0 -344
  153. package/src/Chat/ChatMessageBubble.test.tsx +0 -294
  154. package/src/Chat/ChatMessageList.test.tsx +0 -198
  155. package/src/Chat/ChatMessageMetadata.test.tsx +0 -66
  156. package/src/Chat/ChatPastedTextToken.test.tsx +0 -71
  157. package/src/Chat/ChatSendButton.test.tsx +0 -309
  158. package/src/Chat/ChatSystemMessage.test.tsx +0 -189
  159. package/src/Chat/ChatTokenizedText.test.tsx +0 -163
  160. package/src/Chat/ChatToolCalls.test.tsx +0 -413
  161. package/src/Chat/__tests__/ChatLayoutScrollButton.a11y.chromium.spec.ts +0 -577
  162. package/src/Chat/__tests__/ChatLayoutScrollButtonSurface.a11y.chromium.spec.ts +0 -740
  163. package/src/Chat/__tests__/ChatMessageBubble.a11y.chromium.spec.ts +0 -457
  164. package/src/Chat/__tests__/ChatMessageBubble.auditProvenance.test.ts +0 -91
  165. package/src/Chat/__tests__/ChatMessageBubble.auditProvenance.ts +0 -91
  166. package/src/Chat/__tests__/ChatMessageList.a11y.chromium.spec.ts +0 -465
  167. package/src/Chat/__tests__/ChatMessageMetadata.a11y.chromium.spec.ts +0 -416
  168. package/src/Chat/__tests__/ChatSendButton.a11y.chromium.spec.ts +0 -1523
  169. package/src/Chat/__tests__/ChatSystemMessage.a11y.chromium.spec.ts +0 -1004
  170. package/src/Chat/__tests__/ChatTokenizedText.a11y.chromium.spec.ts +0 -574
  171. package/src/Chat/__tests__/useChatStreamScroll.a11y.chromium.spec.ts +0 -263
  172. package/src/Chat/__tests__/useChatStreamScroll.fixture.tsx +0 -61
  173. package/src/Chat/useChatDictation.test.tsx +0 -369
  174. package/src/Chat/useChatNewMessages.test.tsx +0 -209
  175. package/src/Chat/useChatPasteAsToken.test.ts +0 -191
  176. package/src/Chat/useChatStreamScroll.test.tsx +0 -381
  177. package/src/Chat/useTriggerMenu.test.tsx +0 -994
  178. package/src/CheckboxInput/CheckboxInput.test.tsx +0 -926
  179. package/src/CheckboxInput/__tests__/Checkbox.a11y.chromium.spec.ts +0 -354
  180. package/src/CheckboxInput/__tests__/Checkbox.a11y.known-failures.ts +0 -54
  181. package/src/CheckboxInput/__tests__/Checkbox.a11y.renders.tsx +0 -310
  182. package/src/CheckboxInput/__tests__/Checkbox.a11y.states.ts +0 -404
  183. package/src/CheckboxInput/__tests__/Checkbox.a11y.test.tsx +0 -128
  184. package/src/CheckboxList/CheckboxList.fr10.test.tsx +0 -51
  185. package/src/CheckboxList/CheckboxList.test.tsx +0 -1159
  186. package/src/CheckboxList/__tests__/CheckboxList.a11y.chromium.spec.ts +0 -1998
  187. package/src/Citation/Citation.test.tsx +0 -361
  188. package/src/ClickableCard/ClickableCard.test.tsx +0 -382
  189. package/src/Code/Code.test.tsx +0 -75
  190. package/src/CodeBlock/CodeBlock.highlightStyles.test.tsx +0 -87
  191. package/src/CodeBlock/CodeBlock.test.tsx +0 -752
  192. package/src/CodeBlock/CodeLanguage.test.tsx +0 -173
  193. package/src/CodeBlock/__tests__/CodeBlockCopyClearance.a11y.chromium.spec.ts +0 -325
  194. package/src/CodeBlock/__tests__/CodeBlockLanguages.a11y.chromium.spec.ts +0 -64
  195. package/src/CodeBlock/__tests__/CodeBlockOverflowStop.a11y.chromium.spec.ts +0 -126
  196. package/src/CodeBlock/__tests__/DiffRowPaint.a11y.chromium.spec.ts +0 -94
  197. package/src/CodeBlock/highlightRanges.test.ts +0 -253
  198. package/src/CodeBlock/languages.test.ts +0 -402
  199. package/src/CodeBlock/tokenizer.test.ts +0 -280
  200. package/src/CodeBlock/tokenizerBudget.test.ts +0 -112
  201. package/src/Collapsible/Collapsible.test.tsx +0 -620
  202. package/src/Collapsible/CollapsibleGroup.test.tsx +0 -656
  203. package/src/Collapsible/__tests__/Collapsible.a11y.chromium.spec.ts +0 -194
  204. package/src/Collapsible/__tests__/Collapsible.a11y.states.ts +0 -117
  205. package/src/Collapsible/__tests__/Collapsible.a11y.test.tsx +0 -140
  206. package/src/Collapsible/__tests__/CollapsibleGroup.a11y.chromium.spec.ts +0 -559
  207. package/src/Collapsible/__tests__/CollapsibleGroup.a11y.renders.tsx +0 -45
  208. package/src/Collapsible/__tests__/CollapsibleGroup.a11y.states.ts +0 -139
  209. package/src/Collapsible/__tests__/CollapsibleGroup.a11y.test.tsx +0 -135
  210. package/src/Collapsible/__tests__/CollapsibleGroupAccordion.a11y.chromium.spec.ts +0 -141
  211. package/src/Collapsible/useCollapsible.test.tsx +0 -236
  212. package/src/CommandPalette/CommandPalette.perf.test.ts +0 -100
  213. package/src/CommandPalette/CommandPalette.test.tsx +0 -750
  214. package/src/CommandPalette/CommandPaletteEmpty.test.tsx +0 -101
  215. package/src/CommandPalette/CommandPaletteFooter.test.tsx +0 -122
  216. package/src/CommandPalette/CommandPaletteGroup.test.tsx +0 -129
  217. package/src/CommandPalette/CommandPaletteInput.test.tsx +0 -539
  218. package/src/CommandPalette/CommandPaletteItem.test.tsx +0 -417
  219. package/src/CommandPalette/CommandPaletteList.test.tsx +0 -184
  220. package/src/CommandPalette/__tests__/CommandPalette.a11y.chromium.spec.ts +0 -600
  221. package/src/CommandPalette/__tests__/CommandPaletteEmptyAudit.a11y.chromium.spec.ts +0 -715
  222. package/src/CommandPalette/__tests__/CommandPaletteFooter.a11y.chromium.spec.ts +0 -582
  223. package/src/CommandPalette/__tests__/CommandPaletteInputAudit.a11y.chromium.spec.ts +0 -336
  224. package/src/CommandPalette/__tests__/CommandPaletteItemAudit.a11y.chromium.spec.ts +0 -523
  225. package/src/ComplexSelector/ComplexSelector.focusableDisabled.test.tsx +0 -76
  226. package/src/ComplexSelector/ComplexSelector.fr10.test.tsx +0 -262
  227. package/src/ComplexSelector/ComplexSelector.test.tsx +0 -1171
  228. package/src/ComplexSelector/__tests__/ComplexSelector.a11y.chromium.spec.ts +0 -965
  229. package/src/ContextMenu/ContextMenu.test.tsx +0 -1617
  230. package/src/ContextMenu/ContextMenu.triggerProps.test.tsx +0 -54
  231. package/src/ContextMenu/__tests__/ContextMenuAudit.a11y.chromium.spec.ts +0 -439
  232. package/src/ContextMenu/__tests__/ContextMenuCursorAnchor.a11y.browser.spec.ts +0 -154
  233. package/src/ContextMenu/__tests__/ContextMenuLongPress.a11y.chromium.spec.ts +0 -117
  234. package/src/DateInput/DateInput.fr10.test.tsx +0 -283
  235. package/src/DateInput/DateInput.test.tsx +0 -1571
  236. package/src/DateInput/DateInputTouch.test.tsx +0 -2948
  237. package/src/DateInput/MonthScrollerSettle.test.tsx +0 -165
  238. package/src/DateInput/NativeDateField.test.tsx +0 -741
  239. package/src/DateInput/Presentation.test.tsx +0 -121
  240. package/src/DateInput/__tests__/DateInputAudit.a11y.chromium.spec.ts +0 -577
  241. package/src/DateInput/__tests__/MonthScrollerSteps.interaction.browser.spec.ts +0 -312
  242. package/src/DateInput/__tests__/WheelPinchZoom.a11y.chromium.spec.ts +0 -171
  243. package/src/DateRangeInput/DateRangeInput.fr10.test.tsx +0 -165
  244. package/src/DateRangeInput/DateRangeInput.test.tsx +0 -1151
  245. package/src/DateTimeInput/DateTimeInput.fr10.test.tsx +0 -319
  246. package/src/DateTimeInput/DateTimeInput.test.tsx +0 -2656
  247. package/src/DateTimeInput/DateTimeInputTouch.test.tsx +0 -1071
  248. package/src/DateTimeInput/NativePickerSegments.test.tsx +0 -657
  249. package/src/DateTimeInput/Presentation.test.tsx +0 -89
  250. package/src/Dialog/Dialog.source-build.test.mjs +0 -43
  251. package/src/Dialog/Dialog.test.tsx +0 -1599
  252. package/src/Dialog/DialogHeader.test.tsx +0 -477
  253. package/src/Dialog/__tests__/Dialog.a11y.browser.spec.ts +0 -111
  254. package/src/Dialog/__tests__/Dialog.a11y.chromium.spec.ts +0 -218
  255. package/src/Dialog/__tests__/Dialog.a11y.known-failures.ts +0 -28
  256. package/src/Dialog/__tests__/Dialog.a11y.renders.tsx +0 -107
  257. package/src/Dialog/__tests__/Dialog.a11y.states.ts +0 -104
  258. package/src/Dialog/__tests__/Dialog.a11y.test.tsx +0 -102
  259. package/src/Dialog/__tests__/Dialog.reduced-motion.a11y.browser.spec.ts +0 -252
  260. package/src/Dialog/__tests__/DialogSecondDocument.a11y.browser.spec.ts +0 -202
  261. package/src/Dialog/useImperativeDialog.test.tsx +0 -78
  262. package/src/Divider/Divider.test.tsx +0 -155
  263. package/src/Drawer/Drawer.test.tsx +0 -1335
  264. package/src/Drawer/DrawerDirection.test.tsx +0 -127
  265. package/src/Drawer/DrawerHeader.test.tsx +0 -102
  266. package/src/Drawer/__tests__/DrawerAudit.a11y.chromium.spec.ts +0 -402
  267. package/src/Drawer/__tests__/DrawerHeaderAudit.a11y.chromium.spec.ts +0 -298
  268. package/src/Drawer/__tests__/DrawerNonModalPageScroll.a11y.chromium.spec.ts +0 -267
  269. package/src/DropdownMenu/DropdownMenu.ssr.test.tsx +0 -93
  270. package/src/DropdownMenu/DropdownMenu.test.tsx +0 -4498
  271. package/src/DropdownMenu/DropdownMenuContext.public.test.ts +0 -22
  272. package/src/DropdownMenu/DropdownMenuLazySheet.test.tsx +0 -126
  273. package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +0 -635
  274. package/src/DropdownMenu/DropdownMenuSheetFirstOpen.test.tsx +0 -72
  275. package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +0 -2442
  276. package/src/DropdownMenu/__tests__/DropdownMenuDividerAudit.a11y.chromium.spec.ts +0 -281
  277. package/src/DropdownMenu/__tests__/DropdownMenuDrillIn.a11y.chromium.spec.ts +0 -175
  278. package/src/DropdownMenu/__tests__/DropdownMenuFocusReturn.a11y.browser.spec.ts +0 -74
  279. package/src/DropdownMenu/__tests__/DropdownMenuGroupAudit.a11y.chromium.spec.ts +0 -335
  280. package/src/DropdownMenu/__tests__/DropdownMenuItemAudit.a11y.chromium.spec.ts +0 -367
  281. package/src/DropdownMenu/__tests__/DropdownMenuRadioGroupAudit.a11y.chromium.spec.ts +0 -328
  282. package/src/DropdownMenu/__tests__/DropdownMenuRadioItemAudit.a11y.chromium.spec.ts +0 -540
  283. package/src/DropdownMenu/__tests__/DropdownMenuSubMenuAudit.a11y.chromium.spec.ts +0 -671
  284. package/src/DropdownMenu/__tests__/Menu.a11y.chromium.spec.ts +0 -158
  285. package/src/DropdownMenu/__tests__/Menu.a11y.known-failures.ts +0 -26
  286. package/src/DropdownMenu/__tests__/Menu.a11y.renders.tsx +0 -65
  287. package/src/DropdownMenu/__tests__/Menu.a11y.states.ts +0 -138
  288. package/src/DropdownMenu/__tests__/Menu.a11y.test.tsx +0 -136
  289. package/src/DropdownMenu/__tests__/MenuButton.a11y.chromium.spec.ts +0 -149
  290. package/src/DropdownMenu/__tests__/MenuButton.a11y.known-failures.ts +0 -12
  291. package/src/DropdownMenu/__tests__/MenuButton.a11y.renders.tsx +0 -67
  292. package/src/DropdownMenu/__tests__/MenuButton.a11y.states.ts +0 -141
  293. package/src/DropdownMenu/__tests__/MenuButton.a11y.test.tsx +0 -138
  294. package/src/DropdownMenu/__tests__/MenuHeaderFooter.a11y.chromium.spec.ts +0 -231
  295. package/src/DropdownMenu/__tests__/MenuHoverFocus.a11y.chromium.spec.ts +0 -238
  296. package/src/DropdownMenu/__tests__/MenuPinchZoom.a11y.chromium.spec.ts +0 -173
  297. package/src/DropdownMenu/__tests__/MenuPress.a11y.chromium.spec.ts +0 -503
  298. package/src/DropdownMenu/__tests__/SubMenuHeaderFooter.a11y.chromium.spec.ts +0 -268
  299. package/src/DropdownMenu/__tests__/warmMenuBottomSheet.tsx +0 -43
  300. package/src/DropdownMenu/lazyMenuBottomSheet.test.ts +0 -60
  301. package/src/EmptyState/EmptyState.announce.test.tsx +0 -227
  302. package/src/EmptyState/EmptyState.test.tsx +0 -284
  303. package/src/Field/Field.test.tsx +0 -800
  304. package/src/Field/FieldLabel.test.tsx +0 -244
  305. package/src/Field/InputClearButton.test.tsx +0 -395
  306. package/src/Field/__tests__/FieldLabelAudit.a11y.chromium.spec.ts +0 -297
  307. package/src/Field/fieldInputFamily.test.tsx +0 -160
  308. package/src/FieldStatus/FieldStatus.test.tsx +0 -482
  309. package/src/FieldStatus/__tests__/StatusMessage.a11y.chromium.spec.ts +0 -206
  310. package/src/FieldStatus/__tests__/StatusMessage.a11y.known-failures.ts +0 -13
  311. package/src/FieldStatus/__tests__/StatusMessage.a11y.renders.tsx +0 -287
  312. package/src/FieldStatus/__tests__/StatusMessage.a11y.states.ts +0 -302
  313. package/src/FieldStatus/__tests__/StatusMessage.a11y.test.tsx +0 -154
  314. package/src/FileInput/FileInput.fr10.test.tsx +0 -21
  315. package/src/FileInput/FileInput.test.tsx +0 -1291
  316. package/src/FormLayout/FormLayout.test.tsx +0 -444
  317. package/src/FormLayout/__snapshots__/FormLayout.test.tsx.snap +0 -46
  318. package/src/Grid/Grid.test.tsx +0 -509
  319. package/src/HStack/HStack.test.tsx +0 -129
  320. package/src/Heading/Heading.test.tsx +0 -328
  321. package/src/HoverCard/HoverCard.test.tsx +0 -1339
  322. package/src/Icon/Icon.test.tsx +0 -990
  323. package/src/Icon/globalIconRegistry.test.tsx +0 -613
  324. package/src/IconButton/IconButton.test.tsx +0 -156
  325. package/src/Indicator/Indicator.test.tsx +0 -389
  326. package/src/Indicator/indicatorRegistry.test.tsx +0 -106
  327. package/src/InputGroup/InputGroup.test.tsx +0 -502
  328. package/src/InteractiveRoleContext/InteractiveRoleContext.test.tsx +0 -185
  329. package/src/Item/Item.source-build.test.mjs +0 -88
  330. package/src/Item/Item.swipeLayer.test.tsx +0 -74
  331. package/src/Item/Item.test.tsx +0 -1515
  332. package/src/Item/ItemMarkerColumns.a11y.chromium.spec.ts +0 -211
  333. package/src/Item/__tests__/ItemSwipeActions.a11y.chromium.spec.ts +0 -337
  334. package/src/Item/__tests__/ItemSwipePinchZoom.a11y.chromium.spec.ts +0 -186
  335. package/src/Kbd/Kbd.test.tsx +0 -299
  336. package/src/Layer/LayerProvider.test.tsx +0 -143
  337. package/src/Layer/__tests__/LayerBrowserHide.a11y.chromium.spec.ts +0 -126
  338. package/src/Layer/__tests__/LayerReducedMotion.a11y.chromium.spec.ts +0 -176
  339. package/src/Layer/__tests__/TouchTrigger.a11y.chromium.spec.ts +0 -200
  340. package/src/Layer/anchorName.test.ts +0 -146
  341. package/src/Layer/layerDismissalFamilies.test.tsx +0 -487
  342. package/src/Layer/layerDismissalInvariants.test.tsx +0 -501
  343. package/src/Layer/layerHost.test.ts +0 -117
  344. package/src/Layer/layerScopedContext.test.tsx +0 -499
  345. package/src/Layer/layerViewportInset.source-build.test.mjs +0 -90
  346. package/src/Layer/layerViewportInset.test.ts +0 -120
  347. package/src/Layer/popoverOperations.test.ts +0 -205
  348. package/src/Layer/useLayer.test.tsx +0 -2845
  349. package/src/Layer/useLayerDismissal.test.tsx +0 -580
  350. package/src/Layer/useTouchTrigger.test.ts +0 -70
  351. package/src/Layout/Layout.test.tsx +0 -375
  352. package/src/Layout/LayoutSlots.test.tsx +0 -376
  353. package/src/Layout/__tests__/Layout.a11y.chromium.spec.ts +0 -134
  354. package/src/Layout/__tests__/Layout.a11y.renders.tsx +0 -110
  355. package/src/Layout/__tests__/Layout.a11y.states.ts +0 -93
  356. package/src/Layout/__tests__/Layout.a11y.test.tsx +0 -142
  357. package/src/Layout/__tests__/childrenAsContent.test.tsx +0 -59
  358. package/src/Layout/__tests__/contentWidth.test.tsx +0 -303
  359. package/src/Layout/__tests__/edgeCompensation.test.tsx +0 -191
  360. package/src/Layout/__tests__/nestedPadding.test.tsx +0 -264
  361. package/src/Layout/overlayPaddingReset.test.tsx +0 -224
  362. package/src/Lightbox/Lightbox.test.tsx +0 -837
  363. package/src/Lightbox/LightboxKeyboard.a11y.browser.spec.ts +0 -78
  364. package/src/Lightbox/LightboxViewport.a11y.browser.spec.ts +0 -74
  365. package/src/Lightbox/useLightbox.test.tsx +0 -263
  366. package/src/Link/Link.test.tsx +0 -457
  367. package/src/Link/__tests__/Link.a11y.chromium.spec.ts +0 -153
  368. package/src/Link/__tests__/Link.a11y.renders.tsx +0 -97
  369. package/src/Link/__tests__/Link.a11y.states.ts +0 -104
  370. package/src/Link/__tests__/Link.a11y.test.tsx +0 -127
  371. package/src/Link/__tests__/Link.navigation.a11y.chromium.spec.ts +0 -262
  372. package/src/Link/__tests__/Link.truncation.a11y.chromium.spec.ts +0 -58
  373. package/src/Link/useLinkComponent.test.tsx +0 -572
  374. package/src/Link/useLinkify.test.tsx +0 -146
  375. package/src/List/List.test.tsx +0 -1052
  376. package/src/List/ListItem.source-build.test.mjs +0 -41
  377. package/src/List/ListMarkers.a11y.chromium.spec.ts +0 -904
  378. package/src/Markdown/Markdown.codeLanguages.test.tsx +0 -137
  379. package/src/Markdown/Markdown.fr23.bench.ts +0 -176
  380. package/src/Markdown/Markdown.fr23.perf.test.ts +0 -484
  381. package/src/Markdown/Markdown.helpers.perf.test.ts +0 -495
  382. package/src/Markdown/Markdown.public.test.ts +0 -309
  383. package/src/Markdown/Markdown.renderBoundary.test.tsx +0 -72
  384. package/src/Markdown/Markdown.rerender.test.tsx +0 -143
  385. package/src/Markdown/Markdown.test.tsx +0 -1383
  386. package/src/Markdown/MarkdownTable.a11y.chromium.spec.ts +0 -727
  387. package/src/Markdown/__tests__/MarkdownNesting.a11y.chromium.spec.ts +0 -102
  388. package/src/Markdown/angleDestinations.test.tsx +0 -104
  389. package/src/Markdown/ast.test.ts +0 -374
  390. package/src/Markdown/atxHeadingIndentation.test.tsx +0 -53
  391. package/src/Markdown/blockNesting.test.tsx +0 -124
  392. package/src/Markdown/blockquoteMarkers.test.tsx +0 -69
  393. package/src/Markdown/characterReferences.test.ts +0 -47
  394. package/src/Markdown/codeSpans.test.tsx +0 -65
  395. package/src/Markdown/emphasis.test.tsx +0 -214
  396. package/src/Markdown/fenceInfo.test.tsx +0 -47
  397. package/src/Markdown/imageAlt.test.tsx +0 -84
  398. package/src/Markdown/incremental.test.ts +0 -1198
  399. package/src/Markdown/incrementalIndentation.test.tsx +0 -36
  400. package/src/Markdown/indentedFences.test.tsx +0 -374
  401. package/src/Markdown/indentedTables.test.tsx +0 -53
  402. package/src/Markdown/linkBrackets.test.tsx +0 -116
  403. package/src/Markdown/linkDestinationLinear.test.tsx +0 -54
  404. package/src/Markdown/linkDestinations.test.tsx +0 -127
  405. package/src/Markdown/linkTextCode.test.tsx +0 -65
  406. package/src/Markdown/listBullets.test.tsx +0 -55
  407. package/src/Markdown/listContinuation.test.tsx +0 -94
  408. package/src/Markdown/listThematicBreak.test.tsx +0 -49
  409. package/src/Markdown/longLinkLabels.test.tsx +0 -49
  410. package/src/Markdown/openFenceFinalNewline.test.tsx +0 -85
  411. package/src/Markdown/parser.perf.test.ts +0 -398
  412. package/src/Markdown/parser.public.test.ts +0 -144
  413. package/src/Markdown/parser.test.ts +0 -1925
  414. package/src/Markdown/plugin-renderer/MarkdownPluginNodeRenderer.test.tsx +0 -300
  415. package/src/Markdown/plugins/frontmatter.test.tsx +0 -150
  416. package/src/Markdown/plugins/headingLinks.a11y.chromium.spec.ts +0 -856
  417. package/src/Markdown/plugins/headingLinks.ssr.test.tsx +0 -143
  418. package/src/Markdown/plugins/headingLinks.test.tsx +0 -637
  419. package/src/Markdown/plugins/protocol.test.tsx +0 -1083
  420. package/src/Markdown/plugins/semanticFence.test.tsx +0 -655
  421. package/src/Markdown/plugins/softBreaks.test.tsx +0 -172
  422. package/src/Markdown/plugins/sourceDecoration.test.tsx +0 -799
  423. package/src/Markdown/plugins/sourceLines.test.tsx +0 -715
  424. package/src/Markdown/plugins/textTransform.test.ts +0 -467
  425. package/src/Markdown/remark.test.tsx +0 -1877
  426. package/src/Markdown/streaming.test.ts +0 -148
  427. package/src/Markdown/streamingListShapes.test.tsx +0 -93
  428. package/src/Markdown/trailingSpaces.test.tsx +0 -45
  429. package/src/MetadataList/MetadataList.test.tsx +0 -383
  430. package/src/MobileNav/MobileNav.test.tsx +0 -296
  431. package/src/MobileNav/MobileNavCloseEdgeCases.test.tsx +0 -584
  432. package/src/MobileNav/MobileNavCloseTiming.test.ts +0 -58
  433. package/src/MobileNav/MobileNavCloseVisibility.test.tsx +0 -172
  434. package/src/MobileNav/MobileNavEntryAnimation.test.tsx +0 -212
  435. package/src/MobileNav/MobileNavFocus.test.tsx +0 -553
  436. package/src/MobileNav/MobileNavPinchZoom.a11y.chromium.spec.ts +0 -147
  437. package/src/MobileNav/MobileNavReopen.test.tsx +0 -118
  438. package/src/MobileNav/MobileNavScrollbarGutter.test.tsx +0 -109
  439. package/src/MobileNav/MobileNavToggle.test.tsx +0 -179
  440. package/src/MobileNav/__tests__/MobileNavHeader.a11y.chromium.spec.ts +0 -89
  441. package/src/MobileNav/__tests__/MobileNavWidth.a11y.chromium.spec.ts +0 -103
  442. package/src/MoreMenu/MoreMenu.test.tsx +0 -367
  443. package/src/MultiSelector/CreateRow.a11y.chromium.spec.ts +0 -148
  444. package/src/MultiSelector/MultiSelector.disabledFocus.a11y.chromium.spec.ts +0 -72
  445. package/src/MultiSelector/MultiSelector.focusableDisabled.test.tsx +0 -121
  446. package/src/MultiSelector/MultiSelector.fr10.test.tsx +0 -221
  447. package/src/MultiSelector/MultiSelector.test.tsx +0 -4971
  448. package/src/MultiSelector/OptionActions.a11y.chromium.spec.ts +0 -310
  449. package/src/MultiSelector/__tests__/MultiSelector.a11y.chromium.spec.ts +0 -46
  450. package/src/MultiSelector/__tests__/MultiSelector.listbox.a11y.test.tsx +0 -135
  451. package/src/NavIcon/NavIcon.test.tsx +0 -41
  452. package/src/NavMenu/NavHeadingMenu.test.tsx +0 -714
  453. package/src/NavMenu/__tests__/NavMenusSlideSelection.a11y.chromium.spec.ts +0 -137
  454. package/src/NumberInput/NumberInput.fr10.test.tsx +0 -21
  455. package/src/NumberInput/NumberInput.test.tsx +0 -2697
  456. package/src/NumberInput/__tests__/NumberInput.a11y.chromium.spec.ts +0 -124
  457. package/src/NumberInput/__tests__/NumberInput.a11y.known-failures.ts +0 -26
  458. package/src/NumberInput/__tests__/NumberInput.a11y.renders.tsx +0 -77
  459. package/src/NumberInput/__tests__/NumberInput.a11y.states.ts +0 -116
  460. package/src/NumberInput/__tests__/NumberInput.a11y.test.tsx +0 -154
  461. package/src/NumberInput/__tests__/NumberInput.coarse-pointer.a11y.chromium.spec.ts +0 -102
  462. package/src/NumberInput/numberInputCommit.test.ts +0 -72
  463. package/src/NumberInput/numberParser.docblock.test.ts +0 -234
  464. package/src/NumberInput/numberParser.test.ts +0 -222
  465. package/src/Outline/Outline.test.tsx +0 -1134
  466. package/src/Outline/OutlineFocus.a11y.chromium.spec.ts +0 -551
  467. package/src/Outline/parseOutlineFromMarkdown.test.ts +0 -104
  468. package/src/OverflowList/OverflowList.test.tsx +0 -1073
  469. package/src/Overlay/Overlay.test.tsx +0 -424
  470. package/src/Pagination/Pagination.test.tsx +0 -1754
  471. package/src/Popover/Popover.test.tsx +0 -1508
  472. package/src/Popover/PopoverPresentation.test.tsx +0 -952
  473. package/src/Popover/__tests__/PopoverSecondDocument.a11y.chromium.spec.ts +0 -267
  474. package/src/PowerSearch/PowerSearch.test.tsx +0 -1027
  475. package/src/PowerSearch/PowerSearchEditPopover.test.tsx +0 -1190
  476. package/src/PowerSearch/PowerSearchFilterEditor.test.tsx +0 -377
  477. package/src/PowerSearch/PowerSearchToken.test.tsx +0 -230
  478. package/src/PowerSearch/PowerSearchValueEditor.test.tsx +0 -754
  479. package/src/PowerSearch/formatFilterValue.test.ts +0 -339
  480. package/src/PowerSearch/resolveDateTimeRangePart.test.ts +0 -40
  481. package/src/PowerSearch/usePowerSearchConfig.test.ts +0 -832
  482. package/src/PowerSearch/usePowerSearchSource.test.ts +0 -621
  483. package/src/ProgressBar/ProgressBar.test.tsx +0 -928
  484. package/src/ProgressBar/__tests__/ProgressBar.a11y.chromium.spec.ts +0 -127
  485. package/src/ProgressBar/__tests__/ProgressBar.a11y.known-failures.ts +0 -12
  486. package/src/ProgressBar/__tests__/ProgressBar.a11y.renders.tsx +0 -38
  487. package/src/ProgressBar/__tests__/ProgressBar.a11y.states.ts +0 -123
  488. package/src/ProgressBar/__tests__/ProgressBar.a11y.test.tsx +0 -107
  489. package/src/ProgressBar/__tests__/ProgressBarMotion.a11y.chromium.spec.ts +0 -831
  490. package/src/RadioList/RadioList.fr10.test.tsx +0 -67
  491. package/src/RadioList/RadioList.test.tsx +0 -1051
  492. package/src/RadioList/RadioListItem.press.source-build.test.mjs +0 -80
  493. package/src/RadioList/__tests__/RadioGroup.a11y.chromium.spec.ts +0 -274
  494. package/src/RadioList/__tests__/RadioGroup.a11y.known-failures.ts +0 -12
  495. package/src/RadioList/__tests__/RadioGroup.a11y.renders.tsx +0 -232
  496. package/src/RadioList/__tests__/RadioGroup.a11y.states.ts +0 -503
  497. package/src/RadioList/__tests__/RadioGroup.a11y.test.tsx +0 -217
  498. package/src/Resizable/ResizeHandle.test.tsx +0 -1067
  499. package/src/Resizable/__tests__/ResizeHandle.a11y.chromium.spec.ts +0 -127
  500. package/src/Resizable/__tests__/ResizeHandle.a11y.known-failures.ts +0 -39
  501. package/src/Resizable/__tests__/ResizeHandle.a11y.renders.tsx +0 -80
  502. package/src/Resizable/__tests__/ResizeHandle.a11y.states.ts +0 -197
  503. package/src/Resizable/__tests__/ResizeHandle.a11y.test.tsx +0 -123
  504. package/src/Resizable/shippedDirections.test.ts +0 -462
  505. package/src/Resizable/useResizable.test.ts +0 -1725
  506. package/src/Resizable/utils.test.ts +0 -74
  507. package/src/ScrollableArea/ScrollableArea.test.tsx +0 -304
  508. package/src/Section/Section.test.tsx +0 -376
  509. package/src/SegmentedControl/SegmentedControl.test.tsx +0 -981
  510. package/src/SelectableCard/SelectableCard.test.tsx +0 -216
  511. package/src/Selector/Selector.focusableDisabled.test.tsx +0 -127
  512. package/src/Selector/Selector.fr10.test.tsx +0 -149
  513. package/src/Selector/Selector.sheetActiveDescendant.test.tsx +0 -95
  514. package/src/Selector/Selector.source-build.test.mjs +0 -79
  515. package/src/Selector/Selector.test.tsx +0 -5117
  516. package/src/Selector/__tests__/Combobox.a11y.known-failures.ts +0 -27
  517. package/src/Selector/__tests__/Combobox.a11y.states.ts +0 -145
  518. package/src/Selector/__tests__/ComboboxField.a11y.chromium.spec.ts +0 -458
  519. package/src/Selector/__tests__/EmptyStateAnnouncement.a11y.chromium.spec.ts +0 -91
  520. package/src/Selector/__tests__/Listbox.a11y.chromium.spec.ts +0 -131
  521. package/src/Selector/__tests__/Listbox.a11y.dom.ts +0 -38
  522. package/src/Selector/__tests__/Listbox.a11y.inventory.test.ts +0 -63
  523. package/src/Selector/__tests__/Listbox.a11y.known-failures.ts +0 -12
  524. package/src/Selector/__tests__/Listbox.a11y.states.ts +0 -232
  525. package/src/Selector/__tests__/Selector.a11y.chromium.spec.ts +0 -46
  526. package/src/Selector/__tests__/Selector.combobox.a11y.chromium.spec.ts +0 -109
  527. package/src/Selector/__tests__/Selector.combobox.a11y.test.tsx +0 -133
  528. package/src/Selector/__tests__/Selector.listbox.a11y.test.tsx +0 -128
  529. package/src/Selector/useCombobox.public.test.tsx +0 -89
  530. package/src/Selector/useCombobox.test.tsx +0 -444
  531. package/src/SideNav/SideNav.test.tsx +0 -4275
  532. package/src/SideNav/SideNavRailMotion.test.tsx +0 -620
  533. package/src/SideNav/__tests__/SideNavRailMotion.interaction.browser.spec.ts +0 -542
  534. package/src/SizeContext/SizeContext.test.tsx +0 -226
  535. package/src/Skeleton/Skeleton.test.tsx +0 -148
  536. package/src/Slider/Slider.fr10.test.tsx +0 -93
  537. package/src/Slider/Slider.test.tsx +0 -1661
  538. package/src/Slider/__tests__/Slider.a11y.chromium.spec.ts +0 -125
  539. package/src/Slider/__tests__/Slider.a11y.known-failures.ts +0 -12
  540. package/src/Slider/__tests__/Slider.a11y.renders.tsx +0 -42
  541. package/src/Slider/__tests__/Slider.a11y.states.ts +0 -106
  542. package/src/Slider/__tests__/Slider.a11y.test.tsx +0 -105
  543. package/src/Spinner/Spinner.test.tsx +0 -484
  544. package/src/Stack/Stack.test.tsx +0 -473
  545. package/src/Stack/StackItem.test.tsx +0 -105
  546. package/src/StatusDot/StatusDot.test.tsx +0 -213
  547. package/src/Stepper/Stepper.public.test.ts +0 -91
  548. package/src/Stepper/Stepper.test.tsx +0 -2130
  549. package/src/Switch/Switch.focusRing.source-build.test.mjs +0 -81
  550. package/src/Switch/Switch.test.tsx +0 -1136
  551. package/src/Switch/__tests__/Switch.a11y.chromium.spec.ts +0 -211
  552. package/src/Switch/__tests__/Switch.a11y.known-failures.ts +0 -28
  553. package/src/Switch/__tests__/Switch.a11y.states.ts +0 -275
  554. package/src/Switch/__tests__/Switch.a11y.test.tsx +0 -144
  555. package/src/TabList/TabList.test.tsx +0 -1755
  556. package/src/TabList/__tests__/TabListOverflow.interaction.browser.spec.ts +0 -99
  557. package/src/TabList/__tests__/TabPressPointer.interaction.browser.spec.ts +0 -163
  558. package/src/TabList/__tests__/Tabs.a11y.chromium.spec.ts +0 -199
  559. package/src/TabList/__tests__/Tabs.a11y.known-failures.ts +0 -12
  560. package/src/TabList/__tests__/Tabs.a11y.renders.tsx +0 -92
  561. package/src/TabList/__tests__/Tabs.a11y.states.ts +0 -247
  562. package/src/TabList/__tests__/Tabs.a11y.test.tsx +0 -153
  563. package/src/Table/Table.perf.test.tsx +0 -569
  564. package/src/Table/Table.test.tsx +0 -1883
  565. package/src/Table/TableHeaderCell.test.tsx +0 -291
  566. package/src/Table/TableSelectionToolbar.test.tsx +0 -109
  567. package/src/Table/__tests__/Table.a11y.chromium.spec.ts +0 -141
  568. package/src/Table/__tests__/Table.a11y.contracts.ts +0 -35
  569. package/src/Table/__tests__/Table.a11y.renders.tsx +0 -112
  570. package/src/Table/__tests__/Table.a11y.states.ts +0 -136
  571. package/src/Table/__tests__/Table.a11y.test.tsx +0 -198
  572. package/src/Table/__tests__/TableMinWidth.a11y.chromium.spec.ts +0 -75
  573. package/src/Table/__tests__/TableRowGlide.a11y.browser.spec.ts +0 -382
  574. package/src/Table/__tests__/TableScroll.a11y.chromium.spec.ts +0 -138
  575. package/src/Table/__tests__/TableStickyDividers.a11y.browser.spec.ts +0 -560
  576. package/src/Table/plugins/columnResize/useTableColumnResize.test.tsx +0 -941
  577. package/src/Table/plugins/columnSettings/useTableColumnSettings.test.tsx +0 -175
  578. package/src/Table/plugins/columnSettings/useTableColumnSettingsState.test.tsx +0 -296
  579. package/src/Table/plugins/filtering/useTableFilterState.test.tsx +0 -143
  580. package/src/Table/plugins/filtering/useTableFiltering.test.tsx +0 -357
  581. package/src/Table/plugins/groupedRows/useTableGroupedRows-perf.test.tsx +0 -112
  582. package/src/Table/plugins/groupedRows/useTableGroupedRows.test.tsx +0 -409
  583. package/src/Table/plugins/pagination/useTablePagination-perf.test.tsx +0 -232
  584. package/src/Table/plugins/pagination/useTablePagination.test.tsx +0 -666
  585. package/src/Table/plugins/rowExpansion/useTableRowExpansion.glide.test.tsx +0 -280
  586. package/src/Table/plugins/rowExpansion/useTableRowExpansion.test.tsx +0 -681
  587. package/src/Table/plugins/rowIndex/useTableRowIndex.test.tsx +0 -132
  588. package/src/Table/plugins/rowStatus/useTableRowStatus.public.test.ts +0 -92
  589. package/src/Table/plugins/rowStatus/useTableRowStatus.test.tsx +0 -519
  590. package/src/Table/plugins/selection/useTableSelection-perf.test.tsx +0 -258
  591. package/src/Table/plugins/selection/useTableSelection.test.tsx +0 -424
  592. package/src/Table/plugins/selection/useTableSelectionState.test.tsx +0 -451
  593. package/src/Table/plugins/sortable/__tests__/useTableSortable.a11y.test.tsx +0 -174
  594. package/src/Table/plugins/sortable/__tests__/useTableSortable.a11y.ts +0 -86
  595. package/src/Table/plugins/sortable/useTableSortable.test.tsx +0 -786
  596. package/src/Table/plugins/sortable/useTableSortableState.test.tsx +0 -580
  597. package/src/Table/plugins/stickyColumns/useTableStickyColumns.test.tsx +0 -334
  598. package/src/Table/plugins/tree/useTableTreeData-perf.test.tsx +0 -163
  599. package/src/Table/plugins/tree/useTableTreeData.test.tsx +0 -992
  600. package/src/Table/plugins/tree/useTableTreeState.test.tsx +0 -740
  601. package/src/Table/tableContextMenu.test.tsx +0 -208
  602. package/src/Text/Text.test.tsx +0 -405
  603. package/src/Text/useTruncation.test.tsx +0 -196
  604. package/src/TextArea/TextArea.fr10.test.tsx +0 -21
  605. package/src/TextArea/TextArea.test.tsx +0 -1317
  606. package/src/TextInput/TextInput.fr10.test.tsx +0 -21
  607. package/src/TextInput/TextInput.test.tsx +0 -1280
  608. package/src/TextInput/__tests__/TextInput.a11y.chromium.spec.ts +0 -214
  609. package/src/TextInput/__tests__/TextInput.a11y.known-failures.ts +0 -40
  610. package/src/TextInput/__tests__/TextInput.a11y.renders.tsx +0 -202
  611. package/src/TextInput/__tests__/TextInput.a11y.states.ts +0 -384
  612. package/src/TextInput/__tests__/TextInput.a11y.test.tsx +0 -130
  613. package/src/Thumbnail/Thumbnail.test.tsx +0 -384
  614. package/src/TimeInput/NativeTimeInput.test.tsx +0 -343
  615. package/src/TimeInput/Presentation.test.tsx +0 -192
  616. package/src/TimeInput/TimeInput.fr10.test.tsx +0 -312
  617. package/src/TimeInput/TimeInput.test.tsx +0 -907
  618. package/src/Timer/Timer.test.tsx +0 -302
  619. package/src/Timestamp/Timestamp.test.tsx +0 -1661
  620. package/src/Timestamp/tooltipEntries.test.ts +0 -566
  621. package/src/Toast/ToastViewport.modalHost.test.tsx +0 -891
  622. package/src/Toast/ToastViewport.test.tsx +0 -3130
  623. package/src/Toast/useToast.test.tsx +0 -258
  624. package/src/ToggleButton/ToggleButton.test.tsx +0 -1000
  625. package/src/ToggleButton/__tests__/ToggleButton.a11y.chromium.spec.ts +0 -177
  626. package/src/ToggleButton/__tests__/ToggleButton.a11y.renders.tsx +0 -133
  627. package/src/ToggleButton/__tests__/ToggleButton.a11y.states.ts +0 -167
  628. package/src/ToggleButton/__tests__/ToggleButton.a11y.test.tsx +0 -103
  629. package/src/Token/Token.test.tsx +0 -707
  630. package/src/Token/__tests__/TokenRemoveTarget.a11y.chromium.spec.ts +0 -87
  631. package/src/Tokenizer/Tokenizer.fr10.test.tsx +0 -28
  632. package/src/Tokenizer/Tokenizer.props-type.test.tsx +0 -126
  633. package/src/Tokenizer/Tokenizer.ssr.test.tsx +0 -196
  634. package/src/Tokenizer/Tokenizer.test.tsx +0 -2297
  635. package/src/Tokenizer/TokenizerBottomSheet.test.tsx +0 -2738
  636. package/src/Tokenizer/__tests__/TokenizerEndLane.a11y.chromium.spec.ts +0 -505
  637. package/src/Toolbar/Toolbar.test.tsx +0 -600
  638. package/src/Toolbar/__tests__/Toolbar.a11y.chromium.spec.ts +0 -176
  639. package/src/Toolbar/__tests__/Toolbar.a11y.renders.tsx +0 -63
  640. package/src/Toolbar/__tests__/Toolbar.a11y.states.ts +0 -142
  641. package/src/Toolbar/__tests__/Toolbar.a11y.test.tsx +0 -126
  642. package/src/Tooltip/Tooltip.test.tsx +0 -819
  643. package/src/Tooltip/__tests__/Tooltip.a11y.chromium.spec.ts +0 -141
  644. package/src/Tooltip/__tests__/Tooltip.a11y.renders.tsx +0 -86
  645. package/src/Tooltip/__tests__/Tooltip.a11y.states.ts +0 -132
  646. package/src/Tooltip/__tests__/Tooltip.a11y.test.tsx +0 -126
  647. package/src/Tooltip/__tests__/TooltipFocusReturn.a11y.chromium.spec.ts +0 -130
  648. package/src/Tooltip/__tests__/TooltipHoverBridge.a11y.chromium.spec.ts +0 -75
  649. package/src/Tooltip/__tests__/TooltipTallContent.a11y.browser.spec.ts +0 -673
  650. package/src/Tooltip/__tests__/TooltipTallContentTracking.a11y.browser.spec.ts +0 -1162
  651. package/src/Tooltip/useTooltip.test.tsx +0 -483
  652. package/src/TopNav/TopNav.test.tsx +0 -863
  653. package/src/TopNav/TopNavMegaMenu.reduced-motion.source-build.test.mjs +0 -83
  654. package/src/TopNav/TopNavMegaMenu.test.tsx +0 -1153
  655. package/src/TopNav/TopNavMenu.test.tsx +0 -502
  656. package/src/TopNav/__tests__/TopNavMobileLongHeading.a11y.chromium.spec.ts +0 -75
  657. package/src/TreeList/TreeList.test.tsx +0 -1737
  658. package/src/TreeList/__tests__/TreeList.a11y.chromium.spec.ts +0 -138
  659. package/src/TreeList/__tests__/TreeList.a11y.renders.tsx +0 -44
  660. package/src/TreeList/__tests__/TreeList.a11y.states.ts +0 -164
  661. package/src/TreeList/__tests__/TreeList.a11y.test.tsx +0 -138
  662. package/src/TreeList/__tests__/TreeListGuideAlignment.interaction.browser.spec.ts +0 -127
  663. package/src/TreeList/__tests__/TreeListRowNames.a11y.chromium.spec.ts +0 -83
  664. package/src/Typeahead/BaseTypeahead.emptyState.a11y.chromium.spec.ts +0 -138
  665. package/src/Typeahead/BaseTypeahead.test.tsx +0 -553
  666. package/src/Typeahead/Typeahead.fr10.test.tsx +0 -28
  667. package/src/Typeahead/Typeahead.test.tsx +0 -3063
  668. package/src/Typeahead/TypeaheadItem.test.tsx +0 -134
  669. package/src/VStack/VStack.test.tsx +0 -128
  670. package/src/VisuallyHidden/VisuallyHidden.test.tsx +0 -115
  671. package/src/__tests__/BusyOwnerAnnouncement.test.tsx +0 -853
  672. package/src/__tests__/CardPress.a11y.chromium.spec.ts +0 -353
  673. package/src/__tests__/DisabledTriggerFocus.a11y.chromium.spec.ts +0 -87
  674. package/src/__tests__/PressModality.a11y.chromium.spec.ts +0 -392
  675. package/src/__tests__/PressedState.a11y.chromium.spec.ts +0 -814
  676. package/src/__tests__/TestIcon.tsx +0 -26
  677. package/src/__tests__/babelPluginAddExtensions.test.ts +0 -190
  678. package/src/__tests__/fastRoleQueries.ts +0 -66
  679. package/src/__tests__/forcedColors.ts +0 -60
  680. package/src/__tests__/internalModulesNotExperimental.test.ts +0 -88
  681. package/src/__tests__/pressState.ts +0 -216
  682. package/src/__tests__/stubMatchMedia.test.ts +0 -79
  683. package/src/__tests__/stubMatchMedia.ts +0 -95
  684. package/src/__tests__/stylexDeclarations.ts +0 -51
  685. package/src/__tests__/unthemedLength.ts +0 -34
  686. package/src/docPropLiterals.test.ts +0 -964
  687. package/src/docPropReferences.test.ts +0 -178
  688. package/src/docs-script.test.mjs +0 -55
  689. package/src/gestures/__tests__/GestureHost.test.ts +0 -2136
  690. package/src/gestures/__tests__/LongPressGestureRecognizer.test.ts +0 -141
  691. package/src/gestures/__tests__/PanGestureRecognizer.test.ts +0 -327
  692. package/src/gestures/__tests__/PinchGestureRecognizer.test.ts +0 -74
  693. package/src/gestures/__tests__/PressTracker.test.ts +0 -275
  694. package/src/gestures/__tests__/ScreenEdgePanGestureRecognizer.test.ts +0 -498
  695. package/src/gestures/__tests__/SwipeGestureRecognizer.test.ts +0 -108
  696. package/src/gestures/__tests__/TapGestureRecognizer.test.ts +0 -154
  697. package/src/gestures/__tests__/VelocityTracker.streams.test.ts +0 -243
  698. package/src/gestures/__tests__/VelocityTracker.test.ts +0 -261
  699. package/src/gestures/__tests__/arena.test.ts +0 -864
  700. package/src/gestures/__tests__/arenaContinuous.test.ts +0 -207
  701. package/src/gestures/__tests__/arenaPinchSwipeEdge.test.ts +0 -193
  702. package/src/gestures/__tests__/arenaTapLongPress.test.ts +0 -211
  703. package/src/gestures/__tests__/browser/build-fixture.mjs +0 -46
  704. package/src/gestures/__tests__/browser/fixture.ts +0 -307
  705. package/src/gestures/__tests__/browser/gestures.interaction.browser.spec.ts +0 -1137
  706. package/src/gestures/__tests__/browserNegotiation.test.ts +0 -293
  707. package/src/gestures/__tests__/fixtures/pointer-streams/README.md +0 -82
  708. package/src/gestures/__tests__/fixtures/pointer-streams/android-chromium-fling.json +0 -130
  709. package/src/gestures/__tests__/fixtures/pointer-streams/android-chromium-slow.json +0 -860
  710. package/src/gestures/__tests__/fixtures/pointer-streams/android-chromium-stop-release.json +0 -357
  711. package/src/gestures/__tests__/fixtures/pointer-streams/android-firefox-fling.json +0 -66
  712. package/src/gestures/__tests__/fixtures/pointer-streams/android-firefox-slow.json +0 -268
  713. package/src/gestures/__tests__/fixtures/pointer-streams/android-firefox-stop-release.json +0 -128
  714. package/src/gestures/__tests__/fixtures/pointer-streams/capture.html +0 -123
  715. package/src/gestures/__tests__/fixtures/pointer-streams/desktop-chromium-mouse-fling.json +0 -64
  716. package/src/gestures/__tests__/fixtures/pointer-streams/desktop-chromium-mouse-slow.json +0 -268
  717. package/src/gestures/__tests__/fixtures/pointer-streams/desktop-chromium-mouse-stop-release.json +0 -124
  718. package/src/gestures/__tests__/fixtures/pointer-streams/desktop-chromium-pen-fling.json +0 -64
  719. package/src/gestures/__tests__/fixtures/pointer-streams/desktop-chromium-pen-slow.json +0 -268
  720. package/src/gestures/__tests__/fixtures/pointer-streams/desktop-chromium-pen-stop-release.json +0 -124
  721. package/src/gestures/__tests__/fixtures/pointer-streams/desktop-chromium-touch-fling.json +0 -84
  722. package/src/gestures/__tests__/fixtures/pointer-streams/desktop-chromium-touch-slow.json +0 -381
  723. package/src/gestures/__tests__/fixtures/pointer-streams/desktop-chromium-touch-stop-release.json +0 -171
  724. package/src/gestures/__tests__/fixtures/pointer-streams/ios-safari-fling.json +0 -45
  725. package/src/gestures/__tests__/fixtures/pointer-streams/ios-safari-slow.json +0 -101
  726. package/src/gestures/__tests__/fixtures/pointer-streams/ios-safari-stop-release.json +0 -130
  727. package/src/gestures/__tests__/haptics.test.ts +0 -187
  728. package/src/gestures/__tests__/harness.ts +0 -252
  729. package/src/gestures/__tests__/pressTrackerFactory.test.ts +0 -59
  730. package/src/gestures/__tests__/recognizerKind.test.ts +0 -78
  731. package/src/gestures/__tests__/touchActionRule.test.ts +0 -54
  732. package/src/gestures/__tests__/useGestureRecognizer.test.tsx +0 -570
  733. package/src/hooks/__tests__/MenuPressShadowRoot.a11y.chromium.spec.ts +0 -144
  734. package/src/hooks/__tests__/menuLinkClicks.ts +0 -71
  735. package/src/hooks/actionGroupTracker.exports.test.ts +0 -28
  736. package/src/hooks/actionGroupTracker.test.ts +0 -510
  737. package/src/hooks/computeOverflow.test.ts +0 -422
  738. package/src/hooks/deprecatedApis.public.test.ts +0 -33
  739. package/src/hooks/menuPressGesture.test.ts +0 -253
  740. package/src/hooks/scrollKeyboardDelegation.test.ts +0 -188
  741. package/src/hooks/scrollbarGutter.test.ts +0 -167
  742. package/src/hooks/useAnnounce.modal.test.tsx +0 -1291
  743. package/src/hooks/useAnnounce.replace.test.tsx +0 -516
  744. package/src/hooks/useAnnounce.test.tsx +0 -293
  745. package/src/hooks/useAnnounceRenderedText.adopters.test.tsx +0 -235
  746. package/src/hooks/useAutoMediaMode.test.ts +0 -109
  747. package/src/hooks/useBusyAnnouncement.adopters.test.tsx +0 -69
  748. package/src/hooks/useBusyAnnouncement.test.tsx +0 -112
  749. package/src/hooks/useClickableContainer.test.tsx +0 -236
  750. package/src/hooks/useClipboard.test.tsx +0 -151
  751. package/src/hooks/useContainerReveal.test.tsx +0 -163
  752. package/src/hooks/useDevWarning.test.tsx +0 -45
  753. package/src/hooks/useEntryAnimation.ssr.test.tsx +0 -143
  754. package/src/hooks/useFocusTrap.test.tsx +0 -635
  755. package/src/hooks/useFocusTrapEscapeShim.test.tsx +0 -166
  756. package/src/hooks/useGridFocus.test.tsx +0 -165
  757. package/src/hooks/useHotkeys.test.ts +0 -265
  758. package/src/hooks/useImageMode.test.ts +0 -133
  759. package/src/hooks/useInputContainer.test.tsx +0 -180
  760. package/src/hooks/useInputStatusIcon.test.tsx +0 -240
  761. package/src/hooks/useIsHydrating.test.tsx +0 -70
  762. package/src/hooks/useKeyboardHint.test.tsx +0 -92
  763. package/src/hooks/useListFocus.test.tsx +0 -664
  764. package/src/hooks/useLongPress.test.tsx +0 -152
  765. package/src/hooks/useMediaQuery.test.ts +0 -165
  766. package/src/hooks/useMenuHover.test.tsx +0 -536
  767. package/src/hooks/useMenuPress.test.tsx +0 -565
  768. package/src/hooks/useMergedRefs.test.tsx +0 -62
  769. package/src/hooks/useOverflow.test.ts +0 -504
  770. package/src/hooks/useRenamedProp.test.tsx +0 -95
  771. package/src/hooks/useScrollLock.test.ts +0 -248
  772. package/src/hooks/useScrollOverflow.test.tsx +0 -323
  773. package/src/hooks/useScrollableArea.test.tsx +0 -836
  774. package/src/hooks/useStreamingText.test.ts +0 -237
  775. package/src/hooks/useTreeFocus.test.tsx +0 -351
  776. package/src/hooks/useTypeahead.test.tsx +0 -351
  777. package/src/i18n/__tests__/InternationalizationProvider.test.tsx +0 -128
  778. package/src/i18n/__tests__/e2e-pagination.test.tsx +0 -172
  779. package/src/i18n/__tests__/e2e-powersearch.test.tsx +0 -166
  780. package/src/i18n/__tests__/getLocaleDirection.test.ts +0 -40
  781. package/src/i18n/__tests__/resolve.test.ts +0 -325
  782. package/src/i18n/__tests__/useCollator.test.tsx +0 -115
  783. package/src/i18n/__tests__/useDirection.test.tsx +0 -66
  784. package/src/i18n/__tests__/useLocale.test.tsx +0 -52
  785. package/src/i18n/useTranslator.test.tsx +0 -267
  786. package/src/inputFontFloor.test.ts +0 -106
  787. package/src/internal/tokenizer-presentation.test.ts +0 -29
  788. package/src/internal/touch.test.ts +0 -44
  789. package/src/motion/__tests__/animatedLayout.interaction.browser.spec.ts +0 -1613
  790. package/src/motion/__tests__/animatedLayoutAdopters.interaction.browser.spec.ts +0 -217
  791. package/src/motion/__tests__/animatedLayoutBundle.ts +0 -17
  792. package/src/motion/__tests__/animator.interaction.browser.spec.ts +0 -843
  793. package/src/motion/__tests__/motionBundle.ts +0 -14
  794. package/src/motion/animatedLayout.boundary.test.ts +0 -155
  795. package/src/motion/animatedLayout.swap.test.ts +0 -827
  796. package/src/motion/animatedLayout.test.ts +0 -1154
  797. package/src/motion/animatedLayoutAdopters.test.tsx +0 -500
  798. package/src/motion/animator.test.ts +0 -3746
  799. package/src/motion/bezier.test.ts +0 -52
  800. package/src/motion/coast.test.ts +0 -99
  801. package/src/motion/decay.test.ts +0 -120
  802. package/src/motion/projection.test.ts +0 -107
  803. package/src/motion/reducedMotion.test.ts +0 -129
  804. package/src/motion/reducedMotionPlan.test.ts +0 -208
  805. package/src/motion/resolveMotion.test.ts +0 -123
  806. package/src/motion/resolveSnapPoint.test.ts +0 -189
  807. package/src/motion/roles.test.ts +0 -273
  808. package/src/motion/rubberBand.test.ts +0 -88
  809. package/src/motion/selectPage.test.ts +0 -31
  810. package/src/motion/spring.test.ts +0 -650
  811. package/src/motion/uikit/gestures.test.ts +0 -89
  812. package/src/motion/uikit/springRows.test.ts +0 -71
  813. package/src/motion/uikit/tableImports.test.ts +0 -293
  814. package/src/motion/uikit/uikit.test.ts +0 -364
  815. package/src/motion/uikit/values.test.ts +0 -121
  816. package/src/naming.test.ts +0 -44
  817. package/src/postinstall.test.mjs +0 -216
  818. package/src/reset.a11y.chromium.spec.ts +0 -238
  819. package/src/reset.test.ts +0 -205
  820. package/src/reset.until-found.a11y.browser.spec.ts +0 -76
  821. package/src/reset.until-found.test.ts +0 -32
  822. package/src/serverSafeComponents.test.ts +0 -675
  823. package/src/stylexValidation.source-build.test.mjs +0 -42
  824. package/src/tailwind-theme.test.ts +0 -153
  825. package/src/theme/MediaTheme.dom.test.tsx +0 -97
  826. package/src/theme/Theme.test.tsx +0 -337
  827. package/src/theme/__tests__/SpacingRootFontSize.a11y.chromium.spec.ts +0 -199
  828. package/src/theme/contrast.test.ts +0 -94
  829. package/src/theme/declarationBoundary.test.ts +0 -126
  830. package/src/theme/defineTheme.test.ts +0 -2028
  831. package/src/theme/derivedVarRegistry.test.ts +0 -592
  832. package/src/theme/expandColorScale.test.ts +0 -651
  833. package/src/theme/expandMotionScale.test.ts +0 -145
  834. package/src/theme/expandRadiusScale.test.ts +0 -59
  835. package/src/theme/expandTypeScale.test.ts +0 -351
  836. package/src/theme/extensibleAxes.test.ts +0 -432
  837. package/src/theme/generateThemeRules.test.ts +0 -1870
  838. package/src/theme/hct.test.ts +0 -93
  839. package/src/theme/onMediaTokens.test.ts +0 -431
  840. package/src/theme/publicThemeHelperContract.test.ts +0 -67
  841. package/src/theme/syntax/defineSyntaxTheme.diff.test.ts +0 -91
  842. package/src/theme/syntax/serverSafeSyntax.test.ts +0 -68
  843. package/src/theme/syntax/useSyntaxTheme.diff.test.tsx +0 -40
  844. package/src/theme/themeAdaptations.test.ts +0 -1795
  845. package/src/theme/themeMotion.test.ts +0 -620
  846. package/src/theme/themeRegistry.test.ts +0 -59
  847. package/src/theme/themingTargets.test.ts +0 -589
  848. package/src/theme/tokenValueCompat.test.ts +0 -204
  849. package/src/theme/tokens.test.ts +0 -441
  850. package/src/theme/useTheme.test.tsx +0 -322
  851. package/src/unicodePropertyEscapes.source-build.test.mjs +0 -69
  852. package/src/utils/chainEventHandlers.test.ts +0 -39
  853. package/src/utils/characters.test.ts +0 -141
  854. package/src/utils/color.test.ts +0 -233
  855. package/src/utils/composeEventHandlers.test.ts +0 -71
  856. package/src/utils/customPropertyArmLayering.test.ts +0 -131
  857. package/src/utils/dateParser.test.ts +0 -402
  858. package/src/utils/dateTypes.test.ts +0 -40
  859. package/src/utils/devWarning.test.ts +0 -56
  860. package/src/utils/edgeAutoscroll.test.ts +0 -192
  861. package/src/utils/focusReturn.test.ts +0 -119
  862. package/src/utils/getKey.test.ts +0 -25
  863. package/src/utils/getNodeText.test.ts +0 -24
  864. package/src/utils/hitArea.test.ts +0 -628
  865. package/src/utils/ime.test.ts +0 -32
  866. package/src/utils/inputAria.test.ts +0 -39
  867. package/src/utils/interactionModality.test.ts +0 -144
  868. package/src/utils/isApplePlatform.test.ts +0 -61
  869. package/src/utils/mergeProps.test.ts +0 -166
  870. package/src/utils/mergeRefs.test.ts +0 -48
  871. package/src/utils/parseStyleKey.test.ts +0 -56
  872. package/src/utils/plainDate.test.ts +0 -938
  873. package/src/utils/presentation.test.ts +0 -380
  874. package/src/utils/pressFeedback.test.ts +0 -1012
  875. package/src/utils/pressGesture.test.ts +0 -332
  876. package/src/utils/pressPaintPriority.test.ts +0 -80
  877. package/src/utils/pressPaintSource.test.ts +0 -65
  878. package/src/utils/pressableCoverage.test.ts +0 -192
  879. package/src/utils/primaryClick.test.ts +0 -38
  880. package/src/utils/safeUrl.test.ts +0 -101
  881. package/src/utils/sharedMutationObserver.test.ts +0 -106
  882. package/src/utils/sharedResizeObserver.test.ts +0 -283
  883. package/src/utils/themeProps.test.ts +0 -62
  884. package/src/utils/timeFormat.test.ts +0 -204
  885. package/src/utils/timeParser.test.ts +0 -295
  886. package/src/utils/touchPresentation.test.ts +0 -462
  887. package/src/utils/unicodeDigits.test.ts +0 -44
@@ -1,3063 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- /**
4
- * @file Typeahead.test.tsx
5
- * @input Uses vitest, @testing-library/react, Typeahead, BaseTypeahead
6
- * @output Unit tests for Typeahead components
7
- * @position Testing; validates Typeahead.tsx and BaseTypeahead.tsx
8
- *
9
- * SYNC: When Typeahead components change, update tests to match
10
- */
11
-
12
- import {
13
- describe,
14
- it,
15
- expect,
16
- vi,
17
- beforeAll,
18
- afterAll,
19
- beforeEach,
20
- afterEach,
21
- } from 'vitest';
22
- import {render, screen, fireEvent, waitFor, act} from '@testing-library/react';
23
- import userEvent from '@testing-library/user-event';
24
- import {__resetLiveRegionsForTest} from '../hooks/useAnnounce';
25
- import {
26
- createRef,
27
- Profiler,
28
- startTransition,
29
- StrictMode,
30
- Suspense,
31
- useState,
32
- type ProfilerOnRenderCallback,
33
- } from 'react';
34
- import {FormLayout} from '../FormLayout/FormLayout';
35
- import {Typeahead} from './Typeahead';
36
- import {readFile} from 'node:fs/promises';
37
- import {resolve} from 'node:path';
38
- import {BaseTypeahead} from './BaseTypeahead';
39
- import {
40
- BusyIndicatorLaneProvider,
41
- createBusyIndicatorLane,
42
- } from './busyIndicatorLane';
43
- import type {SearchSource, SearchableItem} from './types';
44
- import {InternationalizationProvider} from '../i18n';
45
- import {InputGroup} from '../InputGroup';
46
-
47
- // Store original matches to restore later
48
- const originalMatches = HTMLElement.prototype.matches;
49
-
50
- // Track popover open state per element
51
- const popoverOpenState = new WeakMap<HTMLElement, boolean>();
52
-
53
- // Mock Popover API for jsdom
54
- beforeAll(() => {
55
- HTMLElement.prototype.showPopover = vi.fn(function (this: HTMLElement) {
56
- popoverOpenState.set(this, true);
57
- const event = new Event('toggle');
58
- Object.defineProperty(event, 'newState', {value: 'open'});
59
- this.dispatchEvent(event);
60
- });
61
- HTMLElement.prototype.hidePopover = vi.fn(function (this: HTMLElement) {
62
- popoverOpenState.set(this, false);
63
- const event = new Event('toggle');
64
- Object.defineProperty(event, 'newState', {value: 'closed'});
65
- this.dispatchEvent(event);
66
- });
67
-
68
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
69
- (HTMLElement.prototype as any).matches = function (
70
- selector: string,
71
- ): boolean {
72
- if (selector === ':popover-open') {
73
- return popoverOpenState.get(this) ?? false;
74
- }
75
- return originalMatches.call(this, selector);
76
- };
77
- });
78
-
79
- afterAll(() => {
80
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
81
- (HTMLElement.prototype as any).matches = originalMatches;
82
- });
83
-
84
- afterEach(() => {
85
- __resetLiveRegionsForTest();
86
- });
87
-
88
- // Test data
89
- const fruits: SearchableItem[] = [
90
- {id: '1', label: 'Apple'},
91
- {id: '2', label: 'Banana'},
92
- {id: '3', label: 'Cherry'},
93
- {id: '4', label: 'Date'},
94
- {id: '5', label: 'Elderberry'},
95
- ];
96
-
97
- const fruitSource: SearchSource = {
98
- search: (query: string) =>
99
- fruits.filter(f => f.label.toLowerCase().includes(query.toLowerCase())),
100
- bootstrap: () => fruits.slice(0, 3),
101
- };
102
-
103
- /**
104
- * The search Spinner: named but not live (family:input-fields FR7,
105
- * spec:AST-086 FR8), so it is found as an image named "Loading".
106
- */
107
- function queryBusySpinner(): Element | null {
108
- return screen.queryByRole('img', {name: 'Loading'});
109
- }
110
- function getBusySpinner(): Element {
111
- return screen.getByRole('img', {name: 'Loading'});
112
- }
113
-
114
- describe('BaseTypeahead', () => {
115
- it('renders input with combobox role', () => {
116
- render(
117
- <BaseTypeahead
118
- searchSource={fruitSource}
119
- value={null}
120
- onChange={() => {}}
121
- />,
122
- );
123
- expect(screen.getByRole('combobox')).toBeInTheDocument();
124
- });
125
-
126
- it('renders placeholder text', () => {
127
- render(
128
- <BaseTypeahead
129
- searchSource={fruitSource}
130
- value={null}
131
- onChange={() => {}}
132
- placeholder="Pick a fruit..."
133
- />,
134
- );
135
- expect(screen.getByPlaceholderText('Pick a fruit...')).toBeInTheDocument();
136
- });
137
-
138
- it('sets aria-expanded=false initially', () => {
139
- render(
140
- <BaseTypeahead
141
- searchSource={fruitSource}
142
- value={null}
143
- onChange={() => {}}
144
- />,
145
- );
146
- expect(screen.getByRole('combobox')).toHaveAttribute(
147
- 'aria-expanded',
148
- 'false',
149
- );
150
- });
151
-
152
- it('shows results on input change', async () => {
153
- render(
154
- <BaseTypeahead
155
- searchSource={fruitSource}
156
- value={null}
157
- onChange={() => {}}
158
- />,
159
- );
160
- const input = screen.getByRole('combobox');
161
- fireEvent.change(input, {target: {value: 'App'}});
162
-
163
- await waitFor(() => {
164
- expect(screen.getByRole('listbox', {hidden: true})).toBeInTheDocument();
165
- });
166
- });
167
-
168
- it('announces the result count to a live region (comboboxes-6)', async () => {
169
- render(
170
- <InternationalizationProvider
171
- locale="fr"
172
- overrides={{
173
- fr: {
174
- '@astryx.typeahead.resultCount':
175
- '{count, number} {count, plural, one {résultat} other {résultats}}',
176
- },
177
- }}>
178
- <BaseTypeahead
179
- searchSource={fruitSource}
180
- value={null}
181
- onChange={() => {}}
182
- debounceMs={0}
183
- />
184
- </InternationalizationProvider>,
185
- );
186
- const input = screen.getByRole('combobox');
187
- // "Ap" matches Apple only — the singular ICU branch.
188
- fireEvent.change(input, {target: {value: 'Ap'}});
189
-
190
- await waitFor(() => {
191
- const region = document.querySelector(
192
- '[data-astryx-live-region="polite"]',
193
- );
194
- // Exact, so the plural branch ("1 résultats") would fail.
195
- expect(region?.textContent).toBe('1 résultat');
196
- });
197
- });
198
-
199
- it('announces the plural result count', async () => {
200
- render(
201
- <InternationalizationProvider
202
- locale="fr"
203
- overrides={{
204
- fr: {
205
- '@astryx.typeahead.resultCount':
206
- '{count, number} {count, plural, one {résultat} other {résultats}}',
207
- },
208
- }}>
209
- <BaseTypeahead
210
- searchSource={fruitSource}
211
- value={null}
212
- onChange={() => {}}
213
- debounceMs={0}
214
- />
215
- </InternationalizationProvider>,
216
- );
217
- const input = screen.getByRole('combobox');
218
- // "err" matches Cherry and Elderberry.
219
- fireEvent.change(input, {target: {value: 'err'}});
220
-
221
- await waitFor(() => {
222
- const region = document.querySelector(
223
- '[data-astryx-live-region="polite"]',
224
- );
225
- expect(region?.textContent).toBe('2 résultats');
226
- });
227
- });
228
-
229
- it('speaks the result count from a provider catalog', async () => {
230
- render(
231
- <InternationalizationProvider
232
- locale="fr"
233
- messages={{
234
- fr: {
235
- '@astryx.typeahead.resultCount': {
236
- defaultMessage:
237
- '{count, number} {count, plural, one {résultat} other {résultats}}',
238
- },
239
- },
240
- }}>
241
- <BaseTypeahead
242
- searchSource={fruitSource}
243
- value={null}
244
- onChange={() => {}}
245
- debounceMs={0}
246
- />
247
- </InternationalizationProvider>,
248
- );
249
- const input = screen.getByRole('combobox');
250
- fireEvent.change(input, {target: {value: 'err'}});
251
-
252
- await waitFor(() => {
253
- const region = document.querySelector(
254
- '[data-astryx-live-region="polite"]',
255
- );
256
- // Same key through the catalog path rather than `overrides`.
257
- expect(region?.textContent).toBe('2 résultats');
258
- });
259
- });
260
-
261
- it('announces "no results found" when the search is empty (comboboxes-6)', async () => {
262
- render(
263
- <BaseTypeahead
264
- searchSource={fruitSource}
265
- value={null}
266
- onChange={() => {}}
267
- debounceMs={0}
268
- emptySearchText="No results found"
269
- />,
270
- );
271
- const input = screen.getByRole('combobox');
272
- fireEvent.change(input, {target: {value: 'zzzzz'}});
273
-
274
- await waitFor(() => {
275
- const region = document.querySelector(
276
- '[data-astryx-live-region="polite"]',
277
- );
278
- expect(region).toHaveTextContent('No results found');
279
- });
280
- });
281
-
282
- it('exposes the empty state as a themeable target', async () => {
283
- const {container} = render(
284
- <BaseTypeahead
285
- searchSource={fruitSource}
286
- value={null}
287
- onChange={() => {}}
288
- debounceMs={0}
289
- emptySearchText="No results found"
290
- />,
291
- );
292
- const input = screen.getByRole('combobox');
293
- fireEvent.change(input, {target: {value: 'zzzzz'}});
294
-
295
- await waitFor(() => {
296
- const emptyState = container.querySelector(
297
- '.astryx-typeahead-empty-state',
298
- );
299
- expect(emptyState).not.toBeNull();
300
- expect(emptyState).toHaveTextContent('No results found');
301
- });
302
- });
303
-
304
- describe('empty results active descendant (#4059)', () => {
305
- it('does not set aria-activedescendant when search has 0 results', async () => {
306
- render(
307
- <BaseTypeahead
308
- searchSource={fruitSource}
309
- value={null}
310
- onChange={() => {}}
311
- debounceMs={0}
312
- />,
313
- );
314
- const input = screen.getByRole('combobox');
315
- fireEvent.change(input, {target: {value: 'zzzzz'}});
316
-
317
- await waitFor(() => {
318
- expect(input).not.toHaveAttribute('aria-activedescendant');
319
- });
320
-
321
- // Press ArrowDown — should NOT set aria-activedescendant to option-0
322
- fireEvent.keyDown(input, {key: 'ArrowDown'});
323
- expect(input).not.toHaveAttribute('aria-activedescendant');
324
-
325
- // Press Home — should NOT set aria-activedescendant
326
- fireEvent.keyDown(input, {key: 'Home'});
327
- expect(input).not.toHaveAttribute('aria-activedescendant');
328
- });
329
- });
330
-
331
- describe('IME composition guard (#4828)', () => {
332
- it('does not select the highlighted result on a composing Enter', async () => {
333
- const onChange = vi.fn();
334
- render(
335
- <BaseTypeahead
336
- searchSource={fruitSource}
337
- value={null}
338
- onChange={onChange}
339
- debounceMs={0}
340
- />,
341
- );
342
- const input = screen.getByRole('combobox');
343
- fireEvent.change(input, {target: {value: 'App'}});
344
- await waitFor(() => {
345
- expect(input).toHaveAttribute('aria-expanded', 'true');
346
- });
347
-
348
- // The browser fires this composing keydown for the Enter that commits
349
- // an IME candidate (isComposing: true, or legacy keyCode 229) before
350
- // compositionend writes the pending syllable into the input. Without
351
- // the guard this both selects the highlighted result AND clears the
352
- // input via handleSelect, so the syllable that compositionend then
353
- // writes lands in an emptied field instead of being part of the word.
354
- fireEvent.keyDown(input, {key: 'Enter', isComposing: true});
355
- expect(onChange).not.toHaveBeenCalled();
356
- expect(input).toHaveValue('App');
357
-
358
- fireEvent.keyDown(input, {key: 'Enter', keyCode: 229});
359
- expect(onChange).not.toHaveBeenCalled();
360
- expect(input).toHaveValue('App');
361
-
362
- // A real, non-composing Enter still selects normally.
363
- fireEvent.keyDown(input, {key: 'Enter'});
364
- expect(onChange).toHaveBeenCalledWith(
365
- expect.objectContaining({label: 'Apple'}),
366
- );
367
- });
368
- });
369
-
370
- it('disables input when isDisabled', () => {
371
- render(
372
- <BaseTypeahead
373
- searchSource={fruitSource}
374
- value={null}
375
- onChange={() => {}}
376
- isDisabled
377
- />,
378
- );
379
- expect(screen.getByRole('combobox')).toBeDisabled();
380
- });
381
-
382
- it('uses anchorRef for dropdown positioning', () => {
383
- const anchorRef = {current: document.createElement('div')};
384
- render(
385
- <BaseTypeahead
386
- searchSource={fruitSource}
387
- value={null}
388
- onChange={() => {}}
389
- anchorRef={anchorRef}
390
- />,
391
- );
392
- // Component renders without error — anchor is wired up internally
393
- expect(screen.getByRole('combobox')).toBeInTheDocument();
394
- });
395
-
396
- it('does not select every result when items lack ids', async () => {
397
- const idlessItems = [
398
- {label: 'Alpha'},
399
- {label: 'Beta'},
400
- ] as unknown as SearchableItem[];
401
- const idlessSource: SearchSource = {
402
- search: () => idlessItems,
403
- bootstrap: () => idlessItems,
404
- };
405
-
406
- render(
407
- <BaseTypeahead
408
- searchSource={idlessSource}
409
- value={idlessItems[0]}
410
- onChange={() => {}}
411
- hasEntriesOnFocus
412
- debounceMs={0}
413
- />,
414
- );
415
-
416
- fireEvent.focus(screen.getByRole('combobox'));
417
-
418
- await waitFor(() => {
419
- expect(screen.getAllByRole('option', {hidden: true})).toHaveLength(2);
420
- });
421
-
422
- const options = screen.getAllByRole('option', {hidden: true});
423
- expect(options[0]).toHaveAttribute('aria-selected', 'true');
424
- expect(options[1]).toHaveAttribute('aria-selected', 'false');
425
- });
426
- });
427
-
428
- describe('BaseTypeahead focus-out', () => {
429
- it('closes the dropdown when focus leaves the input', async () => {
430
- render(
431
- <>
432
- <BaseTypeahead
433
- searchSource={fruitSource}
434
- value={null}
435
- onChange={() => {}}
436
- debounceMs={0}
437
- />
438
- <button type="button">Outside</button>
439
- </>,
440
- );
441
- const input = screen.getByRole('combobox');
442
- fireEvent.change(input, {target: {value: 'App'}});
443
-
444
- await waitFor(() => {
445
- expect(input).toHaveAttribute('aria-expanded', 'true');
446
- });
447
-
448
- // Focus moves to an element outside the field/dropdown → menu closes.
449
- const outside = screen.getByRole('button', {name: 'Outside'});
450
- fireEvent.blur(input, {relatedTarget: outside});
451
-
452
- await waitFor(() => {
453
- expect(input).toHaveAttribute('aria-expanded', 'false');
454
- });
455
- });
456
-
457
- it('closes the list on the Tab keydown, before the blur it produces', async () => {
458
- render(
459
- <BaseTypeahead
460
- searchSource={fruitSource}
461
- value={null}
462
- onChange={() => {}}
463
- debounceMs={0}
464
- />,
465
- );
466
- const input = screen.getByRole('combobox');
467
- input.focus();
468
- fireEvent.change(input, {target: {value: 'App'}});
469
- await waitFor(() => {
470
- expect(input).toHaveAttribute('aria-expanded', 'true');
471
- });
472
-
473
- // No blur is fired here on purpose: dismissing from the blur instead lets
474
- // the popover close mid-focus-move, which Chrome answers by dropping
475
- // focus to <body>.
476
- fireEvent.keyDown(input, {key: 'Tab'});
477
- await waitFor(() => {
478
- expect(input).toHaveAttribute('aria-expanded', 'false');
479
- });
480
- });
481
-
482
- it('Tab from the input with the list open moves focus to the next control', async () => {
483
- const user = userEvent.setup();
484
- render(
485
- <>
486
- <BaseTypeahead
487
- searchSource={fruitSource}
488
- value={null}
489
- onChange={() => {}}
490
- debounceMs={0}
491
- />
492
- <button type="button">Next</button>
493
- </>,
494
- );
495
- const input = screen.getByRole('combobox');
496
- input.focus();
497
- fireEvent.change(input, {target: {value: 'App'}});
498
- await waitFor(() => {
499
- expect(input).toHaveAttribute('aria-expanded', 'true');
500
- });
501
-
502
- await user.keyboard('{Tab}');
503
- expect(screen.getByRole('button', {name: 'Next'})).toHaveFocus();
504
- });
505
-
506
- it('keeps the dropdown open when focus moves into the anchor wrapper', async () => {
507
- const anchor = document.createElement('div');
508
- document.body.appendChild(anchor);
509
- const anchorRef = {current: anchor};
510
- render(
511
- <BaseTypeahead
512
- searchSource={fruitSource}
513
- value={null}
514
- onChange={() => {}}
515
- anchorRef={anchorRef}
516
- debounceMs={0}
517
- />,
518
- );
519
- const input = screen.getByRole('combobox');
520
- // The input lives inside the wrapper we hand to anchorRef.
521
- anchor.appendChild(input.closest('div') ?? input);
522
- fireEvent.change(input, {target: {value: 'App'}});
523
-
524
- await waitFor(() => {
525
- expect(input).toHaveAttribute('aria-expanded', 'true');
526
- });
527
-
528
- // A sibling control inside the field (e.g. a clear button) receives focus.
529
- const sibling = document.createElement('button');
530
- anchor.appendChild(sibling);
531
- fireEvent.blur(input, {relatedTarget: sibling});
532
-
533
- // Menu stays open because focus is still within the field.
534
- expect(input).toHaveAttribute('aria-expanded', 'true');
535
- document.body.removeChild(anchor);
536
- });
537
-
538
- it('does not close when a dropdown option receives focus', async () => {
539
- render(
540
- <BaseTypeahead
541
- searchSource={fruitSource}
542
- value={null}
543
- onChange={() => {}}
544
- debounceMs={0}
545
- />,
546
- );
547
- const input = screen.getByRole('combobox');
548
- fireEvent.change(input, {target: {value: 'App'}});
549
-
550
- await waitFor(() => {
551
- expect(input).toHaveAttribute('aria-expanded', 'true');
552
- });
553
-
554
- const option = screen.getByRole('option', {hidden: true});
555
- fireEvent.blur(input, {relatedTarget: option});
556
-
557
- expect(input).toHaveAttribute('aria-expanded', 'true');
558
- });
559
- });
560
-
561
- describe('Typeahead', () => {
562
- describe('out-of-order async results', () => {
563
- it('discards a stale response that resolves after a newer query', async () => {
564
- const resolvers = new Map<string, (items: SearchableItem[]) => void>();
565
- const rawSource: SearchSource = {
566
- search: async (query: string) =>
567
- new Promise<SearchableItem[]>(resolve => {
568
- resolvers.set(query, resolve);
569
- }),
570
- bootstrap: () => [],
571
- };
572
-
573
- render(
574
- <Typeahead
575
- label="Fruit"
576
- searchSource={rawSource}
577
- value={null}
578
- onChange={() => {}}
579
- debounceMs={0}
580
- />,
581
- );
582
-
583
- const input = screen.getByRole('combobox');
584
- fireEvent.change(input, {target: {value: 'a'}});
585
- fireEvent.change(input, {target: {value: 'ap'}});
586
-
587
- // The newer query resolves first…
588
- await act(async () => {
589
- resolvers.get('ap')!([{id: 'apple', label: 'Apple'}]);
590
- });
591
- expect(screen.getByText('Apple')).toBeInTheDocument();
592
-
593
- // …then the abandoned query's slow response arrives and must be
594
- // discarded rather than overwriting the current results.
595
- await act(async () => {
596
- resolvers.get('a')!([
597
- {id: 'avocado', label: 'Avocado'},
598
- {id: 'apricot', label: 'Apricot'},
599
- ]);
600
- });
601
- expect(screen.getByText('Apple')).toBeInTheDocument();
602
- expect(screen.queryByText('Avocado')).not.toBeInTheDocument();
603
- });
604
-
605
- it('discards a response from a source that was replaced while it was in flight', async () => {
606
- let resolveOld: ((items: SearchableItem[]) => void) | null = null;
607
- const oldSource: SearchSource = {
608
- search: async () =>
609
- new Promise<SearchableItem[]>(resolve => {
610
- resolveOld = resolve;
611
- }),
612
- bootstrap: () => [],
613
- };
614
- const newSource: SearchSource = {
615
- search: () => [{id: 'banana', label: 'Banana'}],
616
- bootstrap: () => [],
617
- };
618
-
619
- const {rerender} = render(
620
- <Typeahead
621
- label="Fruit"
622
- searchSource={oldSource}
623
- value={null}
624
- onChange={() => {}}
625
- debounceMs={0}
626
- />,
627
- );
628
-
629
- const input = screen.getByRole('combobox');
630
- fireEvent.change(input, {target: {value: 'a'}});
631
- await waitFor(() => expect(resolveOld).not.toBeNull());
632
-
633
- // The source is swapped while the old search is still pending and no
634
- // new query is typed. The stale response must not populate a menu that
635
- // now reads from a different source.
636
- rerender(
637
- <Typeahead
638
- label="Fruit"
639
- searchSource={newSource}
640
- value={null}
641
- onChange={() => {}}
642
- debounceMs={0}
643
- />,
644
- );
645
-
646
- await act(async () => {
647
- resolveOld!([{id: 'apple', label: 'Apple'}]);
648
- });
649
- expect(screen.queryByText('Apple')).not.toBeInTheDocument();
650
-
651
- // Work started against the new source still lands normally.
652
- fireEvent.change(input, {target: {value: 'ba'}});
653
- await waitFor(() =>
654
- expect(screen.getByText('Banana')).toBeInTheDocument(),
655
- );
656
- });
657
- });
658
-
659
- it('renders with label', () => {
660
- render(
661
- <Typeahead
662
- label="Fruit"
663
- searchSource={fruitSource}
664
- value={null}
665
- onChange={() => {}}
666
- />,
667
- );
668
- expect(screen.getByLabelText('Fruit')).toBeInTheDocument();
669
- });
670
-
671
- it('renders description text', () => {
672
- render(
673
- <Typeahead
674
- label="Fruit"
675
- description="Pick your favorite fruit"
676
- searchSource={fruitSource}
677
- value={null}
678
- onChange={() => {}}
679
- />,
680
- );
681
- expect(screen.getByText('Pick your favorite fruit')).toBeInTheDocument();
682
- });
683
-
684
- it('shows required indicator', () => {
685
- render(
686
- <Typeahead
687
- label="Fruit"
688
- isRequired
689
- searchSource={fruitSource}
690
- value={null}
691
- onChange={() => {}}
692
- />,
693
- );
694
- expect(screen.getByText(/Required/)).toBeInTheDocument();
695
- });
696
-
697
- it('exposes the resolved required state on the combobox (FormLayout FR5, AR2)', () => {
698
- const field = (props: {
699
- isRequired?: boolean;
700
- isOptional?: boolean;
701
- label: string;
702
- }) => (
703
- <Typeahead
704
- {...props}
705
- searchSource={fruitSource}
706
- value={null}
707
- onChange={() => {}}
708
- />
709
- );
710
- render(
711
- <>
712
- {field({label: 'Plain'})}
713
- {field({label: 'Explicit', isRequired: true})}
714
- <FormLayout defaultOptionality="required">
715
- {field({label: 'By default'})}
716
- {field({label: 'Opted out', isOptional: true})}
717
- </FormLayout>
718
- </>,
719
- );
720
- const required = (name: string) =>
721
- screen
722
- .getByRole('combobox', {name: new RegExp(`^${name}`)})
723
- .getAttribute('aria-required');
724
- expect(required('Plain')).toBeNull();
725
- expect(required('Explicit')).toBe('true');
726
- // The form default hides the visible marker, so assistive technology
727
- // must still hear the field as required.
728
- expect(required('By default')).toBe('true');
729
- expect(required('Opted out')).toBeNull();
730
- });
731
-
732
- it('marks the combobox invalid for an error status only (WCAG 4.1.2)', () => {
733
- const field = (
734
- label: string,
735
- status?: {type: 'error' | 'warning'; message: string},
736
- ) => (
737
- <Typeahead
738
- label={label}
739
- status={status}
740
- searchSource={fruitSource}
741
- value={null}
742
- onChange={() => {}}
743
- />
744
- );
745
- render(
746
- <>
747
- {field('None')}
748
- {field('Warned', {type: 'warning', message: 'Check this'})}
749
- {field('Failed', {type: 'error', message: 'Pick one'})}
750
- </>,
751
- );
752
- const invalid = (name: string) =>
753
- screen.getByRole('combobox', {name}).getAttribute('aria-invalid');
754
- expect(invalid('None')).toBeNull();
755
- expect(invalid('Warned')).toBeNull();
756
- expect(invalid('Failed')).toBe('true');
757
- });
758
-
759
- it("keeps a caller's own aria-invalid and aria-required when the field's own state is off", () => {
760
- render(
761
- <Typeahead
762
- label="Caller"
763
- aria-invalid="true"
764
- aria-required="true"
765
- searchSource={fruitSource}
766
- value={null}
767
- onChange={() => {}}
768
- />,
769
- );
770
- const combobox = screen.getByRole('combobox', {name: 'Caller'});
771
- expect(combobox).toHaveAttribute('aria-invalid', 'true');
772
- expect(combobox).toHaveAttribute('aria-required', 'true');
773
- });
774
-
775
- it('renders error status message', () => {
776
- render(
777
- <Typeahead
778
- label="Fruit"
779
- searchSource={fruitSource}
780
- value={null}
781
- onChange={() => {}}
782
- status={{type: 'error', message: 'Selection required'}}
783
- />,
784
- );
785
- expect(screen.getByText('Selection required')).toBeInTheDocument();
786
- });
787
-
788
- it('shows selected value as a token', () => {
789
- render(
790
- <Typeahead
791
- label="Fruit"
792
- searchSource={fruitSource}
793
- value={fruits[0]}
794
- onChange={() => {}}
795
- />,
796
- );
797
- expect(screen.getByText(fruits[0].label)).toBeInTheDocument();
798
- });
799
-
800
- it('shows clear button when hasClear and value is selected', () => {
801
- render(
802
- <Typeahead
803
- label="Fruit"
804
- searchSource={fruitSource}
805
- value={fruits[0]}
806
- onChange={() => {}}
807
- hasClear
808
- />,
809
- );
810
- expect(
811
- screen.getByRole('button', {name: 'Clear selection'}),
812
- ).toBeInTheDocument();
813
- });
814
-
815
- it('does not show clear button when hasClear is false', () => {
816
- render(
817
- <Typeahead
818
- label="Fruit"
819
- searchSource={fruitSource}
820
- value={fruits[0]}
821
- onChange={() => {}}
822
- hasClear={false}
823
- />,
824
- );
825
- expect(
826
- screen.queryByRole('button', {name: 'Clear selection'}),
827
- ).not.toBeInTheDocument();
828
- });
829
-
830
- it('calls onChange with null when clear button is clicked', () => {
831
- const onChange = vi.fn();
832
- render(
833
- <Typeahead
834
- label="Fruit"
835
- searchSource={fruitSource}
836
- value={fruits[0]}
837
- onChange={onChange}
838
- hasClear
839
- />,
840
- );
841
- fireEvent.click(screen.getByRole('button', {name: 'Clear selection'}));
842
- expect(onChange).toHaveBeenCalledWith(null);
843
- });
844
-
845
- it('forwards DOM props and ref to the combobox input while preserving edit and blur behavior', async () => {
846
- const ref = createRef<HTMLInputElement>();
847
- const onClick = vi.fn();
848
- const onBlur = vi.fn();
849
- const onChange = vi.fn();
850
- const {container} = render(
851
- <Typeahead
852
- ref={ref}
853
- id="fruit-field"
854
- data-tracking="fruit-picker"
855
- data-testid="fruit-input"
856
- containerClassName="custom-typeahead"
857
- containerStyle={{marginTop: 7}}
858
- label="Fruit"
859
- searchSource={fruitSource}
860
- value={fruits[0]}
861
- onChange={onChange}
862
- onClick={onClick}
863
- onBlur={onBlur}
864
- debounceMs={0}
865
- />,
866
- );
867
- const surface = container.querySelector<HTMLElement>('.astryx-typeahead')!;
868
- const input = screen.getByRole('combobox');
869
-
870
- expect(ref.current).toBe(input);
871
- expect(screen.getByTestId('fruit-input')).toBe(input);
872
- expect(input).toHaveAttribute('id', 'fruit-field');
873
- expect(input).toHaveAttribute('data-tracking', 'fruit-picker');
874
- expect(input).toHaveAccessibleName('Fruit');
875
- const fieldRoot = container.querySelector('.custom-typeahead');
876
- expect(fieldRoot).toHaveClass('astryx-field');
877
- expect(fieldRoot).toHaveStyle({marginTop: '7px'});
878
-
879
- fireEvent.click(surface);
880
- await waitFor(() => expect(input).toHaveFocus());
881
- expect(input).toHaveValue('Apple');
882
- fireEvent.click(input);
883
- expect(onClick).toHaveBeenCalledOnce();
884
-
885
- fireEvent.blur(input);
886
- expect(onBlur).toHaveBeenCalledOnce();
887
- expect(onChange).not.toHaveBeenCalled();
888
- });
889
-
890
- it('forwards DOM props and ref to the input inside InputGroup, with control* on the surface', async () => {
891
- const ref = createRef<HTMLInputElement>();
892
- const onClick = vi.fn();
893
- const onBlur = vi.fn();
894
- const onChange = vi.fn();
895
- const {container} = render(
896
- <InputGroup label="Produce">
897
- <Typeahead
898
- ref={ref}
899
- id="fruit-field"
900
- data-tracking="fruit-picker"
901
- controlClassName="custom-typeahead"
902
- controlStyle={{marginTop: 7}}
903
- label="Fruit"
904
- searchSource={fruitSource}
905
- value={fruits[0]}
906
- onChange={onChange}
907
- onClick={onClick}
908
- onBlur={onBlur}
909
- debounceMs={0}
910
- />
911
- </InputGroup>,
912
- );
913
- const surface = container.querySelector<HTMLElement>('.astryx-typeahead')!;
914
- const input = screen.getByRole('combobox');
915
-
916
- expect(ref.current).toBe(input);
917
- expect(input).toHaveAttribute('id', 'fruit-field');
918
- expect(input).toHaveAttribute('data-tracking', 'fruit-picker');
919
- expect(surface).toHaveClass('custom-typeahead');
920
- expect(surface).toHaveStyle({marginTop: '7px'});
921
- expect(input).toHaveAccessibleName('Produce Fruit');
922
-
923
- fireEvent.click(surface);
924
- await waitFor(() => expect(input).toHaveFocus());
925
- expect(input).toHaveValue('Apple');
926
- fireEvent.click(input);
927
- expect(onClick).toHaveBeenCalledOnce();
928
-
929
- fireEvent.blur(input);
930
- expect(onBlur).toHaveBeenCalledOnce();
931
- expect(onChange).not.toHaveBeenCalled();
932
- });
933
- });
934
-
935
- describe('Typeahead size', () => {
936
- it('renders with size="lg"', () => {
937
- render(
938
- <Typeahead
939
- label="Fruit"
940
- searchSource={fruitSource}
941
- value={null}
942
- onChange={() => {}}
943
- size="lg"
944
- />,
945
- );
946
- expect(screen.getByLabelText('Fruit')).toBeInTheDocument();
947
- });
948
- });
949
-
950
- describe('BaseTypeahead hasEntriesOnFocus', () => {
951
- it('does not commit a loading cycle for an empty synchronous bootstrap', async () => {
952
- const bootstrap = vi.fn((): SearchableItem[] => []);
953
- const onRender = vi.fn<ProfilerOnRenderCallback>();
954
- render(
955
- <Profiler id="typeahead" onRender={onRender}>
956
- <BaseTypeahead
957
- searchSource={{search: () => [], bootstrap}}
958
- value={null}
959
- onChange={() => {}}
960
- hasEntriesOnFocus
961
- />
962
- </Profiler>,
963
- );
964
- const input = screen.getByRole('combobox');
965
- onRender.mockClear();
966
-
967
- await act(async () => {
968
- fireEvent.focus(input);
969
- await Promise.resolve();
970
- });
971
-
972
- expect(bootstrap).toHaveBeenCalledOnce();
973
- expect(onRender).not.toHaveBeenCalled();
974
- });
975
-
976
- it('does not report loading for synchronous bootstrap results', async () => {
977
- const lane = createBusyIndicatorLane();
978
- const onLoadingChange = vi.fn(lane.onBusyChange);
979
- render(
980
- <BusyIndicatorLaneProvider
981
- value={{...lane, onBusyChange: onLoadingChange}}>
982
- <BaseTypeahead
983
- searchSource={fruitSource}
984
- value={null}
985
- onChange={() => {}}
986
- hasEntriesOnFocus
987
- />
988
- </BusyIndicatorLaneProvider>,
989
- );
990
- const input = screen.getByRole('combobox');
991
-
992
- fireEvent.focus(input);
993
- await waitFor(() => {
994
- expect(input).toHaveAttribute('aria-expanded', 'true');
995
- });
996
-
997
- expect(onLoadingChange).not.toHaveBeenCalled();
998
- });
999
-
1000
- it('clears a pending search loading state before synchronous bootstrap', async () => {
1001
- let settleSearch: (items: SearchableItem[]) => void = () => {};
1002
- const searchSource: SearchSource = {
1003
- search: async () =>
1004
- new Promise<SearchableItem[]>(resolve => {
1005
- settleSearch = resolve;
1006
- }),
1007
- bootstrap: () => [],
1008
- };
1009
- render(
1010
- <BaseTypeahead
1011
- searchSource={searchSource}
1012
- value={null}
1013
- onChange={() => {}}
1014
- hasEntriesOnFocus
1015
- debounceMs={0}
1016
- />,
1017
- );
1018
- const input = screen.getByRole('combobox');
1019
-
1020
- fireEvent.change(input, {target: {value: 'a'}});
1021
- await waitFor(() => {
1022
- expect(getBusySpinner()).toBeInTheDocument();
1023
- });
1024
-
1025
- fireEvent.change(input, {target: {value: ''}});
1026
- await waitFor(() => {
1027
- expect(queryBusySpinner()).not.toBeInTheDocument();
1028
- });
1029
-
1030
- await act(async () => {
1031
- settleSearch(fruits.slice(0, 1));
1032
- await Promise.resolve();
1033
- });
1034
- expect(input).toHaveAttribute('aria-expanded', 'false');
1035
- });
1036
-
1037
- it('keeps the loading state for an asynchronous bootstrap', async () => {
1038
- let settle: (items: SearchableItem[]) => void = () => {};
1039
- const bootstrap = vi.fn(
1040
- async () =>
1041
- new Promise<SearchableItem[]>(resolve => {
1042
- settle = resolve;
1043
- }),
1044
- );
1045
- render(
1046
- <BaseTypeahead
1047
- searchSource={{search: () => [], bootstrap}}
1048
- value={null}
1049
- onChange={() => {}}
1050
- hasEntriesOnFocus
1051
- />,
1052
- );
1053
- const input = screen.getByRole('combobox');
1054
-
1055
- fireEvent.focus(input);
1056
- await waitFor(() => {
1057
- expect(getBusySpinner()).toBeInTheDocument();
1058
- });
1059
-
1060
- await act(async () => {
1061
- settle([]);
1062
- await Promise.resolve();
1063
- });
1064
- expect(queryBusySpinner()).not.toBeInTheDocument();
1065
- });
1066
-
1067
- it('clears the loading state when the search source is replaced during an in-flight load', async () => {
1068
- let settle: (items: SearchableItem[]) => void = () => {};
1069
- const bootstrap = vi.fn(
1070
- async () =>
1071
- new Promise<SearchableItem[]>(resolve => {
1072
- settle = resolve;
1073
- }),
1074
- );
1075
- const initialSource: SearchSource = {search: () => [], bootstrap};
1076
- const replacementSource: SearchSource = {
1077
- search: () => [],
1078
- bootstrap: () => [],
1079
- };
1080
-
1081
- const {rerender} = render(
1082
- <BaseTypeahead
1083
- searchSource={initialSource}
1084
- value={null}
1085
- onChange={() => {}}
1086
- hasEntriesOnFocus
1087
- />,
1088
- );
1089
- const input = screen.getByRole('combobox');
1090
-
1091
- fireEvent.focus(input);
1092
- await waitFor(() => {
1093
- expect(getBusySpinner()).toBeInTheDocument();
1094
- });
1095
-
1096
- rerender(
1097
- <BaseTypeahead
1098
- searchSource={replacementSource}
1099
- value={null}
1100
- onChange={() => {}}
1101
- hasEntriesOnFocus
1102
- />,
1103
- );
1104
-
1105
- await waitFor(() => {
1106
- expect(queryBusySpinner()).not.toBeInTheDocument();
1107
- });
1108
-
1109
- await act(async () => {
1110
- settle([]);
1111
- await Promise.resolve();
1112
- });
1113
- expect(queryBusySpinner()).not.toBeInTheDocument();
1114
- });
1115
-
1116
- it('keeps the loading state under StrictMode for a pending autofocus bootstrap', async () => {
1117
- let settle: (items: SearchableItem[]) => void = () => {};
1118
- const bootstrap = vi.fn(
1119
- async () =>
1120
- new Promise<SearchableItem[]>(resolve => {
1121
- settle = resolve;
1122
- }),
1123
- );
1124
- render(
1125
- <StrictMode>
1126
- <Typeahead
1127
- label="Test"
1128
- searchSource={{search: () => [], bootstrap}}
1129
- value={null}
1130
- onChange={() => {}}
1131
- hasAutoFocus
1132
- hasEntriesOnFocus
1133
- />
1134
- </StrictMode>,
1135
- );
1136
-
1137
- expect(getBusySpinner()).toBeInTheDocument();
1138
-
1139
- await act(async () => {
1140
- settle([]);
1141
- await Promise.resolve();
1142
- });
1143
- expect(queryBusySpinner()).not.toBeInTheDocument();
1144
- });
1145
-
1146
- let settlePendingTransitionA: (items: SearchableItem[]) => void = () => {};
1147
- const stableTransitionSourceA: SearchSource = {
1148
- search: vi.fn(
1149
- async () =>
1150
- new Promise<SearchableItem[]>(resolve => {
1151
- settlePendingTransitionA = resolve;
1152
- }),
1153
- ),
1154
- bootstrap: () => [],
1155
- };
1156
- const stableTransitionSourceB: SearchSource = {
1157
- search: vi.fn(async () => []),
1158
- bootstrap: () => [],
1159
- };
1160
-
1161
- it('clears loading and aria-busy when source is replaced in a transition that suspends and retries', async () => {
1162
- let isSuspended = false;
1163
- let resolveSuspension: () => void = () => {};
1164
- let pendingSuspension: Promise<void> | null = null;
1165
-
1166
- function SuspendingSibling({shouldSuspend}: {shouldSuspend: boolean}) {
1167
- if (shouldSuspend && isSuspended && pendingSuspension) {
1168
- // eslint-disable-next-line @typescript-eslint/only-throw-error
1169
- throw pendingSuspension;
1170
- }
1171
- return null;
1172
- }
1173
-
1174
- let updateState!: (source: SearchSource, shouldSuspend: boolean) => void;
1175
-
1176
- function TransitionContainer() {
1177
- const [source, setSource] = useState<SearchSource>(
1178
- stableTransitionSourceA,
1179
- );
1180
- const [shouldSuspend, setShouldSuspend] = useState(false);
1181
- updateState = (newSource, suspend) => {
1182
- setSource(newSource);
1183
- setShouldSuspend(suspend);
1184
- };
1185
-
1186
- return (
1187
- <Suspense fallback={<div>Suspended...</div>}>
1188
- <Typeahead
1189
- label="Test"
1190
- searchSource={source}
1191
- value={null}
1192
- onChange={() => {}}
1193
- debounceMs={0}
1194
- />
1195
- <SuspendingSibling shouldSuspend={shouldSuspend} />
1196
- </Suspense>
1197
- );
1198
- }
1199
-
1200
- render(<TransitionContainer />);
1201
- const input = screen.getByRole('combobox');
1202
-
1203
- // Trigger a load so aria-busy="true" is set on the combobox
1204
- fireEvent.change(input, {target: {value: 'query'}});
1205
- await waitFor(() => {
1206
- expect(input).toHaveAttribute('aria-busy', 'true');
1207
- });
1208
- expect(getBusySpinner()).toBeInTheDocument();
1209
-
1210
- // Create the pending promise for suspension
1211
- isSuspended = true;
1212
- pendingSuspension = new Promise<void>(resolve => {
1213
- resolveSuspension = () => {
1214
- isSuspended = false;
1215
- resolve();
1216
- };
1217
- });
1218
-
1219
- // Inside startTransition, switch from source A to source B while sibling suspends
1220
- act(() => {
1221
- startTransition(() => {
1222
- updateState(stableTransitionSourceB, true);
1223
- });
1224
- });
1225
-
1226
- // The render suspended, so the previous committed state remains
1227
- expect(input).toHaveAttribute('aria-busy', 'true');
1228
-
1229
- // Resolve the pending promise and let the transition commit
1230
- await act(async () => {
1231
- resolveSuspension();
1232
- await pendingSuspension;
1233
- });
1234
-
1235
- // Settle the abandoned load
1236
- await act(async () => {
1237
- settlePendingTransitionA([]);
1238
- await Promise.resolve();
1239
- });
1240
-
1241
- // The combobox must not keep aria-busy and the spinner must be gone
1242
- expect(input).not.toHaveAttribute('aria-busy');
1243
- expect(queryBusySpinner()).not.toBeInTheDocument();
1244
- });
1245
-
1246
- it('shows bootstrap results on mouse click', async () => {
1247
- render(
1248
- <BaseTypeahead
1249
- searchSource={fruitSource}
1250
- value={null}
1251
- onChange={() => {}}
1252
- hasEntriesOnFocus
1253
- debounceMs={0}
1254
- />,
1255
- );
1256
- const input = screen.getByRole('combobox');
1257
-
1258
- // Simulate full mouse click sequence (pointerdown → focus → pointerup → click)
1259
- fireEvent.pointerDown(input);
1260
- fireEvent.focus(input);
1261
- fireEvent.pointerUp(input);
1262
- fireEvent.click(input);
1263
-
1264
- await waitFor(() => {
1265
- expect(input).toHaveAttribute('aria-expanded', 'true');
1266
- });
1267
- });
1268
-
1269
- it('shows bootstrap results on keyboard focus', async () => {
1270
- render(
1271
- <BaseTypeahead
1272
- searchSource={fruitSource}
1273
- value={null}
1274
- onChange={() => {}}
1275
- hasEntriesOnFocus
1276
- debounceMs={0}
1277
- />,
1278
- );
1279
- const input = screen.getByRole('combobox');
1280
-
1281
- // Keyboard focus — no pointer events
1282
- fireEvent.focus(input);
1283
-
1284
- await waitFor(() => {
1285
- expect(input).toHaveAttribute('aria-expanded', 'true');
1286
- });
1287
- });
1288
-
1289
- it('re-shows results on refocus when results already exist', async () => {
1290
- render(
1291
- <BaseTypeahead
1292
- searchSource={fruitSource}
1293
- value={null}
1294
- onChange={() => {}}
1295
- hasEntriesOnFocus
1296
- debounceMs={0}
1297
- />,
1298
- );
1299
- const input = screen.getByRole('combobox');
1300
-
1301
- // Initial focus to load bootstrap results
1302
- fireEvent.focus(input);
1303
- await waitFor(() => {
1304
- expect(input).toHaveAttribute('aria-expanded', 'true');
1305
- });
1306
-
1307
- // Blur to close, then refocus
1308
- fireEvent.blur(input);
1309
- fireEvent.focus(input);
1310
-
1311
- await waitFor(() => {
1312
- expect(input).toHaveAttribute('aria-expanded', 'true');
1313
- });
1314
- });
1315
-
1316
- it('reopens on click when the input was already focused and the dropdown closed without a blur (#6845)', async () => {
1317
- const user = userEvent.setup();
1318
- render(
1319
- <BaseTypeahead
1320
- searchSource={fruitSource}
1321
- value={null}
1322
- onChange={() => {}}
1323
- hasEntriesOnFocus
1324
- debounceMs={0}
1325
- />,
1326
- );
1327
- const input = screen.getByRole('combobox');
1328
-
1329
- // A real click focuses the input (justFocusedRef suppresses the click
1330
- // handler's own open logic here, since handleFocus already opened it).
1331
- await user.click(input);
1332
- await waitFor(() => {
1333
- expect(input).toHaveAttribute('aria-expanded', 'true');
1334
- });
1335
-
1336
- // Escape closes the dropdown via popover.hide() alone — same as
1337
- // selecting a result (which also re-focuses the input internally) or a
1338
- // composing field re-focusing this input after committing something
1339
- // elsewhere, the input is never actually blurred.
1340
- await user.keyboard('{Escape}');
1341
- await waitFor(() => {
1342
- expect(input).toHaveAttribute('aria-expanded', 'false');
1343
- });
1344
- expect(input).toHaveFocus();
1345
-
1346
- // Clicking the still-focused input must reopen it — before the fix,
1347
- // this click dispatched no focus event (the input never lost focus) and
1348
- // nothing else reopened the dropdown.
1349
- await user.click(input);
1350
- await waitFor(() => {
1351
- expect(input).toHaveAttribute('aria-expanded', 'true');
1352
- });
1353
- });
1354
-
1355
- it('does not double-fire an async bootstrap source on a single first click (#6848 review)', async () => {
1356
- // A microtask-based "did a focus event just run" flag, tried first,
1357
- // cleared before the click event arrived for an async source — the
1358
- // click path then ran openIfEligible a second time while the first
1359
- // bootstrap call was still in flight. The fix reads document.activeElement
1360
- // at pointerdown instead, which has no event-loop timing dependency.
1361
- const user = userEvent.setup();
1362
- const bootstrap = vi.fn(async (): Promise<SearchableItem[]> => {
1363
- await Promise.resolve();
1364
- return fruits.slice(0, 3);
1365
- });
1366
- render(
1367
- <BaseTypeahead
1368
- searchSource={{search: () => [], bootstrap}}
1369
- value={null}
1370
- onChange={() => {}}
1371
- hasEntriesOnFocus
1372
- debounceMs={0}
1373
- />,
1374
- );
1375
- const input = screen.getByRole('combobox');
1376
-
1377
- await user.click(input);
1378
- await waitFor(() => {
1379
- expect(input).toHaveAttribute('aria-expanded', 'true');
1380
- });
1381
-
1382
- expect(bootstrap).toHaveBeenCalledTimes(1);
1383
- });
1384
- });
1385
-
1386
- describe('BaseTypeahead minQueryLength', () => {
1387
- it('does not search or open the menu below the threshold', async () => {
1388
- const search = vi.fn((query: string) =>
1389
- fruits.filter(f => f.label.toLowerCase().includes(query.toLowerCase())),
1390
- );
1391
- render(
1392
- <BaseTypeahead
1393
- searchSource={{search, bootstrap: () => []}}
1394
- value={null}
1395
- onChange={() => {}}
1396
- minQueryLength={3}
1397
- debounceMs={0}
1398
- />,
1399
- );
1400
- const input = screen.getByRole('combobox');
1401
-
1402
- fireEvent.change(input, {target: {value: 'Ap'}});
1403
- await act(async () => {
1404
- await Promise.resolve();
1405
- });
1406
-
1407
- expect(search).not.toHaveBeenCalled();
1408
- expect(input).toHaveAttribute('aria-expanded', 'false');
1409
-
1410
- // Positive control: the third character crosses the threshold, so the
1411
- // same harness does see the search and the open menu.
1412
- fireEvent.change(input, {target: {value: 'App'}});
1413
- await waitFor(() => {
1414
- expect(input).toHaveAttribute('aria-expanded', 'true');
1415
- });
1416
- expect(search).toHaveBeenCalledExactlyOnceWith('App');
1417
- });
1418
-
1419
- it('closes the menu again when the query falls back below the threshold', async () => {
1420
- render(
1421
- <BaseTypeahead
1422
- searchSource={fruitSource}
1423
- value={null}
1424
- onChange={() => {}}
1425
- minQueryLength={3}
1426
- debounceMs={0}
1427
- />,
1428
- );
1429
- const input = screen.getByRole('combobox');
1430
-
1431
- fireEvent.change(input, {target: {value: 'App'}});
1432
- await waitFor(() => {
1433
- expect(input).toHaveAttribute('aria-expanded', 'true');
1434
- });
1435
-
1436
- fireEvent.change(input, {target: {value: 'Ap'}});
1437
- await waitFor(() => {
1438
- expect(input).toHaveAttribute('aria-expanded', 'false');
1439
- });
1440
- });
1441
-
1442
- it('does not fall back to bootstrap entries on ArrowDown below the threshold', async () => {
1443
- const bootstrap = vi.fn(() => fruits.slice(0, 3));
1444
- render(
1445
- <BaseTypeahead
1446
- searchSource={{search: () => [], bootstrap}}
1447
- value={null}
1448
- onChange={() => {}}
1449
- hasEntriesOnFocus
1450
- minQueryLength={3}
1451
- debounceMs={0}
1452
- />,
1453
- );
1454
- const input = screen.getByRole('combobox');
1455
-
1456
- fireEvent.focus(input);
1457
- await waitFor(() => {
1458
- expect(input).toHaveAttribute('aria-expanded', 'true');
1459
- });
1460
- bootstrap.mockClear();
1461
-
1462
- // Typing below the threshold closes the bootstrap menu...
1463
- fireEvent.change(input, {target: {value: 'Ap'}});
1464
- await waitFor(() => {
1465
- expect(input).toHaveAttribute('aria-expanded', 'false');
1466
- });
1467
-
1468
- // ...and ArrowDown must not re-open it with entries that ignore the
1469
- // two characters already typed.
1470
- fireEvent.keyDown(input, {key: 'ArrowDown'});
1471
- await act(async () => {
1472
- await Promise.resolve();
1473
- });
1474
- expect(bootstrap).not.toHaveBeenCalled();
1475
- expect(input).toHaveAttribute('aria-expanded', 'false');
1476
- });
1477
-
1478
- it('searches on the first character when minQueryLength is not set', async () => {
1479
- const search = vi.fn(() => fruits.slice(0, 1));
1480
- render(
1481
- <BaseTypeahead
1482
- searchSource={{search, bootstrap: () => []}}
1483
- value={null}
1484
- onChange={() => {}}
1485
- debounceMs={0}
1486
- />,
1487
- );
1488
- const input = screen.getByRole('combobox');
1489
-
1490
- fireEvent.change(input, {target: {value: 'A'}});
1491
- await waitFor(() => {
1492
- expect(input).toHaveAttribute('aria-expanded', 'true');
1493
- });
1494
- expect(search).toHaveBeenCalledExactlyOnceWith('A');
1495
- });
1496
-
1497
- it('stops reporting "Loading" when the query falls below the threshold mid-search', async () => {
1498
- // Falling below the threshold abandons the in-flight search by bumping the
1499
- // search generation, which also makes that search decline to clear the
1500
- // loading flag on its way out. Backspacing from three characters to two on
1501
- // a remote source is the everyday way to hit it, and the field would
1502
- // otherwise report "Loading" to a screen reader until the third character
1503
- // went back in.
1504
- let settle: (items: SearchableItem[]) => void = () => {};
1505
- const search = vi.fn(
1506
- async () =>
1507
- new Promise<SearchableItem[]>(resolve => {
1508
- settle = resolve;
1509
- }),
1510
- );
1511
- render(
1512
- <BaseTypeahead
1513
- searchSource={{search, bootstrap: () => []}}
1514
- value={null}
1515
- onChange={() => {}}
1516
- minQueryLength={3}
1517
- debounceMs={0}
1518
- />,
1519
- );
1520
- const input = screen.getByRole('combobox');
1521
-
1522
- fireEvent.change(input, {target: {value: 'App'}});
1523
- await waitFor(() => {
1524
- expect(getBusySpinner()).toBeInTheDocument();
1525
- });
1526
- expect(input).toHaveAttribute('aria-busy', 'true');
1527
-
1528
- fireEvent.change(input, {target: {value: 'Ap'}});
1529
- await act(async () => {
1530
- settle(fruits.slice(0, 1));
1531
- await Promise.resolve();
1532
- });
1533
-
1534
- expect(queryBusySpinner()).not.toBeInTheDocument();
1535
- expect(input).not.toHaveAttribute('aria-busy');
1536
- expect(input).toHaveAttribute('aria-expanded', 'false');
1537
- });
1538
- });
1539
-
1540
- describe('BaseTypeahead hasSearched reset', () => {
1541
- it('does not show "No results found" after selecting an item and re-entering', async () => {
1542
- const onChange = vi.fn();
1543
- const {rerender} = render(
1544
- <BaseTypeahead
1545
- searchSource={fruitSource}
1546
- value={null}
1547
- onChange={onChange}
1548
- debounceMs={0}
1549
- />,
1550
- );
1551
- const input = screen.getByRole('combobox');
1552
-
1553
- // Type a query that returns results
1554
- fireEvent.change(input, {target: {value: 'Apple'}});
1555
- await waitFor(() => {
1556
- expect(screen.getByText('Apple')).toBeInTheDocument();
1557
- });
1558
-
1559
- // Select the item
1560
- fireEvent.click(screen.getByText('Apple'));
1561
- expect(onChange).toHaveBeenCalledWith(fruits[0]);
1562
-
1563
- // Re-render with the selected value
1564
- rerender(
1565
- <BaseTypeahead
1566
- searchSource={fruitSource}
1567
- value={fruits[0]}
1568
- onChange={onChange}
1569
- debounceMs={0}
1570
- />,
1571
- );
1572
-
1573
- // Focus the input again — "No results found" should NOT appear
1574
- fireEvent.focus(input);
1575
-
1576
- // The empty state text should not be visible since hasSearched was reset
1577
- expect(screen.queryByText('No results found')).not.toBeInTheDocument();
1578
- });
1579
-
1580
- it('resets hasSearched when query is cleared without hasEntriesOnFocus', async () => {
1581
- render(
1582
- <BaseTypeahead
1583
- searchSource={fruitSource}
1584
- value={null}
1585
- onChange={() => {}}
1586
- debounceMs={0}
1587
- />,
1588
- );
1589
- const input = screen.getByRole('combobox');
1590
-
1591
- // Type a query that returns no results
1592
- fireEvent.change(input, {target: {value: 'xyz'}});
1593
- await waitFor(() => {
1594
- expect(screen.getByText('No results found')).toBeInTheDocument();
1595
- });
1596
-
1597
- // Clear the query
1598
- fireEvent.change(input, {target: {value: ''}});
1599
-
1600
- // "No results found" should disappear since hasSearched is reset
1601
- expect(screen.queryByText('No results found')).not.toBeInTheDocument();
1602
- });
1603
-
1604
- it('resets hasSearched and does not announce "No results found" when search source is replaced during a pending search', async () => {
1605
- let settle: (items: SearchableItem[]) => void = () => {};
1606
- const initialSource: SearchSource = {
1607
- search: vi.fn(
1608
- async () =>
1609
- new Promise<SearchableItem[]>(resolve => {
1610
- settle = resolve;
1611
- }),
1612
- ),
1613
- bootstrap: () => [],
1614
- };
1615
- const replacementSource: SearchSource = {
1616
- search: vi.fn(async () => []),
1617
- bootstrap: () => [],
1618
- };
1619
-
1620
- const {rerender} = render(
1621
- <BaseTypeahead
1622
- searchSource={initialSource}
1623
- value={null}
1624
- onChange={() => {}}
1625
- debounceMs={0}
1626
- />,
1627
- );
1628
- const input = screen.getByRole('combobox');
1629
-
1630
- // Type a query that starts an asynchronous search
1631
- fireEvent.change(input, {target: {value: 'abc'}});
1632
- expect(initialSource.search).toHaveBeenCalledWith('abc');
1633
-
1634
- // Replace the search source while the search is still pending
1635
- rerender(
1636
- <BaseTypeahead
1637
- searchSource={replacementSource}
1638
- value={null}
1639
- onChange={() => {}}
1640
- debounceMs={0}
1641
- />,
1642
- );
1643
-
1644
- // Settle the abandoned search
1645
- await act(async () => {
1646
- settle([]);
1647
- await Promise.resolve();
1648
- });
1649
-
1650
- await act(
1651
- async () =>
1652
- new Promise<void>(resolve => requestAnimationFrame(() => resolve())),
1653
- );
1654
- expect(
1655
- document.querySelector('[data-astryx-live-region="polite"]')
1656
- ?.textContent ?? '',
1657
- ).not.toContain('No results found');
1658
- expect(screen.queryByText('No results found')).not.toBeInTheDocument();
1659
- });
1660
- });
1661
-
1662
- describe('BaseTypeahead popover after selection', () => {
1663
- it('does not show an empty popover after selecting an item with hasEntriesOnFocus', async () => {
1664
- const onChange = vi.fn();
1665
- render(
1666
- <BaseTypeahead
1667
- searchSource={fruitSource}
1668
- value={null}
1669
- onChange={onChange}
1670
- hasEntriesOnFocus
1671
- debounceMs={0}
1672
- />,
1673
- );
1674
- const input = screen.getByRole('combobox');
1675
-
1676
- // Focus to open bootstrap results
1677
- fireEvent.focus(input);
1678
- await waitFor(() => {
1679
- expect(input).toHaveAttribute('aria-expanded', 'true');
1680
- });
1681
-
1682
- // Select an item — popover should close
1683
- fireEvent.click(screen.getByText('Apple'));
1684
- expect(onChange).toHaveBeenCalledWith(fruits[0]);
1685
-
1686
- // After selection, input is refocused but popover should NOT reopen
1687
- // with an empty menu. The handleFocus handler should be suppressed.
1688
- await waitFor(() => {
1689
- expect(input).toHaveAttribute('aria-expanded', 'false');
1690
- });
1691
- });
1692
- });
1693
-
1694
- describe('Typeahead edit mode', () => {
1695
- // iOS raises the software keyboard only for a focus made while the browser
1696
- // still dispatches the person's tap, so the focus must not wait for a frame.
1697
- it.each([
1698
- ['the token', () => screen.getByText(fruits[0].label)],
1699
- [
1700
- 'the field around it',
1701
- () => screen.getByText(fruits[0].label).closest('div')!,
1702
- ],
1703
- ])(
1704
- 'focuses the input with the label selected inside the tap on %s',
1705
- (_name, target) => {
1706
- const onChangeQuery = vi.fn();
1707
- render(
1708
- <Typeahead
1709
- label="Fruit"
1710
- searchSource={fruitSource}
1711
- value={fruits[0]}
1712
- onChange={vi.fn()}
1713
- onChangeQuery={onChangeQuery}
1714
- />,
1715
- );
1716
- const input = screen.getByRole<HTMLInputElement>('combobox');
1717
- // The event the browser is dispatching when focus() runs.
1718
- const focusedDuring: (string | undefined)[] = [];
1719
- // An own property: some test setups make `focus` an accessor on the
1720
- // prototype, which a plain assignment cannot shadow.
1721
- const focus = HTMLElement.prototype.focus;
1722
- Object.defineProperty(input, 'focus', {
1723
- configurable: true,
1724
- value(options?: FocusOptions) {
1725
- focusedDuring.push(window.event?.type);
1726
- focus.call(input, options);
1727
- },
1728
- });
1729
-
1730
- fireEvent.click(target());
1731
-
1732
- // No frame has run: the focus, the text and its selection are all in
1733
- // place by the time the tap's own dispatch returns.
1734
- expect(input).toHaveFocus();
1735
- expect(input.value).toBe(fruits[0].label);
1736
- expect(input.selectionStart).toBe(0);
1737
- expect(input.selectionEnd).toBe(fruits[0].label.length);
1738
- expect(focusedDuring.length).toBeGreaterThan(0);
1739
- expect(focusedDuring.every(type => type === 'click')).toBe(true);
1740
- expect(onChangeQuery).toHaveBeenLastCalledWith(fruits[0].label);
1741
- },
1742
- );
1743
-
1744
- // The base sees the label as its query by the time the focus lands, so a
1745
- // hasEntriesOnFocus field searches the label instead of opening its
1746
- // bootstrap entries, and Enter cannot commit a bootstrap item.
1747
- it('with hasEntriesOnFocus, editing the value searches its label instead of opening the bootstrap entries', async () => {
1748
- const onChange = vi.fn();
1749
- render(
1750
- <Typeahead
1751
- label="Fruit"
1752
- searchSource={fruitSource}
1753
- value={fruits[3]}
1754
- onChange={onChange}
1755
- hasEntriesOnFocus
1756
- debounceMs={0}
1757
- />,
1758
- );
1759
- const input = screen.getByRole<HTMLInputElement>('combobox');
1760
-
1761
- fireEvent.click(screen.getByText(fruits[3].label));
1762
-
1763
- expect(input).toHaveFocus();
1764
- expect(input.value).toBe(fruits[3].label);
1765
- // Let any bootstrap or search settle (debounceMs is 0).
1766
- await act(async () => {
1767
- await new Promise(resolve => setTimeout(resolve, 20));
1768
- });
1769
-
1770
- // Apple is the first bootstrap entry, which the label does not match.
1771
- fireEvent.keyDown(input, {key: 'Enter'});
1772
- expect(onChange).not.toHaveBeenCalledWith(fruits[0]);
1773
- });
1774
-
1775
- it('enters edit mode on token container click', () => {
1776
- const onChange = vi.fn();
1777
- render(
1778
- <Typeahead
1779
- label="Fruit"
1780
- searchSource={fruitSource}
1781
- value={fruits[0]}
1782
- onChange={onChange}
1783
- />,
1784
- );
1785
- screen.getByRole('combobox');
1786
-
1787
- // Click the token text to enter edit mode
1788
- const tokenText = screen.getByText(fruits[0].label);
1789
- const tokenContainer = tokenText.closest('div')!;
1790
- fireEvent.click(tokenContainer);
1791
-
1792
- // onChange should NOT have been called (value is preserved for restore)
1793
- expect(onChange).not.toHaveBeenCalled();
1794
- });
1795
-
1796
- it('empties the input when cleared while editing the selected value (#7038)', async () => {
1797
- const onChangeQuery = vi.fn();
1798
- function Controlled() {
1799
- const [value, setValue] = useState<SearchableItem | null>(fruits[0]);
1800
- return (
1801
- <Typeahead
1802
- label="Fruit"
1803
- searchSource={fruitSource}
1804
- value={value}
1805
- onChange={setValue}
1806
- onChangeQuery={onChangeQuery}
1807
- hasClear
1808
- />
1809
- );
1810
- }
1811
- render(<Controlled />);
1812
- const input = screen.getByRole<HTMLInputElement>('combobox');
1813
-
1814
- // Enter edit mode: the token gives way to the label as editable text
1815
- fireEvent.click(screen.getByText(fruits[0].label).closest('div')!);
1816
- await waitFor(() => expect(input.value).toBe(fruits[0].label));
1817
-
1818
- fireEvent.click(screen.getByRole('button', {name: 'Clear selection'}));
1819
-
1820
- // The value and the visible text agree: nothing selected, nothing typed
1821
- await waitFor(() => expect(input.value).toBe(''));
1822
- expect(onChangeQuery).toHaveBeenLastCalledWith('');
1823
- expect(input).toHaveFocus();
1824
- });
1825
-
1826
- // Escape and blur restore the token but leave the edit-mode label in the
1827
- // base's query, so a clear from the restored token faces the same stale text.
1828
- it.each([
1829
- [
1830
- 'Escape',
1831
- (input: HTMLElement) => fireEvent.keyDown(input, {key: 'Escape'}),
1832
- ],
1833
- ['blur', (input: HTMLElement) => fireEvent.blur(input)],
1834
- ])(
1835
- 'empties the input when cleared after %s restored the token (#7038)',
1836
- async (_name, leaveEditMode) => {
1837
- const onChangeQuery = vi.fn();
1838
- function Controlled() {
1839
- const [value, setValue] = useState<SearchableItem | null>(fruits[0]);
1840
- return (
1841
- <Typeahead
1842
- label="Fruit"
1843
- searchSource={fruitSource}
1844
- value={value}
1845
- onChange={setValue}
1846
- onChangeQuery={onChangeQuery}
1847
- hasClear
1848
- />
1849
- );
1850
- }
1851
- render(<Controlled />);
1852
- const input = screen.getByRole<HTMLInputElement>('combobox');
1853
-
1854
- fireEvent.click(screen.getByText(fruits[0].label).closest('div')!);
1855
- await waitFor(() => expect(input.value).toBe(fruits[0].label));
1856
-
1857
- leaveEditMode(input);
1858
- await screen.findByRole('button', {name: 'Clear selection'});
1859
-
1860
- fireEvent.click(screen.getByRole('button', {name: 'Clear selection'}));
1861
-
1862
- await waitFor(() => expect(input.value).toBe(''));
1863
- expect(onChangeQuery).toHaveBeenLastCalledWith('');
1864
- },
1865
- );
1866
-
1867
- it('does not report a query change when cleared outside edit mode', () => {
1868
- const onChangeQuery = vi.fn();
1869
- render(
1870
- <Typeahead
1871
- label="Fruit"
1872
- searchSource={fruitSource}
1873
- value={fruits[0]}
1874
- onChange={vi.fn()}
1875
- onChangeQuery={onChangeQuery}
1876
- hasClear
1877
- />,
1878
- );
1879
-
1880
- fireEvent.click(screen.getByRole('button', {name: 'Clear selection'}));
1881
-
1882
- expect(onChangeQuery).not.toHaveBeenCalled();
1883
- });
1884
-
1885
- it('restores token on blur without action', async () => {
1886
- const onChange = vi.fn();
1887
- render(
1888
- <Typeahead
1889
- label="Fruit"
1890
- searchSource={fruitSource}
1891
- value={fruits[0]}
1892
- onChange={onChange}
1893
- />,
1894
- );
1895
- const input = screen.getByRole('combobox');
1896
-
1897
- // Enter edit mode
1898
- const tokenText = screen.getByText(fruits[0].label);
1899
- fireEvent.click(tokenText.closest('div')!);
1900
-
1901
- // Blur without selecting anything
1902
- fireEvent.blur(input);
1903
-
1904
- // onChange should not have been called — value restored
1905
- expect(onChange).not.toHaveBeenCalled();
1906
- });
1907
-
1908
- it('does not exit edit mode on a composing Escape (IME)', async () => {
1909
- const onChange = vi.fn();
1910
- render(
1911
- <Typeahead
1912
- label="Fruit"
1913
- searchSource={fruitSource}
1914
- value={fruits[0]}
1915
- onChange={onChange}
1916
- />,
1917
- );
1918
-
1919
- // Enter edit mode by clicking the token; the input uncollapses and
1920
- // rejoins the Tab order (tabindex is cleared).
1921
- const tokenText = screen.getByText(fruits[0].label);
1922
- fireEvent.click(tokenText.closest('div')!);
1923
- await act(async () => {
1924
- await new Promise(r => requestAnimationFrame(r));
1925
- });
1926
- const input = screen.getByRole('combobox');
1927
- expect(input).not.toHaveAttribute('tabindex', '-1');
1928
-
1929
- // BaseTypeahead invokes this external handler before its own IME guard, so
1930
- // a composing Escape (isComposing / legacy keyCode 229) — which an IME uses
1931
- // to cancel the pending candidate — must not exit edit mode here.
1932
- fireEvent.keyDown(input, {key: 'Escape', isComposing: true});
1933
- expect(screen.getByRole('combobox')).not.toHaveAttribute('tabindex', '-1');
1934
- fireEvent.keyDown(input, {key: 'Escape', keyCode: 229});
1935
- expect(screen.getByRole('combobox')).not.toHaveAttribute('tabindex', '-1');
1936
-
1937
- // A real, non-composing Escape still exits edit mode: the token is
1938
- // restored and the collapsed input drops back out of the Tab order.
1939
- fireEvent.keyDown(input, {key: 'Escape'});
1940
- expect(screen.getByRole('combobox')).toHaveAttribute('tabindex', '-1');
1941
- });
1942
- });
1943
-
1944
- describe('Typeahead collapsed input tab order', () => {
1945
- it('removes the invisible input from the Tab order while a token is shown', () => {
1946
- render(
1947
- <Typeahead
1948
- label="Fruit"
1949
- searchSource={fruitSource}
1950
- value={fruits[0]}
1951
- onChange={() => {}}
1952
- />,
1953
- );
1954
- // While the token is shown the input is collapsed (width 0 / opacity 0);
1955
- // it must stay programmatically focusable for token interactions but must
1956
- // not be an invisible Tab stop (WCAG 2.4.3 / 2.4.7).
1957
- expect(screen.getByRole('combobox')).toHaveAttribute('tabindex', '-1');
1958
- });
1959
-
1960
- it('Tab from the token skips the invisible input', async () => {
1961
- const user = userEvent.setup();
1962
- render(
1963
- <Typeahead
1964
- label="Fruit"
1965
- searchSource={fruitSource}
1966
- value={fruits[0]}
1967
- onChange={() => {}}
1968
- />,
1969
- );
1970
- // Focus the token's internal button, then Tab away — focus must not land
1971
- // on the visually hidden combobox input.
1972
- const tokenButton = screen.getByRole('button', {name: fruits[0].label});
1973
- tokenButton.focus();
1974
- await user.tab();
1975
- expect(screen.getByRole('combobox')).not.toHaveFocus();
1976
- });
1977
-
1978
- it('keeps the input in the Tab order when no token is shown', () => {
1979
- render(
1980
- <Typeahead
1981
- label="Fruit"
1982
- searchSource={fruitSource}
1983
- value={null}
1984
- onChange={() => {}}
1985
- />,
1986
- );
1987
- expect(screen.getByRole('combobox')).not.toHaveAttribute('tabindex');
1988
- });
1989
-
1990
- it('restores the input to the Tab order in edit mode', () => {
1991
- render(
1992
- <Typeahead
1993
- label="Fruit"
1994
- searchSource={fruitSource}
1995
- value={fruits[0]}
1996
- onChange={() => {}}
1997
- />,
1998
- );
1999
- // Entering edit mode removes the token and uncollapses the input
2000
- const tokenText = screen.getByText(fruits[0].label);
2001
- fireEvent.click(tokenText.closest('div')!);
2002
- expect(screen.getByRole('combobox')).not.toHaveAttribute('tabindex');
2003
- });
2004
- });
2005
-
2006
- describe('BaseTypeahead paste behavior', () => {
2007
- it('pasting text triggers search results like typing', async () => {
2008
- const user = userEvent.setup();
2009
- render(
2010
- <BaseTypeahead
2011
- searchSource={fruitSource}
2012
- value={null}
2013
- onChange={() => {}}
2014
- debounceMs={0}
2015
- />,
2016
- );
2017
-
2018
- const input = screen.getByRole('combobox');
2019
- await user.click(input);
2020
- await user.paste('App');
2021
-
2022
- await waitFor(() => {
2023
- expect(screen.getByText('Apple')).toBeInTheDocument();
2024
- });
2025
- });
2026
-
2027
- it('pasting non-matching text shows no results', async () => {
2028
- const user = userEvent.setup();
2029
- render(
2030
- <BaseTypeahead
2031
- searchSource={fruitSource}
2032
- value={null}
2033
- onChange={() => {}}
2034
- debounceMs={0}
2035
- />,
2036
- );
2037
-
2038
- const input = screen.getByRole('combobox');
2039
- await user.click(input);
2040
- await user.paste('xyz');
2041
-
2042
- await waitFor(() => {
2043
- expect(screen.getByText('No results found')).toBeInTheDocument();
2044
- });
2045
- });
2046
-
2047
- it('scrolls the highlighted option into view during arrow navigation', async () => {
2048
- const scrollIntoView = vi.fn();
2049
- Object.defineProperty(HTMLElement.prototype, 'scrollIntoView', {
2050
- configurable: true,
2051
- value: scrollIntoView,
2052
- });
2053
- try {
2054
- const user = userEvent.setup();
2055
- render(
2056
- <BaseTypeahead
2057
- searchSource={fruitSource}
2058
- value={null}
2059
- onChange={() => {}}
2060
- debounceMs={0}
2061
- />,
2062
- );
2063
-
2064
- const input = screen.getByRole('combobox');
2065
- await user.click(input);
2066
- await user.paste('e'); // matches multiple fruits, opens listbox
2067
- await waitFor(() => {
2068
- expect(screen.getByRole('listbox', {hidden: true})).toBeInTheDocument();
2069
- });
2070
-
2071
- scrollIntoView.mockClear();
2072
- await user.keyboard('{ArrowDown}');
2073
- await user.keyboard('{ArrowDown}');
2074
-
2075
- expect(scrollIntoView).toHaveBeenCalledWith({block: 'nearest'});
2076
- } finally {
2077
- delete (HTMLElement.prototype as unknown as {scrollIntoView?: unknown})
2078
- .scrollIntoView;
2079
- }
2080
- });
2081
-
2082
- it('highlights on hover without scrolling, keyboard still scrolls (#6077)', async () => {
2083
- // Hover must highlight only: scrollIntoView under a stationary pointer
2084
- // moves the next option under it, re-highlighting and scrolling again —
2085
- // a runaway auto-scroll loop with no user input.
2086
- const scrollIntoView = vi.fn();
2087
- Object.defineProperty(HTMLElement.prototype, 'scrollIntoView', {
2088
- configurable: true,
2089
- value: scrollIntoView,
2090
- });
2091
- try {
2092
- const user = userEvent.setup();
2093
- render(
2094
- <BaseTypeahead
2095
- searchSource={fruitSource}
2096
- value={null}
2097
- onChange={() => {}}
2098
- debounceMs={0}
2099
- />,
2100
- );
2101
-
2102
- const input = screen.getByRole('combobox');
2103
- await user.click(input);
2104
- await user.paste('e'); // matches Cherry, Date, Elderberry
2105
- await waitFor(() => {
2106
- expect(screen.getByRole('listbox', {hidden: true})).toBeInTheDocument();
2107
- });
2108
-
2109
- scrollIntoView.mockClear();
2110
- const options = screen.getAllByRole('option', {hidden: true});
2111
- fireEvent.mouseMove(options[1]);
2112
-
2113
- expect(input.getAttribute('aria-activedescendant')).toBe(options[1].id);
2114
- expect(scrollIntoView).not.toHaveBeenCalled();
2115
-
2116
- await user.keyboard('{ArrowDown}');
2117
- expect(scrollIntoView).toHaveBeenCalledWith({block: 'nearest'});
2118
- } finally {
2119
- delete (HTMLElement.prototype as unknown as {scrollIntoView?: unknown})
2120
- .scrollIntoView;
2121
- }
2122
- });
2123
- });
2124
-
2125
- describe('BaseTypeahead highlight under a resting pointer', () => {
2126
- it('keeps the keyboard highlight when an option appears under the pointer, and follows pointer movement', async () => {
2127
- // A menu that opens or reflows under a pointer the user is not moving
2128
- // fires mouseenter on the option that lands beneath it. Taking the
2129
- // highlight from that would make Enter pick an option the user never
2130
- // pointed at; only real pointer movement moves the highlight.
2131
- const user = userEvent.setup();
2132
- render(
2133
- <BaseTypeahead
2134
- searchSource={fruitSource}
2135
- value={null}
2136
- onChange={() => {}}
2137
- debounceMs={0}
2138
- />,
2139
- );
2140
-
2141
- const input = screen.getByRole('combobox');
2142
- await user.click(input);
2143
- await user.paste('e'); // matches Cherry, Date, Elderberry
2144
- await waitFor(() => {
2145
- expect(screen.getByRole('listbox', {hidden: true})).toBeInTheDocument();
2146
- });
2147
- const options = screen.getAllByRole('option', {hidden: true});
2148
- expect(input.getAttribute('aria-activedescendant')).toBe(options[0].id);
2149
-
2150
- fireEvent.mouseEnter(options[2]);
2151
- expect(input.getAttribute('aria-activedescendant')).toBe(options[0].id);
2152
-
2153
- fireEvent.mouseMove(options[2]);
2154
- expect(input.getAttribute('aria-activedescendant')).toBe(options[2].id);
2155
- });
2156
- });
2157
-
2158
- describe('Typeahead disabledMessage', () => {
2159
- // jsdom does not implement the Popover API used by the tooltip, so mock
2160
- // showPopover/hidePopover to toggle a `popover-open` attribute the tests
2161
- // can assert on.
2162
- beforeEach(() => {
2163
- HTMLElement.prototype.showPopover = vi.fn(function (this: HTMLElement) {
2164
- this.setAttribute('popover-open', '');
2165
- });
2166
- HTMLElement.prototype.hidePopover = vi.fn(function (this: HTMLElement) {
2167
- this.removeAttribute('popover-open');
2168
- });
2169
- });
2170
-
2171
- // jsdom popover content is in the DOM but not "visible" in the
2172
- // accessibility tree; use hidden: true to find it.
2173
- const h = {hidden: true} as const;
2174
-
2175
- it('shows the reason tooltip on hover when disabled with a reason', async () => {
2176
- render(
2177
- <Typeahead
2178
- label="Assignee"
2179
- searchSource={fruitSource}
2180
- value={null}
2181
- onChange={() => {}}
2182
- isDisabled
2183
- disabledMessage="You need the Editor role"
2184
- />,
2185
- );
2186
-
2187
- // The field itself, by its own class: the input no longer sits directly
2188
- // inside it — it is in the content lane that bounds the value — and
2189
- // `mouseEnter` does not bubble, so the hover has to land on the element
2190
- // the tooltip is actually bound to.
2191
- const container = document.querySelector(
2192
- '.astryx-typeahead',
2193
- ) as HTMLElement;
2194
- const tooltip = screen.getByRole('tooltip', h);
2195
- expect(tooltip).toHaveTextContent('You need the Editor role');
2196
-
2197
- fireEvent.mouseEnter(container);
2198
- await waitFor(() => {
2199
- expect(tooltip).toHaveAttribute('popover-open');
2200
- });
2201
-
2202
- fireEvent.mouseLeave(container);
2203
- await waitFor(() => {
2204
- expect(tooltip).not.toHaveAttribute('popover-open');
2205
- });
2206
- });
2207
-
2208
- it('shows the reason tooltip on keyboard focus', async () => {
2209
- const user = userEvent.setup();
2210
- render(
2211
- <Typeahead
2212
- label="Assignee"
2213
- searchSource={fruitSource}
2214
- value={null}
2215
- onChange={() => {}}
2216
- isDisabled
2217
- disabledMessage="You need the Editor role"
2218
- />,
2219
- );
2220
-
2221
- const tooltip = screen.getByRole('tooltip', h);
2222
- await user.tab();
2223
- expect(screen.getByRole('combobox')).toHaveFocus();
2224
- await waitFor(() => {
2225
- expect(tooltip).toHaveAttribute('popover-open');
2226
- });
2227
- });
2228
-
2229
- it('does not render a tooltip when not disabled', () => {
2230
- render(
2231
- <Typeahead
2232
- label="Assignee"
2233
- searchSource={fruitSource}
2234
- value={null}
2235
- onChange={() => {}}
2236
- disabledMessage="You need the Editor role"
2237
- />,
2238
- );
2239
- expect(screen.queryByRole('tooltip', h)).not.toBeInTheDocument();
2240
- });
2241
-
2242
- it('does not render a tooltip when disabled without a reason', () => {
2243
- render(
2244
- <Typeahead
2245
- label="Assignee"
2246
- searchSource={fruitSource}
2247
- value={null}
2248
- onChange={() => {}}
2249
- isDisabled
2250
- />,
2251
- );
2252
- expect(screen.queryByRole('tooltip', h)).not.toBeInTheDocument();
2253
- });
2254
-
2255
- it('keeps the input focusable via aria-disabled when a reason is provided', () => {
2256
- render(
2257
- <Typeahead
2258
- label="Assignee"
2259
- searchSource={fruitSource}
2260
- value={null}
2261
- onChange={() => {}}
2262
- isDisabled
2263
- disabledMessage="You need the Editor role"
2264
- />,
2265
- );
2266
- const input = screen.getByRole('combobox');
2267
- expect(input).not.toBeDisabled();
2268
- expect(input).toHaveAttribute('aria-disabled', 'true');
2269
- expect(input).toHaveAttribute('readonly');
2270
- });
2271
-
2272
- it('links the reason tooltip from the input via aria-describedby', () => {
2273
- render(
2274
- <Typeahead
2275
- label="Assignee"
2276
- searchSource={fruitSource}
2277
- value={null}
2278
- onChange={() => {}}
2279
- isDisabled
2280
- disabledMessage="You need the Editor role"
2281
- />,
2282
- );
2283
- const input = screen.getByRole('combobox');
2284
- const tooltip = screen.getByRole('tooltip', h);
2285
- expect(input.getAttribute('aria-describedby')).toContain(tooltip.id);
2286
- });
2287
-
2288
- it('blocks typing and selection while focusable-disabled', async () => {
2289
- const user = userEvent.setup();
2290
- const onChange = vi.fn();
2291
- render(
2292
- <Typeahead
2293
- label="Assignee"
2294
- searchSource={fruitSource}
2295
- value={null}
2296
- onChange={onChange}
2297
- isDisabled
2298
- disabledMessage="You need the Editor role"
2299
- />,
2300
- );
2301
-
2302
- const input = screen.getByRole('combobox');
2303
- await user.click(input);
2304
- await user.type(input, 'App');
2305
- expect(input).toHaveValue('');
2306
- expect(input).toHaveAttribute('aria-expanded', 'false');
2307
- expect(onChange).not.toHaveBeenCalled();
2308
- });
2309
-
2310
- it('opens no list and picks nothing while focusable-disabled (spec:AST-085 FR2)', async () => {
2311
- const user = userEvent.setup();
2312
- const onChange = vi.fn();
2313
- render(
2314
- <Typeahead
2315
- label="Assignee"
2316
- searchSource={fruitSource}
2317
- value={null}
2318
- onChange={onChange}
2319
- hasEntriesOnFocus
2320
- isDisabled
2321
- disabledMessage="You need the Editor role"
2322
- />,
2323
- );
2324
- const input = screen.getByRole('combobox');
2325
- await user.click(input);
2326
- await user.click(input);
2327
- await user.keyboard('{ArrowDown}{Enter}');
2328
- expect(input).toHaveAttribute('aria-expanded', 'false');
2329
- expect(screen.queryByRole('option', {hidden: true})).toBeNull();
2330
- expect(onChange).not.toHaveBeenCalled();
2331
- });
2332
-
2333
- it('does not reopen on a click into the still-focused input once disabled', async () => {
2334
- const user = userEvent.setup();
2335
- const field = (isDisabled: boolean) => (
2336
- <Typeahead
2337
- label="Assignee"
2338
- searchSource={fruitSource}
2339
- value={null}
2340
- onChange={() => {}}
2341
- hasEntriesOnFocus
2342
- isDisabled={isDisabled}
2343
- disabledMessage={isDisabled ? 'You need the Editor role' : undefined}
2344
- />
2345
- );
2346
- const {rerender} = render(field(false));
2347
- const input = screen.getByRole('combobox');
2348
- await user.click(input);
2349
- await waitFor(() => expect(input).toHaveAttribute('aria-expanded', 'true'));
2350
- // A pick closes the list and leaves focus in the input.
2351
- await user.click(screen.getAllByRole('option', {hidden: true})[0]);
2352
- await waitFor(() =>
2353
- expect(input).toHaveAttribute('aria-expanded', 'false'),
2354
- );
2355
- rerender(field(true));
2356
- const disabled = screen.getByRole('combobox');
2357
- await user.click(disabled);
2358
- await user.click(disabled);
2359
- expect(disabled).toHaveAttribute('aria-expanded', 'false');
2360
- });
2361
-
2362
- it('picks nothing from a list opened before the field became disabled, and lets it close', async () => {
2363
- const user = userEvent.setup();
2364
- const onChange = vi.fn();
2365
- const onSubmit = vi.fn((event: React.FormEvent) => event.preventDefault());
2366
- const field = (isDisabled: boolean) => (
2367
- <form onSubmit={onSubmit}>
2368
- <Typeahead
2369
- label="Assignee"
2370
- searchSource={fruitSource}
2371
- value={null}
2372
- onChange={onChange}
2373
- hasEntriesOnFocus
2374
- isDisabled={isDisabled}
2375
- disabledMessage="You need the Editor role"
2376
- />
2377
- </form>
2378
- );
2379
- const {rerender} = render(field(false));
2380
- const input = screen.getByRole('combobox');
2381
- await user.click(input);
2382
- await waitFor(() => expect(input).toHaveAttribute('aria-expanded', 'true'));
2383
- await user.keyboard('{ArrowDown}');
2384
-
2385
- rerender(field(true));
2386
- // The open list stays in the DOM (hidden from the accessibility tree).
2387
- await user.keyboard('{Enter}');
2388
- fireEvent.click(screen.getAllByRole('option', {hidden: true})[0]);
2389
- expect(onChange).not.toHaveBeenCalled();
2390
- // Enter on the still-open list does not submit the form.
2391
- expect(onSubmit).not.toHaveBeenCalled();
2392
- await user.keyboard('{Escape}');
2393
- expect(input).toHaveAttribute('aria-expanded', 'false');
2394
- });
2395
-
2396
- it('does not open a list for a search that settles after the field became disabled', async () => {
2397
- let resolveSearch: (items: SearchableItem[]) => void = () => {};
2398
- const pendingSource: SearchSource = {
2399
- search: async () =>
2400
- new Promise(resolve => {
2401
- resolveSearch = resolve;
2402
- }),
2403
- bootstrap: () => [],
2404
- };
2405
- const user = userEvent.setup();
2406
- const field = (isDisabled: boolean) => (
2407
- <Typeahead
2408
- label="Assignee"
2409
- searchSource={pendingSource}
2410
- value={null}
2411
- onChange={() => {}}
2412
- debounceMs={0}
2413
- isDisabled={isDisabled}
2414
- disabledMessage={isDisabled ? 'You need the Editor role' : undefined}
2415
- />
2416
- );
2417
- const {rerender} = render(field(false));
2418
- const input = screen.getByRole('combobox');
2419
- await user.type(input, 'ap');
2420
- rerender(field(true));
2421
- await act(async () => {
2422
- resolveSearch(fruits);
2423
- });
2424
- expect(screen.getByRole('combobox')).toHaveAttribute(
2425
- 'aria-expanded',
2426
- 'false',
2427
- );
2428
- });
2429
-
2430
- it('remains natively disabled when disabled without a reason', () => {
2431
- render(
2432
- <Typeahead
2433
- label="Assignee"
2434
- searchSource={fruitSource}
2435
- value={null}
2436
- onChange={() => {}}
2437
- isDisabled
2438
- />,
2439
- );
2440
- const input = screen.getByRole('combobox');
2441
- expect(input).toBeDisabled();
2442
- expect(input).not.toHaveAttribute('aria-disabled');
2443
- });
2444
- });
2445
-
2446
- describe('Typeahead statusVariant forwarding', () => {
2447
- it('defaults to attached (status renders with data-variant="attached")', () => {
2448
- const {container} = render(
2449
- <Typeahead
2450
- label="Fruit"
2451
- searchSource={fruitSource}
2452
- value={null}
2453
- onChange={() => {}}
2454
- status={{type: 'error', message: 'Required'}}
2455
- />,
2456
- );
2457
- expect(container.querySelector('.astryx-field-status')).toHaveAttribute(
2458
- 'data-variant',
2459
- 'attached',
2460
- );
2461
- });
2462
-
2463
- it('forwards statusVariant="detached" to the underlying Field status', () => {
2464
- const {container} = render(
2465
- <Typeahead
2466
- label="Fruit"
2467
- searchSource={fruitSource}
2468
- value={null}
2469
- onChange={() => {}}
2470
- status={{type: 'error', message: 'Required'}}
2471
- statusVariant="detached"
2472
- />,
2473
- );
2474
- expect(container.querySelector('.astryx-field-status')).toHaveAttribute(
2475
- 'data-variant',
2476
- 'detached',
2477
- );
2478
- });
2479
- });
2480
-
2481
- describe('busy indicator ownership', () => {
2482
- /** A source that stays in flight until the test settles it. */
2483
- const pendingSource = () => {
2484
- let settle: (items: SearchableItem[]) => void = () => {};
2485
- return {
2486
- source: {
2487
- search: async () =>
2488
- new Promise<SearchableItem[]>(resolve => {
2489
- settle = resolve;
2490
- }),
2491
- bootstrap: () => [],
2492
- },
2493
- settle: (items: SearchableItem[] = []) => settle(items),
2494
- };
2495
- };
2496
-
2497
- it('renders its own indicator for a direct caller', async () => {
2498
- // BaseTypeaheadProps is re-exported from the package entry point, so the
2499
- // base has direct callers this repo cannot see. They painted no indicator
2500
- // of their own — the base did it for them — so it keeps doing it. The
2501
- // Spinner is decorative and the base announces the search's busy start
2502
- // through the shared channel (spec:AST-086 FR8).
2503
- const {source, settle} = pendingSource();
2504
- render(
2505
- <BaseTypeahead
2506
- searchSource={source}
2507
- value={null}
2508
- onChange={() => {}}
2509
- debounceMs={0}
2510
- />,
2511
- );
2512
- const input = screen.getByRole('combobox');
2513
-
2514
- fireEvent.change(input, {target: {value: 'App'}});
2515
- await waitFor(() => {
2516
- expect(getBusySpinner()).toBeInTheDocument();
2517
- });
2518
- // A Spinner, not the static clock glyph this used to render: `clock`
2519
- // means *time* everywhere else in core, and nothing about it moved while
2520
- // a search was out.
2521
- expect(getBusySpinner().querySelector('svg')).toBeInTheDocument();
2522
-
2523
- await act(async () => {
2524
- settle();
2525
- await Promise.resolve();
2526
- });
2527
- expect(queryBusySpinner()).not.toBeInTheDocument();
2528
- });
2529
-
2530
- it('hands the indicator over to a field that takes it, and renders none itself', async () => {
2531
- // Typeahead and Tokenizer paint the spinner in the one inline-end lane
2532
- // they already own. Two indicators in one field is the defect this PR
2533
- // exists to fix, so the base must yield rather than add to it.
2534
- const {source, settle} = pendingSource();
2535
- // A real lane with its notify spied, rather than a hand-built object: the
2536
- // lane is a store now, and the base must drive the store the wrappers use.
2537
- const lane = createBusyIndicatorLane();
2538
- const onLoadingChange = vi.fn(lane.onBusyChange);
2539
- render(
2540
- <BusyIndicatorLaneProvider
2541
- value={{...lane, onBusyChange: onLoadingChange}}>
2542
- <BaseTypeahead
2543
- searchSource={source}
2544
- value={null}
2545
- onChange={() => {}}
2546
- debounceMs={0}
2547
- />
2548
- </BusyIndicatorLaneProvider>,
2549
- );
2550
- const input = screen.getByRole('combobox');
2551
-
2552
- fireEvent.change(input, {target: {value: 'App'}});
2553
- await waitFor(() => {
2554
- expect(input).toHaveAttribute('aria-busy', 'true');
2555
- });
2556
- // Scoped by name: the announcer for result counts is a role="status"
2557
- // live region too, and it is not the indicator.
2558
- expect(queryBusySpinner()).not.toBeInTheDocument();
2559
- expect(onLoadingChange).toHaveBeenLastCalledWith(true);
2560
-
2561
- await act(async () => {
2562
- settle();
2563
- await Promise.resolve();
2564
- });
2565
- expect(onLoadingChange).toHaveBeenLastCalledWith(false);
2566
- });
2567
-
2568
- it('keeps the busy handoff out of the exported prop surface', async () => {
2569
- // The handoff used to be `__onLoadingChange` on BaseTypeaheadProps, which
2570
- // the package entry point re-exports — so a builder reading the exported
2571
- // declaration would find it and could reasonably wire it, pinning a detail
2572
- // between two wrappers and their base as permanent API. An `@internal` tag
2573
- // is a note to a reader; a module boundary is the actual seam.
2574
- const entry = await import('./index');
2575
- expect(Object.keys(entry)).not.toContain('BusyIndicatorLaneProvider');
2576
- expect(Object.keys(entry)).not.toContain('useBusyIndicatorLane');
2577
-
2578
- const source = await readFile(
2579
- resolve(__dirname, 'BaseTypeahead.tsx'),
2580
- 'utf8',
2581
- );
2582
- expect(source).not.toContain('__onLoadingChange');
2583
- });
2584
-
2585
- it('reports each transition once, and reports nothing when nothing changed', async () => {
2586
- // Edge-triggered on purpose. Every keystroke below the query threshold
2587
- // clears the flag, and an unconditional report would hand the wrapper a
2588
- // `false` per character — each one a state write, and on a field that is
2589
- // re-rendering as the user types.
2590
- const {source, settle} = pendingSource();
2591
- // A real lane with its notify spied, rather than a hand-built object: the
2592
- // lane is a store now, and the base must drive the store the wrappers use.
2593
- const lane = createBusyIndicatorLane();
2594
- const onLoadingChange = vi.fn(lane.onBusyChange);
2595
- render(
2596
- <BusyIndicatorLaneProvider
2597
- value={{...lane, onBusyChange: onLoadingChange}}>
2598
- <BaseTypeahead
2599
- searchSource={source}
2600
- value={null}
2601
- onChange={() => {}}
2602
- minQueryLength={3}
2603
- debounceMs={0}
2604
- />
2605
- </BusyIndicatorLaneProvider>,
2606
- );
2607
- const input = screen.getByRole('combobox');
2608
-
2609
- // Below the threshold: no search, so nothing to report.
2610
- fireEvent.change(input, {target: {value: 'A'}});
2611
- fireEvent.change(input, {target: {value: 'Ap'}});
2612
- await act(async () => {
2613
- await Promise.resolve();
2614
- });
2615
- expect(onLoadingChange).not.toHaveBeenCalled();
2616
-
2617
- fireEvent.change(input, {target: {value: 'App'}});
2618
- await waitFor(() => {
2619
- expect(onLoadingChange).toHaveBeenCalledTimes(1);
2620
- });
2621
- expect(onLoadingChange).toHaveBeenCalledWith(true);
2622
-
2623
- // Back below the threshold: one report out, not one per keystroke.
2624
- fireEvent.change(input, {target: {value: 'Ap'}});
2625
- fireEvent.change(input, {target: {value: 'A'}});
2626
- fireEvent.change(input, {target: {value: ''}});
2627
- await act(async () => {
2628
- settle();
2629
- await Promise.resolve();
2630
- });
2631
- expect(onLoadingChange).toHaveBeenCalledTimes(2);
2632
- expect(onLoadingChange).toHaveBeenLastCalledWith(false);
2633
- });
2634
- });
2635
-
2636
- describe('end controls stay in flow', () => {
2637
- // The fix for the transform bug, expressed as a rule rather than a
2638
- // measurement: these controls are ordinary flex siblings of the input, so
2639
- // they take up room and nothing has to reserve it for them. jsdom performs
2640
- // no layout, so what is asserted is the absence of the two things that
2641
- // stopped that being true — an out-of-flow lane, and a padding reserve fed
2642
- // by a measured width. The geometry itself is browser-verified in the PR.
2643
- it('renders the clear button without taking it out of flow', () => {
2644
- const {container} = render(
2645
- <Typeahead
2646
- label="Fruit"
2647
- searchSource={fruitSource}
2648
- value={fruits[0]}
2649
- onChange={() => {}}
2650
- />,
2651
- );
2652
- const field = container.querySelector('.astryx-typeahead');
2653
- const clear = screen.getByRole('button', {name: /clear/i});
2654
- expect(field).toContainElement(clear);
2655
- // The controls sit in a lane that is itself an ordinary in-flow child of
2656
- // the field — not a box positioned over it, which is what reserved no
2657
- // room and put the input underneath.
2658
- const lane = clear.parentElement as HTMLElement;
2659
- expect(lane.parentElement).toBe(field);
2660
- expect(getComputedStyle(lane).position).not.toBe('absolute');
2661
- });
2662
-
2663
- it('never reserves room with a measured width', () => {
2664
- // The custom property is Tokenizer's mechanism and must not reappear
2665
- // here: a width measured in viewport space and spent as local padding is
2666
- // wrong under any CSS transform (measured on Tokenizer's 123px lane,
2667
- // scale(.5) covered 14px of the query and scale(2) left a 125.95px gap).
2668
- const {container} = render(
2669
- <Typeahead
2670
- label="Fruit"
2671
- searchSource={fruitSource}
2672
- value={fruits[0]}
2673
- onChange={() => {}}
2674
- />,
2675
- );
2676
- expect(
2677
- container.querySelector('[style*="--_tokenizer-end-lane-width"]'),
2678
- ).toBeNull();
2679
- });
2680
-
2681
- it('holds the controls at the inline end when a token shows', () => {
2682
- // `auto` gives free space to the margin rather than to a sibling, so the
2683
- // controls stay in the corner in the states where the content lane is not
2684
- // the only flexible item in the row. Without it the clear button sat
2685
- // against the token in mid-field instead of in the corner (measured:
2686
- // x=39 in a 300px field, against TextInput's 281).
2687
- render(
2688
- <Typeahead
2689
- label="Fruit"
2690
- searchSource={fruitSource}
2691
- value={fruits[0]}
2692
- onChange={() => {}}
2693
- />,
2694
- );
2695
- const clear = screen.getByRole('button', {name: /clear/i});
2696
- const lane = clear.parentElement as HTMLElement;
2697
- expect(getComputedStyle(lane).marginInlineStart).toBe('auto');
2698
- });
2699
-
2700
- it('keeps the field on one row so the controls cannot be pushed off it', () => {
2701
- // `flex-wrap` moves an item to a new line rather than shrinking it, so a
2702
- // wrapping field put the controls on a row of their own once the token
2703
- // got long. The shared field base does not wrap; this must not either.
2704
- const {container} = render(
2705
- <Typeahead
2706
- label="Fruit"
2707
- searchSource={fruitSource}
2708
- value={fruits[0]}
2709
- onChange={() => {}}
2710
- />,
2711
- );
2712
- const field = container.querySelector('.astryx-typeahead') as HTMLElement;
2713
- expect(getComputedStyle(field).flexWrap).not.toBe('wrap');
2714
- });
2715
-
2716
- it('keeps the input in flow while a token shows, so the field keeps its width', () => {
2717
- // A field's width must not depend on its value. Every other field in the
2718
- // family gets that for free: the `<input>` stays in flow and the field is
2719
- // as wide as the input's own intrinsic width. This one used to take the
2720
- // input out of flow and zero its width when a token showed, leaving the
2721
- // field measuring the token — in a shrink-to-fit parent it snapped to the
2722
- // value's length (199px to 57px in Chromium, #5560). Block parents hid it,
2723
- // which is why no story caught it. jsdom resolves no layout, so assert the
2724
- // mechanism: the input still occupies the row.
2725
- render(
2726
- <Typeahead
2727
- label="Fruit"
2728
- searchSource={fruitSource}
2729
- value={fruits[0]}
2730
- onChange={() => {}}
2731
- />,
2732
- );
2733
- const input = screen.getByRole('combobox');
2734
- const style = getComputedStyle(input);
2735
- expect(style.position).not.toBe('absolute');
2736
- expect(style.width).not.toBe('0px');
2737
- expect(style.flex).not.toBe('0 0 0');
2738
- });
2739
-
2740
- it('paints the token over the input rather than beside it', () => {
2741
- // In flow the token would add its own width to the row — the same
2742
- // value-dependent sizing from the other direction, where a long value
2743
- // grows the field instead of collapsing it.
2744
- const {container} = render(
2745
- <Typeahead
2746
- label="Fruit"
2747
- searchSource={fruitSource}
2748
- value={fruits[0]}
2749
- onChange={() => {}}
2750
- />,
2751
- );
2752
- const token = container.querySelector('.astryx-token') as HTMLElement;
2753
- expect(getComputedStyle(token).position).toBe('absolute');
2754
- });
2755
-
2756
- it('lets the token take the pointer that the hidden input would swallow', () => {
2757
- // The input still covers that space, so it has to stop intercepting the
2758
- // clicks that enter edit mode.
2759
- render(
2760
- <Typeahead
2761
- label="Fruit"
2762
- searchSource={fruitSource}
2763
- value={fruits[0]}
2764
- onChange={() => {}}
2765
- />,
2766
- );
2767
- expect(getComputedStyle(screen.getByRole('combobox')).pointerEvents).toBe(
2768
- 'none',
2769
- );
2770
- });
2771
- });
2772
-
2773
- describe('the value is bounded by the content lane', () => {
2774
- // Keeping the input in flow fixed the field's width, but it left the token
2775
- // positioned against the whole field, which has no idea where the end
2776
- // controls start. A value longer than the input then ran under the clear
2777
- // button and out past the field's own border — measured in Chromium at
2778
- // 28-33px of overlap and up to 4px outside the border, worse than the 12px
2779
- // of overlap on main. The lane is the box the value may occupy: an ordinary
2780
- // flex item that ends exactly where the end lane begins.
2781
- //
2782
- // jsdom resolves no layout, so what is asserted here is the mechanism. The
2783
- // geometry is browser-verified in the PR.
2784
- const renderWithValue = () =>
2785
- render(
2786
- <Typeahead
2787
- label="Fruit"
2788
- searchSource={fruitSource}
2789
- value={fruits[0]}
2790
- onChange={() => {}}
2791
- hasClear
2792
- />,
2793
- );
2794
-
2795
- it('puts the input and the token in one lane, inside the field', () => {
2796
- const {container} = renderWithValue();
2797
- const field = container.querySelector('.astryx-typeahead');
2798
- const input = screen.getByRole('combobox');
2799
- const token = container.querySelector('.astryx-token') as HTMLElement;
2800
-
2801
- const lane = input.parentElement as HTMLElement;
2802
- expect(lane).not.toBe(field);
2803
- expect(lane.parentElement).toBe(field);
2804
- // The token's containing block is the lane, which is what bounds it.
2805
- expect(token.parentElement).toBe(lane);
2806
- expect(getComputedStyle(lane).position).toBe('relative');
2807
- });
2808
-
2809
- it('lets the lane yield its whole width, so a narrow field cannot overflow', () => {
2810
- // `min-width: 0` is the half of this that the earlier `200px` floor got
2811
- // wrong: the field states no width of its own, so it still shrinks to
2812
- // whatever a narrow parent or an InputGroup gives it.
2813
- const {container} = renderWithValue();
2814
- const lane = screen.getByRole('combobox').parentElement as HTMLElement;
2815
- const style = getComputedStyle(lane);
2816
- expect(style.minWidth).toBe('0');
2817
- expect(style.flexGrow).toBe('1');
2818
- // Nothing states a width: a narrow parent gets all of it back.
2819
- expect(
2820
- (container.querySelector('.astryx-typeahead') as HTMLElement).style
2821
- .minWidth,
2822
- ).toBe('');
2823
- });
2824
-
2825
- it("anchors the token at the lane's end, not just its start", () => {
2826
- // The bound that stops the value reaching the end controls. Anchored at
2827
- // one end only, the token is capped by the field's own padding box, which
2828
- // is past the clear button.
2829
- const {container} = renderWithValue();
2830
- const token = container.querySelector('.astryx-token') as HTMLElement;
2831
- const style = getComputedStyle(token);
2832
- expect(style.insetInlineEnd).toBe('0');
2833
- // `fit-content` against that pair of insets is what shrink-wraps the
2834
- // label yet still caps it at the lane; the `auto` end margin is what
2835
- // keeps the pair from being over-constrained and dropping the end inset.
2836
- expect(style.width).toBe('fit-content');
2837
- expect(style.marginInlineEnd).toBe('auto');
2838
- });
2839
- });
2840
-
2841
- // `emptySearchResultsText` was renamed to `emptySearchText` and widened from
2842
- // `string` to `ReactNode` (`spec:AST-056` FR1). The overlap ended in a minor
2843
- // release (`CLN-0001`–`CLN-0004`, `spec:AST-017` FR31): the old name is no
2844
- // longer part of the type and is no longer read.
2845
- describe('emptySearchText rename', () => {
2846
- function searchForNothing() {
2847
- fireEvent.change(screen.getByRole('combobox'), {
2848
- target: {value: 'zzzzz'},
2849
- });
2850
- }
2851
-
2852
- describe('Typeahead', () => {
2853
- it('renders an element, which the old string type could not express', async () => {
2854
- render(
2855
- <Typeahead
2856
- label="Fruit"
2857
- searchSource={fruitSource}
2858
- value={null}
2859
- onChange={() => {}}
2860
- debounceMs={0}
2861
- emptySearchText={
2862
- <span>
2863
- Nothing ripe. <a href="/add">Add a fruit</a>
2864
- </span>
2865
- }
2866
- />,
2867
- );
2868
- searchForNothing();
2869
-
2870
- await waitFor(() => {
2871
- expect(screen.getByText('Add a fruit')).toBeInTheDocument();
2872
- });
2873
- });
2874
-
2875
- it('no longer reads the removed emptySearchResultsText (CLN-0002)', async () => {
2876
- const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
2877
- // The unknown prop reaches the root element, which React reports.
2878
- const error = vi.spyOn(console, 'error').mockImplementation(() => {});
2879
- render(
2880
- <Typeahead
2881
- label="Fruit"
2882
- searchSource={fruitSource}
2883
- value={null}
2884
- onChange={() => {}}
2885
- debounceMs={0}
2886
- // @ts-expect-error -- removed in the minor release (CLN-0002)
2887
- emptySearchResultsText="Nothing ripe"
2888
- />,
2889
- );
2890
- searchForNothing();
2891
-
2892
- await waitFor(() => {
2893
- expect(screen.getByText('No results found')).toBeInTheDocument();
2894
- });
2895
- expect(screen.queryByText('Nothing ripe')).not.toBeInTheDocument();
2896
- expect(warn).not.toHaveBeenCalledWith(
2897
- expect.stringContaining('is deprecated'),
2898
- );
2899
- warn.mockRestore();
2900
- error.mockRestore();
2901
- });
2902
- });
2903
-
2904
- describe('BaseTypeahead', () => {
2905
- it('announces the text of an element, not the catalog default', async () => {
2906
- render(
2907
- <BaseTypeahead
2908
- searchSource={fruitSource}
2909
- value={null}
2910
- onChange={() => {}}
2911
- debounceMs={0}
2912
- emptySearchText={
2913
- <span>
2914
- Nothing ripe. <a href="/add">Add a fruit</a>
2915
- </span>
2916
- }
2917
- />,
2918
- );
2919
- searchForNothing();
2920
-
2921
- // Widening to ReactNode without this would hand the live region an
2922
- // object: the AR1 defect, spread to a fourth component.
2923
- await waitFor(() => {
2924
- expect(
2925
- document.querySelector('[data-astryx-live-region="polite"]'),
2926
- ).toHaveTextContent('Nothing ripe. Add a fruit');
2927
- });
2928
- });
2929
-
2930
- it('no longer announces the removed emptySearchResultsText (CLN-0003)', async () => {
2931
- // The unknown prop reaches the root element, which React reports.
2932
- const error = vi.spyOn(console, 'error').mockImplementation(() => {});
2933
- render(
2934
- <BaseTypeahead
2935
- searchSource={fruitSource}
2936
- value={null}
2937
- onChange={() => {}}
2938
- debounceMs={0}
2939
- // @ts-expect-error -- removed in the minor release (CLN-0003)
2940
- emptySearchResultsText="Nothing ripe"
2941
- />,
2942
- );
2943
- searchForNothing();
2944
-
2945
- await waitFor(() => {
2946
- expect(
2947
- document.querySelector('[data-astryx-live-region="polite"]'),
2948
- ).toHaveTextContent('No results found');
2949
- });
2950
- expect(
2951
- document.querySelector('[data-astryx-live-region="polite"]')
2952
- ?.textContent,
2953
- ).not.toContain('Nothing ripe');
2954
- error.mockRestore();
2955
- });
2956
- });
2957
- });
2958
-
2959
- describe('Typeahead consumer key handler order', () => {
2960
- it('runs onKeyDown on the input after built-in key handling, so it cannot cancel navigation', async () => {
2961
- let currentTarget: EventTarget | null = null;
2962
- const onKeyDown = vi.fn((event: React.KeyboardEvent) => {
2963
- currentTarget = event.currentTarget;
2964
- event.preventDefault();
2965
- });
2966
- render(
2967
- <Typeahead
2968
- label="Fruit"
2969
- searchSource={fruitSource}
2970
- value={null}
2971
- onChange={() => {}}
2972
- debounceMs={0}
2973
- hasEntriesOnFocus
2974
- onKeyDown={onKeyDown}
2975
- />,
2976
- );
2977
- const input = screen.getByRole('combobox');
2978
- fireEvent.focus(input);
2979
- await waitFor(() => {
2980
- expect(
2981
- screen.getAllByRole('option', {hidden: true}).length,
2982
- ).toBeGreaterThan(0);
2983
- });
2984
- fireEvent.keyDown(input, {key: 'ArrowDown'});
2985
- expect(onKeyDown).toHaveBeenCalledOnce();
2986
- expect(currentTarget).toBe(screen.getByRole('combobox'));
2987
- expect(input).toHaveAttribute('aria-activedescendant');
2988
- });
2989
- });
2990
-
2991
- describe('Typeahead consumer key handler target', () => {
2992
- it('still runs onKeyDown for keys on the clear button', () => {
2993
- const onKeyDown = vi.fn();
2994
- render(
2995
- <Typeahead
2996
- label="Fruit"
2997
- searchSource={fruitSource}
2998
- value={fruits[0]}
2999
- onChange={() => {}}
3000
- onKeyDown={onKeyDown}
3001
- />,
3002
- );
3003
- fireEvent.keyDown(screen.getByRole('button', {name: /clear/i}), {
3004
- key: 'Enter',
3005
- });
3006
- expect(onKeyDown).toHaveBeenCalledOnce();
3007
- });
3008
-
3009
- it('runs onKeyDown once for a key on the input', () => {
3010
- const onKeyDown = vi.fn();
3011
- render(
3012
- <Typeahead
3013
- label="Fruit"
3014
- searchSource={fruitSource}
3015
- value={null}
3016
- onChange={() => {}}
3017
- onKeyDown={onKeyDown}
3018
- />,
3019
- );
3020
- fireEvent.keyDown(screen.getByRole('combobox'), {key: 'a'});
3021
- expect(onKeyDown).toHaveBeenCalledOnce();
3022
- });
3023
- });
3024
-
3025
- describe('Typeahead caller aria-labelledby', () => {
3026
- it('names the input by its label and a caller aria-labelledby', () => {
3027
- render(
3028
- <>
3029
- <span id="billing-heading">Billing</span>
3030
- <Typeahead
3031
- label="Fruit"
3032
- searchSource={fruitSource}
3033
- value={null}
3034
- onChange={() => {}}
3035
- aria-labelledby="billing-heading"
3036
- />
3037
- </>,
3038
- );
3039
- expect(
3040
- screen.getByRole('combobox', {name: 'Fruit Billing'}),
3041
- ).toBeInTheDocument();
3042
- });
3043
-
3044
- it('names a grouped input by the group, its label, and a caller aria-labelledby', () => {
3045
- render(
3046
- <>
3047
- <span id="billing-heading">Billing</span>
3048
- <InputGroup label="Contact">
3049
- <Typeahead
3050
- label="Fruit"
3051
- searchSource={fruitSource}
3052
- value={null}
3053
- onChange={() => {}}
3054
- aria-labelledby="billing-heading"
3055
- />
3056
- </InputGroup>
3057
- </>,
3058
- );
3059
- expect(
3060
- screen.getByRole('combobox', {name: 'Contact Fruit Billing'}),
3061
- ).toBeInTheDocument();
3062
- });
3063
- });