@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,234 @@
1
+ import { expect, test } from '@playwright/test'
2
+
3
+ import { setupPage } from './test-utils'
4
+
5
+ test.beforeEach(async ({ page }) => {
6
+ await setupPage(page, { name: 'PopoverCase', type: 'useCase' })
7
+ })
8
+
9
+ // Test simple popover functionality
10
+ test('simple popover opens and closes', async ({ page }) => {
11
+ // Wait for page to load
12
+ await page.waitForLoadState('networkidle')
13
+
14
+ const trigger = page.locator('#simple-popover-trigger')
15
+ const content = page.locator('#simple-popover-content')
16
+ const closeButton = page.locator('#simple-popover-close')
17
+
18
+ // Check initial state
19
+ await expect(trigger).toBeVisible()
20
+ await expect(content).not.toBeVisible()
21
+
22
+ // Click trigger to open popover
23
+ await trigger.click()
24
+
25
+ // Wait for content to be visible
26
+ await expect(content).toBeVisible({ timeout: 5000 })
27
+
28
+ // Click close button
29
+ await closeButton.click()
30
+
31
+ // Verify popover is closed
32
+ await expect(content).not.toBeVisible()
33
+ })
34
+
35
+ // Test basic popover with icon trigger
36
+ test('basic popover with icon trigger', async ({ page }) => {
37
+ // Wait for page to load
38
+ await page.waitForLoadState('networkidle')
39
+
40
+ const trigger = page.locator('#popover-bottom-trigger')
41
+ const content = page.locator('#popover-bottom-content')
42
+ const input = page.locator('#popover-bottom-input')
43
+ const closeButton = page.locator('#popover-bottom-close')
44
+
45
+ // Check initial state
46
+ await expect(trigger).toBeVisible()
47
+ await expect(content).not.toBeVisible()
48
+
49
+ // Click trigger to open popover
50
+ await trigger.click()
51
+
52
+ // Wait for enter animation to start
53
+ await page.waitForTimeout(100)
54
+
55
+ // Wait for content to be visible
56
+ await expect(content).toBeVisible({ timeout: 5000 })
57
+
58
+ // Click close button
59
+ await closeButton.click()
60
+
61
+ // Wait for exit animation
62
+ await page.waitForTimeout(500)
63
+
64
+ // Verify popover is closed
65
+ await expect(content).not.toBeVisible()
66
+ })
67
+
68
+ // Test popover placement variations
69
+ test('popover placement variations', async ({ page }) => {
70
+ // Wait for page to load
71
+ await page.waitForLoadState('networkidle')
72
+
73
+ const placements = ['left', 'top', 'right']
74
+
75
+ for (const placement of placements) {
76
+ const trigger = page.locator(`#popover-${placement}-trigger`)
77
+ const content = page.locator(`#popover-${placement}-content`)
78
+
79
+ // Check initial state
80
+ await expect(trigger).toBeVisible()
81
+ await expect(content).not.toBeVisible()
82
+
83
+ // Click trigger to open popover
84
+ await trigger.click()
85
+
86
+ // Wait for enter animation to start
87
+ await page.waitForTimeout(100)
88
+
89
+ // Wait for content to be visible
90
+ await expect(content).toBeVisible({ timeout: 5000 })
91
+
92
+ // Close by clicking outside (ESC key)
93
+ await page.keyboard.press('Escape')
94
+
95
+ // Wait for exit animation to complete
96
+ await page.waitForTimeout(500)
97
+
98
+ // Verify popover is closed
99
+ await expect(content).not.toBeVisible()
100
+ }
101
+ })
102
+
103
+ // Test popover with keyboard navigation
104
+ test('popover keyboard navigation', async ({ page }) => {
105
+ // Wait for page to load
106
+ await page.waitForLoadState('networkidle')
107
+
108
+ const trigger = page.locator('#simple-popover-trigger')
109
+ const content = page.locator('#simple-popover-content')
110
+
111
+ // Focus trigger and open with Enter key
112
+ await trigger.focus()
113
+ await page.keyboard.press('Enter')
114
+
115
+ // Wait for content to be visible
116
+ await expect(content).toBeVisible({ timeout: 5000 })
117
+
118
+ // Close with Escape key
119
+ await page.keyboard.press('Escape')
120
+
121
+ // Verify popover is closed
122
+ await expect(content).not.toBeVisible()
123
+ })
124
+
125
+ // Test popover accessibility
126
+ test('popover accessibility attributes', async ({ page }) => {
127
+ // Wait for page to load
128
+ await page.waitForLoadState('networkidle')
129
+
130
+ const trigger = page.locator('#simple-popover-trigger')
131
+ const content = page.locator('#simple-popover-content')
132
+
133
+ // Check initial state
134
+ await expect(trigger).toHaveAttribute('aria-expanded', 'false')
135
+
136
+ // Open popover
137
+ await trigger.click()
138
+ await expect(content).toBeVisible({ timeout: 5000 })
139
+
140
+ // Check expanded state
141
+ await expect(trigger).toHaveAttribute('aria-expanded', 'true')
142
+
143
+ // Close popover
144
+ await page.keyboard.press('Escape')
145
+ await expect(content).not.toBeVisible()
146
+
147
+ // Check collapsed state
148
+ await expect(trigger).toHaveAttribute('aria-expanded', 'false')
149
+ })
150
+
151
+ // Test multiple popovers behavior
152
+ test('multiple popovers - opening one closes others', async ({ page }) => {
153
+ // Wait for page to load
154
+ await page.waitForLoadState('networkidle')
155
+
156
+ const leftTrigger = page.locator('#popover-left-trigger')
157
+ const leftContent = page.locator('#popover-left-content')
158
+ const rightTrigger = page.locator('#popover-right-trigger')
159
+ const rightContent = page.locator('#popover-right-content')
160
+ const topTrigger = page.locator('#popover-top-trigger')
161
+ const topContent = page.locator('#popover-top-content')
162
+
163
+ // Check initial state - all popovers should be closed
164
+ await expect(leftContent).not.toBeVisible()
165
+ await expect(rightContent).not.toBeVisible()
166
+ await expect(topContent).not.toBeVisible()
167
+
168
+ // Open first popover (left)
169
+ await leftTrigger.click()
170
+ await page.waitForTimeout(100) // Wait for enter animation
171
+ await expect(leftContent).toBeVisible({ timeout: 5000 })
172
+ await expect(rightContent).not.toBeVisible()
173
+ await expect(topContent).not.toBeVisible()
174
+
175
+ // Open second popover (right) - should close the first one
176
+ await rightTrigger.click()
177
+ await page.waitForTimeout(600) // Wait for previous exit + new enter animations
178
+ await expect(leftContent).not.toBeVisible()
179
+ await expect(rightContent).toBeVisible({ timeout: 5000 })
180
+ await expect(topContent).not.toBeVisible()
181
+
182
+ // Open third popover (top) - should close the second one
183
+ await topTrigger.click()
184
+ await page.waitForTimeout(600) // Wait for previous exit + new enter animations
185
+ await expect(leftContent).not.toBeVisible()
186
+ await expect(rightContent).not.toBeVisible()
187
+ await expect(topContent).toBeVisible({ timeout: 5000 })
188
+
189
+ // Close the top popover
190
+ await page.keyboard.press('Escape')
191
+ await page.waitForTimeout(500) // Wait for exit animation
192
+ await expect(leftContent).not.toBeVisible()
193
+ await expect(rightContent).not.toBeVisible()
194
+ await expect(topContent).not.toBeVisible()
195
+ })
196
+
197
+ // Test multiple popovers with simple popover
198
+ test('multiple popovers - simple popover with others', async ({ page }) => {
199
+ // Wait for page to load
200
+ await page.waitForLoadState('networkidle')
201
+
202
+ const simpleTrigger = page.locator('#simple-popover-trigger')
203
+ const simpleContent = page.locator('#simple-popover-content')
204
+ const leftTrigger = page.locator('#popover-left-trigger')
205
+ const leftContent = page.locator('#popover-left-content')
206
+
207
+ // Check initial state
208
+ await expect(simpleContent).not.toBeVisible()
209
+ await expect(leftContent).not.toBeVisible()
210
+
211
+ // Open simple popover
212
+ await simpleTrigger.click()
213
+ await page.waitForTimeout(100) // Wait for enter animation
214
+ await expect(simpleContent).toBeVisible({ timeout: 5000 })
215
+ await expect(leftContent).not.toBeVisible()
216
+
217
+ // Open left popover - should close simple popover
218
+ await leftTrigger.click()
219
+ await page.waitForTimeout(600) // Wait for previous exit + new enter animations
220
+ await expect(simpleContent).not.toBeVisible()
221
+ await expect(leftContent).toBeVisible({ timeout: 5000 })
222
+
223
+ // Open simple popover again - should close left popover
224
+ await simpleTrigger.click()
225
+ await page.waitForTimeout(600) // Wait for previous exit + new enter animations
226
+ await expect(simpleContent).toBeVisible({ timeout: 5000 })
227
+ await expect(leftContent).not.toBeVisible()
228
+
229
+ // Close simple popover
230
+ await page.keyboard.press('Escape')
231
+ await page.waitForTimeout(500) // Wait for exit animation
232
+ await expect(simpleContent).not.toBeVisible()
233
+ await expect(leftContent).not.toBeVisible()
234
+ })
@@ -0,0 +1,184 @@
1
+ import { expect, test, type Page } from '@playwright/test'
2
+ import { getBoundingRect, setupPage } from './test-utils'
3
+
4
+ // shared multi-trigger tests for both Popover and Menu
5
+ // validates render isolation, position re-anchoring, state tracking,
6
+ // focus return, and rapid cycling — the same infra backs both components
7
+
8
+ function parseRenderCount(text: string | null): number {
9
+ if (!text) return 0
10
+ const match = text.match(/renders:\s*(\d+)/)
11
+ return match ? Number(match[1]) : 0
12
+ }
13
+
14
+ // abstracts the common interaction + assertion pattern for any floating element
15
+ // with multiple triggers, regardless of whether it's Popover or Menu
16
+ type MultiTriggerHarness = {
17
+ prefix: string // "pop" | "menu"
18
+ contentTestId: string
19
+ triggerCount: number
20
+ open: (page: Page, idx: number) => Promise<void>
21
+ close: (page: Page) => Promise<void>
22
+ }
23
+
24
+ const popoverHarness: MultiTriggerHarness = {
25
+ prefix: 'pop',
26
+ contentTestId: 'pop-content',
27
+ triggerCount: 3,
28
+ open: async (page, idx) => {
29
+ await page.getByTestId(`pop-trigger-${idx}`).click()
30
+ await page.waitForTimeout(300)
31
+ },
32
+ close: async (page) => {
33
+ // use Escape (same as Menu) so focus returns to trigger
34
+ await page.keyboard.press('Escape')
35
+ await page.waitForTimeout(300)
36
+ },
37
+ }
38
+
39
+ const menuHarness: MultiTriggerHarness = {
40
+ prefix: 'menu',
41
+ contentTestId: 'menu-content',
42
+ triggerCount: 3,
43
+ open: async (page, idx) => {
44
+ await page.getByTestId(`menu-trigger-${idx}`).click()
45
+ await page.waitForTimeout(300)
46
+ },
47
+ close: async (page) => {
48
+ await page.keyboard.press('Escape')
49
+ await page.waitForTimeout(300)
50
+ },
51
+ }
52
+
53
+ function multiTriggerSuite(h: MultiTriggerHarness) {
54
+ const triggerTestId = (idx: number) => `${h.prefix}-trigger-${idx}`
55
+
56
+ test(`[${h.prefix}] only the active trigger re-renders`, async ({ page }) => {
57
+ const getRenderCounts = async () => {
58
+ const counts: number[] = []
59
+ for (let i = 1; i <= h.triggerCount; i++) {
60
+ counts.push(
61
+ parseRenderCount(
62
+ await page.getByTestId(`${triggerTestId(i)}-render-count`).textContent()
63
+ )
64
+ )
65
+ }
66
+ return counts
67
+ }
68
+
69
+ const initial = await getRenderCounts()
70
+
71
+ // open via trigger 1
72
+ await h.open(page, 1)
73
+ await expect(page.getByTestId(h.contentTestId)).toBeVisible()
74
+
75
+ const afterOpen = await getRenderCounts()
76
+ // trigger 2 and 3 should NOT re-render
77
+ expect(afterOpen[1]).toBe(initial[1])
78
+ expect(afterOpen[2]).toBe(initial[2])
79
+
80
+ // close
81
+ await h.close(page)
82
+ await expect(page.getByTestId(h.contentTestId)).not.toBeVisible()
83
+
84
+ const afterClose = await getRenderCounts()
85
+ expect(afterClose[1]).toBe(initial[1])
86
+ expect(afterClose[2]).toBe(initial[2])
87
+ })
88
+
89
+ test(`[${h.prefix}] content re-anchors when switching triggers`, async ({ page }) => {
90
+ // open via trigger 1
91
+ await h.open(page, 1)
92
+ await expect(page.getByTestId(h.contentTestId)).toBeVisible()
93
+
94
+ const trigger1Box = await getBoundingRect(page, `[data-testid="${triggerTestId(1)}"]`)
95
+ const contentBox1 = await getBoundingRect(page, `[data-testid="${h.contentTestId}"]`)
96
+ expect(trigger1Box).not.toBeNull()
97
+ expect(contentBox1).not.toBeNull()
98
+ expect(Math.abs(contentBox1!.x - trigger1Box!.x)).toBeLessThan(50)
99
+
100
+ // close then open via trigger 3
101
+ await h.close(page)
102
+ await h.open(page, 3)
103
+ await expect(page.getByTestId(h.contentTestId)).toBeVisible()
104
+
105
+ const trigger3Box = await getBoundingRect(page, `[data-testid="${triggerTestId(3)}"]`)
106
+ const contentBox3 = await getBoundingRect(page, `[data-testid="${h.contentTestId}"]`)
107
+ expect(trigger3Box).not.toBeNull()
108
+ expect(contentBox3).not.toBeNull()
109
+ expect(Math.abs(contentBox3!.x - trigger3Box!.x)).toBeLessThan(50)
110
+
111
+ // content should have moved — trigger 3 is rightward of trigger 1
112
+ expect(contentBox3!.x).toBeGreaterThan(contentBox1!.x + 20)
113
+ })
114
+
115
+ test(`[${h.prefix}] only the active trigger has data-state=open`, async ({ page }) => {
116
+ await h.open(page, 2)
117
+ await expect(page.getByTestId(h.contentTestId)).toBeVisible()
118
+
119
+ await expect(page.getByTestId(triggerTestId(1))).toHaveAttribute(
120
+ 'data-state',
121
+ 'closed'
122
+ )
123
+ await expect(page.getByTestId(triggerTestId(2))).toHaveAttribute('data-state', 'open')
124
+ await expect(page.getByTestId(triggerTestId(3))).toHaveAttribute(
125
+ 'data-state',
126
+ 'closed'
127
+ )
128
+ })
129
+
130
+ test(`[${h.prefix}] close returns focus to active trigger`, async ({ page }) => {
131
+ const trigger = page.getByTestId(triggerTestId(2))
132
+ await trigger.click()
133
+ await page.waitForTimeout(300)
134
+ await expect(page.getByTestId(h.contentTestId)).toBeVisible()
135
+
136
+ await h.close(page)
137
+ await page.waitForTimeout(300)
138
+ await expect(trigger).toBeFocused()
139
+ })
140
+
141
+ test(`[${h.prefix}] rapid open/close cycles work reliably`, async ({ page }) => {
142
+ for (let cycle = 0; cycle < 4; cycle++) {
143
+ const idx = (cycle % h.triggerCount) + 1
144
+ await h.open(page, idx)
145
+ await expect(page.getByTestId(h.contentTestId)).toBeVisible()
146
+ await h.close(page)
147
+ await expect(page.getByTestId(h.contentTestId)).not.toBeVisible()
148
+ }
149
+ })
150
+
151
+ test(`[${h.prefix}] rapid trigger switching re-anchors each time`, async ({ page }) => {
152
+ for (let idx = 1; idx <= h.triggerCount; idx++) {
153
+ await h.open(page, idx)
154
+ await expect(page.getByTestId(h.contentTestId)).toBeVisible()
155
+
156
+ const triggerBox = await getBoundingRect(
157
+ page,
158
+ `[data-testid="${triggerTestId(idx)}"]`
159
+ )
160
+ const contentBox = await getBoundingRect(page, `[data-testid="${h.contentTestId}"]`)
161
+ expect(Math.abs(contentBox!.x - triggerBox!.x)).toBeLessThan(50)
162
+
163
+ await h.close(page)
164
+ }
165
+ })
166
+ }
167
+
168
+ test.describe('Popover & Menu Multi-Trigger', () => {
169
+ test.beforeEach(async ({ page }) => {
170
+ await setupPage(page, {
171
+ name: 'PopoverAndMenuMultiTriggerCase',
172
+ type: 'useCase',
173
+ waitExtra: true,
174
+ })
175
+ })
176
+
177
+ test.describe('Popover', () => {
178
+ multiTriggerSuite(popoverHarness)
179
+ })
180
+
181
+ test.describe('Menu', () => {
182
+ multiTriggerSuite(menuHarness)
183
+ })
184
+ })
@@ -0,0 +1,51 @@
1
+ import { expect, test } from '@playwright/test'
2
+
3
+ import { setupPage } from './test-utils'
4
+
5
+ test.beforeEach(async ({ page }) => {
6
+ await setupPage(page, { name: 'PopoverAnimatePositionCase', type: 'useCase' })
7
+ })
8
+
9
+ // TODO: test passes on actual site but fails here - likely test setup difference
10
+ test.skip('popover with animatePosition shows correct position on re-open', async ({
11
+ page,
12
+ }) => {
13
+ await page.waitForLoadState('networkidle')
14
+
15
+ const trigger = page.locator('#animate-position-trigger')
16
+ const content = page.locator('#animate-position-content')
17
+ const closeButton = page.locator('#animate-position-close')
18
+
19
+ await expect(trigger).toBeVisible()
20
+
21
+ // first open
22
+ await trigger.click()
23
+ await expect(content).toBeVisible({ timeout: 5000 })
24
+ // wait for animation to settle
25
+ await page.waitForTimeout(500)
26
+
27
+ const box1 = await content.boundingBox()
28
+
29
+ // close
30
+ await closeButton.click()
31
+ await expect(content).not.toBeVisible({ timeout: 5000 })
32
+
33
+ // second open
34
+ await trigger.click()
35
+ await expect(content).toBeVisible({ timeout: 5000 })
36
+ // wait for animation to settle
37
+ await page.waitForTimeout(500)
38
+
39
+ const box2 = await content.boundingBox()
40
+
41
+ // verify position is consistent between opens
42
+ // the x position should be the same (within small tolerance for animation)
43
+ expect(box1).toBeTruthy()
44
+ expect(box2).toBeTruthy()
45
+ if (box1 && box2) {
46
+ const xDiff = Math.abs(box1.x - box2.x)
47
+ const yDiff = Math.abs(box1.y - box2.y)
48
+ expect(xDiff).toBeLessThan(5)
49
+ expect(yDiff).toBeLessThan(5)
50
+ }
51
+ })
@@ -0,0 +1,197 @@
1
+ import { expect, test } from '@playwright/test'
2
+ import { setupPage } from './test-utils'
3
+
4
+ /**
5
+ * Tests that clicking a hoverable popover trigger while the enter animation
6
+ * is still playing produces a smooth exit - no snap-back, no re-enter cycle.
7
+ *
8
+ * Bug: when clicking during enter, the popover:
9
+ * 1. Snaps to closed (no animation)
10
+ * 2. Starts animating back in
11
+ * 3. Then finally animates out
12
+ */
13
+
14
+ test.beforeEach(async ({ page }, testInfo) => {
15
+ test.skip(
16
+ testInfo.project.name === 'animated-native',
17
+ 'Native driver does not support hover animations on web'
18
+ )
19
+ await setupPage(page, { name: 'PopoverHoverableDelayCase', type: 'useCase' })
20
+ await page.waitForLoadState('networkidle')
21
+ await page.waitForTimeout(500)
22
+ })
23
+
24
+ test('click during enter animation produces smooth exit (no snap-back)', async ({
25
+ page,
26
+ }) => {
27
+ const trigger = page.locator('#delay-trigger')
28
+ const content = page.locator('#delay-content')
29
+
30
+ await expect(content).not.toBeVisible()
31
+
32
+ // start per-frame opacity sampling
33
+ await page.evaluate(() => {
34
+ ;(window as any).__opLog = [] as number[]
35
+ ;(window as any).__rafOn = true
36
+ const track = () => {
37
+ if (!(window as any).__rafOn) return
38
+ const el = document.getElementById('delay-content')
39
+ if (el) {
40
+ ;(window as any).__opLog.push(parseFloat(getComputedStyle(el).opacity))
41
+ }
42
+ requestAnimationFrame(track)
43
+ }
44
+ requestAnimationFrame(track)
45
+ })
46
+
47
+ // hover to trigger open (delay is 400ms)
48
+ await trigger.hover()
49
+ // wait for delay + a bit of animation time
50
+ await page.waitForTimeout(500)
51
+
52
+ // popover should be visible and animating
53
+ const isVisible = await content.isVisible().catch(() => false)
54
+ if (!isVisible) {
55
+ // if not visible yet, wait more
56
+ await page.waitForTimeout(300)
57
+ }
58
+
59
+ // now click the trigger (should close the popover)
60
+ await trigger.click()
61
+
62
+ // wait for exit animation
63
+ await page.waitForTimeout(1500)
64
+
65
+ // stop sampling
66
+ await page.evaluate(() => {
67
+ ;(window as any).__rafOn = false
68
+ })
69
+
70
+ const frames: number[] = await page.evaluate(() => (window as any).__opLog || [])
71
+
72
+ console.log(
73
+ 'opacity timeline:',
74
+ JSON.stringify(frames.slice(0, 40).map((f) => +f.toFixed(3)))
75
+ )
76
+
77
+ if (frames.length < 3) {
78
+ // not enough data, skip analysis
79
+ console.log('Not enough frames captured')
80
+ return
81
+ }
82
+
83
+ // find the peak opacity
84
+ const peak = Math.max(...frames)
85
+ const peakIdx = frames.indexOf(peak)
86
+
87
+ // look for dips (snap-back indicators)
88
+ let maxDip = 0
89
+ for (let i = 1; i < frames.length - 1; i++) {
90
+ const prev = frames[i - 1]
91
+ const curr = frames[i]
92
+ const next = frames[i + 1]
93
+ if (curr < prev && curr < next) {
94
+ const dip = Math.min(prev - curr, next - curr)
95
+ if (dip > maxDip) maxDip = dip
96
+ }
97
+ }
98
+
99
+ // look for re-enter pattern: after opacity decreases, it increases again significantly
100
+ let foundDecrease = false
101
+ let maxReIncrease = 0
102
+ for (let i = peakIdx + 1; i < frames.length; i++) {
103
+ if (frames[i] < frames[i - 1] - 0.01) {
104
+ foundDecrease = true
105
+ }
106
+ if (foundDecrease && frames[i] > frames[i - 1] + 0.05) {
107
+ const increase = frames[i] - frames[i - 1]
108
+ if (increase > maxReIncrease) maxReIncrease = increase
109
+ }
110
+ }
111
+
112
+ console.log(`peak: ${peak.toFixed(3)} at frame ${peakIdx}`)
113
+ console.log(`max dip: ${maxDip.toFixed(3)}`)
114
+ console.log(`max re-increase after exit starts: ${maxReIncrease.toFixed(3)}`)
115
+
116
+ // no significant dip (snap-back)
117
+ expect(
118
+ maxDip,
119
+ `Opacity dip of ${maxDip.toFixed(3)} detected - element snapped back during exit`
120
+ ).toBeLessThan(0.15)
121
+
122
+ // no significant re-increase after exit starts (re-enter)
123
+ expect(
124
+ maxReIncrease,
125
+ `Opacity increased by ${maxReIncrease.toFixed(3)} after exit started - re-enter detected`
126
+ ).toBeLessThan(0.15)
127
+ })
128
+
129
+ test('click during mid-enter: transform should not jump', async ({ page }) => {
130
+ const trigger = page.locator('#delay-trigger')
131
+ const content = page.locator('#delay-content')
132
+
133
+ await expect(content).not.toBeVisible()
134
+
135
+ // start per-frame Y position sampling
136
+ await page.evaluate(() => {
137
+ ;(window as any).__yLog = [] as number[]
138
+ ;(window as any).__rafOn = true
139
+ const track = () => {
140
+ if (!(window as any).__rafOn) return
141
+ const el = document.getElementById('delay-content')
142
+ if (el) {
143
+ const transform = getComputedStyle(el).transform
144
+ let y = 0
145
+ if (transform && transform !== 'none') {
146
+ const matrix = new DOMMatrix(transform)
147
+ y = matrix.m42
148
+ }
149
+ ;(window as any).__yLog.push(y)
150
+ }
151
+ requestAnimationFrame(track)
152
+ }
153
+ requestAnimationFrame(track)
154
+ })
155
+
156
+ // hover to trigger open
157
+ await trigger.hover()
158
+ await page.waitForTimeout(500)
159
+
160
+ const isVisible = await content.isVisible().catch(() => false)
161
+ if (!isVisible) {
162
+ await page.waitForTimeout(300)
163
+ }
164
+
165
+ // click to close during enter animation
166
+ await trigger.click()
167
+ await page.waitForTimeout(1500)
168
+
169
+ await page.evaluate(() => {
170
+ ;(window as any).__rafOn = false
171
+ })
172
+
173
+ const frames: number[] = await page.evaluate(() => (window as any).__yLog || [])
174
+
175
+ console.log(
176
+ 'Y positions:',
177
+ JSON.stringify(frames.slice(0, 30).map((f) => +f.toFixed(2)))
178
+ )
179
+
180
+ if (frames.length < 3) {
181
+ console.log('Not enough frames captured')
182
+ return
183
+ }
184
+
185
+ // check for single-frame jumps
186
+ let maxJump = 0
187
+ for (let i = 1; i < frames.length; i++) {
188
+ const jump = Math.abs(frames[i] - frames[i - 1])
189
+ if (jump > maxJump) maxJump = jump
190
+ }
191
+
192
+ console.log(`max Y jump: ${maxJump.toFixed(2)}`)
193
+
194
+ expect(maxJump, `Y position jumped ${maxJump.toFixed(1)}px in one frame`).toBeLessThan(
195
+ 5
196
+ )
197
+ })