@astryxdesign/core 0.7.6-canary.f73623d → 0.7.6-canary.f899313

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