@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,468 @@
1
+ import { expect, test } from '@playwright/test'
2
+ import { setupPage } from './test-utils'
3
+
4
+ test.use({
5
+ viewport: { width: 600, height: 400 },
6
+ launchOptions: {
7
+ args: ['--window-position=1400,700', '--window-size=620,420'],
8
+ },
9
+ })
10
+
11
+ test.beforeEach(async ({ page }, testInfo) => {
12
+ // native driver has fundamental issues with hover/animation on web
13
+ test.skip(
14
+ testInfo.project.name === 'animated-native',
15
+ 'Native driver does not support hover animations on web'
16
+ )
17
+
18
+ await setupPage(page, { name: 'TabHoverAnimationCase', type: 'useCase' })
19
+ await page.waitForTimeout(500)
20
+ })
21
+
22
+ const TAB_IDS = ['tab-tab-a', 'tab-tab-b', 'tab-tab-c', 'tab-tab-d', 'tab-tab-e']
23
+
24
+ // hover a tab by moving the mouse to its center
25
+ async function hoverTab(page: any, tabId: string) {
26
+ const tab = page.locator(`[data-testid="${tabId}"]`)
27
+ await tab.hover()
28
+ await page.waitForTimeout(50)
29
+ }
30
+
31
+ // rapidly sweep across tabs left-to-right or right-to-left
32
+ async function sweepTabs(page: any, ids: string[], delayMs = 30) {
33
+ for (const id of ids) {
34
+ await page.locator(`[data-testid="${id}"]`).hover()
35
+ await page.waitForTimeout(delayMs)
36
+ }
37
+ }
38
+
39
+ async function getGoingDirection(page: any): Promise<number> {
40
+ return page.locator('#going-direction').evaluate((el: HTMLElement) => {
41
+ return Number(el.dataset.going || '0')
42
+ })
43
+ }
44
+
45
+ async function getSlideContentInfo(page: any) {
46
+ return page.evaluate(() => {
47
+ const els = document.querySelectorAll('[data-testid="slide-content"]')
48
+ return Array.from(els).map((el) => ({
49
+ tab: (el as HTMLElement).dataset.tab,
50
+ going: (el as HTMLElement).dataset.going,
51
+ transform: getComputedStyle(el).transform,
52
+ opacity: getComputedStyle(el).opacity,
53
+ }))
54
+ })
55
+ }
56
+
57
+ // track translateX over several rAF frames
58
+ async function trackTranslateX(page: any, selector: string, frames = 6) {
59
+ return page.evaluate(
60
+ ({ selector, frames }) => {
61
+ return new Promise<number[]>((resolve) => {
62
+ const el = document.querySelector(selector)
63
+ if (!el) return resolve([])
64
+ const values: number[] = []
65
+ let count = 0
66
+ function tick() {
67
+ const style = getComputedStyle(el!)
68
+ const matrix = new DOMMatrix(style.transform)
69
+ values.push(matrix.m41)
70
+ count++
71
+ if (count < frames) {
72
+ requestAnimationFrame(tick)
73
+ } else {
74
+ resolve(values)
75
+ }
76
+ }
77
+ requestAnimationFrame(tick)
78
+ })
79
+ },
80
+ { selector, frames }
81
+ )
82
+ }
83
+
84
+ // === Bug 1: AnimatePresence direction ===
85
+
86
+ test('direction: hover right sets going=1', async ({ page }) => {
87
+ await hoverTab(page, 'tab-tab-a')
88
+ await page.waitForTimeout(200)
89
+ await hoverTab(page, 'tab-tab-c')
90
+ await page.waitForTimeout(200)
91
+ const going = await getGoingDirection(page)
92
+ expect(going).toBe(1)
93
+ })
94
+
95
+ test('direction: hover left sets going=-1', async ({ page }) => {
96
+ await hoverTab(page, 'tab-tab-d')
97
+ await page.waitForTimeout(200)
98
+ await hoverTab(page, 'tab-tab-b')
99
+ await page.waitForTimeout(200)
100
+ const going = await getGoingDirection(page)
101
+ expect(going).toBe(-1)
102
+ })
103
+
104
+ test('direction: rapid sweep right then left preserves correct direction', async ({
105
+ page,
106
+ }) => {
107
+ // sweep all the way right
108
+ await sweepTabs(page, TAB_IDS, 30)
109
+ await page.waitForTimeout(100)
110
+ const goingAfterRight = await getGoingDirection(page)
111
+ expect(goingAfterRight).toBe(1)
112
+
113
+ // sweep all the way left
114
+ await sweepTabs(page, [...TAB_IDS].reverse(), 30)
115
+ await page.waitForTimeout(100)
116
+ const goingAfterLeft = await getGoingDirection(page)
117
+ expect(goingAfterLeft).toBe(-1)
118
+ })
119
+
120
+ test('direction: exiting content slides opposite to entering content', async ({
121
+ page,
122
+ }) => {
123
+ // hover tab A and wait for it to be visible
124
+ await hoverTab(page, 'tab-tab-a')
125
+ await page.waitForTimeout(300)
126
+
127
+ // now hover tab D (to the right) - tab A should exit LEFT, tab D enters from RIGHT
128
+ await hoverTab(page, 'tab-tab-d')
129
+
130
+ // wait for entering element to appear (may take longer in slow CI)
131
+ await page.waitForTimeout(150)
132
+ const infos = await getSlideContentInfo(page)
133
+
134
+ // the entering one should have data-tab="Tab D"
135
+ const entering = infos.find((i: any) => i.tab === 'Tab D')
136
+ expect(entering).toBeTruthy()
137
+
138
+ // going direction should be 1 (rightward)
139
+ const going = await getGoingDirection(page)
140
+ expect(going).toBe(1)
141
+ })
142
+
143
+ test('direction: exiting element keeps original direction when going reverses', async ({
144
+ page,
145
+ }) => {
146
+ // hover Tab A → wait for it to appear
147
+ await hoverTab(page, 'tab-tab-a')
148
+ await page.waitForTimeout(300)
149
+
150
+ // collect console logs
151
+ const consoleLogs: string[] = []
152
+ page.on('console', (msg: any) => {
153
+ if (msg.text().includes('[exit-debug]')) {
154
+ consoleLogs.push(msg.text())
155
+ }
156
+ })
157
+
158
+ // hover Tab E → Tab A starts exiting LEFT (going=1, exitStyle x=-100)
159
+ await hoverTab(page, 'tab-tab-e')
160
+ await page.waitForTimeout(80) // let exit animation start
161
+
162
+ // hover Tab B → going changes to -1
163
+ // BUG: this should NOT change Tab A's exit direction
164
+ await hoverTab(page, 'tab-tab-b')
165
+ await page.waitForTimeout(30)
166
+
167
+ // print debug logs
168
+ for (const log of consoleLogs) {
169
+ console.log(log)
170
+ }
171
+
172
+ // sample Tab A's current translateX - it should be NEGATIVE (exiting left)
173
+ const tabAX = await page.evaluate(() => {
174
+ const els = Array.from(document.querySelectorAll('[data-testid="slide-content"]'))
175
+ for (let i = 0; i < els.length; i++) {
176
+ const el = els[i] as HTMLElement
177
+ if (el.dataset.tab === 'Tab A') {
178
+ const matrix = new DOMMatrix(getComputedStyle(el).transform)
179
+ return matrix.m41
180
+ }
181
+ }
182
+ return null
183
+ })
184
+
185
+ // Tab A should be exiting LEFT (negative x), not RIGHT (positive x)
186
+ // null means it already exited (which is fine)
187
+ if (tabAX !== null) {
188
+ expect(tabAX, `Tab A x=${tabAX}, should be negative (exiting left)`).toBeLessThan(0)
189
+ }
190
+ })
191
+
192
+ // === Bug 2: CSS driver x animation ===
193
+
194
+ test('x animation fires during tab switch', async ({ page }) => {
195
+ await hoverTab(page, 'tab-tab-a')
196
+ await page.waitForTimeout(300)
197
+
198
+ // hover tab D - should trigger x animation
199
+ await hoverTab(page, 'tab-tab-d')
200
+
201
+ const values = await trackTranslateX(page, '[data-testid="slide-content"]', 6)
202
+
203
+ // at least one frame should show non-zero translateX during enter animation
204
+ const hasMovement = values.some((v) => Math.abs(v) > 1)
205
+ expect(hasMovement).toBe(true)
206
+ })
207
+
208
+ // === Bug 3: Exit completion / ghost content ===
209
+
210
+ test('exit completes: no ghost content after mouse leaves', async ({ page }) => {
211
+ await hoverTab(page, 'tab-tab-b')
212
+ await page.waitForTimeout(300)
213
+
214
+ // move mouse away
215
+ await page.mouse.move(0, 0)
216
+ await page.waitForTimeout(600)
217
+
218
+ const slideContent = page.locator('[data-testid="slide-content"]')
219
+ await expect(slideContent).toHaveCount(0, { timeout: 2000 })
220
+ })
221
+
222
+ test('exit completes: rapid sweep then leave has no ghosts', async ({
223
+ page,
224
+ }, testInfo) => {
225
+ // reanimated driver doesn't complete exit after rapid sweep (known limitation)
226
+ test.skip(
227
+ testInfo.project.name === 'animated-reanimated',
228
+ 'reanimated exit incomplete after rapid sweep'
229
+ )
230
+
231
+ // rapidly sweep across all tabs
232
+ await sweepTabs(page, TAB_IDS, 30)
233
+ await page.waitForTimeout(100)
234
+
235
+ // move mouse away
236
+ await page.mouse.move(0, 0)
237
+
238
+ const slideContent = page.locator('[data-testid="slide-content"]')
239
+ await expect(slideContent).toHaveCount(0, { timeout: 3000 })
240
+ })
241
+
242
+ test('rapid switching: no stuck ghost elements', async ({ page }) => {
243
+ // rapidly sweep right
244
+ await sweepTabs(page, TAB_IDS, 30)
245
+ // settle on last tab
246
+ await page.waitForTimeout(400)
247
+
248
+ // should only have ONE slide-content element
249
+ const slideContents = page.locator('[data-testid="slide-content"]')
250
+ const count = await slideContents.count()
251
+ expect(count).toBeLessThanOrEqual(1)
252
+ })
253
+
254
+ test('rapid back-and-forth: no stuck elements', async ({ page }) => {
255
+ // quickly bounce between tabs
256
+ for (let i = 0; i < 3; i++) {
257
+ await sweepTabs(page, TAB_IDS, 20)
258
+ await sweepTabs(page, [...TAB_IDS].reverse(), 20)
259
+ }
260
+
261
+ // settle on tab C
262
+ await hoverTab(page, 'tab-tab-c')
263
+ await page.waitForTimeout(500)
264
+
265
+ const slideContents = page.locator('[data-testid="slide-content"]')
266
+ const count = await slideContents.count()
267
+ expect(count).toBeLessThanOrEqual(1)
268
+
269
+ // and it should show Tab C content
270
+ if (count === 1) {
271
+ const tab = await slideContents.getAttribute('data-tab')
272
+ expect(tab).toBe('Tab C')
273
+ }
274
+ })
275
+
276
+ test('exit animation runs to completion on tab switch', async ({ page }) => {
277
+ await hoverTab(page, 'tab-tab-c')
278
+ await page.waitForTimeout(300)
279
+
280
+ await hoverTab(page, 'tab-tab-a')
281
+ await page.waitForTimeout(500)
282
+
283
+ const slideContent = page.locator('[data-testid="slide-content"]')
284
+ const tab = await slideContent.getAttribute('data-tab')
285
+ expect(tab).toBe('Tab A')
286
+ })
287
+
288
+ // === Bug 4: Popover position tracks active tab ===
289
+
290
+ test('popover position moves when hovering different tabs', async ({ page }) => {
291
+ // hover tab A, record popover position
292
+ await hoverTab(page, 'tab-tab-a')
293
+ await page.waitForTimeout(400)
294
+
295
+ const content = page.locator('[data-testid="hover-content"]')
296
+ const box1 = await content.boundingBox()
297
+
298
+ // hover tab E (far right)
299
+ await hoverTab(page, 'tab-tab-e')
300
+ await page.waitForTimeout(400)
301
+
302
+ const box2 = await content.boundingBox()
303
+
304
+ // popover should have moved right
305
+ if (box1 && box2) {
306
+ expect(box2.x).toBeGreaterThan(box1.x)
307
+ } else {
308
+ // if popover didn't render, fail
309
+ expect(box1).toBeTruthy()
310
+ expect(box2).toBeTruthy()
311
+ }
312
+ })
313
+
314
+ test('popover position follows rapid sweep', async ({ page }) => {
315
+ // hover tab A first
316
+ await hoverTab(page, 'tab-tab-a')
317
+ await page.waitForTimeout(300)
318
+
319
+ const content = page.locator('[data-testid="hover-content"]')
320
+ const boxStart = await content.boundingBox()
321
+
322
+ // rapidly sweep to tab E
323
+ await sweepTabs(page, TAB_IDS, 40)
324
+ await page.waitForTimeout(400)
325
+
326
+ const boxEnd = await content.boundingBox()
327
+
328
+ if (boxStart && boxEnd) {
329
+ // should have moved substantially to the right
330
+ expect(boxEnd.x).toBeGreaterThan(boxStart.x)
331
+ }
332
+ })
333
+
334
+ // === Bug 5: safePolygon race condition on trigger switch ===
335
+
336
+ test('trigger switch: popover stays open when rapidly switching triggers', async ({
337
+ page,
338
+ }) => {
339
+ // open on tab A
340
+ await hoverTab(page, 'tab-tab-a')
341
+ await page.waitForTimeout(300)
342
+
343
+ const content = page.locator('[data-testid="hover-content"]')
344
+ await expect(content).toBeVisible()
345
+
346
+ // rapidly switch between triggers - popover must stay open
347
+ await hoverTab(page, 'tab-tab-c')
348
+ await page.waitForTimeout(50)
349
+ await hoverTab(page, 'tab-tab-e')
350
+ await page.waitForTimeout(50)
351
+ await hoverTab(page, 'tab-tab-b')
352
+ await page.waitForTimeout(50)
353
+ await hoverTab(page, 'tab-tab-d')
354
+ await page.waitForTimeout(200)
355
+
356
+ await expect(content).toBeVisible()
357
+ })
358
+
359
+ test('trigger switch: back-and-forth between two triggers stays open', async ({
360
+ page,
361
+ }) => {
362
+ await hoverTab(page, 'tab-tab-b')
363
+ await page.waitForTimeout(300)
364
+
365
+ const content = page.locator('[data-testid="hover-content"]')
366
+ await expect(content).toBeVisible()
367
+
368
+ // bounce between two triggers rapidly
369
+ for (let i = 0; i < 5; i++) {
370
+ await hoverTab(page, 'tab-tab-d')
371
+ await page.waitForTimeout(30)
372
+ await hoverTab(page, 'tab-tab-b')
373
+ await page.waitForTimeout(30)
374
+ }
375
+ await page.waitForTimeout(200)
376
+
377
+ await expect(content).toBeVisible()
378
+ })
379
+
380
+ // simulate realistic mouse movement from one tab to another with real time gaps
381
+ async function realisticMouseMove(
382
+ page: any,
383
+ fromId: string,
384
+ toId: string,
385
+ durationMs = 80
386
+ ) {
387
+ const from = await page.locator(`[data-testid="${fromId}"]`).boundingBox()
388
+ const to = await page.locator(`[data-testid="${toId}"]`).boundingBox()
389
+ if (!from || !to) return
390
+ const x1 = from.x + from.width / 2,
391
+ y1 = from.y + from.height / 2
392
+ const x2 = to.x + to.width / 2,
393
+ y2 = to.y + to.height / 2
394
+ const steps = Math.max(8, Math.round(durationMs / 8))
395
+ const delay = Math.round(durationMs / steps)
396
+ for (let i = 1; i <= steps; i++) {
397
+ const t = i / steps
398
+ await page.mouse.move(x1 + (x2 - x1) * t, y1 + (y2 - y1) * t)
399
+ if (delay > 0) await page.waitForTimeout(delay)
400
+ }
401
+ }
402
+
403
+ test('trigger switch race: no close events during realistic switching', async ({
404
+ page,
405
+ }, testInfo) => {
406
+ // reanimated driver has different animation timing that causes brief close/reopen cycles
407
+ // during multi-trigger hover switching - this is a known limitation on web
408
+ test.skip(
409
+ testInfo.project.name === 'animated-reanimated',
410
+ 'reanimated hover timing causes close events during trigger switch'
411
+ )
412
+ await page.evaluate(() => {
413
+ ;(window as any).__popoverCloseCount = 0
414
+ })
415
+
416
+ await hoverTab(page, 'tab-tab-a')
417
+ await page.waitForTimeout(300)
418
+ const content = page.locator('[data-testid="hover-content"]')
419
+ await expect(content).toBeVisible({ timeout: 2000 })
420
+ await page.evaluate(() => {
421
+ ;(window as any).__popoverCloseCount = 0
422
+ })
423
+
424
+ // realistic mouse movement between triggers
425
+ await realisticMouseMove(page, 'tab-tab-a', 'tab-tab-c', 80)
426
+ await page.waitForTimeout(50)
427
+ await realisticMouseMove(page, 'tab-tab-c', 'tab-tab-e', 60)
428
+ await page.waitForTimeout(50)
429
+ await realisticMouseMove(page, 'tab-tab-e', 'tab-tab-b', 100)
430
+ await page.waitForTimeout(200)
431
+
432
+ const closeCount = await page.evaluate(() => (window as any).__popoverCloseCount)
433
+ expect(closeCount, 'popover closed during trigger switching').toBe(0)
434
+ await expect(content).toBeVisible()
435
+ })
436
+
437
+ test('trigger switch race with restMs: no close events', async ({ page }) => {
438
+ await setupPage(page, {
439
+ name: 'TabHoverAnimationCase',
440
+ type: 'useCase',
441
+ searchParams: { restMs: '100' },
442
+ })
443
+ await page.waitForTimeout(500)
444
+
445
+ await page.evaluate(() => {
446
+ ;(window as any).__popoverCloseCount = 0
447
+ })
448
+ await hoverTab(page, 'tab-tab-b')
449
+ await page.waitForTimeout(600)
450
+ const content = page.locator('[data-testid="hover-content"]')
451
+ await expect(content).toBeVisible({ timeout: 2000 })
452
+ await page.evaluate(() => {
453
+ ;(window as any).__popoverCloseCount = 0
454
+ })
455
+
456
+ // back-and-forth with realistic mouse movement
457
+ for (let i = 0; i < 3; i++) {
458
+ await realisticMouseMove(page, 'tab-tab-b', 'tab-tab-d', 60)
459
+ await page.waitForTimeout(30)
460
+ await realisticMouseMove(page, 'tab-tab-d', 'tab-tab-b', 60)
461
+ await page.waitForTimeout(30)
462
+ }
463
+ await page.waitForTimeout(300)
464
+
465
+ const closeCount = await page.evaluate(() => (window as any).__popoverCloseCount)
466
+ expect(closeCount, 'popover closed during trigger switching with restMs').toBe(0)
467
+ await expect(content).toBeVisible()
468
+ })
@@ -0,0 +1,129 @@
1
+ import { expect, test } from '@playwright/test'
2
+ import { setupPage } from './test-utils'
3
+
4
+ /**
5
+ * Tests that popover position ANIMATES smoothly (no jumps) when switching tabs.
6
+ * The key bug: motion driver often JUMPS to the new position instead of animating.
7
+ * We detect this by sampling translateX every frame and checking for smooth interpolation.
8
+ */
9
+
10
+ test.beforeEach(async ({ page }, testInfo) => {
11
+ test.skip(
12
+ testInfo.project.name === 'animated-native',
13
+ 'Native driver does not support hover animations on web'
14
+ )
15
+ test.skip(
16
+ testInfo.project.name === 'animated-reanimated',
17
+ 'Reanimated driver has larger frame jumps during rapid position changes on web'
18
+ )
19
+ await setupPage(page, { name: 'TabHoverAnimationCase', type: 'useCase' })
20
+ await page.waitForTimeout(500)
21
+ })
22
+
23
+ // start continuous position sampling via rAF using getBoundingClientRect
24
+ // (avoids getComputedStyle which forces style recalc and can interfere with animations)
25
+ async function startSampling(page: any) {
26
+ await page.evaluate(() => {
27
+ const el = document.querySelector('[data-popper-animate-position]')
28
+ if (!el) return
29
+ const log: { t: number; x: number }[] = []
30
+ ;(window as any).__posLog = log
31
+ const start = performance.now()
32
+ let running = true
33
+ ;(window as any).__stopPosLog = () => {
34
+ running = false
35
+ }
36
+ function tick() {
37
+ if (!running) return
38
+ log.push({ t: performance.now() - start, x: el!.getBoundingClientRect().left })
39
+ requestAnimationFrame(tick)
40
+ }
41
+ requestAnimationFrame(tick)
42
+ })
43
+ }
44
+
45
+ async function stopSampling(page: any) {
46
+ return page.evaluate(() => {
47
+ ;(window as any).__stopPosLog?.()
48
+ return (window as any).__posLog as { t: number; x: number }[]
49
+ })
50
+ }
51
+
52
+ function analyzeJumps(log: { t: number; x: number }[], threshold = 30) {
53
+ let maxDelta = 0
54
+ for (let i = 1; i < log.length; i++) {
55
+ const delta = Math.abs(log[i].x - log[i - 1].x)
56
+ if (delta > maxDelta) maxDelta = delta
57
+ }
58
+ return { maxDelta }
59
+ }
60
+
61
+ test('popover animates smoothly from A to E', async ({ page }) => {
62
+ await page.locator('[data-testid="tab-tab-a"]').hover()
63
+ await page.waitForTimeout(800)
64
+
65
+ await startSampling(page)
66
+ await page.locator('[data-testid="tab-tab-e"]').hover()
67
+ await page.waitForTimeout(800)
68
+
69
+ const log = await stopSampling(page)
70
+ const { maxDelta } = analyzeJumps(log)
71
+
72
+ // with 500ms animation, smooth movement should have max ~15px per frame at 60fps
73
+ // allow some tolerance but no single-frame jumps > 50px
74
+ expect(maxDelta, `Max single-frame jump was ${maxDelta.toFixed(1)}px`).toBeLessThan(90)
75
+ })
76
+
77
+ test('popover animates smoothly during rapid sweep A→E', async ({ page }) => {
78
+ const tabs = ['tab-tab-a', 'tab-tab-b', 'tab-tab-c', 'tab-tab-d', 'tab-tab-e']
79
+
80
+ await page.locator('[data-testid="tab-tab-a"]').hover()
81
+ await page.waitForTimeout(600)
82
+
83
+ await startSampling(page)
84
+
85
+ for (const id of tabs) {
86
+ await page.locator(`[data-testid="${id}"]`).hover()
87
+ await page.waitForTimeout(60)
88
+ }
89
+ await page.waitForTimeout(800)
90
+
91
+ const log = await stopSampling(page)
92
+ const { maxDelta } = analyzeJumps(log)
93
+
94
+ // during rapid sweep, interruptions cause animation restarts
95
+ // but each restart should be from CURRENT position, not from origin
96
+ expect(maxDelta, `Max single-frame jump was ${maxDelta.toFixed(1)}px`).toBeLessThan(90)
97
+ })
98
+
99
+ test('popover animates smoothly during back-and-forth', async ({ page }) => {
100
+ const tabs = ['tab-tab-a', 'tab-tab-b', 'tab-tab-c', 'tab-tab-d', 'tab-tab-e']
101
+
102
+ await page.locator('[data-testid="tab-tab-a"]').hover()
103
+ await page.waitForTimeout(600)
104
+
105
+ await startSampling(page)
106
+
107
+ // sweep right then left then right
108
+ for (const id of tabs) {
109
+ await page.locator(`[data-testid="${id}"]`).hover()
110
+ await page.waitForTimeout(50)
111
+ }
112
+ for (const id of [...tabs].reverse()) {
113
+ await page.locator(`[data-testid="${id}"]`).hover()
114
+ await page.waitForTimeout(50)
115
+ }
116
+ for (const id of tabs) {
117
+ await page.locator(`[data-testid="${id}"]`).hover()
118
+ await page.waitForTimeout(50)
119
+ }
120
+
121
+ await page.waitForTimeout(800)
122
+
123
+ const log = await stopSampling(page)
124
+ const { maxDelta } = analyzeJumps(log)
125
+
126
+ // back-and-forth is the hardest case for animation interruption
127
+ // but should still not have massive teleports
128
+ expect(maxDelta, `Max single-frame jump was ${maxDelta.toFixed(1)}px`).toBeLessThan(90)
129
+ })
@@ -0,0 +1,93 @@
1
+ import { expect, test } from '@playwright/test'
2
+
3
+ import { getStyles } from './utils'
4
+ import { setupPage } from './test-utils'
5
+
6
+ test.beforeEach(async ({ page }) => {
7
+ await setupPage(page, { name: 'TextNestedInheritance', type: 'useCase' })
8
+ })
9
+
10
+ test(`nested hanzo-gui Text does NOT inherit color (sets own theme color)`, async ({
11
+ page,
12
+ }) => {
13
+ const parentStyles = await getStyles(page.getByTestId('parent-color').first())
14
+ const nestedStyles = await getStyles(page.getByTestId('nested-color').first())
15
+
16
+ // parent should have blue color
17
+ expect(parentStyles.color).toBe('rgb(0, 0, 255)')
18
+
19
+ // nested hanzo-gui Text sets color: '$color' so should NOT be blue
20
+ expect(nestedStyles.color).not.toBe(parentStyles.color)
21
+ })
22
+
23
+ test(`nested core Text inherits color from parent via CSS`, async ({ page }) => {
24
+ const parentStyles = await getStyles(page.getByTestId('parent-core-color').first())
25
+ const nestedStyles = await getStyles(page.getByTestId('nested-core-color').first())
26
+
27
+ // parent should have blue
28
+ expect(parentStyles.color).toBe('rgb(0, 0, 255)')
29
+
30
+ // core Text does not set color, so it inherits blue via CSS
31
+ expect(nestedStyles.color).toBe(parentStyles.color)
32
+ })
33
+
34
+ test(`nested text inherits whiteSpace from parent (for numberOfLines)`, async ({
35
+ page,
36
+ }) => {
37
+ const parentStyles = await getStyles(page.getByTestId('parent-number-of-lines').first())
38
+ const nestedStyles = await getStyles(
39
+ page.getByTestId('nested-in-number-of-lines').first()
40
+ )
41
+
42
+ // parent with numberOfLines=1 should have nowrap
43
+ expect(parentStyles.whiteSpace).toBe('nowrap')
44
+
45
+ // nested text should inherit nowrap via CSS
46
+ expect(nestedStyles.whiteSpace).toBe('nowrap')
47
+ })
48
+
49
+ test(`nested text inherits explicit whiteSpace from parent`, async ({ page }) => {
50
+ const parentStyles = await getStyles(page.getByTestId('parent-whitespace').first())
51
+ const nestedStyles = await getStyles(page.getByTestId('nested-whitespace').first())
52
+
53
+ // parent should have nowrap
54
+ expect(parentStyles.whiteSpace).toBe('nowrap')
55
+
56
+ // nested text should inherit nowrap
57
+ expect(nestedStyles.whiteSpace).toBe('nowrap')
58
+ })
59
+
60
+ test(`nested text inherits letterSpacing from parent`, async ({ page }) => {
61
+ const parentStyles = await getStyles(page.getByTestId('parent-letter-spacing').first())
62
+ const nestedStyles = await getStyles(page.getByTestId('nested-letter-spacing').first())
63
+
64
+ // parent should have letter-spacing of 5px
65
+ expect(parentStyles.letterSpacing).toBe('5px')
66
+
67
+ // nested text should inherit letter-spacing
68
+ expect(nestedStyles.letterSpacing).toBe('5px')
69
+ })
70
+
71
+ test(`styled nested text does NOT inherit color (sets own theme color)`, async ({
72
+ page,
73
+ }) => {
74
+ const parentStyles = await getStyles(page.getByTestId('parent-styled').first())
75
+ const nestedStyles = await getStyles(page.getByTestId('nested-styled').first())
76
+
77
+ // parent should have green color
78
+ expect(parentStyles.color).toBe('rgb(0, 128, 0)')
79
+
80
+ // styled Text (BoldText) also sets color: '$color', should NOT be green
81
+ expect(nestedStyles.color).not.toBe(parentStyles.color)
82
+ })
83
+
84
+ test(`explicit color override on nested text still works`, async ({ page }) => {
85
+ const parentStyles = await getStyles(page.getByTestId('parent-override').first())
86
+ const nestedStyles = await getStyles(page.getByTestId('nested-override').first())
87
+
88
+ // parent should have purple color
89
+ expect(parentStyles.color).toBe('rgb(128, 0, 128)')
90
+
91
+ // nested text with explicit color should be orange
92
+ expect(nestedStyles.color).toBe('rgb(255, 165, 0)')
93
+ })