@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,208 @@
1
+ import { expect, test } from '@playwright/test'
2
+ import { setupPage } from './test-utils'
3
+
4
+ /**
5
+ * Global tooltip pattern: single scoped tooltip with triggers in opposite corners.
6
+ *
7
+ * Two behaviors to verify:
8
+ * 1. While open, switching triggers → smooth position animation
9
+ * 2. Close at A, reopen at B → snap position (animateOnly: []), then animate in
10
+ */
11
+
12
+ test.describe('Tooltip Global Pattern', () => {
13
+ test.beforeEach(async ({ page }, testInfo) => {
14
+ const driver = (testInfo.project?.metadata as any)?.animationDriver
15
+ if (driver !== 'motion') {
16
+ test.skip()
17
+ return
18
+ }
19
+
20
+ await setupPage(page, { name: 'TooltipGlobalPatternCase', type: 'useCase' })
21
+ await page.waitForLoadState('networkidle')
22
+ await page.waitForTimeout(500)
23
+ })
24
+
25
+ test('while open: smoothly animates position when switching triggers', async ({
26
+ page,
27
+ }) => {
28
+ const triggerTL = page.locator('[data-testid="trigger-tl"]')
29
+ const triggerBR = page.locator('[data-testid="trigger-br"]')
30
+ const content = page.locator('[data-testid="global-tip-content"]')
31
+
32
+ const tlBox = await triggerTL.boundingBox()
33
+ const brBox = await triggerBR.boundingBox()
34
+ expect(tlBox).toBeTruthy()
35
+ expect(brBox).toBeTruthy()
36
+
37
+ const tlCenter = {
38
+ x: tlBox!.x + tlBox!.width / 2,
39
+ y: tlBox!.y + tlBox!.height / 2,
40
+ }
41
+ const brCenter = {
42
+ x: brBox!.x + brBox!.width / 2,
43
+ y: brBox!.y + brBox!.height / 2,
44
+ }
45
+
46
+ // hover top-left trigger to open
47
+ await page.mouse.move(tlCenter.x, tlCenter.y)
48
+ await page.waitForTimeout(100)
49
+ await page.mouse.move(tlCenter.x + 1, tlCenter.y + 1)
50
+ await page.waitForTimeout(800)
51
+ await expect(content).toBeVisible({ timeout: 5000 })
52
+ await page.waitForTimeout(300)
53
+
54
+ const posA = await content.evaluate((el) => {
55
+ const rect = el.getBoundingClientRect()
56
+ return { x: rect.left, y: rect.top }
57
+ })
58
+
59
+ // track positions
60
+ await page.evaluate(() => {
61
+ ;(window as any).__positions = []
62
+ const track = () => {
63
+ const el = document.querySelector('[data-testid="global-tip-content"]')
64
+ if (el) {
65
+ const rect = el.getBoundingClientRect()
66
+ ;(window as any).__positions.push({
67
+ x: rect.left,
68
+ y: rect.top,
69
+ time: Date.now(),
70
+ })
71
+ }
72
+ ;(window as any).__rafId = requestAnimationFrame(track)
73
+ }
74
+ ;(window as any).__rafId = requestAnimationFrame(track)
75
+ })
76
+
77
+ // switch to bottom-right (tooltip stays open via triggerElements)
78
+ await page.mouse.move(brCenter.x, brCenter.y)
79
+ await page.waitForTimeout(100)
80
+ await page.mouse.move(brCenter.x + 1, brCenter.y + 1)
81
+ await page.waitForTimeout(400)
82
+
83
+ await page.evaluate(() => cancelAnimationFrame((window as any).__rafId))
84
+
85
+ const posB = await content.evaluate((el) => {
86
+ const rect = el.getBoundingClientRect()
87
+ return { x: rect.left, y: rect.top }
88
+ })
89
+
90
+ const positions: { x: number; y: number }[] = await page.evaluate(
91
+ () => (window as any).__positions
92
+ )
93
+
94
+ const xDiff = Math.abs(posB.x - posA.x)
95
+ expect(xDiff).toBeGreaterThan(100)
96
+
97
+ const minX = Math.min(posA.x, posB.x)
98
+ const maxX = Math.max(posA.x, posB.x)
99
+ const margin = xDiff * 0.15
100
+
101
+ const intermediatePositions = positions.filter(
102
+ (p) => p.x > minX + margin && p.x < maxX - margin
103
+ )
104
+
105
+ expect(
106
+ intermediatePositions.length,
107
+ `Should smoothly animate while open. ` +
108
+ `posA.x=${posA.x.toFixed(1)}, posB.x=${posB.x.toFixed(1)}, ` +
109
+ `intermediate=${intermediatePositions.length}/${positions.length}`
110
+ ).toBeGreaterThan(2)
111
+ })
112
+
113
+ test('close then reopen: snaps position, does not slide from old position', async ({
114
+ page,
115
+ }) => {
116
+ const triggerTL = page.locator('[data-testid="trigger-tl"]')
117
+ const triggerBR = page.locator('[data-testid="trigger-br"]')
118
+ const content = page.locator('[data-testid="global-tip-content"]')
119
+
120
+ const tlBox = await triggerTL.boundingBox()
121
+ const brBox = await triggerBR.boundingBox()
122
+ expect(tlBox).toBeTruthy()
123
+ expect(brBox).toBeTruthy()
124
+
125
+ const tlCenter = {
126
+ x: tlBox!.x + tlBox!.width / 2,
127
+ y: tlBox!.y + tlBox!.height / 2,
128
+ }
129
+ const brCenter = {
130
+ x: brBox!.x + brBox!.width / 2,
131
+ y: brBox!.y + brBox!.height / 2,
132
+ }
133
+
134
+ // open at top-left
135
+ await page.mouse.move(tlCenter.x, tlCenter.y)
136
+ await page.waitForTimeout(100)
137
+ await page.mouse.move(tlCenter.x + 1, tlCenter.y + 1)
138
+ await page.waitForTimeout(800)
139
+ await expect(content).toBeVisible({ timeout: 5000 })
140
+ await page.waitForTimeout(300)
141
+
142
+ const posA = await content.evaluate((el) => {
143
+ const rect = el.getBoundingClientRect()
144
+ return { x: rect.left, y: rect.top }
145
+ })
146
+
147
+ // close: move mouse far away
148
+ await page.mouse.move(400, 300)
149
+ await page.waitForTimeout(1000)
150
+ await expect(content).not.toBeVisible({ timeout: 5000 })
151
+
152
+ // start tracking
153
+ await page.evaluate(() => {
154
+ ;(window as any).__positions = []
155
+ const track = () => {
156
+ const el = document.querySelector('[data-testid="global-tip-content"]')
157
+ if (el) {
158
+ const rect = el.getBoundingClientRect()
159
+ const style = getComputedStyle(el)
160
+ ;(window as any).__positions.push({
161
+ x: rect.left,
162
+ y: rect.top,
163
+ opacity: parseFloat(style.opacity),
164
+ time: Date.now(),
165
+ })
166
+ }
167
+ ;(window as any).__rafId = requestAnimationFrame(track)
168
+ }
169
+ ;(window as any).__rafId = requestAnimationFrame(track)
170
+ })
171
+
172
+ // reopen at bottom-right
173
+ await page.mouse.move(brCenter.x, brCenter.y)
174
+ await page.waitForTimeout(100)
175
+ await page.mouse.move(brCenter.x + 1, brCenter.y + 1)
176
+ await page.waitForTimeout(1000)
177
+ await expect(content).toBeVisible({ timeout: 5000 })
178
+
179
+ await page.evaluate(() => cancelAnimationFrame((window as any).__rafId))
180
+
181
+ const posB = await content.evaluate((el) => {
182
+ const rect = el.getBoundingClientRect()
183
+ return { x: rect.left, y: rect.top }
184
+ })
185
+
186
+ const positions: { x: number; y: number; opacity: number }[] = await page.evaluate(
187
+ () => (window as any).__positions
188
+ )
189
+
190
+ const totalDistance = Math.sqrt((posB.x - posA.x) ** 2 + (posB.y - posA.y) ** 2)
191
+ expect(totalDistance).toBeGreaterThan(200)
192
+
193
+ // no VISIBLE frame should be in the "middle zone" between A and B
194
+ // the tooltip should snap to B's position, not slide from A
195
+ const threshold = totalDistance * 0.25
196
+ const middlePositions = positions.filter((p) => {
197
+ if (p.opacity < 0.1) return false
198
+ const distFromA = Math.sqrt((p.x - posA.x) ** 2 + (p.y - posA.y) ** 2)
199
+ const distFromB = Math.sqrt((p.x - posB.x) ** 2 + (p.y - posB.y) ** 2)
200
+ return distFromA > threshold && distFromB > threshold
201
+ })
202
+
203
+ expect(
204
+ middlePositions.length,
205
+ `Close→reopen should snap, not slide. Found ${middlePositions.length} frames in middle zone`
206
+ ).toBeLessThanOrEqual(2)
207
+ })
208
+ })
@@ -0,0 +1,79 @@
1
+ import { expect, test, type Page } from '@playwright/test'
2
+ import { setupPage } from './test-utils'
3
+
4
+ /**
5
+ * TooltipGroup Tests
6
+ *
7
+ * TooltipGroup enables grouped delay behavior:
8
+ * - First tooltip in group: waits for full delay
9
+ * - Subsequent tooltips: show immediately while within the group's timeout window
10
+ *
11
+ * This is implemented via FloatingDelayGroup from @hanzogui/floating.
12
+ */
13
+
14
+ async function tooltipVisible(page: Page, testId: string): Promise<boolean> {
15
+ return page.evaluate((id) => !!document.querySelector(`[data-testid="${id}"]`), testId)
16
+ }
17
+
18
+ test.describe('TooltipGroup', () => {
19
+ test.beforeEach(async ({ page }) => {
20
+ await setupPage(page, { name: 'TooltipGroupCase', type: 'useCase' })
21
+ })
22
+
23
+ test('subsequent grouped tooltips skip delay', async ({ page }) => {
24
+ // Config: 1000ms open delay, 500ms timeout
25
+ const DELAY = 1000
26
+
27
+ // Show first tooltip (with delay)
28
+ await page.getByTestId('tooltip-trigger-1').hover()
29
+ await page.waitForTimeout(DELAY + 200)
30
+ expect(await tooltipVisible(page, 'tooltip-content-1')).toBe(true)
31
+
32
+ // Move to second tooltip - should appear quickly (no full delay)
33
+ await page.getByTestId('tooltip-trigger-2').hover()
34
+ await page.waitForTimeout(300)
35
+
36
+ expect(
37
+ await tooltipVisible(page, 'tooltip-content-2'),
38
+ 'Second grouped tooltip should appear without waiting for full delay'
39
+ ).toBe(true)
40
+ })
41
+
42
+ test('first grouped tooltip respects delay', async ({ page }) => {
43
+ const DELAY = 1000
44
+
45
+ await page.getByTestId('tooltip-trigger-1').hover()
46
+
47
+ // Should NOT appear before delay
48
+ await page.waitForTimeout(200)
49
+ expect(
50
+ await tooltipVisible(page, 'tooltip-content-1'),
51
+ 'Should not appear before delay'
52
+ ).toBe(false)
53
+
54
+ // Should appear after delay
55
+ await page.waitForTimeout(DELAY)
56
+ expect(
57
+ await tooltipVisible(page, 'tooltip-content-1'),
58
+ 'Should appear after delay'
59
+ ).toBe(true)
60
+ })
61
+
62
+ test('standalone tooltips always have delay', async ({ page }) => {
63
+ const DELAY = 1000
64
+
65
+ // Show standalone A
66
+ await page.getByTestId('tooltip-trigger-standalone-a').hover()
67
+ await page.waitForTimeout(DELAY + 200)
68
+ expect(await tooltipVisible(page, 'tooltip-content-standalone-a')).toBe(true)
69
+
70
+ // Move to standalone B - should NOT appear quickly (has its own delay)
71
+ await page.getByTestId('tooltip-trigger-standalone-b').hover()
72
+ await page.waitForTimeout(300)
73
+
74
+ expect(
75
+ await tooltipVisible(page, 'tooltip-content-standalone-b'),
76
+ 'Standalone tooltips should always wait for their delay'
77
+ ).toBe(false)
78
+ })
79
+ })
@@ -0,0 +1,116 @@
1
+ import { expect, test } from '@playwright/test'
2
+ import { getBoundingRect, setupPage } from './test-utils'
3
+
4
+ test.describe('Tooltip multi-trigger rapid hover', () => {
5
+ test.beforeEach(async ({ page }) => {
6
+ await setupPage(page, {
7
+ name: 'TooltipMultiTriggerCase',
8
+ type: 'useCase',
9
+ })
10
+ await page.waitForLoadState('networkidle')
11
+ })
12
+
13
+ test('rapid sweep: tooltip stays open and tracks final trigger', async ({ page }) => {
14
+ const content = page.locator('#tip-content')
15
+
16
+ // hover first trigger to open
17
+ await page.locator('#tip-trigger-a').hover()
18
+ await page.waitForTimeout(200)
19
+ await expect(content).toBeVisible({ timeout: 3000 })
20
+
21
+ // sweep through triggers with gaps
22
+ const ids = ['a', 'b', 'c']
23
+ for (let i = 0; i < ids.length - 1; i++) {
24
+ const curr = page.locator(`#tip-trigger-${ids[i]}`)
25
+ const next = page.locator(`#tip-trigger-${ids[i + 1]}`)
26
+ const currBox = await curr.boundingBox()
27
+ const nextBox = await next.boundingBox()
28
+ if (currBox && nextBox) {
29
+ // move to gap between triggers
30
+ const gapX =
31
+ currBox.x + currBox.width + (nextBox.x - (currBox.x + currBox.width)) / 2
32
+ const gapY = currBox.y + currBox.height / 2
33
+ await page.mouse.move(gapX, gapY)
34
+ await page.waitForTimeout(30)
35
+ // move to next trigger
36
+ await page.mouse.move(
37
+ nextBox.x + nextBox.width / 2,
38
+ nextBox.y + nextBox.height / 2
39
+ )
40
+ await page.waitForTimeout(20)
41
+ }
42
+ }
43
+
44
+ await page.waitForTimeout(200)
45
+ await expect(content).toBeVisible({ timeout: 2000 })
46
+
47
+ const label = page.locator('#tip-label')
48
+ await expect(label).toContainText('Expert', { timeout: 2000 })
49
+ })
50
+
51
+ test('arrow stays aligned with hovered trigger after switch', async ({ page }) => {
52
+ const content = page.locator('#tip-content')
53
+
54
+ // hover first trigger to open
55
+ await page.locator('#tip-trigger-a').hover()
56
+ await page.waitForTimeout(200)
57
+ await expect(content).toBeVisible({ timeout: 3000 })
58
+
59
+ // verify arrow is near trigger A
60
+ const arrowA = await getBoundingRect(page, '#tip-arrow')
61
+ const triggerARect = await getBoundingRect(page, '#tip-trigger-a')
62
+ expect(arrowA).toBeTruthy()
63
+ const arrowACX = arrowA!.x + arrowA!.width / 2
64
+ const triggerACX = triggerARect!.x + triggerARect!.width / 2
65
+ // arrow should be within 30px of trigger center
66
+ expect(Math.abs(arrowACX - triggerACX)).toBeLessThan(30)
67
+
68
+ // quickly jump to trigger C (2 steps = very fast mouse move)
69
+ const triggerC = page.locator('#tip-trigger-c')
70
+ const cBox = await triggerC.boundingBox()
71
+ await page.mouse.move(cBox!.x + cBox!.width / 2, cBox!.y + cBox!.height / 2, {
72
+ steps: 2,
73
+ })
74
+
75
+ // wait for position to settle (animatePosition transition)
76
+ await page.waitForTimeout(500)
77
+ await expect(content).toBeVisible({ timeout: 2000 })
78
+
79
+ // arrow should now be near trigger C, not still near trigger A
80
+ const arrowC = await getBoundingRect(page, '#tip-arrow')
81
+ const triggerCRect = await getBoundingRect(page, '#tip-trigger-c')
82
+ const contentRect = await getBoundingRect(page, '#tip-content')
83
+ expect(arrowC).toBeTruthy()
84
+ expect(contentRect).toBeTruthy()
85
+
86
+ const arrowCCX = arrowC!.x + arrowC!.width / 2
87
+ const triggerCCX = triggerCRect!.x + triggerCRect!.width / 2
88
+ const contentCCX = contentRect!.x + contentRect!.width / 2
89
+
90
+ // arrow center should be within content bounds
91
+ expect(arrowCCX).toBeGreaterThanOrEqual(contentRect!.x)
92
+ expect(arrowCCX).toBeLessThanOrEqual(contentRect!.x + contentRect!.width)
93
+
94
+ // arrow should be reasonably near trigger C (not still at A)
95
+ // with offset=20 and animatePosition, arrow should track the trigger
96
+ expect(
97
+ Math.abs(arrowCCX - triggerCCX),
98
+ `arrow center (${arrowCCX}) should be near trigger C center (${triggerCCX}), content center at ${contentCCX}`
99
+ ).toBeLessThan(40)
100
+ })
101
+
102
+ test('tooltip closes when cursor leaves all triggers', async ({ page }) => {
103
+ const content = page.locator('#tip-content')
104
+
105
+ // hover to open
106
+ await page.locator('#tip-trigger-b').hover()
107
+ await page.waitForTimeout(200)
108
+ await expect(content).toBeVisible({ timeout: 3000 })
109
+
110
+ // move cursor far away from triggers
111
+ await page.mouse.move(10, 10)
112
+ await page.waitForTimeout(500)
113
+
114
+ await expect(content).not.toBeVisible({ timeout: 3000 })
115
+ })
116
+ })
@@ -0,0 +1,229 @@
1
+ import { expect, test, type Page } from '@playwright/test'
2
+ import { setupPage } from './test-utils'
3
+
4
+ /**
5
+ * TOOLTIP POSITION JUMP TEST (Motion Driver Only)
6
+ *
7
+ * Bug: When using animatePosition with a scoped tooltip,
8
+ * rapidly moving between triggers causes the tooltip to JUMP to wrong position
9
+ * (often near origin/top-left) before animating back.
10
+ *
11
+ * Key: Must actually MOVE the mouse across, not just call hover() which teleports.
12
+ */
13
+
14
+ test.describe('Tooltip Position Jump', () => {
15
+ test.beforeEach(async ({ page }, testInfo) => {
16
+ // only test with motion driver - this is where the bug manifests
17
+ const driver = (testInfo.project?.metadata as any)?.animationDriver
18
+ if (driver !== 'motion') {
19
+ test.skip()
20
+ return
21
+ }
22
+
23
+ await setupPage(page, { name: 'TooltipPositionJumpCase', type: 'useCase' })
24
+ await page.waitForTimeout(500)
25
+ })
26
+
27
+ test('scoped tooltip should not jump when moving quickly between triggers', async ({
28
+ page,
29
+ }) => {
30
+ // get button positions
31
+ const hireBtn = page.locator('[data-testid="tooltip-trigger-hire"]')
32
+ const bentoBtn = page.locator('[data-testid="tooltip-trigger-bento"]')
33
+ const takeoutBtn = page.locator('[data-testid="tooltip-trigger-takeout"]')
34
+
35
+ const hireBox = await hireBtn.boundingBox()
36
+ const bentoBox = await bentoBtn.boundingBox()
37
+ const takeoutBox = await takeoutBtn.boundingBox()
38
+
39
+ if (!hireBox || !bentoBox || !takeoutBox) {
40
+ throw new Error('Could not find button positions')
41
+ }
42
+
43
+ // inject position tracking script
44
+ await page.evaluate(() => {
45
+ ;(window as any).__tooltipPositions = []
46
+ ;(window as any).__trackTooltip = () => {
47
+ const el = document.querySelector(
48
+ '[data-testid="tooltip-jump-content"]'
49
+ ) as HTMLElement
50
+ if (!el) return null
51
+
52
+ const style = getComputedStyle(el)
53
+ const transform = style.transform
54
+ if (!transform || transform === 'none') return null
55
+
56
+ const match = transform.match(
57
+ /matrix\([^,]+,\s*[^,]+,\s*[^,]+,\s*[^,]+,\s*([^,]+),\s*([^)]+)\)/
58
+ )
59
+ if (!match) return null
60
+
61
+ const pos = { x: parseFloat(match[1]), y: parseFloat(match[2]), time: Date.now() }
62
+ ;(window as any).__tooltipPositions.push(pos)
63
+ return pos
64
+ }
65
+
66
+ // start tracking at 60fps
67
+ const track = () => {
68
+ ;(window as any).__trackTooltip()
69
+ requestAnimationFrame(track)
70
+ }
71
+ requestAnimationFrame(track)
72
+ })
73
+
74
+ // step 1: hover on rightmost button (HIRE) and wait for tooltip to fully appear
75
+ const hireCenter = {
76
+ x: hireBox.x + hireBox.width / 2,
77
+ y: hireBox.y + hireBox.height / 2,
78
+ }
79
+ await page.mouse.move(hireCenter.x, hireCenter.y)
80
+ await page.waitForTimeout(800)
81
+
82
+ // step 2: move mouse QUICKLY across to leftmost button (TAKEOUT)
83
+ // must actually traverse the path, not teleport
84
+ const takeoutCenter = {
85
+ x: takeoutBox.x + takeoutBox.width / 2,
86
+ y: takeoutBox.y + takeoutBox.height / 2,
87
+ }
88
+
89
+ // do fast sweep: HIRE -> TAKEOUT in ~100ms total
90
+ const steps = 10
91
+ const totalTime = 100
92
+ const stepDelay = totalTime / steps
93
+
94
+ for (let i = 1; i <= steps; i++) {
95
+ const t = i / steps
96
+ const x = hireCenter.x + (takeoutCenter.x - hireCenter.x) * t
97
+ const y = hireCenter.y + (takeoutCenter.y - hireCenter.y) * t
98
+ await page.mouse.move(x, y)
99
+ await page.waitForTimeout(stepDelay)
100
+ }
101
+
102
+ await page.waitForTimeout(300)
103
+
104
+ // analyze for jumps
105
+ const positions = await page.evaluate(() => (window as any).__tooltipPositions)
106
+ const jumps: any[] = []
107
+
108
+ for (let i = 1; i < positions.length; i++) {
109
+ const prev = positions[i - 1]
110
+ const curr = positions[i]
111
+ const dx = Math.abs(curr.x - prev.x)
112
+ const dy = Math.abs(curr.y - prev.y)
113
+ const delta = Math.sqrt(dx * dx + dy * dy)
114
+ const timeDelta = curr.time - prev.time
115
+
116
+ // jump: large delta in short time, going toward origin
117
+ if (timeDelta < 50 && delta > 100) {
118
+ const jumpingLeft = curr.x < prev.x - 100
119
+ const jumpingUp = curr.y < prev.y - 50
120
+ const jumpToNearOrigin = curr.x < 200 && curr.y < 200 && prev.x > 300
121
+
122
+ if (jumpingLeft || jumpingUp || jumpToNearOrigin) {
123
+ jumps.push({ from: prev, to: curr, delta: Math.round(delta) })
124
+ }
125
+ }
126
+ }
127
+
128
+ console.log(`Collected ${positions.length} positions, found ${jumps.length} jumps`)
129
+ if (jumps.length > 0) {
130
+ console.log('Jumps:', JSON.stringify(jumps, null, 2))
131
+ }
132
+
133
+ expect(jumps.length, `Detected ${jumps.length} position jumps!`).toBe(0)
134
+ })
135
+
136
+ test('rapid back-and-forth should not cause jumps', async ({ page }) => {
137
+ const hireBtn = page.locator('[data-testid="tooltip-trigger-hire"]')
138
+ const takeoutBtn = page.locator('[data-testid="tooltip-trigger-takeout"]')
139
+
140
+ const hireBox = await hireBtn.boundingBox()
141
+ const takeoutBox = await takeoutBtn.boundingBox()
142
+
143
+ if (!hireBox || !takeoutBox) {
144
+ throw new Error('Could not find button positions')
145
+ }
146
+
147
+ // inject tracking
148
+ await page.evaluate(() => {
149
+ ;(window as any).__tooltipPositions = []
150
+ const track = () => {
151
+ const el = document.querySelector(
152
+ '[data-testid="tooltip-jump-content"]'
153
+ ) as HTMLElement
154
+ if (el) {
155
+ const transform = getComputedStyle(el).transform
156
+ if (transform && transform !== 'none') {
157
+ const match = transform.match(
158
+ /matrix\([^,]+,\s*[^,]+,\s*[^,]+,\s*[^,]+,\s*([^,]+),\s*([^)]+)\)/
159
+ )
160
+ if (match) {
161
+ ;(window as any).__tooltipPositions.push({
162
+ x: parseFloat(match[1]),
163
+ y: parseFloat(match[2]),
164
+ time: Date.now(),
165
+ })
166
+ }
167
+ }
168
+ }
169
+ requestAnimationFrame(track)
170
+ }
171
+ requestAnimationFrame(track)
172
+ })
173
+
174
+ const hireCenter = {
175
+ x: hireBox.x + hireBox.width / 2,
176
+ y: hireBox.y + hireBox.height / 2,
177
+ }
178
+ const takeoutCenter = {
179
+ x: takeoutBox.x + takeoutBox.width / 2,
180
+ y: takeoutBox.y + takeoutBox.height / 2,
181
+ }
182
+
183
+ // hover HIRE first
184
+ await page.mouse.move(hireCenter.x, hireCenter.y)
185
+ await page.waitForTimeout(600)
186
+
187
+ // do 3 rapid back-and-forth sweeps
188
+ for (let sweep = 0; sweep < 3; sweep++) {
189
+ for (let i = 0; i <= 8; i++) {
190
+ const t = i / 8
191
+ await page.mouse.move(
192
+ hireCenter.x + (takeoutCenter.x - hireCenter.x) * t,
193
+ hireCenter.y + (takeoutCenter.y - hireCenter.y) * t
194
+ )
195
+ await page.waitForTimeout(10)
196
+ }
197
+ for (let i = 0; i <= 8; i++) {
198
+ const t = i / 8
199
+ await page.mouse.move(
200
+ takeoutCenter.x + (hireCenter.x - takeoutCenter.x) * t,
201
+ takeoutCenter.y + (hireCenter.y - takeoutCenter.y) * t
202
+ )
203
+ await page.waitForTimeout(10)
204
+ }
205
+ }
206
+
207
+ await page.waitForTimeout(300)
208
+
209
+ const positions = await page.evaluate(() => (window as any).__tooltipPositions)
210
+ const jumps: any[] = []
211
+
212
+ for (let i = 1; i < positions.length; i++) {
213
+ const prev = positions[i - 1]
214
+ const curr = positions[i]
215
+ const delta = Math.sqrt((curr.x - prev.x) ** 2 + (curr.y - prev.y) ** 2)
216
+ const timeDelta = curr.time - prev.time
217
+
218
+ if (timeDelta < 50 && delta > 100) {
219
+ const jumpingTowardOrigin = curr.x < prev.x - 50 && curr.x < 200
220
+ if (jumpingTowardOrigin) {
221
+ jumps.push({ from: prev, to: curr, delta: Math.round(delta) })
222
+ }
223
+ }
224
+ }
225
+
226
+ console.log(`Found ${jumps.length} jumps in back-and-forth test`)
227
+ expect(jumps.length).toBe(0)
228
+ })
229
+ })