@hanzogui/kitchen-sink 3.0.5

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 (431) hide show
  1. package/.detoxrc.js +130 -0
  2. package/.env.production +2 -0
  3. package/.maestro/config.yaml +4 -0
  4. package/.maestro/flows/shorthand-variables.yaml +23 -0
  5. package/.watchmanconfig +1 -0
  6. package/LICENSE +21 -0
  7. package/README.md +11 -0
  8. package/app.json +43 -0
  9. package/assets/adaptive-icon.png +0 -0
  10. package/assets/favicon.png +0 -0
  11. package/assets/icon.png +0 -0
  12. package/assets/splash.png +0 -0
  13. package/babel.config.js +25 -0
  14. package/e2e/CompilerExtraction.test.ts +147 -0
  15. package/e2e/GroupPressNative.test.ts +167 -0
  16. package/e2e/MediaQueryGtMd.test.ts +71 -0
  17. package/e2e/NativePortal.test.ts +113 -0
  18. package/e2e/PointerEvents.test.ts +116 -0
  19. package/e2e/PressStyleNative.noRngh.test.ts +191 -0
  20. package/e2e/PressStyleNative.test.ts +231 -0
  21. package/e2e/SafeArea.test.ts +57 -0
  22. package/e2e/SelectAndroidOnPress.test.ts +181 -0
  23. package/e2e/SelectRemount.test.ts +137 -0
  24. package/e2e/SheetDragResist.test.ts +370 -0
  25. package/e2e/SheetKeyboardDrag.test.ts +249 -0
  26. package/e2e/SheetScrollableDrag.test.ts +560 -0
  27. package/e2e/ShorthandVariables.test.ts +53 -0
  28. package/e2e/ThemeChangeBasic.test.ts +123 -0
  29. package/e2e/ThemeMutation.test.ts +80 -0
  30. package/e2e/check-rngh-status.test.ts +31 -0
  31. package/e2e/jest.config.js +19 -0
  32. package/e2e/utils/colors.ts +75 -0
  33. package/e2e/utils/navigation.ts +53 -0
  34. package/eas.json +22 -0
  35. package/flows/AlertDialog.yaml +17 -0
  36. package/flows/OpenApp.yaml +25 -0
  37. package/flows/Select.yaml +13 -0
  38. package/flows/Sheet.yaml +12 -0
  39. package/flows/Tabs.yaml +13 -0
  40. package/flows/Toast.yaml +14 -0
  41. package/flows/WarmUp.yaml +24 -0
  42. package/index.html +21 -0
  43. package/index.js +17 -0
  44. package/metro.config.js +64 -0
  45. package/next-router-shim.ts +9 -0
  46. package/package.json +118 -0
  47. package/plans/toast-2.md +471 -0
  48. package/playwright.config.ts +71 -0
  49. package/plugins/expo-modules-core-swift6.js +76 -0
  50. package/pod-install.sh +7 -0
  51. package/public/favicon.svg +70 -0
  52. package/public/fonts/inter.css +15 -0
  53. package/public/fonts/noto-cn.otf +0 -0
  54. package/public/gui-icon.svg +68 -0
  55. package/run-detox.sh +230 -0
  56. package/run-native-tests.sh +4 -0
  57. package/run-tests-parallel.ts +195 -0
  58. package/screenshots/Screenshotter.test.tsx +48 -0
  59. package/src/AnimationDemos.tsx +131 -0
  60. package/src/App.native.tsx +121 -0
  61. package/src/App.tsx +121 -0
  62. package/src/Navigation.tsx +98 -0
  63. package/src/Sandbox.tsx +87 -0
  64. package/src/TestDynamicEval.tsx +33 -0
  65. package/src/TestNativeSheet.tsx +100 -0
  66. package/src/components/TimedRender.tsx +18 -0
  67. package/src/constants/test-ids.ts +52 -0
  68. package/src/features/demos/demo-screen.tsx +72 -0
  69. package/src/features/home/ColorSchemeListItem.tsx +41 -0
  70. package/src/features/home/TestBuildAButton.tsx +102 -0
  71. package/src/features/home/TestSeparator.tsx +0 -0
  72. package/src/features/home/screen.tsx +285 -0
  73. package/src/features/testcases/screen.tsx +59 -0
  74. package/src/features/testcases/test-screen.tsx +50 -0
  75. package/src/generatedV5Theme.ts +112 -0
  76. package/src/gui.config.ts +411 -0
  77. package/src/guy.png +0 -0
  78. package/src/index.tsx +6 -0
  79. package/src/provider/index.tsx +18 -0
  80. package/src/test-gui-stack.tsx +11 -0
  81. package/src/test.tsx +3 -0
  82. package/src/useKitchenSinkTheme.tsx +15 -0
  83. package/src/usecases/ActionsSheetComparison.tsx +194 -0
  84. package/src/usecases/AnimatePresenceEnterExitCase.tsx +255 -0
  85. package/src/usecases/AnimatePresenceExitTest.tsx +69 -0
  86. package/src/usecases/AnimatedByProp.tsx +39 -0
  87. package/src/usecases/AnimationComprehensiveCase.tsx +2515 -0
  88. package/src/usecases/AnimationValueLoggingCase.tsx +526 -0
  89. package/src/usecases/AnimationsWithMediaQueriesCase.tsx +110 -0
  90. package/src/usecases/Benchmark.tsx +148 -0
  91. package/src/usecases/BenchmarkSelect.tsx +34 -0
  92. package/src/usecases/ButtonCircular.tsx +3 -0
  93. package/src/usecases/ButtonCustom.tsx +33 -0
  94. package/src/usecases/ButtonIconColor.tsx +18 -0
  95. package/src/usecases/ButtonInverse.tsx +30 -0
  96. package/src/usecases/ButtonUnstyled.tsx +31 -0
  97. package/src/usecases/CheckboxDisabledOnPress.tsx +62 -0
  98. package/src/usecases/ClickDuringEnterCase.tsx +59 -0
  99. package/src/usecases/CodeExamplesInput.tsx +9 -0
  100. package/src/usecases/ColorTokenFallback.tsx +52 -0
  101. package/src/usecases/CompilerExtraction.tsx +380 -0
  102. package/src/usecases/ComplexVariants.tsx +164 -0
  103. package/src/usecases/CrashAdaptSheet.tsx +98 -0
  104. package/src/usecases/CustomStyledAnimatedPopover.tsx +42 -0
  105. package/src/usecases/CustomStyledAnimatedTooltip.tsx +72 -0
  106. package/src/usecases/DOMNodeAPIs.tsx +154 -0
  107. package/src/usecases/DialogFocusScopeCase.tsx +277 -0
  108. package/src/usecases/DialogFocusScopeDebug.tsx +85 -0
  109. package/src/usecases/DialogNestedCase.tsx +121 -0
  110. package/src/usecases/DialogOpenControlled.tsx +49 -0
  111. package/src/usecases/DialogPointerEventsCase.tsx +58 -0
  112. package/src/usecases/DialogScopedCase.tsx +106 -0
  113. package/src/usecases/DialogSheetAdaptCase.tsx +178 -0
  114. package/src/usecases/DialogSheetAdaptResizeCase.tsx +98 -0
  115. package/src/usecases/DismissLayerStackingCase.tsx +223 -0
  116. package/src/usecases/DriverDisableAnimationPropsCase.tsx +44 -0
  117. package/src/usecases/Example.tsx +10 -0
  118. package/src/usecases/ExitCompletionCase.tsx +713 -0
  119. package/src/usecases/FocusVisibleButton.tsx +14 -0
  120. package/src/usecases/FocusVisibleButtonPointer.tsx +13 -0
  121. package/src/usecases/FocusVisibleButtonWithFocusStyle.tsx +16 -0
  122. package/src/usecases/FocusWithinCase.tsx +55 -0
  123. package/src/usecases/FontTokensInVariants.tsx +14 -0
  124. package/src/usecases/FormButtonTypeCase.tsx +34 -0
  125. package/src/usecases/GlobalScopedTriggerIsolationCase.tsx +178 -0
  126. package/src/usecases/GroupHoverMobile.tsx +39 -0
  127. package/src/usecases/GroupPressInVariant.tsx +92 -0
  128. package/src/usecases/GroupPressNative.tsx +200 -0
  129. package/src/usecases/GroupProp.tsx +96 -0
  130. package/src/usecases/GroupPseudoVariantOverride.tsx +56 -0
  131. package/src/usecases/GroupUseCases.tsx +94 -0
  132. package/src/usecases/HeightMediaQueryOverrideCase.tsx +183 -0
  133. package/src/usecases/InputAutoFocusAfterMenuCase.tsx +105 -0
  134. package/src/usecases/InputAutoFocusStyledCase.tsx +39 -0
  135. package/src/usecases/KeyboardControllerTest.tsx +146 -0
  136. package/src/usecases/ListItem.tsx +123 -0
  137. package/src/usecases/MediaQueriesV5.tsx +137 -0
  138. package/src/usecases/MediaQueryGtMd.tsx +73 -0
  139. package/src/usecases/MenuAboveDialogCase.tsx +75 -0
  140. package/src/usecases/MenuAccessibilityCase.tsx +133 -0
  141. package/src/usecases/MenuAnimatePositionCase.tsx +41 -0
  142. package/src/usecases/MenuArrowAnimatePresenceCase.tsx +98 -0
  143. package/src/usecases/MenuAsChildPositionCase.tsx +24 -0
  144. package/src/usecases/MenuAutoResizeCase.tsx +57 -0
  145. package/src/usecases/MenuBottomCase.tsx +55 -0
  146. package/src/usecases/MenuFocusLeaveCase.tsx +135 -0
  147. package/src/usecases/MenuHighlightCase.tsx +44 -0
  148. package/src/usecases/MenuItemFocusCase.tsx +79 -0
  149. package/src/usecases/MenuItemPseudoOverrideCase.tsx +270 -0
  150. package/src/usecases/MenuMultiTriggerCase.tsx +47 -0
  151. package/src/usecases/MenuOverflowCase.tsx +60 -0
  152. package/src/usecases/MenuSubCase.tsx +223 -0
  153. package/src/usecases/MenuSubLeftCase.tsx +178 -0
  154. package/src/usecases/MenuSubNestedPositionCase.tsx +171 -0
  155. package/src/usecases/MenuSubStyledCase.tsx +145 -0
  156. package/src/usecases/MenuThemeCase.tsx +50 -0
  157. package/src/usecases/MenuUnstyledCase.tsx +52 -0
  158. package/src/usecases/MultiDriverAnimation.tsx +118 -0
  159. package/src/usecases/NativePortalTest.tsx +179 -0
  160. package/src/usecases/NewInputBasic.tsx +16 -0
  161. package/src/usecases/NewInputEvents.tsx +29 -0
  162. package/src/usecases/NonGuiTextStyledType.tsx +23 -0
  163. package/src/usecases/OnLayoutCase.tsx +134 -0
  164. package/src/usecases/OnLayoutScaleCase.tsx +88 -0
  165. package/src/usecases/OnLayoutStressCase.tsx +353 -0
  166. package/src/usecases/OpacityModifierCase.tsx +113 -0
  167. package/src/usecases/OverlayStyled.tsx +66 -0
  168. package/src/usecases/ParagraphSpanFontInheritance.tsx +53 -0
  169. package/src/usecases/PlaceholderTextColor.tsx +20 -0
  170. package/src/usecases/PointerEventsCase.tsx +100 -0
  171. package/src/usecases/PopoverAndMenuMultiTriggerCase.tsx +138 -0
  172. package/src/usecases/PopoverCase.tsx +222 -0
  173. package/src/usecases/PopoverContentStyledPlusAnimations.tsx +44 -0
  174. package/src/usecases/PopoverFocusScopeCase.tsx +171 -0
  175. package/src/usecases/PopoverHoverableCase.tsx +167 -0
  176. package/src/usecases/PopoverHoverableDisableClickCase.tsx +118 -0
  177. package/src/usecases/PopoverHoverableRapidCase.tsx +103 -0
  178. package/src/usecases/PopoverHoverableScopedCase.tsx +135 -0
  179. package/src/usecases/PopoverScopedCase.tsx +76 -0
  180. package/src/usecases/PopoverTriggerIsolationCase.tsx +80 -0
  181. package/src/usecases/PressStyleNative.tsx +143 -0
  182. package/src/usecases/PseudoStyleMerge.tsx +25 -0
  183. package/src/usecases/PseudoTransitionCase.tsx +174 -0
  184. package/src/usecases/RawAnimatedValueCase.tsx +231 -0
  185. package/src/usecases/RemoveScrollCase.tsx +66 -0
  186. package/src/usecases/RenderPropCase.tsx +263 -0
  187. package/src/usecases/SafeAreaCase.tsx +236 -0
  188. package/src/usecases/ScrollViewRefCase.tsx +88 -0
  189. package/src/usecases/SecondPage.tsx +5 -0
  190. package/src/usecases/SelectAndroidOnPress.tsx +129 -0
  191. package/src/usecases/SelectFocusScopeCase.tsx +270 -0
  192. package/src/usecases/SelectRemount.tsx +136 -0
  193. package/src/usecases/Shadows.tsx +5 -0
  194. package/src/usecases/SheetAnimationCase.tsx +155 -0
  195. package/src/usecases/SheetDragCase.tsx +183 -0
  196. package/src/usecases/SheetDragResistCase.tsx +433 -0
  197. package/src/usecases/SheetDragResistCase.web.tsx +359 -0
  198. package/src/usecases/SheetKeyboardDragCase.tsx +328 -0
  199. package/src/usecases/SheetKeyboardFitContentCase.tsx +165 -0
  200. package/src/usecases/SheetOnAnimationCompleteCase.tsx +54 -0
  201. package/src/usecases/SheetScrollLockCase.tsx +166 -0
  202. package/src/usecases/SheetScrollableDrag.tsx +249 -0
  203. package/src/usecases/SheetSnapPointsFitCase.tsx +393 -0
  204. package/src/usecases/ShorthandVariables.tsx +49 -0
  205. package/src/usecases/SlowThemeReRender.tsx +48 -0
  206. package/src/usecases/SpinnerCustomColors.tsx +34 -0
  207. package/src/usecases/StackZIndex.tsx +82 -0
  208. package/src/usecases/StressPage.tsx +301 -0
  209. package/src/usecases/StylePlatform.tsx +30 -0
  210. package/src/usecases/StyleProp.tsx +29 -0
  211. package/src/usecases/StyledAnchor.tsx +27 -0
  212. package/src/usecases/StyledButtonAnimationAuto.tsx +99 -0
  213. package/src/usecases/StyledButtonTheme.tsx +63 -0
  214. package/src/usecases/StyledButtonVariantPseudo.tsx +25 -0
  215. package/src/usecases/StyledButtonVariantPseudoMerge.tsx +77 -0
  216. package/src/usecases/StyledCheckboxTheme.tsx +23 -0
  217. package/src/usecases/StyledContextColor.tsx +246 -0
  218. package/src/usecases/StyledContextTokens.tsx +147 -0
  219. package/src/usecases/StyledHOCNamed.tsx +20 -0
  220. package/src/usecases/StyledHtmlCase.tsx +144 -0
  221. package/src/usecases/StyledIconColor.tsx +19 -0
  222. package/src/usecases/StyledInputFocusStyle.tsx +21 -0
  223. package/src/usecases/StyledInputOnFocus.tsx +30 -0
  224. package/src/usecases/StyledMediaQueryMerge.tsx +95 -0
  225. package/src/usecases/StyledOverridePsuedo.tsx +26 -0
  226. package/src/usecases/StyledRNW.tsx +61 -0
  227. package/src/usecases/StyledStyleableInputOnFocus.tsx +34 -0
  228. package/src/usecases/StyledStyleableInputVariant.tsx +48 -0
  229. package/src/usecases/StyledStyledStyleableInputOnFocus.tsx +36 -0
  230. package/src/usecases/StyledVariantTextColor.tsx +25 -0
  231. package/src/usecases/StyledViewOnFocus.tsx +32 -0
  232. package/src/usecases/TabHoverAnimationCase.tsx +212 -0
  233. package/src/usecases/TextNestedInheritance.tsx +80 -0
  234. package/src/usecases/ThemeChange.tsx +100 -0
  235. package/src/usecases/ThemeChangeBasic.tsx +52 -0
  236. package/src/usecases/ThemeComponentResolution.tsx +119 -0
  237. package/src/usecases/ThemeConditionalName.tsx +31 -0
  238. package/src/usecases/ThemeMediaAnimationCase.tsx +39 -0
  239. package/src/usecases/ThemeMutation.tsx +86 -0
  240. package/src/usecases/ThemeNested.tsx +103 -0
  241. package/src/usecases/ThemeReset.tsx +62 -0
  242. package/src/usecases/ThemeShallowCase.tsx +83 -0
  243. package/src/usecases/ToastCase.tsx +46 -0
  244. package/src/usecases/ToggleGroupActiveProps.tsx +40 -0
  245. package/src/usecases/ToggleGroupXGroupCase.tsx +104 -0
  246. package/src/usecases/TooltipAnimationCase.tsx +99 -0
  247. package/src/usecases/TooltipCase.tsx +32 -0
  248. package/src/usecases/TooltipGlobalPatternCase.tsx +83 -0
  249. package/src/usecases/TooltipGroupCase.tsx +102 -0
  250. package/src/usecases/TooltipMultiTriggerCase.tsx +88 -0
  251. package/src/usecases/TooltipPositionJumpCase.tsx +91 -0
  252. package/src/usecases/TooltipTriggerInlineCase.tsx +60 -0
  253. package/src/usecases/TransformMediaQueryMerge.tsx +98 -0
  254. package/src/usecases/UseCases.tsx +409 -0
  255. package/src/usecases/UseTheme.tsx +41 -0
  256. package/src/usecases/V5ThemeBuilderOutput.tsx +231 -0
  257. package/src/usecases/VariantFontFamily.tsx +25 -0
  258. package/src/usecases/VariantsOrder.tsx +117 -0
  259. package/src/usecases/ZIndex.tsx +155 -0
  260. package/src/usecases/helpers.tsx +44 -0
  261. package/src/usecases/index.native.ts +122 -0
  262. package/src/usecases/index.ts +3 -0
  263. package/src/usecases/index.web.ts +177 -0
  264. package/tests/AnimatePresenceEnterExit.animated.test.tsx +176 -0
  265. package/tests/AnimatedByProp.animated.test.tsx +138 -0
  266. package/tests/AnimationBehavior.animated.test.tsx +543 -0
  267. package/tests/AnimationTiming.animated.test.tsx +195 -0
  268. package/tests/AnimationsWithMediaQueries.animated.test.tsx +154 -0
  269. package/tests/BuildAButton.test.tsx +87 -0
  270. package/tests/ButtonCircular.test.tsx +17 -0
  271. package/tests/ButtonCustom.test.tsx +17 -0
  272. package/tests/ButtonIconColor.test.tsx +23 -0
  273. package/tests/ButtonUnstyled.test.tsx +56 -0
  274. package/tests/ClickDuringEnter.animated.test.tsx +174 -0
  275. package/tests/ColorTokenFallback.test.tsx +45 -0
  276. package/tests/DOMNodeAPIs.test.tsx +161 -0
  277. package/tests/DialogFocusScope.animated.test.tsx +309 -0
  278. package/tests/DialogNested.test.tsx +128 -0
  279. package/tests/DialogOpenControlled.test.tsx +42 -0
  280. package/tests/DialogPointerEvents.animated.test.tsx +108 -0
  281. package/tests/DialogScoped.test.tsx +137 -0
  282. package/tests/DialogSheetAdapt.test.tsx +68 -0
  283. package/tests/DialogSheetAdaptResize.test.tsx +161 -0
  284. package/tests/DismissLayerStacking.test.tsx +292 -0
  285. package/tests/DriverDisableAnimationProps.animated.test.tsx +157 -0
  286. package/tests/ExitCompletion.animated.test.tsx +425 -0
  287. package/tests/ExitTimingCheck.animated.test.ts +34 -0
  288. package/tests/FocusVisibleButton.test.tsx +41 -0
  289. package/tests/FocusVisibleButtonPointerFocus.test.tsx +23 -0
  290. package/tests/FocusVisibleButtonPointerFocusWithFocusStyle.test.tsx +40 -0
  291. package/tests/FocusWithinStyle.animated.test.tsx +66 -0
  292. package/tests/FocusWithinStyle.test.tsx +60 -0
  293. package/tests/FormButtonType.test.tsx +42 -0
  294. package/tests/GlobalScopedTriggerIsolation.test.tsx +89 -0
  295. package/tests/GroupHoverMobile.test.tsx +52 -0
  296. package/tests/GroupPressInVariant.test.tsx +82 -0
  297. package/tests/GroupProp.test.tsx +30 -0
  298. package/tests/GroupPseudoVariantOverride.test.tsx +57 -0
  299. package/tests/GroupUseCases.test.tsx +111 -0
  300. package/tests/GuiSiteMotion.test.ts +481 -0
  301. package/tests/HeightMediaQueryOverride.test.tsx +112 -0
  302. package/tests/InputAutoFocusAfterMenu.test.tsx +55 -0
  303. package/tests/InputAutoFocusStyled.test.tsx +22 -0
  304. package/tests/ListItem.test.tsx +129 -0
  305. package/tests/MediaQueriesV5.test.tsx +113 -0
  306. package/tests/MediaQueryGtMd.test.tsx +84 -0
  307. package/tests/MenuAboveDialog.test.tsx +108 -0
  308. package/tests/MenuAccessibility.test.tsx +346 -0
  309. package/tests/MenuAnimatePosition.animated.test.tsx +57 -0
  310. package/tests/MenuArrowAnimatePresence.animated.test.tsx +71 -0
  311. package/tests/MenuAsChildPosition.test.tsx +16 -0
  312. package/tests/MenuAutoResize.test.tsx +54 -0
  313. package/tests/MenuFocusLeave.test.tsx +181 -0
  314. package/tests/MenuHighlight.test.tsx +165 -0
  315. package/tests/MenuHoverKeyboardBugs.test.tsx +252 -0
  316. package/tests/MenuItemFocus.test.tsx +59 -0
  317. package/tests/MenuItemPseudoOverride.test.tsx +231 -0
  318. package/tests/MenuMultiTrigger.test.tsx +101 -0
  319. package/tests/MenuOverflow.test.tsx +93 -0
  320. package/tests/MenuStayInFrame.test.tsx +102 -0
  321. package/tests/MenuSubKeyboardFocus.test.tsx +220 -0
  322. package/tests/MenuSubLeftSafePolygon.test.tsx +88 -0
  323. package/tests/MenuSubNestedPosition.test.tsx +48 -0
  324. package/tests/MenuSubSafePolygon.test.tsx +97 -0
  325. package/tests/MenuSubStyled.test.tsx +40 -0
  326. package/tests/MenuTheme.test.tsx +34 -0
  327. package/tests/MenuUnstyled.test.tsx +56 -0
  328. package/tests/MultiDriverAnimation.test.tsx +207 -0
  329. package/tests/NewInputBasic.test.tsx +50 -0
  330. package/tests/NewInputEvents.test.tsx +55 -0
  331. package/tests/OnLayout.test.tsx +163 -0
  332. package/tests/OnLayoutScale.test.tsx +100 -0
  333. package/tests/OnLayoutStress.test.tsx +304 -0
  334. package/tests/ParagraphSpanFontInheritance.test.tsx +73 -0
  335. package/tests/PointerEvents.test.tsx +123 -0
  336. package/tests/Popover.animated.test.tsx +234 -0
  337. package/tests/PopoverAndMenuMultiTrigger.test.tsx +184 -0
  338. package/tests/PopoverAnimatePosition.animated.test.tsx +51 -0
  339. package/tests/PopoverClickDuringEnter.animated.test.tsx +197 -0
  340. package/tests/PopoverFocusScope.test.tsx +242 -0
  341. package/tests/PopoverHoverable.test.tsx +383 -0
  342. package/tests/PopoverHoverableDisableClick.test.tsx +106 -0
  343. package/tests/PopoverHoverableRapid.test.tsx +129 -0
  344. package/tests/PopoverHoverableReposition.test.tsx +111 -0
  345. package/tests/PopoverHoverableScoped.animated.test.tsx +103 -0
  346. package/tests/PopoverHoverableStress.test.tsx +169 -0
  347. package/tests/PopoverInitialPosition.animated.test.tsx +82 -0
  348. package/tests/PopoverMiddlewareSkipRegression.animated.test.tsx +221 -0
  349. package/tests/PopoverScoped.test.tsx +128 -0
  350. package/tests/PopoverScopedPositionGlitch.animated.test.tsx +184 -0
  351. package/tests/PopoverTriggerIsolation.test.tsx +62 -0
  352. package/tests/PseudoTransition.animated.test.tsx +319 -0
  353. package/tests/RawAnimatedValue.test.tsx +147 -0
  354. package/tests/RemoveScroll.test.tsx +223 -0
  355. package/tests/RenderProp.test.tsx +293 -0
  356. package/tests/ScrollViewRef.test.tsx +39 -0
  357. package/tests/SelectClickHold.test.tsx +147 -0
  358. package/tests/SelectFocusScope.test.tsx +176 -0
  359. package/tests/SelectInnerPositioning.test.tsx +82 -0
  360. package/tests/SelectKeyboardNav.test.tsx +173 -0
  361. package/tests/SelectPositioning.test.tsx +56 -0
  362. package/tests/SelectTypeahead.test.tsx +63 -0
  363. package/tests/Shadows.test.tsx +14 -0
  364. package/tests/SheetAnimation.animated.test.tsx +413 -0
  365. package/tests/SheetDrag.animated.test.tsx +223 -0
  366. package/tests/SheetDragResist.animated.test.tsx +393 -0
  367. package/tests/SheetOnAnimationComplete.animated.test.tsx +62 -0
  368. package/tests/SheetScrollLock.animated.test.tsx +287 -0
  369. package/tests/SheetScrollableDrag.animated.test.tsx +1264 -0
  370. package/tests/SheetSnapPointsFit.animated.test.tsx +259 -0
  371. package/tests/ShorthandVariables.test.tsx +44 -0
  372. package/tests/SpinnerCustomColors.test.tsx +67 -0
  373. package/tests/StackZIndex.test.tsx +51 -0
  374. package/tests/StressPagePerf.test.tsx +76 -0
  375. package/tests/StylePlatform.test.tsx +38 -0
  376. package/tests/StyleProp.test.tsx +20 -0
  377. package/tests/StyledAnchor.test.tsx +17 -0
  378. package/tests/StyledButtonTheme.test.tsx +22 -0
  379. package/tests/StyledButtonVariantPseudo.test.tsx +20 -0
  380. package/tests/StyledButtonVariantPseudoMerge.animated.test.tsx +33 -0
  381. package/tests/StyledCheckboxTheme.test.tsx +16 -0
  382. package/tests/StyledContextColor.test.tsx +119 -0
  383. package/tests/StyledContextTokens.test.tsx +56 -0
  384. package/tests/StyledHOCNamed.test.tsx +16 -0
  385. package/tests/StyledHtml.test.tsx +161 -0
  386. package/tests/StyledIconColor.test.tsx +32 -0
  387. package/tests/StyledInputFocusStyle.test.tsx +19 -0
  388. package/tests/StyledInputOnFocus.test.tsx +27 -0
  389. package/tests/StyledMediaQueryMerge.test.tsx +66 -0
  390. package/tests/StyledRNW.test.tsx +17 -0
  391. package/tests/StyledStyleableInputOnFocus.test.tsx +27 -0
  392. package/tests/StyledStyleableInputVariant.test.tsx +22 -0
  393. package/tests/StyledStyledStyleableInputOnFocus.test.tsx +27 -0
  394. package/tests/StyledVariantTextColor.test.tsx +24 -0
  395. package/tests/StyledViewOnFocus.test.tsx +27 -0
  396. package/tests/TabHoverAnimation.animated.test.tsx +468 -0
  397. package/tests/TabHoverPositionSmooth.animated.test.tsx +129 -0
  398. package/tests/TextNestedInheritance.test.tsx +93 -0
  399. package/tests/ThemeChange.test.tsx +70 -0
  400. package/tests/ThemeComponentResolution.test.tsx +82 -0
  401. package/tests/ThemeConditionalName.test.tsx +34 -0
  402. package/tests/ThemeMediaAnimation.test.tsx +65 -0
  403. package/tests/ThemeNested.test.tsx +141 -0
  404. package/tests/ThemeReset.test.tsx +63 -0
  405. package/tests/ThemeShallow.test.tsx +95 -0
  406. package/tests/Toast.test.tsx +106 -0
  407. package/tests/ToggleGroup.test.tsx +61 -0
  408. package/tests/ToggleGroupActiveProps.test.tsx +38 -0
  409. package/tests/ToggleGroupXGroup.test.tsx +172 -0
  410. package/tests/TooltipAnimation.animated.test.tsx +260 -0
  411. package/tests/TooltipEnterInterrupt.animated.test.tsx +76 -0
  412. package/tests/TooltipGlobalPattern.animated.test.tsx +208 -0
  413. package/tests/TooltipGroup.animated.test.tsx +79 -0
  414. package/tests/TooltipMultiTrigger.test.tsx +116 -0
  415. package/tests/TooltipPositionJump.animated.test.tsx +229 -0
  416. package/tests/TooltipPositionJumpNotes.md +219 -0
  417. package/tests/TooltipRapidSwitch.animated.test.tsx +399 -0
  418. package/tests/TooltipTriggerInline.test.tsx +65 -0
  419. package/tests/TransformMediaQueryMerge.test.tsx +104 -0
  420. package/tests/TransitionEnterExit.animated.test.tsx +311 -0
  421. package/tests/UseTheme.test.tsx +16 -0
  422. package/tests/V5ThemeBuilderOutput.test.tsx +164 -0
  423. package/tests/VariantFontFamily.test.tsx +11 -0
  424. package/tests/VariantsOrder.test.tsx +53 -0
  425. package/tests/_debug_position.mjs +52 -0
  426. package/tests/test-utils.ts +106 -0
  427. package/tests/utils.tsx +54 -0
  428. package/tsconfig.json +45 -0
  429. package/vite-env.d.ts +1 -0
  430. package/vite.config.ts +14 -0
  431. package/webpack.config.js +139 -0
@@ -0,0 +1,106 @@
1
+ import { expect, test } from '@playwright/test'
2
+ import { setupPage } from './test-utils'
3
+
4
+ /**
5
+ * Tests the disableClick prop on Popover.Trigger.
6
+ *
7
+ * Bug: when a hoverable popover is open and the user clicks the trigger,
8
+ * the browser fires pointerdown then click. If onPressIn calls setOpen(false),
9
+ * React re-renders between the two events. The built-in onPress handler then
10
+ * reads the updated open=false and toggles it back to true, causing a
11
+ * close → re-open → close oscillation (visible as a flash/glitch).
12
+ *
13
+ * Fix: disableClick prop suppresses the built-in click toggle on the trigger,
14
+ * allowing the user to control open/close entirely through their own handlers.
15
+ */
16
+
17
+ test.beforeEach(async ({ page }) => {
18
+ await setupPage(page, {
19
+ name: 'PopoverHoverableDisableClickCase',
20
+ type: 'useCase',
21
+ })
22
+ await page.waitForLoadState('networkidle')
23
+ await page.waitForTimeout(500)
24
+ })
25
+
26
+ test('disableClick: hover opens, click dismisses without re-open oscillation', async ({
27
+ page,
28
+ }) => {
29
+ const trigger = page.locator('#disableclick-trigger')
30
+ const content = page.locator('#disableclick-content')
31
+
32
+ await expect(content).not.toBeVisible()
33
+
34
+ // hover to open
35
+ await trigger.hover()
36
+ await page.waitForTimeout(300)
37
+ await expect(content).toBeVisible({ timeout: 2000 })
38
+
39
+ // click to dismiss (onPressIn sets open=false)
40
+ await trigger.click()
41
+ await page.waitForTimeout(500)
42
+
43
+ // popover should be closed — NOT re-opened by the built-in onPress toggle
44
+ await expect(content).not.toBeVisible({ timeout: 2000 })
45
+
46
+ // check the log for oscillation: if we see onOpenChange: true AFTER
47
+ // onPressIn: dismissing, that's the bug
48
+ const logEntries = await page.evaluate(() => {
49
+ const logEl = document.getElementById('open-state-log')
50
+ if (!logEl) return []
51
+ return Array.from(logEl.querySelectorAll('span')).map((el) => el.textContent || '')
52
+ })
53
+
54
+ console.log('State log:', logEntries)
55
+
56
+ // after the dismiss, there should be no onOpenChange: true
57
+ const dismissIdx = logEntries.findIndex((e) => e.includes('dismissing'))
58
+ if (dismissIdx >= 0) {
59
+ const afterDismiss = logEntries.slice(dismissIdx + 1)
60
+ const reOpened = afterDismiss.some((e) => e.includes('onOpenChange: true'))
61
+ expect(reOpened, 'Popover re-opened after dismiss (state oscillation bug)').toBe(
62
+ false
63
+ )
64
+ }
65
+ })
66
+
67
+ test('disableClick: hover opens, move away closes, re-hover re-opens', async ({
68
+ page,
69
+ }) => {
70
+ const trigger = page.locator('#disableclick-trigger')
71
+ const content = page.locator('#disableclick-content')
72
+
73
+ await expect(content).not.toBeVisible()
74
+
75
+ // hover to open
76
+ await trigger.hover()
77
+ await page.waitForTimeout(300)
78
+ await expect(content).toBeVisible({ timeout: 2000 })
79
+
80
+ // move away to close
81
+ await page.mouse.move(10, 10)
82
+ await page.waitForTimeout(800)
83
+ await expect(content).not.toBeVisible({ timeout: 2000 })
84
+
85
+ // hover again should re-open (hover still works with disableClick)
86
+ await trigger.hover()
87
+ await page.waitForTimeout(300)
88
+ await expect(content).toBeVisible({ timeout: 2000 })
89
+ })
90
+
91
+ test('normal trigger: click toggles popover open and closed', async ({ page }) => {
92
+ const trigger = page.locator('#withclick-trigger')
93
+ const content = page.locator('#withclick-content')
94
+
95
+ await expect(content).not.toBeVisible()
96
+
97
+ // click should open (normal trigger without disableClick)
98
+ await trigger.click()
99
+ await page.waitForTimeout(500)
100
+ await expect(content).toBeVisible({ timeout: 2000 })
101
+
102
+ // click again should close
103
+ await trigger.click()
104
+ await page.waitForTimeout(500)
105
+ await expect(content).not.toBeVisible({ timeout: 2000 })
106
+ })
@@ -0,0 +1,129 @@
1
+ import { expect, test } from '@playwright/test'
2
+ import { getBoundingRect, setupPage } from './test-utils'
3
+
4
+ // bug: rapidly sweeping across many side-by-side hoverable triggers with short
5
+ // restMs can cause the popover to get "stuck" on a past trigger or close
6
+ // unexpectedly. the root cause is a race between safePolygon closing during the
7
+ // gap between triggers and stale openRef preventing re-open.
8
+
9
+ test.describe('Popover hoverable rapid trigger switching', () => {
10
+ test.beforeEach(async ({ page }) => {
11
+ await setupPage(page, {
12
+ name: 'PopoverHoverableRapidCase',
13
+ type: 'useCase',
14
+ searchParams: { animationDriver: 'css' },
15
+ })
16
+ await page.waitForLoadState('networkidle')
17
+ })
18
+
19
+ test('rapid sweep: popover stays open and tracks final trigger', async ({ page }) => {
20
+ const content = page.locator('#rapid-content')
21
+
22
+ // hover first trigger to open
23
+ const triggerA = page.locator('#rapid-trigger-a')
24
+ await triggerA.hover()
25
+ await page.waitForTimeout(150) // restMs is 60ms + buffer
26
+ await expect(content).toBeVisible({ timeout: 2000 })
27
+
28
+ // move through gaps between triggers to expose the race condition:
29
+ // when the mouse is in the gap, onTriggerRef is false and safePolygon
30
+ // can fire a close. then when entering the next trigger, stale openRef
31
+ // prevents re-open.
32
+ const ids = ['a', 'b', 'c', 'd', 'e', 'f']
33
+ for (let i = 0; i < ids.length - 1; i++) {
34
+ const curr = page.locator(`#rapid-trigger-${ids[i]}`)
35
+ const next = page.locator(`#rapid-trigger-${ids[i + 1]}`)
36
+ const currBox = await curr.boundingBox()
37
+ const nextBox = await next.boundingBox()
38
+ if (currBox && nextBox) {
39
+ // move to the gap between current and next trigger
40
+ const gapX =
41
+ currBox.x + currBox.width + (nextBox.x - (currBox.x + currBox.width)) / 2
42
+ const gapY = currBox.y + currBox.height / 2
43
+ await page.mouse.move(gapX, gapY)
44
+ // wait in the gap for safePolygon's document mousemove handler to fire
45
+ // the mouse is now between triggers (onTriggerRef is false)
46
+ await page.waitForTimeout(30)
47
+ // now move to the next trigger
48
+ await page.mouse.move(
49
+ nextBox.x + nextBox.width / 2,
50
+ nextBox.y + nextBox.height / 2
51
+ )
52
+ await page.waitForTimeout(20)
53
+ }
54
+ }
55
+
56
+ // rest on the final trigger
57
+ await page.waitForTimeout(200)
58
+
59
+ // popover should still be visible (not closed/stuck)
60
+ await expect(content).toBeVisible({ timeout: 2000 })
61
+
62
+ // the panel label should show the final trigger
63
+ const label = page.locator('#rapid-panel-label')
64
+ await expect(label).toHaveText('Panel F', { timeout: 2000 })
65
+ })
66
+
67
+ test('rapid back-and-forth: popover stays open', async ({ page }) => {
68
+ const content = page.locator('#rapid-content')
69
+
70
+ // open on trigger c
71
+ const triggerC = page.locator('#rapid-trigger-c')
72
+ await triggerC.hover()
73
+ await page.waitForTimeout(150)
74
+ await expect(content).toBeVisible({ timeout: 2000 })
75
+
76
+ // sweep back and forth quickly with abrupt jumps
77
+ const sweep = ['d', 'e', 'f', 'e', 'd', 'c', 'b', 'a', 'b', 'c', 'd', 'e', 'f']
78
+ for (const id of sweep) {
79
+ const trigger = page.locator(`#rapid-trigger-${id}`)
80
+ const box = await trigger.boundingBox()
81
+ if (box) {
82
+ await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2, {
83
+ steps: 1,
84
+ })
85
+ }
86
+ await page.waitForTimeout(5)
87
+ }
88
+
89
+ await page.waitForTimeout(200)
90
+
91
+ // should still be open and tracking final trigger
92
+ await expect(content).toBeVisible({ timeout: 2000 })
93
+ const label = page.locator('#rapid-panel-label')
94
+ await expect(label).toHaveText('Panel F', { timeout: 2000 })
95
+ })
96
+
97
+ test('rapid sweep then rest: content positioned near final trigger', async ({
98
+ page,
99
+ }) => {
100
+ const content = page.locator('#rapid-content')
101
+
102
+ // open on first trigger
103
+ await page.locator('#rapid-trigger-a').hover()
104
+ await page.waitForTimeout(150)
105
+ await expect(content).toBeVisible({ timeout: 2000 })
106
+
107
+ // quickly move to last trigger
108
+ const triggerF = page.locator('#rapid-trigger-f')
109
+ const box = await triggerF.boundingBox()
110
+ if (box) {
111
+ await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2, {
112
+ steps: 3,
113
+ })
114
+ }
115
+ await page.waitForTimeout(200)
116
+
117
+ await expect(content).toBeVisible({ timeout: 2000 })
118
+
119
+ // content should be near trigger F, not stuck at trigger A
120
+ const triggerFRect = await getBoundingRect(page, '#rapid-trigger-f')
121
+ const triggerARect = await getBoundingRect(page, '#rapid-trigger-a')
122
+ const contentRect = await getBoundingRect(page, '#rapid-content')
123
+
124
+ // content x should be closer to trigger F than to trigger A
125
+ const distToF = Math.abs(contentRect!.x - triggerFRect!.x)
126
+ const distToA = Math.abs(contentRect!.x - triggerARect!.x)
127
+ expect(distToF).toBeLessThan(distToA)
128
+ })
129
+ })
@@ -0,0 +1,111 @@
1
+ import { expect, test } from '@playwright/test'
2
+ import { setupPage } from './test-utils'
3
+
4
+ // regression: when a scoped hoverable popover with animatePosition fully
5
+ // closes then reopens on a DIFFERENT trigger, it should appear at the new
6
+ // trigger's position — not animate/slide from the old trigger's position.
7
+
8
+ test.describe('Popover hoverable reposition after close', () => {
9
+ test.beforeEach(async ({ page }) => {
10
+ await setupPage(page, {
11
+ name: 'PopoverHoverableScopedCase',
12
+ type: 'useCase',
13
+ searchParams: { animationDriver: 'css' },
14
+ })
15
+ })
16
+
17
+ test('close then reopen on different trigger: no slide from old position', async ({
18
+ page,
19
+ }) => {
20
+ const content = page.locator('#nav-content')
21
+
22
+ // hover "about" trigger to open
23
+ const aboutTrigger = page.locator('#nav-trigger-about')
24
+ await aboutTrigger.hover()
25
+ await page.waitForTimeout(500)
26
+ await expect(content).toBeVisible()
27
+
28
+ // record content position when anchored to "about"
29
+ const aboutContentBox = await content.boundingBox()
30
+ expect(aboutContentBox).toBeTruthy()
31
+
32
+ // move mouse far away to fully close
33
+ await page.mouse.move(10, 600, { steps: 2 })
34
+ await page.waitForTimeout(800) // wait for exit animation (500ms transition)
35
+ await expect(content).not.toBeVisible()
36
+
37
+ // now hover "contact" trigger (rightmost, far from "about")
38
+ const contactTrigger = page.locator('#nav-trigger-contact')
39
+ const contactBox = await contactTrigger.boundingBox()
40
+ expect(contactBox).toBeTruthy()
41
+ await contactTrigger.hover()
42
+ await page.waitForTimeout(500)
43
+ await expect(content).toBeVisible()
44
+
45
+ // record content position when anchored to "contact"
46
+ const contactContentBox = await content.boundingBox()
47
+ expect(contactContentBox).toBeTruthy()
48
+
49
+ // the content x should be near "contact" trigger, NOT near "about" trigger.
50
+ // if the bug is present, content would start near aboutContentBox.x and
51
+ // slide to contactContentBox.x — we catch this by checking the FIRST frame.
52
+ // since we waited 500ms (full transition), the final position should be correct
53
+ // either way, but let's verify the position is near the contact trigger.
54
+ const contactTriggerCenter = contactBox!.x + contactBox!.width / 2
55
+ const contentCenter = contactContentBox!.x + contactContentBox!.width / 2
56
+
57
+ // content should be roughly centered on the contact trigger
58
+ expect(Math.abs(contactTriggerCenter - contentCenter)).toBeLessThan(
59
+ contactContentBox!.width / 2 + 50
60
+ )
61
+ })
62
+
63
+ test('close then reopen on different trigger: initial position is correct (no animation from old)', async ({
64
+ page,
65
+ }) => {
66
+ const content = page.locator('#nav-content')
67
+
68
+ // hover "about" trigger
69
+ const aboutTrigger = page.locator('#nav-trigger-about')
70
+ await aboutTrigger.hover()
71
+ await page.waitForTimeout(500)
72
+ await expect(content).toBeVisible()
73
+
74
+ const aboutContentBox = await content.boundingBox()
75
+
76
+ // fully close
77
+ await page.mouse.move(10, 600, { steps: 2 })
78
+ await page.waitForTimeout(800)
79
+ await expect(content).not.toBeVisible()
80
+
81
+ // hover "contact" trigger
82
+ const contactTrigger = page.locator('#nav-trigger-contact')
83
+ await contactTrigger.hover()
84
+
85
+ // check position EARLY — before the full 500ms transition
86
+ // if there's a slide-from-old-position bug, the early frame shows the old x
87
+ await page.waitForTimeout(100) // just enough for popover to appear
88
+ await expect(content).toBeVisible()
89
+
90
+ const earlyBox = await content.boundingBox()
91
+ expect(earlyBox).toBeTruthy()
92
+
93
+ // wait for full settle
94
+ await page.waitForTimeout(500)
95
+ const finalBox = await content.boundingBox()
96
+ expect(finalBox).toBeTruthy()
97
+
98
+ // the early position should be very close to the final position
99
+ // (no sliding from old trigger). allow tolerance for enter animation y offset.
100
+ const xDrift = Math.abs(earlyBox!.x - finalBox!.x)
101
+ expect(xDrift).toBeLessThan(20) // should be ~0 if no slide
102
+
103
+ // also verify it's NOT near the "about" position
104
+ if (aboutContentBox) {
105
+ const distFromAbout = Math.abs(earlyBox!.x - aboutContentBox.x)
106
+ const distFromFinal = Math.abs(earlyBox!.x - finalBox!.x)
107
+ // early box should be closer to final (contact) than to about
108
+ expect(distFromFinal).toBeLessThan(distFromAbout + 10)
109
+ }
110
+ })
111
+ })
@@ -0,0 +1,103 @@
1
+ import { expect, test } from '@playwright/test'
2
+ import { setupPage } from './test-utils'
3
+
4
+ /**
5
+ * Scoped multi-trigger popover with animatePosition:
6
+ * when switching between triggers, the popover content should smoothly
7
+ * animate its position — NOT snap instantly.
8
+ */
9
+ test.describe('Popover hoverable scoped position animation', () => {
10
+ test.beforeEach(async ({ page }, testInfo) => {
11
+ const driver = (testInfo.project?.metadata as any)?.animationDriver
12
+ // animatePosition works via CSS transitions — only test on css/motion drivers
13
+ if (driver !== 'css' && driver !== 'motion') {
14
+ test.skip()
15
+ return
16
+ }
17
+
18
+ await setupPage(page, {
19
+ name: 'PopoverHoverableScopedCase',
20
+ type: 'useCase',
21
+ searchParams: { animationDriver: driver },
22
+ })
23
+ await page.waitForLoadState('networkidle')
24
+ })
25
+
26
+ test('position smoothly animates when switching between triggers', async ({ page }) => {
27
+ const aboutTrigger = page.locator('#nav-trigger-about')
28
+ const contactTrigger = page.locator('#nav-trigger-contact')
29
+ const content = page.locator('#nav-content')
30
+
31
+ // hover about to open popover
32
+ await aboutTrigger.hover()
33
+ await page.waitForTimeout(450)
34
+ await expect(content).toBeVisible({ timeout: 3000 })
35
+
36
+ // wait for enter animation to settle
37
+ await page.waitForTimeout(600)
38
+
39
+ // get initial content X position (anchored near "about")
40
+ const posA = await content.evaluate((el) => {
41
+ const rect = el.getBoundingClientRect()
42
+ return { x: rect.left, y: rect.top }
43
+ })
44
+
45
+ // start tracking positions at 60fps
46
+ await page.evaluate(() => {
47
+ ;(window as any).__xPositions = []
48
+ const track = () => {
49
+ const el = document.getElementById('nav-content')
50
+ if (el) {
51
+ const rect = el.getBoundingClientRect()
52
+ ;(window as any).__xPositions.push({
53
+ x: rect.left,
54
+ time: Date.now(),
55
+ })
56
+ }
57
+ ;(window as any).__trackRaf = requestAnimationFrame(track)
58
+ }
59
+ ;(window as any).__trackRaf = requestAnimationFrame(track)
60
+ })
61
+
62
+ // switch to contact trigger (rightmost)
63
+ await contactTrigger.hover()
64
+
65
+ // wait for the 500ms position transition to complete
66
+ await page.waitForTimeout(700)
67
+
68
+ // stop tracking
69
+ await page.evaluate(() => cancelAnimationFrame((window as any).__trackRaf))
70
+
71
+ // get final content X position (should be anchored near "contact")
72
+ const posB = await content.evaluate((el) => {
73
+ const rect = el.getBoundingClientRect()
74
+ return { x: rect.left, y: rect.top }
75
+ })
76
+
77
+ const positions: { x: number; time: number }[] = await page.evaluate(
78
+ () => (window as any).__xPositions
79
+ )
80
+
81
+ // the two positions should be different (contact is to the right of about)
82
+ const xDiff = Math.abs(posB.x - posA.x)
83
+ expect(xDiff).toBeGreaterThan(20)
84
+
85
+ // check for intermediate positions: if animatePosition works correctly,
86
+ // we should see X values between posA.x and posB.x during the transition.
87
+ // if it snaps, all positions will be either at posA.x or posB.x.
88
+ const minX = Math.min(posA.x, posB.x)
89
+ const maxX = Math.max(posA.x, posB.x)
90
+ const margin = xDiff * 0.15
91
+
92
+ const intermediatePositions = positions.filter((p) => {
93
+ return p.x > minX + margin && p.x < maxX - margin
94
+ })
95
+
96
+ expect(
97
+ intermediatePositions.length,
98
+ `Position should animate smoothly between triggers. ` +
99
+ `posA.x=${posA.x.toFixed(1)}, posB.x=${posB.x.toFixed(1)}, ` +
100
+ `found ${intermediatePositions.length} intermediate frames out of ${positions.length} total`
101
+ ).toBeGreaterThan(2)
102
+ })
103
+ })
@@ -0,0 +1,169 @@
1
+ import { expect, test } from '@playwright/test'
2
+ import { setupPage } from './test-utils'
3
+
4
+ // stress tests for scoped hoverable popover with multiple triggers.
5
+ // hammers the interaction between safePolygon, multi-trigger switching,
6
+ // content interaction, diagonal movement, and open/close cycles.
7
+
8
+ test.describe('Popover hoverable stress', () => {
9
+ test.beforeEach(async ({ page }) => {
10
+ await setupPage(page, {
11
+ name: 'PopoverHoverableRapidCase',
12
+ type: 'useCase',
13
+ searchParams: { animationDriver: 'css' },
14
+ })
15
+ })
16
+
17
+ test('trigger → content → different trigger keeps popover open', async ({ page }) => {
18
+ const content = page.locator('#rapid-content')
19
+ const triggerA = page.locator('#rapid-trigger-a')
20
+ const triggerD = page.locator('#rapid-trigger-d')
21
+
22
+ // open on A
23
+ await triggerA.hover()
24
+ await page.waitForTimeout(150)
25
+ await expect(content).toBeVisible()
26
+
27
+ // move into the content
28
+ const cBox = await content.boundingBox()
29
+ await page.mouse.move(cBox!.x + cBox!.width / 2, cBox!.y + cBox!.height / 2)
30
+ await page.waitForTimeout(100)
31
+ await expect(content).toBeVisible()
32
+
33
+ // move from content back up to trigger D (skipping B, C)
34
+ const dBox = await triggerD.boundingBox()
35
+ await page.mouse.move(dBox!.x + dBox!.width / 2, dBox!.y + dBox!.height / 2, {
36
+ steps: 5,
37
+ })
38
+ await page.waitForTimeout(150)
39
+
40
+ await expect(content).toBeVisible()
41
+ await expect(page.locator('#rapid-panel-label')).toHaveText('Panel D')
42
+ })
43
+
44
+ test('diagonal movement through safe polygon keeps popover open', async ({ page }) => {
45
+ const content = page.locator('#rapid-content')
46
+ const triggerA = page.locator('#rapid-trigger-a')
47
+
48
+ // open on A
49
+ await triggerA.hover()
50
+ await page.waitForTimeout(150)
51
+ await expect(content).toBeVisible()
52
+
53
+ // get positions for diagonal sweep from trigger toward content corner
54
+ const aBox = await triggerA.boundingBox()
55
+ const cBox = await content.boundingBox()
56
+
57
+ // move diagonally from trigger bottom-right toward content top-left
58
+ const steps = 8
59
+ for (let i = 1; i <= steps; i++) {
60
+ const t = i / steps
61
+ const x = aBox!.x + aBox!.width * (1 - t * 0.3) // drift slightly left
62
+ const y = aBox!.y + aBox!.height + (cBox!.y - aBox!.y - aBox!.height) * t
63
+ await page.mouse.move(x, y)
64
+ await page.waitForTimeout(15)
65
+ }
66
+
67
+ // land on content
68
+ await page.mouse.move(cBox!.x + cBox!.width / 2, cBox!.y + 10)
69
+ await page.waitForTimeout(100)
70
+ await expect(content).toBeVisible()
71
+ })
72
+
73
+ test('mouse leaving to empty space closes popover', async ({ page }) => {
74
+ const content = page.locator('#rapid-content')
75
+ const triggerC = page.locator('#rapid-trigger-c')
76
+
77
+ await triggerC.hover()
78
+ await page.waitForTimeout(150)
79
+ await expect(content).toBeVisible()
80
+
81
+ // move mouse far away from both trigger and content
82
+ await page.mouse.move(10, 10, { steps: 2 })
83
+ await page.waitForTimeout(300)
84
+ await expect(content).not.toBeVisible()
85
+ })
86
+
87
+ test('repeated open/close cycles work reliably', async ({ page }) => {
88
+ const content = page.locator('#rapid-content')
89
+ const triggerB = page.locator('#rapid-trigger-b')
90
+
91
+ for (let cycle = 0; cycle < 3; cycle++) {
92
+ // open
93
+ await triggerB.hover()
94
+ await page.waitForTimeout(150)
95
+ await expect(content).toBeVisible()
96
+
97
+ // close by moving away
98
+ await page.mouse.move(10, 10, { steps: 2 })
99
+ await page.waitForTimeout(300)
100
+ await expect(content).not.toBeVisible()
101
+ }
102
+ })
103
+
104
+ test('fast sweep then slow traverse through gap', async ({ page }) => {
105
+ const content = page.locator('#rapid-content')
106
+ const triggerA = page.locator('#rapid-trigger-a')
107
+
108
+ // open on A
109
+ await triggerA.hover()
110
+ await page.waitForTimeout(150)
111
+ await expect(content).toBeVisible()
112
+
113
+ // fast sweep to E
114
+ const triggerE = page.locator('#rapid-trigger-e')
115
+ const eBox = await triggerE.boundingBox()
116
+ await page.mouse.move(eBox!.x + eBox!.width / 2, eBox!.y + eBox!.height / 2, {
117
+ steps: 2,
118
+ })
119
+ await page.waitForTimeout(100)
120
+
121
+ // now SLOWLY move down through the gap to content
122
+ const cBox = await content.boundingBox()
123
+ const startY = eBox!.y + eBox!.height
124
+ const endY = cBox!.y + 10
125
+ const slowSteps = 12
126
+ for (let i = 1; i <= slowSteps; i++) {
127
+ const t = i / slowSteps
128
+ await page.mouse.move(eBox!.x + eBox!.width / 2, startY + (endY - startY) * t)
129
+ await page.waitForTimeout(30)
130
+ }
131
+
132
+ await expect(content).toBeVisible()
133
+ })
134
+
135
+ // skipped in CI: runner is too slow for the tight timing in this sweep+enter sequence
136
+ test.skip(!!process.env.CI, 'flaky on slow CI runners')
137
+ test('sweep all triggers then enter content from last', async ({ page }) => {
138
+ const content = page.locator('#rapid-content')
139
+ const ids = ['a', 'b', 'c', 'd', 'e', 'f']
140
+
141
+ // open on first
142
+ await page.locator('#rapid-trigger-a').hover()
143
+ await page.waitForTimeout(150)
144
+ await expect(content).toBeVisible()
145
+
146
+ // sweep through all triggers
147
+ for (const id of ids.slice(1)) {
148
+ const el = page.locator(`#rapid-trigger-${id}`)
149
+ const box = await el.boundingBox()
150
+ await page.mouse.move(box!.x + box!.width / 2, box!.y + box!.height / 2, {
151
+ steps: 2,
152
+ })
153
+ await page.waitForTimeout(20)
154
+ }
155
+
156
+ // rest on F
157
+ await page.waitForTimeout(100)
158
+ await expect(content).toBeVisible()
159
+ await expect(page.locator('#rapid-panel-label')).toHaveText('Panel F')
160
+
161
+ // move into content from F
162
+ const cBox = await content.boundingBox()
163
+ await page.mouse.move(cBox!.x + cBox!.width / 2, cBox!.y + cBox!.height / 2, {
164
+ steps: 5,
165
+ })
166
+ await page.waitForTimeout(100)
167
+ await expect(content).toBeVisible()
168
+ })
169
+ })