@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,319 @@
1
+ import { expect, test, type Page } from '@playwright/test'
2
+ import { setupPage } from './test-utils'
3
+
4
+ /**
5
+ * PSEUDO TRANSITION TESTS
6
+ *
7
+ * Tests for the `transition` prop inside pseudo-style props (hoverStyle, pressStyle, etc.)
8
+ *
9
+ * CSS semantics:
10
+ * - Enter pseudo state (e.g., hover): use that pseudo's transition (200ms)
11
+ * - Exit pseudo state (e.g., unhover): use base transition (1000ms)
12
+ *
13
+ * These tests run across all animation drivers (css, native, reanimated, motion).
14
+ */
15
+
16
+ async function getBackgroundColor(page: Page, testId: string): Promise<string> {
17
+ return page.evaluate((id) => {
18
+ const el = document.querySelector(`[data-testid="${id}"]`)
19
+ if (!el) return ''
20
+ return getComputedStyle(el).backgroundColor
21
+ }, testId)
22
+ }
23
+
24
+ async function getOpacity(page: Page, testId: string): Promise<number> {
25
+ return page.evaluate((id) => {
26
+ const el = document.querySelector(`[data-testid="${id}"]`)
27
+ if (!el) return -1
28
+ return Number.parseFloat(getComputedStyle(el).opacity)
29
+ }, testId)
30
+ }
31
+
32
+ function parseRgb(color: string): { r: number; g: number; b: number } | null {
33
+ const match = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/)
34
+ if (!match) return null
35
+ return {
36
+ r: Number.parseInt(match[1], 10),
37
+ g: Number.parseInt(match[2], 10),
38
+ b: Number.parseInt(match[3], 10),
39
+ }
40
+ }
41
+
42
+ test.describe('Pseudo Transition Tests', () => {
43
+ test.beforeEach(async ({ page }) => {
44
+ const driver = (test.info().project?.metadata as any)?.animationDriver
45
+ test.skip(driver === 'native', 'native driver has issues on web')
46
+
47
+ await setupPage(page, {
48
+ name: 'PseudoTransitionCase',
49
+ type: 'useCase',
50
+ })
51
+ // wait for initial render
52
+ await page.waitForTimeout(500)
53
+ })
54
+
55
+ test('scenario 1: hoverStyle transition - fast enter (200ms), slow exit (1000ms)', async ({
56
+ page,
57
+ }) => {
58
+ const target = page.getByTestId('scenario-1-target')
59
+ const initialColor = await getBackgroundColor(page, 'scenario-1-target')
60
+
61
+ // hover to trigger enter animation (should be fast - 200ms)
62
+ await target.hover()
63
+
64
+ // wait for enter to complete
65
+ await page.waitForTimeout(350)
66
+ const hoverColor = await getBackgroundColor(page, 'scenario-1-target')
67
+ expect(hoverColor, 'Color should change on hover').not.toBe(initialColor)
68
+
69
+ // move mouse away to trigger exit animation (should be slow - 1000ms)
70
+ await page.mouse.move(0, 0)
71
+
72
+ // at 400ms, exit should still be in progress (1000ms animation)
73
+ await page.waitForTimeout(400)
74
+ const midExitColor = await getBackgroundColor(page, 'scenario-1-target')
75
+ const midExitRgb = parseRgb(midExitColor)
76
+ const hoverRgb = parseRgb(hoverColor)
77
+ const initialRgb = parseRgb(initialColor)
78
+
79
+ // check that exit is still in progress (color not yet back to initial)
80
+ if (midExitRgb && hoverRgb && initialRgb) {
81
+ expect(
82
+ midExitColor,
83
+ 'At 400ms into 1000ms exit, color should not be fully reset'
84
+ ).not.toBe(initialColor)
85
+ }
86
+
87
+ // wait for full exit
88
+ await page.waitForTimeout(800)
89
+ const finalColor = await getBackgroundColor(page, 'scenario-1-target')
90
+ expect(finalColor, 'Color should return to initial').toBe(initialColor)
91
+ })
92
+
93
+ test('scenario 2: pressStyle transition - fast press (200ms), slow release (1000ms)', async ({
94
+ page,
95
+ }) => {
96
+ const target = page.getByTestId('scenario-2-target')
97
+ const initialColor = await getBackgroundColor(page, 'scenario-2-target')
98
+
99
+ // press to trigger enter animation
100
+ const box = await target.boundingBox()
101
+ if (!box) throw new Error('Target not found')
102
+
103
+ await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2)
104
+ await page.mouse.down()
105
+
106
+ // wait for press animation (200ms + buffer)
107
+ await page.waitForTimeout(350)
108
+ const pressColor = await getBackgroundColor(page, 'scenario-2-target')
109
+ expect(pressColor, 'Color should change on press').not.toBe(initialColor)
110
+
111
+ // release to trigger exit animation (should be slow - 1000ms)
112
+ await page.mouse.up()
113
+
114
+ // at 400ms, exit should still be in progress if using 1000ms base
115
+ await page.waitForTimeout(400)
116
+ const midReleaseColor = await getBackgroundColor(page, 'scenario-2-target')
117
+
118
+ // check exit is still in progress across drivers
119
+ const midRgb = parseRgb(midReleaseColor)
120
+ const pressRgb = parseRgb(pressColor)
121
+ const initRgb = parseRgb(initialColor)
122
+
123
+ if (midRgb && pressRgb && initRgb) {
124
+ expect(
125
+ midReleaseColor,
126
+ 'At 400ms into 1000ms release, color should not be fully reset'
127
+ ).not.toBe(initialColor)
128
+ }
129
+
130
+ // wait for full exit
131
+ await page.waitForTimeout(800)
132
+ const finalColor = await getBackgroundColor(page, 'scenario-2-target')
133
+ expect(finalColor, 'Color should return to initial').toBe(initialColor)
134
+ })
135
+
136
+ test('scenario 6: opacity hover - fast fade in (200ms), slow fade out (1000ms)', async ({
137
+ page,
138
+ }) => {
139
+ // CSS driver now supports pseudo-class transitions via !important CSS rules
140
+
141
+ const target = page.getByTestId('scenario-6-target')
142
+ const initialOpacity = await getOpacity(page, 'scenario-6-target')
143
+ expect(initialOpacity, 'Initial opacity should be ~0.3').toBeCloseTo(0.3, 1)
144
+
145
+ // hover to trigger opacity increase
146
+ await target.hover()
147
+
148
+ // wait for enter to complete (200ms + buffer for springs)
149
+ await page.waitForTimeout(400)
150
+ const hoverOpacity = await getOpacity(page, 'scenario-6-target')
151
+ expect(hoverOpacity, 'Hover opacity should be > 0.6').toBeGreaterThan(0.6)
152
+
153
+ // move away to trigger opacity decrease (slow - 1000ms)
154
+ await page.mouse.move(0, 0)
155
+
156
+ // at 300ms, opacity should still be elevated if using 1000ms exit
157
+ await page.waitForTimeout(300)
158
+ const midExitOpacity = await getOpacity(page, 'scenario-6-target')
159
+
160
+ expect(
161
+ midExitOpacity,
162
+ 'Opacity at 300ms of 1000ms exit should still be elevated'
163
+ ).toBeGreaterThan(0.4)
164
+
165
+ // wait for full exit
166
+ await page.waitForTimeout(800)
167
+ const finalOpacity = await getOpacity(page, 'scenario-6-target')
168
+ expect(finalOpacity, 'Final opacity should be ~0.3').toBeCloseTo(0.3, 1)
169
+ })
170
+
171
+ // BUG TEST: First pseudo interaction should use pseudo transition
172
+ // Regression test for: prevPseudoState not initialized, causing first hover to use base transition
173
+ test('first hover should use pseudo transition (not base)', async ({ page }) => {
174
+ const driver = (test.info().project?.metadata as any)?.animationDriver
175
+
176
+ const target = page.getByTestId('scenario-6-target')
177
+ const initialOpacity = await getOpacity(page, 'scenario-6-target')
178
+ expect(initialOpacity, 'Initial opacity should be ~0.3').toBeCloseTo(0.3, 1)
179
+
180
+ // FIRST hover - should use fast 200ms pseudo transition
181
+ await target.hover()
182
+
183
+ // at 350ms, a 200ms animation should be complete (extra buffer for springs)
184
+ await page.waitForTimeout(350)
185
+ const firstHoverOpacity = await getOpacity(page, 'scenario-6-target')
186
+
187
+ // if prevPseudoState wasn't initialized, this would use 1000ms base transition
188
+ // and opacity would still be low (~0.4-0.5). with proper 200ms, should be > 0.6
189
+ expect(
190
+ firstHoverOpacity,
191
+ `First hover should complete quickly with pseudo transition (got ${firstHoverOpacity}, driver: ${driver})`
192
+ ).toBeGreaterThan(0.6)
193
+ })
194
+
195
+ // BUG TEST: Exit should use base transition, not cached pseudo transition
196
+ // Regression test for: reanimated keeps pseudo config on exit instead of restoring base
197
+ test('exit should use base transition timing (not cached pseudo)', async ({ page }) => {
198
+ const driver = (test.info().project?.metadata as any)?.animationDriver
199
+
200
+ const target = page.getByTestId('scenario-6-target')
201
+
202
+ // hover to enter (fast 200ms) - wait longer for springs
203
+ await target.hover()
204
+ await page.waitForTimeout(400)
205
+ const hoverOpacity = await getOpacity(page, 'scenario-6-target')
206
+ expect(hoverOpacity, 'Should be at hover state').toBeGreaterThan(0.7)
207
+
208
+ // exit hover - should use SLOW 1000ms base transition
209
+ await page.mouse.move(0, 0)
210
+
211
+ // at 250ms into a 1000ms exit, should still be > 0.4
212
+ // if using cached 200ms pseudo transition, would already be near 0.3
213
+ await page.waitForTimeout(250)
214
+ const midExitOpacity = await getOpacity(page, 'scenario-6-target')
215
+
216
+ expect(
217
+ midExitOpacity,
218
+ `Exit at 250ms of 1000ms should still be > 0.4 (got ${midExitOpacity}, driver: ${driver})`
219
+ ).toBeGreaterThan(0.4)
220
+
221
+ // wait for exit to complete
222
+ await page.waitForTimeout(900)
223
+ const finalOpacity = await getOpacity(page, 'scenario-6-target')
224
+ expect(finalOpacity, 'Should return to initial').toBeCloseTo(0.3, 1)
225
+ })
226
+
227
+ test('scenario 6: repeated hover cycles keep slow exit timing (reanimated regression)', async ({
228
+ page,
229
+ }) => {
230
+ const driver = (test.info().project?.metadata as any)?.animationDriver
231
+ test.skip(
232
+ driver !== 'reanimated',
233
+ 'Reanimated-specific config cache regression coverage'
234
+ )
235
+
236
+ const target = page.getByTestId('scenario-6-target')
237
+ const initialOpacity = await getOpacity(page, 'scenario-6-target')
238
+ expect(initialOpacity, 'Initial opacity should be ~0.3').toBeCloseTo(0.3, 1)
239
+
240
+ const runCycle = async () => {
241
+ await target.hover()
242
+ await page.waitForTimeout(350)
243
+ const hoveredOpacity = await getOpacity(page, 'scenario-6-target')
244
+ expect(
245
+ hoveredOpacity,
246
+ 'Enter should move quickly toward hover opacity'
247
+ ).toBeGreaterThan(0.7)
248
+
249
+ await page.mouse.move(0, 0)
250
+ await page.waitForTimeout(300)
251
+ const midExitOpacity = await getOpacity(page, 'scenario-6-target')
252
+ expect(
253
+ midExitOpacity,
254
+ 'Exit should still be in progress at 300ms (base 1000ms transition)'
255
+ ).toBeGreaterThan(0.5)
256
+
257
+ await page.waitForTimeout(900)
258
+ const finalOpacity = await getOpacity(page, 'scenario-6-target')
259
+ expect(finalOpacity, 'Cycle should return to base opacity').toBeCloseTo(0.3, 1)
260
+ }
261
+
262
+ await runCycle()
263
+ await runCycle()
264
+ })
265
+
266
+ test('scenario 4: group hover transition - fast enter (200ms)', async ({ page }) => {
267
+ const driver = (test.info().project?.metadata as any)?.animationDriver
268
+
269
+ const container = page.getByTestId('scenario-4-container')
270
+ const initialOpacity = await getOpacity(page, 'scenario-4-target')
271
+ expect(initialOpacity, 'Initial opacity should be ~0.3').toBeCloseTo(0.3, 1)
272
+
273
+ // hover over container to trigger group hover (should be fast - 200ms)
274
+ await container.hover()
275
+
276
+ // at 350ms, a 200ms animation should be complete (extra buffer for springs)
277
+ await page.waitForTimeout(350)
278
+ const hoverOpacity = await getOpacity(page, 'scenario-4-target')
279
+
280
+ // if using 200ms transition, opacity should be close to 1 (>0.8)
281
+ // if using 1000ms base transition incorrectly, would be ~0.5
282
+ expect(
283
+ hoverOpacity,
284
+ `Group hover enter should complete quickly with 200ms transition (got ${hoverOpacity}, driver: ${driver})`
285
+ ).toBeGreaterThan(0.8)
286
+ })
287
+
288
+ test('scenario 4: group hover transition - slow exit (1000ms base)', async ({
289
+ page,
290
+ }) => {
291
+ const container = page.getByTestId('scenario-4-container')
292
+ const initialOpacity = await getOpacity(page, 'scenario-4-target')
293
+ expect(initialOpacity, 'Initial opacity should be ~0.3').toBeCloseTo(0.3, 1)
294
+
295
+ // hover over container to trigger group hover
296
+ await container.hover()
297
+
298
+ // wait for hover state to be fully applied (use longer time for all drivers)
299
+ await page.waitForTimeout(1200)
300
+ const hoverOpacity = await getOpacity(page, 'scenario-4-target')
301
+ expect(hoverOpacity, 'Hover opacity should be ~1').toBeGreaterThan(0.9)
302
+
303
+ // move away to trigger exit (slow - 1000ms base transition)
304
+ await page.mouse.move(0, 0)
305
+
306
+ // KEY TEST: at 300ms into 1000ms linear exit, opacity should still be > 0.5
307
+ await page.waitForTimeout(300)
308
+ const midExitOpacity = await getOpacity(page, 'scenario-4-target')
309
+ expect(
310
+ midExitOpacity,
311
+ 'Opacity should still be elevated during slow group-hover exit'
312
+ ).toBeGreaterThan(0.5)
313
+
314
+ // wait for full exit
315
+ await page.waitForTimeout(900)
316
+ const finalOpacity = await getOpacity(page, 'scenario-4-target')
317
+ expect(finalOpacity, 'Final opacity should be ~0.3').toBeCloseTo(0.3, 1)
318
+ })
319
+ })
@@ -0,0 +1,147 @@
1
+ import { expect, test } from '@playwright/test'
2
+ import { setupPage } from './test-utils'
3
+
4
+ /**
5
+ * Tests for raw Animated.Value with Animated.createAnimatedComponent
6
+ *
7
+ * This validates that react-native-web-lite properly handles AnimatedValue
8
+ * objects when passed as style props to Animated components.
9
+ */
10
+
11
+ test.describe('Raw Animated.Value', () => {
12
+ test.beforeEach(async ({ page }) => {
13
+ await setupPage(page, {
14
+ name: 'RawAnimatedValueCase',
15
+ type: 'useCase',
16
+ })
17
+ await page.waitForTimeout(500)
18
+ })
19
+
20
+ test('initial state applies animated values correctly', async ({ page }) => {
21
+ const box = page.getByTestId('animated-box')
22
+
23
+ // initial values: opacity=0, scale=0.5, translateY=50, backgroundColor=red
24
+ const opacity = await box.evaluate((el) => getComputedStyle(el).opacity)
25
+ const transform = await box.evaluate((el) => getComputedStyle(el).transform)
26
+ const bgColor = await box.evaluate((el) => getComputedStyle(el).backgroundColor)
27
+
28
+ expect(opacity).toBe('0')
29
+ // matrix(scaleX, 0, 0, scaleY, translateX, translateY)
30
+ expect(transform).toContain('matrix')
31
+ expect(transform).toContain('0.5') // scale
32
+ expect(transform).toContain('50') // translateY
33
+ expect(bgColor).toBe('rgb(255, 0, 0)') // red
34
+ })
35
+
36
+ test('animate in changes values over time', async ({ page }) => {
37
+ const box = page.getByTestId('animated-box')
38
+ const trigger = page.getByTestId('animate-in-trigger')
39
+
40
+ // capture initial
41
+ const initialOpacity = await box.evaluate((el) =>
42
+ parseFloat(getComputedStyle(el).opacity)
43
+ )
44
+ expect(initialOpacity).toBe(0)
45
+
46
+ // click animate in
47
+ await trigger.click()
48
+
49
+ // wait a bit for animation to progress
50
+ await page.waitForTimeout(100)
51
+
52
+ // capture mid-animation value
53
+ const midOpacity = await box.evaluate((el) =>
54
+ parseFloat(getComputedStyle(el).opacity)
55
+ )
56
+
57
+ // wait for animation to complete
58
+ await page.waitForTimeout(400)
59
+
60
+ // capture final
61
+ const finalOpacity = await box.evaluate((el) =>
62
+ parseFloat(getComputedStyle(el).opacity)
63
+ )
64
+ const finalTransform = await box.evaluate((el) => getComputedStyle(el).transform)
65
+ const finalBgColor = await box.evaluate((el) => getComputedStyle(el).backgroundColor)
66
+
67
+ // final values should be: opacity=1, scale=1, translateY=0, backgroundColor=green
68
+ expect(finalOpacity).toBeCloseTo(1, 1)
69
+
70
+ // transform should be identity or close to it (scale=1, translateY=0)
71
+ // matrix(1, 0, 0, 1, 0, 0) or 'none'
72
+ if (finalTransform !== 'none') {
73
+ expect(finalTransform).toMatch(/matrix\(1,?\s*0,?\s*0,?\s*1,?\s*0,?\s*0\)/)
74
+ }
75
+
76
+ // backgroundColor should be green
77
+ expect(finalBgColor).toBe('rgb(0, 255, 0)')
78
+
79
+ // the mid-animation value should show progress (not jumped instantly)
80
+ // if animation is working, mid should be between 0 and 1
81
+ console.log(
82
+ `Animation progress: initial=${initialOpacity}, mid=${midOpacity}, final=${finalOpacity}`
83
+ )
84
+
85
+ // key test: animation should have progressed, not jumped
86
+ const animationProgressed = midOpacity > 0.05 && midOpacity < 0.95
87
+ expect(
88
+ animationProgressed || finalOpacity === 1,
89
+ `Animation should progress smoothly. mid=${midOpacity}`
90
+ ).toBe(true)
91
+ })
92
+
93
+ test('animate out returns to initial values', async ({ page }) => {
94
+ const box = page.getByTestId('animated-box')
95
+ const animateIn = page.getByTestId('animate-in-trigger')
96
+ const animateOut = page.getByTestId('animate-out-trigger')
97
+
98
+ // first animate in
99
+ await animateIn.click()
100
+ await page.waitForTimeout(400)
101
+
102
+ // verify we're at end state
103
+ let opacity = await box.evaluate((el) => parseFloat(getComputedStyle(el).opacity))
104
+ expect(opacity).toBeCloseTo(1, 1)
105
+
106
+ // now animate out
107
+ await animateOut.click()
108
+ await page.waitForTimeout(400)
109
+
110
+ // should be back to initial
111
+ opacity = await box.evaluate((el) => parseFloat(getComputedStyle(el).opacity))
112
+ const transform = await box.evaluate((el) => getComputedStyle(el).transform)
113
+ const bgColor = await box.evaluate((el) => getComputedStyle(el).backgroundColor)
114
+
115
+ expect(opacity).toBeCloseTo(0, 1)
116
+ expect(transform).toContain('0.5') // scale back to 0.5
117
+ expect(transform).toContain('50') // translateY back to 50
118
+ expect(bgColor).toBe('rgb(255, 0, 0)') // back to red
119
+ })
120
+
121
+ test('interpolated color animates correctly', async ({ page }) => {
122
+ const box = page.getByTestId('animated-box')
123
+ const trigger = page.getByTestId('animate-in-trigger')
124
+
125
+ // initial color is red
126
+ let bgColor = await box.evaluate((el) => getComputedStyle(el).backgroundColor)
127
+ expect(bgColor).toBe('rgb(255, 0, 0)')
128
+
129
+ // animate
130
+ await trigger.click()
131
+
132
+ // capture mid-animation color
133
+ await page.waitForTimeout(150)
134
+ const midColor = await box.evaluate((el) => getComputedStyle(el).backgroundColor)
135
+
136
+ // wait for completion
137
+ await page.waitForTimeout(300)
138
+ bgColor = await box.evaluate((el) => getComputedStyle(el).backgroundColor)
139
+
140
+ // final should be green
141
+ expect(bgColor).toBe('rgb(0, 255, 0)')
142
+
143
+ // mid color should be somewhere between red and green
144
+ // (not exactly red and not exactly green if interpolation works)
145
+ console.log(`Color interpolation: mid=${midColor}, final=${bgColor}`)
146
+ })
147
+ })
@@ -0,0 +1,223 @@
1
+ import { expect, test, type Page } from '@playwright/test'
2
+ import { setupPage } from './test-utils'
3
+
4
+ test.beforeEach(async ({ page }) => {
5
+ await setupPage(page, { name: 'RemoveScrollCase', type: 'useCase' })
6
+ })
7
+
8
+ /**
9
+ * helper: scroll, open select, wait for viewport
10
+ */
11
+ async function scrollAndOpenSelect(page: Page, scrollY: number) {
12
+ if (scrollY > 0) {
13
+ await page.evaluate((y) => window.scrollTo(0, y), scrollY)
14
+ await page.waitForTimeout(100)
15
+ const actual = await page.evaluate(() => window.scrollY)
16
+ expect(actual).toBeGreaterThanOrEqual(scrollY - 2)
17
+ }
18
+
19
+ const trigger = page.getByTestId('rs-select-trigger')
20
+ await trigger.scrollIntoViewIfNeeded()
21
+ await trigger.click()
22
+
23
+ const viewport = page.getByTestId('rs-select-viewport')
24
+ await expect(viewport).toBeVisible({ timeout: 5000 })
25
+ await page.waitForTimeout(200)
26
+
27
+ return viewport
28
+ }
29
+
30
+ // --- positive: scroll position IS restored ---
31
+
32
+ test.describe('scroll position restored after close', () => {
33
+ test('preserved when closing by selecting an item', async ({ page }) => {
34
+ await scrollAndOpenSelect(page, 500)
35
+ const preClose = await page.evaluate(() => window.scrollY)
36
+
37
+ const item = page.getByTestId('rs-select-apple')
38
+ await item.click()
39
+ await page.waitForTimeout(300)
40
+
41
+ const viewport = page.getByTestId('rs-select-viewport')
42
+ await expect(viewport).not.toBeVisible()
43
+
44
+ const postClose = await page.evaluate(() => window.scrollY)
45
+ expect(Math.abs(postClose - preClose)).toBeLessThanOrEqual(2)
46
+ })
47
+
48
+ test('preserved when closing by pressing Escape', async ({ page }) => {
49
+ await scrollAndOpenSelect(page, 500)
50
+ const preClose = await page.evaluate(() => window.scrollY)
51
+
52
+ await page.keyboard.press('Escape')
53
+ await page.waitForTimeout(300)
54
+
55
+ const viewport = page.getByTestId('rs-select-viewport')
56
+ await expect(viewport).not.toBeVisible()
57
+
58
+ const postClose = await page.evaluate(() => window.scrollY)
59
+ expect(Math.abs(postClose - preClose)).toBeLessThanOrEqual(2)
60
+ })
61
+
62
+ test('preserved when closing by clicking outside', async ({ page }) => {
63
+ await scrollAndOpenSelect(page, 500)
64
+ const preClose = await page.evaluate(() => window.scrollY)
65
+
66
+ // click far from the select content to dismiss
67
+ await page.mouse.click(10, 10)
68
+ await page.waitForTimeout(300)
69
+
70
+ const viewport = page.getByTestId('rs-select-viewport')
71
+ await expect(viewport).not.toBeVisible({ timeout: 5000 })
72
+
73
+ const postClose = await page.evaluate(() => window.scrollY)
74
+ expect(Math.abs(postClose - preClose)).toBeLessThanOrEqual(2)
75
+ })
76
+ })
77
+
78
+ // --- negative: things that should NOT happen ---
79
+
80
+ test.describe('scroll lock prevents body scroll while open', () => {
81
+ test('overflow:hidden applied to html while Select is open', async ({ page }) => {
82
+ await scrollAndOpenSelect(page, 300)
83
+
84
+ const htmlOverflow = await page.evaluate(
85
+ () => getComputedStyle(document.documentElement).overflow
86
+ )
87
+ expect(htmlOverflow).toBe('hidden')
88
+
89
+ // close and verify released
90
+ await page.keyboard.press('Escape')
91
+ await page.waitForTimeout(300)
92
+
93
+ const afterOverflow = await page.evaluate(
94
+ () => getComputedStyle(document.documentElement).overflow
95
+ )
96
+ expect(afterOverflow).not.toBe('hidden')
97
+ })
98
+ })
99
+
100
+ test.describe('edge cases', () => {
101
+ test('no scroll jump when scrollY is 0', async ({ page }) => {
102
+ // already at top
103
+ await scrollAndOpenSelect(page, 0)
104
+
105
+ const item = page.getByTestId('rs-select-banana')
106
+ await item.click()
107
+ await page.waitForTimeout(300)
108
+
109
+ const postClose = await page.evaluate(() => window.scrollY)
110
+ // should still be at 0, no negative jump or weird offset
111
+ expect(postClose).toBe(0)
112
+ })
113
+
114
+ test('scroll position stable across multiple open/close cycles', async ({ page }) => {
115
+ await page.evaluate(() => window.scrollTo(0, 400))
116
+ await page.waitForTimeout(100)
117
+
118
+ for (let i = 0; i < 3; i++) {
119
+ const trigger = page.getByTestId('rs-select-trigger')
120
+ await trigger.scrollIntoViewIfNeeded()
121
+ await trigger.click()
122
+
123
+ const viewport = page.getByTestId('rs-select-viewport')
124
+ await expect(viewport).toBeVisible({ timeout: 5000 })
125
+ await page.waitForTimeout(200)
126
+
127
+ await page.keyboard.press('Escape')
128
+ await page.waitForTimeout(300)
129
+ await expect(viewport).not.toBeVisible()
130
+ }
131
+
132
+ const finalScroll = await page.evaluate(() => window.scrollY)
133
+ expect(Math.abs(finalScroll - 400)).toBeLessThanOrEqual(2)
134
+ })
135
+
136
+ test('selecting different items preserves scroll each time', async ({ page }) => {
137
+ const items = ['rs-select-apple', 'rs-select-banana', 'rs-select-orange']
138
+
139
+ for (const itemId of items) {
140
+ // re-scroll to target before each iteration
141
+ await page.evaluate(() => window.scrollTo(0, 600))
142
+ await page.waitForTimeout(200)
143
+
144
+ const trigger = page.getByTestId('rs-select-trigger')
145
+ // click trigger directly without scrollIntoViewIfNeeded
146
+ await trigger.click()
147
+
148
+ const viewport = page.getByTestId('rs-select-viewport')
149
+ await expect(viewport).toBeVisible({ timeout: 5000 })
150
+ await page.waitForTimeout(200)
151
+
152
+ // capture scroll position while select is open (scroll is locked here)
153
+ const whileOpen = await page.evaluate(() => window.scrollY)
154
+
155
+ const item = page.getByTestId(itemId)
156
+ await item.click()
157
+ await page.waitForTimeout(500)
158
+ await expect(viewport).not.toBeVisible()
159
+
160
+ const afterClose = await page.evaluate(() => window.scrollY)
161
+ // scroll should be restored to where it was when the select opened
162
+ expect(Math.abs(afterClose - whileOpen)).toBeLessThanOrEqual(2)
163
+ }
164
+ })
165
+ })
166
+
167
+ // --- mobile/touch device tests (chrome mobile emulation) ---
168
+
169
+ test.describe('mobile touch device', () => {
170
+ test.use({
171
+ viewport: { width: 390, height: 844 },
172
+ hasTouch: true,
173
+ isMobile: true,
174
+ })
175
+
176
+ test('scroll position restored after selecting item on mobile', async ({ page }) => {
177
+ await page.evaluate(() => window.scrollTo(0, 400))
178
+ await page.waitForTimeout(100)
179
+
180
+ const trigger = page.getByTestId('rs-select-trigger')
181
+ await trigger.scrollIntoViewIfNeeded()
182
+ const preOpen = await page.evaluate(() => window.scrollY)
183
+ await trigger.click()
184
+
185
+ const viewport = page.getByTestId('rs-select-viewport')
186
+ await expect(viewport).toBeVisible({ timeout: 5000 })
187
+ await page.waitForTimeout(200)
188
+
189
+ const item = page.getByTestId('rs-select-apple')
190
+ await item.click()
191
+ await page.waitForTimeout(300)
192
+ await expect(viewport).not.toBeVisible()
193
+
194
+ const postClose = await page.evaluate(() => window.scrollY)
195
+ expect(Math.abs(postClose - preOpen)).toBeLessThanOrEqual(2)
196
+ })
197
+
198
+ test('scroll lock active on mobile while Select open', async ({ page }) => {
199
+ await page.evaluate(() => window.scrollTo(0, 300))
200
+ await page.waitForTimeout(100)
201
+
202
+ const trigger = page.getByTestId('rs-select-trigger')
203
+ await trigger.scrollIntoViewIfNeeded()
204
+ await trigger.click()
205
+
206
+ const viewport = page.getByTestId('rs-select-viewport')
207
+ await expect(viewport).toBeVisible({ timeout: 5000 })
208
+ await page.waitForTimeout(200)
209
+
210
+ const htmlOverflow = await page.evaluate(
211
+ () => getComputedStyle(document.documentElement).overflow
212
+ )
213
+ expect(htmlOverflow).toBe('hidden')
214
+
215
+ await page.keyboard.press('Escape')
216
+ await page.waitForTimeout(300)
217
+
218
+ const afterOverflow = await page.evaluate(
219
+ () => getComputedStyle(document.documentElement).overflow
220
+ )
221
+ expect(afterOverflow).not.toBe('hidden')
222
+ })
223
+ })