@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,425 @@
1
+ import { expect, test, type Page } from '@playwright/test'
2
+ import { setupPage } from './test-utils'
3
+
4
+ /**
5
+ * EXIT COMPLETION INVARIANT TESTS
6
+ *
7
+ * Tests that sendExitComplete is called exactly once per exit cycle,
8
+ * at the correct time (after animations complete), and handles edge cases
9
+ * like rapid toggling, interruptions, and zero-duration animations.
10
+ *
11
+ * These tests catch bugs in exit completion tracking across all animation drivers.
12
+ */
13
+
14
+ interface ExitTrackingData {
15
+ counts: Record<string, number>
16
+ times: Record<string, number[]>
17
+ }
18
+
19
+ async function getExitTrackingData(page: Page): Promise<ExitTrackingData> {
20
+ return page.evaluate(() => ({
21
+ counts: (window as any).__exitCompletionCounts || {},
22
+ times: (window as any).__exitCompletionTimes || {},
23
+ }))
24
+ }
25
+
26
+ async function resetExitTracking(page: Page): Promise<void> {
27
+ await page.evaluate(() => {
28
+ ;(window as any).__resetExitTracking?.()
29
+ })
30
+ }
31
+
32
+ async function waitForExitComplete(
33
+ page: Page,
34
+ scenarioId: string,
35
+ timeout = 2000
36
+ ): Promise<number> {
37
+ const startTime = Date.now()
38
+ while (Date.now() - startTime < timeout) {
39
+ const data = await getExitTrackingData(page)
40
+ if (data.counts[scenarioId] && data.counts[scenarioId] > 0) {
41
+ return data.counts[scenarioId]
42
+ }
43
+ await page.waitForTimeout(50)
44
+ }
45
+ // return count even if zero (timed out)
46
+ const data = await getExitTrackingData(page)
47
+ return data.counts[scenarioId] || 0
48
+ }
49
+
50
+ async function expectStableCompletionCount(
51
+ page: Page,
52
+ scenarioId: string,
53
+ expectedCount: number,
54
+ settleMs = 300
55
+ ): Promise<void> {
56
+ await page.waitForTimeout(settleMs)
57
+ const data = await getExitTrackingData(page)
58
+ expect(
59
+ data.counts[scenarioId] || 0,
60
+ `${scenarioId} should remain at ${expectedCount} completion(s)`
61
+ ).toBe(expectedCount)
62
+ }
63
+
64
+ async function elementExists(page: Page, testId: string): Promise<boolean> {
65
+ return page.evaluate((id) => !!document.querySelector(`[data-testid="${id}"]`), testId)
66
+ }
67
+
68
+ test.beforeEach(async ({ page }) => {
69
+ await setupPage(page, { name: 'ExitCompletionCase', type: 'useCase' })
70
+ await page.waitForTimeout(500) // let initial render settle
71
+ await resetExitTracking(page)
72
+ })
73
+
74
+ test.describe('Basic Exit Completion', () => {
75
+ test('scenario 01: basic exit completes exactly once', async ({ page }) => {
76
+ await page.getByTestId('exit-01-trigger').click()
77
+
78
+ // wait for exit to complete
79
+ const count = await waitForExitComplete(page, '01-basic-exit')
80
+
81
+ expect(count, 'Exit should complete exactly once').toBe(1)
82
+ await expectStableCompletionCount(page, '01-basic-exit', 1, 400)
83
+ expect(await elementExists(page, 'exit-01-target'), 'Element should be gone').toBe(
84
+ false
85
+ )
86
+ })
87
+
88
+ test('scenario 02: zero duration exit completes exactly once', async ({ page }) => {
89
+ await page.getByTestId('exit-02-trigger').click()
90
+
91
+ // zero duration should still complete once
92
+ const count = await waitForExitComplete(page, '02-zero-duration', 500)
93
+
94
+ expect(count, 'Zero duration exit should complete exactly once').toBe(1)
95
+ await expectStableCompletionCount(page, '02-zero-duration', 1, 200)
96
+ expect(await elementExists(page, 'exit-02-target'), 'Element should be gone').toBe(
97
+ false
98
+ )
99
+ })
100
+
101
+ test('scenario 03: very short duration (30ms) completes exactly once', async ({
102
+ page,
103
+ }) => {
104
+ await page.getByTestId('exit-03-trigger').click()
105
+
106
+ const count = await waitForExitComplete(page, '03-short-duration', 2000)
107
+
108
+ expect(count, 'Short duration exit should complete exactly once').toBe(1)
109
+ await expectStableCompletionCount(page, '03-short-duration', 1, 400)
110
+ expect(await elementExists(page, 'exit-03-target'), 'Element should be gone').toBe(
111
+ false
112
+ )
113
+ })
114
+ })
115
+
116
+ test.describe('Duplicate Completion Guards', () => {
117
+ test('scenario 04: rapid toggle does not cause duplicate completions', async ({
118
+ page,
119
+ }) => {
120
+ // this test catches finding #4/#5: duplicate completions on re-renders
121
+ await page.getByTestId('exit-04-trigger').click()
122
+
123
+ // wait for the sequence to complete (off -> on -> off)
124
+ // CI can be slow, give plenty of time for animations
125
+ await page.waitForTimeout(1500)
126
+
127
+ const data = await getExitTrackingData(page)
128
+ const count = data.counts['04-rapid-toggle'] || 0
129
+
130
+ // first exit is interrupted; only final exit should complete
131
+ expect(count, 'Rapid toggle should complete exactly once for the final exit').toBe(1)
132
+ expect(await elementExists(page, 'exit-04-target'), 'Element should be gone').toBe(
133
+ false
134
+ )
135
+ })
136
+
137
+ test('scenario 05: re-renders during exit do not cause duplicate completions', async ({
138
+ page,
139
+ }) => {
140
+ // this test catches finding #4: flush restart causing duplicate completions
141
+ await page.getByTestId('exit-05-trigger').click()
142
+
143
+ // wait for exit + re-renders to complete
144
+ // CI can be slow, give plenty of time
145
+ await page.waitForTimeout(1500)
146
+
147
+ const data = await getExitTrackingData(page)
148
+ const count = data.counts['05-rerender-during-exit'] || 0
149
+
150
+ expect(count, 'Re-renders during exit should not cause duplicate completions').toBe(1)
151
+ expect(await elementExists(page, 'exit-05-target'), 'Element should be gone').toBe(
152
+ false
153
+ )
154
+ })
155
+
156
+ test('scenario 06: multiple children exiting fires completion once', async ({
157
+ page,
158
+ }) => {
159
+ await page.getByTestId('exit-06-trigger').click()
160
+
161
+ // wait for all 3 children to exit
162
+ await page.waitForTimeout(800)
163
+
164
+ const data = await getExitTrackingData(page)
165
+ const count = data.counts['06-multiple-children'] || 0
166
+
167
+ // AnimatePresence should fire onExitComplete once after ALL children exit
168
+ expect(count, 'Multiple children should trigger one completion').toBe(1)
169
+ expect(await elementExists(page, 'exit-06-target-1'), 'Child 1 should be gone').toBe(
170
+ false
171
+ )
172
+ expect(await elementExists(page, 'exit-06-target-2'), 'Child 2 should be gone').toBe(
173
+ false
174
+ )
175
+ expect(await elementExists(page, 'exit-06-target-3'), 'Child 3 should be gone').toBe(
176
+ false
177
+ )
178
+ })
179
+ })
180
+
181
+ test.describe('Timing Validation', () => {
182
+ test('scenario 07: long animation (500ms) completes after animation finishes', async ({
183
+ page,
184
+ }) => {
185
+ await page.getByTestId('exit-07-trigger').click()
186
+
187
+ const startTime = Date.now()
188
+ await waitForExitComplete(page, '07-long-animation', 2000)
189
+ const elapsed = Date.now() - startTime
190
+
191
+ const data = await getExitTrackingData(page)
192
+ const completionTime = data.times['07-long-animation']?.[0] || 0
193
+
194
+ // completion should happen after ~500ms, not immediately
195
+ // allow some tolerance for CI variance
196
+ expect(
197
+ completionTime,
198
+ 'Exit should complete after animation (>350ms)'
199
+ ).toBeGreaterThan(350)
200
+ expect(data.counts['07-long-animation'], 'Should complete exactly once').toBe(1)
201
+ })
202
+
203
+ test('scenario 08: interrupted exit completes at most once', async ({ page }) => {
204
+ // this test catches finding #2: canceled animations counted as complete
205
+ await page.getByTestId('exit-08-trigger').click()
206
+
207
+ // wait for the full sequence: exit -> interrupt -> exit again
208
+ await page.waitForTimeout(1200)
209
+
210
+ const data = await getExitTrackingData(page)
211
+ const count = data.counts['08-interrupted'] || 0
212
+
213
+ // the interrupted exit should not count as complete:
214
+ // only the final successful exit should complete
215
+ expect(count, 'Interrupted exit should complete exactly once').toBe(1)
216
+
217
+ const completionTime = data.times['08-interrupted']?.[0] || 0
218
+ expect(
219
+ completionTime,
220
+ 'Interrupted scenario should not complete early (final completion should be late)'
221
+ ).toBeGreaterThan(350)
222
+
223
+ // final state should be hidden
224
+ const status = await page.getByTestId('exit-08-status').textContent()
225
+ expect(status).toBe('hidden')
226
+ })
227
+
228
+ test('scenario 09: stress test with many cancel/restart cycles', async ({ page }) => {
229
+ await page.getByTestId('exit-09-trigger').click()
230
+
231
+ // wait for the stress test sequence to complete
232
+ await page.waitForTimeout(1500)
233
+
234
+ const data = await getExitTrackingData(page)
235
+ const count = data.counts['09-cancel-restart'] || 0
236
+
237
+ // all interim exits are interrupted; only the final exit should complete
238
+ expect(count, 'Stress test should complete exactly once').toBe(1)
239
+
240
+ // final state should be hidden
241
+ const status = await page.getByTestId('exit-09-status').textContent()
242
+ expect(status).toBe('hidden')
243
+ })
244
+ })
245
+
246
+ test.describe('Per-Property Exit', () => {
247
+ test('scenario 10: per-property exit waits for longest animation', async ({
248
+ page,
249
+ }, testInfo) => {
250
+ // motion driver can't differentiate transform sub-keys (scale becomes transform)
251
+ // so per-property timing for scale vs opacity doesn't work
252
+ const driver = (testInfo.project?.metadata as any)?.animationDriver
253
+ if (driver === 'motion') {
254
+ test.skip()
255
+ return
256
+ }
257
+
258
+ // this test catches finding #1: emitter-driven keys not tracked
259
+ // opacity=100ms, scale=500ms - should wait for scale
260
+ await page.getByTestId('exit-10-trigger').click()
261
+
262
+ await waitForExitComplete(page, '10-per-property', 2000)
263
+
264
+ const data = await getExitTrackingData(page)
265
+ const completionTime = data.times['10-per-property']?.[0] || 0
266
+
267
+ // should wait for the slow scale animation (500ms), not just opacity (100ms)
268
+ // allow tolerance for CI
269
+ expect(
270
+ completionTime,
271
+ 'Per-property exit should wait for longest animation (>350ms)'
272
+ ).toBeGreaterThan(350)
273
+ expect(data.counts['10-per-property'], 'Should complete exactly once').toBe(1)
274
+ await expectStableCompletionCount(page, '10-per-property', 1, 350)
275
+ })
276
+
277
+ test('scenario 11: mixed duration exit completes after slowest property', async ({
278
+ page,
279
+ }, testInfo) => {
280
+ // motion driver can't differentiate transform sub-keys
281
+ const driver = (testInfo.project?.metadata as any)?.animationDriver
282
+ if (driver === 'motion') {
283
+ test.skip()
284
+ return
285
+ }
286
+
287
+ // opacity=100ms, scale/y=400ms
288
+ await page.getByTestId('exit-11-trigger').click()
289
+
290
+ await waitForExitComplete(page, '11-mixed-duration', 2000)
291
+
292
+ const data = await getExitTrackingData(page)
293
+ const completionTime = data.times['11-mixed-duration']?.[0] || 0
294
+
295
+ // should wait for 400ms animations, not 100ms opacity
296
+ expect(
297
+ completionTime,
298
+ 'Mixed duration should wait for slowest property (>300ms)'
299
+ ).toBeGreaterThan(300)
300
+ expect(data.counts['11-mixed-duration'], 'Should complete exactly once').toBe(1)
301
+ await expectStableCompletionCount(page, '11-mixed-duration', 1, 350)
302
+ })
303
+ })
304
+
305
+ test.describe('Element removal timing', () => {
306
+ test('element should exist during exit animation', async ({ page }) => {
307
+ // native driver has timing issues on web - animations complete too quickly
308
+ const driver = (test.info().project?.metadata as any)?.animationDriver
309
+ test.skip(driver === 'native', 'native driver has animation timing issues on web')
310
+
311
+ // verify element stays in DOM during exit animation
312
+ await page.getByTestId('exit-07-trigger').click()
313
+
314
+ // immediately after triggering, element should still exist
315
+ await page.waitForTimeout(50)
316
+ expect(
317
+ await elementExists(page, 'exit-07-target'),
318
+ 'Element should exist during exit animation'
319
+ ).toBe(true)
320
+
321
+ // at 200ms into 500ms animation, should still exist
322
+ await page.waitForTimeout(150)
323
+ expect(
324
+ await elementExists(page, 'exit-07-target'),
325
+ 'Element should still exist at 200ms into 500ms animation'
326
+ ).toBe(true)
327
+
328
+ // after animation completes, should be gone
329
+ await page.waitForTimeout(500)
330
+ expect(
331
+ await elementExists(page, 'exit-07-target'),
332
+ 'Element should be gone after animation completes'
333
+ ).toBe(false)
334
+ })
335
+ })
336
+
337
+ test.describe('AnimateOnly & Transform Sub-Keys', () => {
338
+ test('scenario 51: animateOnly excludes scale from pending set', async ({
339
+ page,
340
+ }, testInfo) => {
341
+ // this test uses CSS-style duration strings which only CSS driver supports
342
+ const driver = (testInfo.project?.metadata as any)?.animationDriver
343
+ if (driver !== 'css') {
344
+ test.skip()
345
+ return
346
+ }
347
+
348
+ // animateOnly=['opacity'] with scale=500ms, opacity=100ms
349
+ // should complete based on opacity timing (~100ms), not scale
350
+ await page.getByTestId('exit-51-trigger').click()
351
+
352
+ await waitForExitComplete(page, '51-animateonly-exclusion', 2000)
353
+
354
+ const data = await getExitTrackingData(page)
355
+ const completionTime = data.times['51-animateonly-exclusion']?.[0] || 0
356
+
357
+ // should complete after opacity (100ms) finishes, not wait for excluded scale (500ms)
358
+ // allow tolerance but should NOT wait 400-500ms
359
+ expect(
360
+ completionTime,
361
+ 'AnimateOnly should exclude scale - complete around opacity time (not 400+ms)'
362
+ ).toBeLessThan(400)
363
+ expect(data.counts['51-animateonly-exclusion'], 'Should complete exactly once').toBe(
364
+ 1
365
+ )
366
+ await expectStableCompletionCount(page, '51-animateonly-exclusion', 1, 350)
367
+ expect(await elementExists(page, 'exit-51-target'), 'Element should be gone').toBe(
368
+ false
369
+ )
370
+ })
371
+
372
+ test('scenario 53: transform sub-keys with different durations wait for longest', async ({
373
+ page,
374
+ }, testInfo) => {
375
+ // this test uses CSS-style duration strings ('100ms', '500ms') which only CSS driver supports
376
+ const driver = (testInfo.project?.metadata as any)?.animationDriver
377
+ if (driver !== 'css') {
378
+ test.skip()
379
+ return
380
+ }
381
+
382
+ // scale=100ms, y=500ms - should wait for y
383
+ await page.getByTestId('exit-53-trigger').click()
384
+
385
+ await waitForExitComplete(page, '53-transform-subkeys', 2000)
386
+
387
+ const data = await getExitTrackingData(page)
388
+ const completionTime = data.times['53-transform-subkeys']?.[0] || 0
389
+
390
+ // should wait for the slower y animation (500ms), not just scale (100ms)
391
+ expect(
392
+ completionTime,
393
+ 'Transform sub-keys should wait for longest (y=500ms, should be >350ms)'
394
+ ).toBeGreaterThan(350)
395
+ expect(data.counts['53-transform-subkeys'], 'Should complete exactly once').toBe(1)
396
+ await expectStableCompletionCount(page, '53-transform-subkeys', 1, 350)
397
+ expect(await elementExists(page, 'exit-53-target'), 'Element should be gone').toBe(
398
+ false
399
+ )
400
+ })
401
+
402
+ test('scenario 55: zero animatable props completes immediately', async ({ page }) => {
403
+ // animateOnly=[] means nothing should animate, immediate completion
404
+ await page.getByTestId('exit-55-trigger').click()
405
+
406
+ const startTime = Date.now()
407
+ await waitForExitComplete(page, '55-zero-animatable', 1000)
408
+ const elapsed = Date.now() - startTime
409
+
410
+ const data = await getExitTrackingData(page)
411
+ const completionTime = data.times['55-zero-animatable']?.[0] || 0
412
+
413
+ // should complete almost immediately since no animations
414
+ // 300ms threshold to account for CI overhead (actual should be <10ms)
415
+ expect(
416
+ completionTime,
417
+ 'Zero animatable props should complete immediately (<300ms)'
418
+ ).toBeLessThan(300)
419
+ expect(data.counts['55-zero-animatable'], 'Should complete exactly once').toBe(1)
420
+ await expectStableCompletionCount(page, '55-zero-animatable', 1, 200)
421
+ expect(await elementExists(page, 'exit-55-target'), 'Element should be gone').toBe(
422
+ false
423
+ )
424
+ })
425
+ })
@@ -0,0 +1,34 @@
1
+ import { test, expect } from '@playwright/test'
2
+ import { setupPage } from './test-utils'
3
+
4
+ test('exit animation holds element during tab switch', async ({ page }) => {
5
+ // native driver has hover/animation issues on web
6
+ const driver = (test.info().project?.metadata as any)?.animationDriver
7
+ test.skip(driver === 'native', 'native driver has element detection issues on web')
8
+
9
+ await setupPage(page, { name: 'TabHoverAnimationCase', type: 'useCase' })
10
+ await page.waitForTimeout(500)
11
+
12
+ await page.locator('[data-testid="tab-tab-a"]').hover()
13
+ await page.waitForTimeout(400)
14
+
15
+ const content = page.locator('[data-testid="slide-content"]')
16
+ expect(await content.count()).toBe(1)
17
+
18
+ await page.locator('[data-testid="tab-tab-d"]').hover()
19
+
20
+ await page.waitForTimeout(60)
21
+ const countAt60 = await content.count()
22
+
23
+ const elemsAt60 = await page.evaluate(() =>
24
+ Array.from(document.querySelectorAll('[data-testid="slide-content"]')).map((el) => ({
25
+ tab: (el as HTMLElement).dataset.tab,
26
+ opacity: getComputedStyle(el).opacity,
27
+ }))
28
+ )
29
+ console.log(`At 60ms - count: ${countAt60}, elements: ${JSON.stringify(elemsAt60)}`)
30
+
31
+ // If exit animation works: 2 elements (A exiting + D entering)
32
+ // If exit is immediate: 1 element (only D)
33
+ expect(countAt60).toBe(2)
34
+ })
@@ -0,0 +1,41 @@
1
+ import { expect, test } from '@playwright/test'
2
+ import { setupPage } from './test-utils'
3
+
4
+ test.beforeEach(async ({ page }) => {
5
+ await setupPage(page, { name: 'FocusVisibleButton', type: 'useCase' })
6
+ })
7
+
8
+ test(`button + focusVisibleStyle`, async ({ page }) => {
9
+ const button = page.locator('#focus-visible-button')
10
+
11
+ // Ensure the button is visible and ready
12
+ await button.waitFor({ state: 'visible' })
13
+
14
+ // Click the button first to ensure it's interactable, then focus via keyboard
15
+ await button.click()
16
+ await page.keyboard.press('Tab')
17
+ await page.keyboard.press('Shift+Tab')
18
+
19
+ // Wait for focus to be applied and styles to update
20
+ await page.waitForTimeout(100)
21
+
22
+ // Verify the button is focused
23
+ const isFocused = await button.evaluate((el) => {
24
+ return document.activeElement === el
25
+ })
26
+
27
+ expect(isFocused).toBe(true)
28
+
29
+ // Check if focus-visible pseudo-class is applied
30
+ const hasFocusVisible = await button.evaluate((el) => {
31
+ return el.matches(':focus-visible')
32
+ })
33
+
34
+ // Get computed styles
35
+ const styles = await button.evaluate((el) => {
36
+ return window.getComputedStyle(el)
37
+ })
38
+
39
+ expect(hasFocusVisible).toBe(true)
40
+ expect(styles.borderWidth).toBe(`2px`)
41
+ })
@@ -0,0 +1,23 @@
1
+ import { expect, test } from '@playwright/test'
2
+ import { setupPage } from './test-utils'
3
+
4
+ test.beforeEach(async ({ page }) => {
5
+ await setupPage(page, { name: 'FocusVisibleButtonPointer', type: 'useCase' })
6
+ })
7
+
8
+ test(`button + focusVisibleStyle + non keyboard focus`, async ({ page }) => {
9
+ const button = page.locator('#focus-visible-button')
10
+
11
+ const box = await button.boundingBox()
12
+ await page.mouse.move(box!.x + box!.width / 2, box!.y + box!.height / 2)
13
+ await page.mouse.down({
14
+ button: 'left',
15
+ clickCount: 1,
16
+ })
17
+
18
+ const styles = await button.evaluate((el) => {
19
+ return window.getComputedStyle(el)
20
+ })
21
+
22
+ expect(styles.borderWidth).toBe(`1px`)
23
+ })
@@ -0,0 +1,40 @@
1
+ import { expect, test } from '@playwright/test'
2
+ import { setupPage } from './test-utils'
3
+
4
+ test.beforeEach(async ({ page }) => {
5
+ await setupPage(page, { name: 'FocusVisibleButtonWithFocusStyle', type: 'useCase' })
6
+ })
7
+
8
+ test(`button + focusVisibleStyle + focusVisible`, async ({ page }) => {
9
+ const button = page.locator('#focus-visible-button')
10
+
11
+ const beforeFocusStyles = await button.evaluate((el) => {
12
+ return window.getComputedStyle(el)
13
+ })
14
+
15
+ expect(beforeFocusStyles.borderWidth).toBe(`1px`)
16
+
17
+ await page.keyboard.press('Tab')
18
+
19
+ const stylesAfterKeyboardFocus = await button.evaluate((el) => {
20
+ return window.getComputedStyle(el)
21
+ })
22
+
23
+ expect(stylesAfterKeyboardFocus.borderWidth).toBe(`3px`)
24
+
25
+ await button.blur()
26
+
27
+ const box = await button.boundingBox()
28
+ await page.mouse.move(box!.x + box!.width / 2, box!.y + box!.height / 2)
29
+ await page.mouse.down({
30
+ button: 'left',
31
+ clickCount: 1,
32
+ })
33
+ await page.mouse.up()
34
+
35
+ const stylesAfterMouseFocus = await button.evaluate((el) => {
36
+ return window.getComputedStyle(el)
37
+ })
38
+
39
+ expect(stylesAfterMouseFocus.borderWidth).toBe(`2px`)
40
+ })
@@ -0,0 +1,66 @@
1
+ import { expect, test } from '@playwright/test'
2
+ import { setupPage } from './test-utils'
3
+
4
+ test.beforeEach(async ({ page }) => {
5
+ await setupPage(page, { name: 'FocusWithinCase', type: 'useCase' })
6
+ })
7
+
8
+ test('animated focusWithinStyle applies on focus', async ({ page }, testInfo) => {
9
+ // native driver uses RN Animated API which can't animate CSS border colors on web
10
+ test.skip(
11
+ testInfo.project.name === 'animated-native',
12
+ 'Native driver cannot animate CSS properties on web'
13
+ )
14
+ const input = page.locator('[data-testid="animated-input"]')
15
+ const parent = page.locator('[data-testid="animated-parent"]')
16
+
17
+ await input.waitFor({ state: 'visible' })
18
+ await input.focus()
19
+ // reanimated spring needs more time to settle than motion
20
+ await page.waitForTimeout(1500)
21
+
22
+ const borderColor = await parent.evaluate((el) => getComputedStyle(el).borderColor)
23
+ expect(borderColor).toBe('rgb(0, 128, 0)')
24
+ })
25
+
26
+ test('animated focusWithinStyle removes on blur', async ({ page }, testInfo) => {
27
+ test.skip(
28
+ testInfo.project.name === 'animated-native',
29
+ 'Native driver cannot animate CSS properties on web'
30
+ )
31
+ const input = page.locator('[data-testid="animated-input"]')
32
+ const parent = page.locator('[data-testid="animated-parent"]')
33
+
34
+ await input.waitFor({ state: 'visible' })
35
+ await input.focus()
36
+ await page.waitForTimeout(1500)
37
+ await input.blur()
38
+ await page.waitForTimeout(1500)
39
+
40
+ const borderColor = await parent.evaluate((el) => getComputedStyle(el).borderColor)
41
+ expect(borderColor).not.toBe('rgb(0, 128, 0)')
42
+ })
43
+
44
+ test('animated focusWithinStyle does not cause React re-render (avoidReRenders)', async ({
45
+ page,
46
+ }, testInfo) => {
47
+ // native driver doesn't support avoidReRenders
48
+ test.skip(
49
+ testInfo.project.name === 'animated-native',
50
+ 'Native driver does not support avoidReRenders'
51
+ )
52
+
53
+ const input = page.locator('[data-testid="animated-input"]')
54
+ const renders = page.locator('[data-testid="animated-renders"]')
55
+
56
+ await input.waitFor({ state: 'visible' })
57
+ const before = await renders.textContent()
58
+
59
+ await input.focus()
60
+ await page.waitForTimeout(300)
61
+ await input.blur()
62
+ await page.waitForTimeout(300)
63
+
64
+ const after = await renders.textContent()
65
+ expect(after).toBe(before)
66
+ })
@@ -0,0 +1,60 @@
1
+ import { expect, test } from '@playwright/test'
2
+ import { setupPage } from './test-utils'
3
+
4
+ test.beforeEach(async ({ page }) => {
5
+ await setupPage(page, { name: 'FocusWithinCase', type: 'useCase' })
6
+ })
7
+
8
+ test('focusWithinStyle applies via direct prop', async ({ page }) => {
9
+ const input = page.locator('[data-testid="direct-input"]')
10
+ const parent = page.locator('[data-testid="direct-parent"]')
11
+
12
+ await input.waitFor({ state: 'visible' })
13
+ await input.focus()
14
+ await page.waitForTimeout(100)
15
+
16
+ const borderColor = await parent.evaluate((el) => getComputedStyle(el).borderColor)
17
+ expect(borderColor).toBe('rgb(255, 0, 0)')
18
+ })
19
+
20
+ test('focusWithinStyle applies via styled() component', async ({ page }) => {
21
+ const input = page.locator('[data-testid="styled-input"]')
22
+ const parent = page.locator('[data-testid="styled-parent"]')
23
+
24
+ await input.waitFor({ state: 'visible' })
25
+ await input.focus()
26
+ await page.waitForTimeout(100)
27
+
28
+ const borderColor = await parent.evaluate((el) => getComputedStyle(el).borderColor)
29
+ expect(borderColor).toBe('rgb(0, 0, 255)')
30
+ })
31
+
32
+ test('focusWithinStyle removes on blur', async ({ page }) => {
33
+ const input = page.locator('[data-testid="direct-input"]')
34
+ const parent = page.locator('[data-testid="direct-parent"]')
35
+
36
+ await input.waitFor({ state: 'visible' })
37
+ await input.focus()
38
+ await page.waitForTimeout(100)
39
+ await input.blur()
40
+ await page.waitForTimeout(100)
41
+
42
+ const borderColor = await parent.evaluate((el) => getComputedStyle(el).borderColor)
43
+ expect(borderColor).not.toBe('rgb(255, 0, 0)')
44
+ })
45
+
46
+ test('plain focusWithinStyle does not re-render parent', async ({ page }) => {
47
+ const input = page.locator('[data-testid="direct-input"]')
48
+ const renders = page.locator('[data-testid="direct-renders"]')
49
+
50
+ await input.waitFor({ state: 'visible' })
51
+ const before = await renders.textContent()
52
+
53
+ await input.focus()
54
+ await page.waitForTimeout(200)
55
+ await input.blur()
56
+ await page.waitForTimeout(200)
57
+
58
+ const after = await renders.textContent()
59
+ expect(after).toBe(before)
60
+ })