@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.
- package/.detoxrc.js +130 -0
- package/.env.production +2 -0
- package/.maestro/config.yaml +4 -0
- package/.maestro/flows/shorthand-variables.yaml +23 -0
- package/.watchmanconfig +1 -0
- package/LICENSE +21 -0
- package/README.md +11 -0
- package/app.json +43 -0
- package/assets/adaptive-icon.png +0 -0
- package/assets/favicon.png +0 -0
- package/assets/icon.png +0 -0
- package/assets/splash.png +0 -0
- package/babel.config.js +25 -0
- package/e2e/CompilerExtraction.test.ts +147 -0
- package/e2e/GroupPressNative.test.ts +167 -0
- package/e2e/MediaQueryGtMd.test.ts +71 -0
- package/e2e/NativePortal.test.ts +113 -0
- package/e2e/PointerEvents.test.ts +116 -0
- package/e2e/PressStyleNative.noRngh.test.ts +191 -0
- package/e2e/PressStyleNative.test.ts +231 -0
- package/e2e/SafeArea.test.ts +57 -0
- package/e2e/SelectAndroidOnPress.test.ts +181 -0
- package/e2e/SelectRemount.test.ts +137 -0
- package/e2e/SheetDragResist.test.ts +370 -0
- package/e2e/SheetKeyboardDrag.test.ts +249 -0
- package/e2e/SheetScrollableDrag.test.ts +560 -0
- package/e2e/ShorthandVariables.test.ts +53 -0
- package/e2e/ThemeChangeBasic.test.ts +123 -0
- package/e2e/ThemeMutation.test.ts +80 -0
- package/e2e/check-rngh-status.test.ts +31 -0
- package/e2e/jest.config.js +19 -0
- package/e2e/utils/colors.ts +75 -0
- package/e2e/utils/navigation.ts +53 -0
- package/eas.json +22 -0
- package/flows/AlertDialog.yaml +17 -0
- package/flows/OpenApp.yaml +25 -0
- package/flows/Select.yaml +13 -0
- package/flows/Sheet.yaml +12 -0
- package/flows/Tabs.yaml +13 -0
- package/flows/Toast.yaml +14 -0
- package/flows/WarmUp.yaml +24 -0
- package/index.html +21 -0
- package/index.js +17 -0
- package/metro.config.js +64 -0
- package/next-router-shim.ts +9 -0
- package/package.json +118 -0
- package/plans/toast-2.md +471 -0
- package/playwright.config.ts +71 -0
- package/plugins/expo-modules-core-swift6.js +76 -0
- package/pod-install.sh +7 -0
- package/public/favicon.svg +70 -0
- package/public/fonts/inter.css +15 -0
- package/public/fonts/noto-cn.otf +0 -0
- package/public/gui-icon.svg +68 -0
- package/run-detox.sh +230 -0
- package/run-native-tests.sh +4 -0
- package/run-tests-parallel.ts +195 -0
- package/screenshots/Screenshotter.test.tsx +48 -0
- package/src/AnimationDemos.tsx +131 -0
- package/src/App.native.tsx +121 -0
- package/src/App.tsx +121 -0
- package/src/Navigation.tsx +98 -0
- package/src/Sandbox.tsx +87 -0
- package/src/TestDynamicEval.tsx +33 -0
- package/src/TestNativeSheet.tsx +100 -0
- package/src/components/TimedRender.tsx +18 -0
- package/src/constants/test-ids.ts +52 -0
- package/src/features/demos/demo-screen.tsx +72 -0
- package/src/features/home/ColorSchemeListItem.tsx +41 -0
- package/src/features/home/TestBuildAButton.tsx +102 -0
- package/src/features/home/TestSeparator.tsx +0 -0
- package/src/features/home/screen.tsx +285 -0
- package/src/features/testcases/screen.tsx +59 -0
- package/src/features/testcases/test-screen.tsx +50 -0
- package/src/generatedV5Theme.ts +112 -0
- package/src/gui.config.ts +411 -0
- package/src/guy.png +0 -0
- package/src/index.tsx +6 -0
- package/src/provider/index.tsx +18 -0
- package/src/test-gui-stack.tsx +11 -0
- package/src/test.tsx +3 -0
- package/src/useKitchenSinkTheme.tsx +15 -0
- package/src/usecases/ActionsSheetComparison.tsx +194 -0
- package/src/usecases/AnimatePresenceEnterExitCase.tsx +255 -0
- package/src/usecases/AnimatePresenceExitTest.tsx +69 -0
- package/src/usecases/AnimatedByProp.tsx +39 -0
- package/src/usecases/AnimationComprehensiveCase.tsx +2515 -0
- package/src/usecases/AnimationValueLoggingCase.tsx +526 -0
- package/src/usecases/AnimationsWithMediaQueriesCase.tsx +110 -0
- package/src/usecases/Benchmark.tsx +148 -0
- package/src/usecases/BenchmarkSelect.tsx +34 -0
- package/src/usecases/ButtonCircular.tsx +3 -0
- package/src/usecases/ButtonCustom.tsx +33 -0
- package/src/usecases/ButtonIconColor.tsx +18 -0
- package/src/usecases/ButtonInverse.tsx +30 -0
- package/src/usecases/ButtonUnstyled.tsx +31 -0
- package/src/usecases/CheckboxDisabledOnPress.tsx +62 -0
- package/src/usecases/ClickDuringEnterCase.tsx +59 -0
- package/src/usecases/CodeExamplesInput.tsx +9 -0
- package/src/usecases/ColorTokenFallback.tsx +52 -0
- package/src/usecases/CompilerExtraction.tsx +380 -0
- package/src/usecases/ComplexVariants.tsx +164 -0
- package/src/usecases/CrashAdaptSheet.tsx +98 -0
- package/src/usecases/CustomStyledAnimatedPopover.tsx +42 -0
- package/src/usecases/CustomStyledAnimatedTooltip.tsx +72 -0
- package/src/usecases/DOMNodeAPIs.tsx +154 -0
- package/src/usecases/DialogFocusScopeCase.tsx +277 -0
- package/src/usecases/DialogFocusScopeDebug.tsx +85 -0
- package/src/usecases/DialogNestedCase.tsx +121 -0
- package/src/usecases/DialogOpenControlled.tsx +49 -0
- package/src/usecases/DialogPointerEventsCase.tsx +58 -0
- package/src/usecases/DialogScopedCase.tsx +106 -0
- package/src/usecases/DialogSheetAdaptCase.tsx +178 -0
- package/src/usecases/DialogSheetAdaptResizeCase.tsx +98 -0
- package/src/usecases/DismissLayerStackingCase.tsx +223 -0
- package/src/usecases/DriverDisableAnimationPropsCase.tsx +44 -0
- package/src/usecases/Example.tsx +10 -0
- package/src/usecases/ExitCompletionCase.tsx +713 -0
- package/src/usecases/FocusVisibleButton.tsx +14 -0
- package/src/usecases/FocusVisibleButtonPointer.tsx +13 -0
- package/src/usecases/FocusVisibleButtonWithFocusStyle.tsx +16 -0
- package/src/usecases/FocusWithinCase.tsx +55 -0
- package/src/usecases/FontTokensInVariants.tsx +14 -0
- package/src/usecases/FormButtonTypeCase.tsx +34 -0
- package/src/usecases/GlobalScopedTriggerIsolationCase.tsx +178 -0
- package/src/usecases/GroupHoverMobile.tsx +39 -0
- package/src/usecases/GroupPressInVariant.tsx +92 -0
- package/src/usecases/GroupPressNative.tsx +200 -0
- package/src/usecases/GroupProp.tsx +96 -0
- package/src/usecases/GroupPseudoVariantOverride.tsx +56 -0
- package/src/usecases/GroupUseCases.tsx +94 -0
- package/src/usecases/HeightMediaQueryOverrideCase.tsx +183 -0
- package/src/usecases/InputAutoFocusAfterMenuCase.tsx +105 -0
- package/src/usecases/InputAutoFocusStyledCase.tsx +39 -0
- package/src/usecases/KeyboardControllerTest.tsx +146 -0
- package/src/usecases/ListItem.tsx +123 -0
- package/src/usecases/MediaQueriesV5.tsx +137 -0
- package/src/usecases/MediaQueryGtMd.tsx +73 -0
- package/src/usecases/MenuAboveDialogCase.tsx +75 -0
- package/src/usecases/MenuAccessibilityCase.tsx +133 -0
- package/src/usecases/MenuAnimatePositionCase.tsx +41 -0
- package/src/usecases/MenuArrowAnimatePresenceCase.tsx +98 -0
- package/src/usecases/MenuAsChildPositionCase.tsx +24 -0
- package/src/usecases/MenuAutoResizeCase.tsx +57 -0
- package/src/usecases/MenuBottomCase.tsx +55 -0
- package/src/usecases/MenuFocusLeaveCase.tsx +135 -0
- package/src/usecases/MenuHighlightCase.tsx +44 -0
- package/src/usecases/MenuItemFocusCase.tsx +79 -0
- package/src/usecases/MenuItemPseudoOverrideCase.tsx +270 -0
- package/src/usecases/MenuMultiTriggerCase.tsx +47 -0
- package/src/usecases/MenuOverflowCase.tsx +60 -0
- package/src/usecases/MenuSubCase.tsx +223 -0
- package/src/usecases/MenuSubLeftCase.tsx +178 -0
- package/src/usecases/MenuSubNestedPositionCase.tsx +171 -0
- package/src/usecases/MenuSubStyledCase.tsx +145 -0
- package/src/usecases/MenuThemeCase.tsx +50 -0
- package/src/usecases/MenuUnstyledCase.tsx +52 -0
- package/src/usecases/MultiDriverAnimation.tsx +118 -0
- package/src/usecases/NativePortalTest.tsx +179 -0
- package/src/usecases/NewInputBasic.tsx +16 -0
- package/src/usecases/NewInputEvents.tsx +29 -0
- package/src/usecases/NonGuiTextStyledType.tsx +23 -0
- package/src/usecases/OnLayoutCase.tsx +134 -0
- package/src/usecases/OnLayoutScaleCase.tsx +88 -0
- package/src/usecases/OnLayoutStressCase.tsx +353 -0
- package/src/usecases/OpacityModifierCase.tsx +113 -0
- package/src/usecases/OverlayStyled.tsx +66 -0
- package/src/usecases/ParagraphSpanFontInheritance.tsx +53 -0
- package/src/usecases/PlaceholderTextColor.tsx +20 -0
- package/src/usecases/PointerEventsCase.tsx +100 -0
- package/src/usecases/PopoverAndMenuMultiTriggerCase.tsx +138 -0
- package/src/usecases/PopoverCase.tsx +222 -0
- package/src/usecases/PopoverContentStyledPlusAnimations.tsx +44 -0
- package/src/usecases/PopoverFocusScopeCase.tsx +171 -0
- package/src/usecases/PopoverHoverableCase.tsx +167 -0
- package/src/usecases/PopoverHoverableDisableClickCase.tsx +118 -0
- package/src/usecases/PopoverHoverableRapidCase.tsx +103 -0
- package/src/usecases/PopoverHoverableScopedCase.tsx +135 -0
- package/src/usecases/PopoverScopedCase.tsx +76 -0
- package/src/usecases/PopoverTriggerIsolationCase.tsx +80 -0
- package/src/usecases/PressStyleNative.tsx +143 -0
- package/src/usecases/PseudoStyleMerge.tsx +25 -0
- package/src/usecases/PseudoTransitionCase.tsx +174 -0
- package/src/usecases/RawAnimatedValueCase.tsx +231 -0
- package/src/usecases/RemoveScrollCase.tsx +66 -0
- package/src/usecases/RenderPropCase.tsx +263 -0
- package/src/usecases/SafeAreaCase.tsx +236 -0
- package/src/usecases/ScrollViewRefCase.tsx +88 -0
- package/src/usecases/SecondPage.tsx +5 -0
- package/src/usecases/SelectAndroidOnPress.tsx +129 -0
- package/src/usecases/SelectFocusScopeCase.tsx +270 -0
- package/src/usecases/SelectRemount.tsx +136 -0
- package/src/usecases/Shadows.tsx +5 -0
- package/src/usecases/SheetAnimationCase.tsx +155 -0
- package/src/usecases/SheetDragCase.tsx +183 -0
- package/src/usecases/SheetDragResistCase.tsx +433 -0
- package/src/usecases/SheetDragResistCase.web.tsx +359 -0
- package/src/usecases/SheetKeyboardDragCase.tsx +328 -0
- package/src/usecases/SheetKeyboardFitContentCase.tsx +165 -0
- package/src/usecases/SheetOnAnimationCompleteCase.tsx +54 -0
- package/src/usecases/SheetScrollLockCase.tsx +166 -0
- package/src/usecases/SheetScrollableDrag.tsx +249 -0
- package/src/usecases/SheetSnapPointsFitCase.tsx +393 -0
- package/src/usecases/ShorthandVariables.tsx +49 -0
- package/src/usecases/SlowThemeReRender.tsx +48 -0
- package/src/usecases/SpinnerCustomColors.tsx +34 -0
- package/src/usecases/StackZIndex.tsx +82 -0
- package/src/usecases/StressPage.tsx +301 -0
- package/src/usecases/StylePlatform.tsx +30 -0
- package/src/usecases/StyleProp.tsx +29 -0
- package/src/usecases/StyledAnchor.tsx +27 -0
- package/src/usecases/StyledButtonAnimationAuto.tsx +99 -0
- package/src/usecases/StyledButtonTheme.tsx +63 -0
- package/src/usecases/StyledButtonVariantPseudo.tsx +25 -0
- package/src/usecases/StyledButtonVariantPseudoMerge.tsx +77 -0
- package/src/usecases/StyledCheckboxTheme.tsx +23 -0
- package/src/usecases/StyledContextColor.tsx +246 -0
- package/src/usecases/StyledContextTokens.tsx +147 -0
- package/src/usecases/StyledHOCNamed.tsx +20 -0
- package/src/usecases/StyledHtmlCase.tsx +144 -0
- package/src/usecases/StyledIconColor.tsx +19 -0
- package/src/usecases/StyledInputFocusStyle.tsx +21 -0
- package/src/usecases/StyledInputOnFocus.tsx +30 -0
- package/src/usecases/StyledMediaQueryMerge.tsx +95 -0
- package/src/usecases/StyledOverridePsuedo.tsx +26 -0
- package/src/usecases/StyledRNW.tsx +61 -0
- package/src/usecases/StyledStyleableInputOnFocus.tsx +34 -0
- package/src/usecases/StyledStyleableInputVariant.tsx +48 -0
- package/src/usecases/StyledStyledStyleableInputOnFocus.tsx +36 -0
- package/src/usecases/StyledVariantTextColor.tsx +25 -0
- package/src/usecases/StyledViewOnFocus.tsx +32 -0
- package/src/usecases/TabHoverAnimationCase.tsx +212 -0
- package/src/usecases/TextNestedInheritance.tsx +80 -0
- package/src/usecases/ThemeChange.tsx +100 -0
- package/src/usecases/ThemeChangeBasic.tsx +52 -0
- package/src/usecases/ThemeComponentResolution.tsx +119 -0
- package/src/usecases/ThemeConditionalName.tsx +31 -0
- package/src/usecases/ThemeMediaAnimationCase.tsx +39 -0
- package/src/usecases/ThemeMutation.tsx +86 -0
- package/src/usecases/ThemeNested.tsx +103 -0
- package/src/usecases/ThemeReset.tsx +62 -0
- package/src/usecases/ThemeShallowCase.tsx +83 -0
- package/src/usecases/ToastCase.tsx +46 -0
- package/src/usecases/ToggleGroupActiveProps.tsx +40 -0
- package/src/usecases/ToggleGroupXGroupCase.tsx +104 -0
- package/src/usecases/TooltipAnimationCase.tsx +99 -0
- package/src/usecases/TooltipCase.tsx +32 -0
- package/src/usecases/TooltipGlobalPatternCase.tsx +83 -0
- package/src/usecases/TooltipGroupCase.tsx +102 -0
- package/src/usecases/TooltipMultiTriggerCase.tsx +88 -0
- package/src/usecases/TooltipPositionJumpCase.tsx +91 -0
- package/src/usecases/TooltipTriggerInlineCase.tsx +60 -0
- package/src/usecases/TransformMediaQueryMerge.tsx +98 -0
- package/src/usecases/UseCases.tsx +409 -0
- package/src/usecases/UseTheme.tsx +41 -0
- package/src/usecases/V5ThemeBuilderOutput.tsx +231 -0
- package/src/usecases/VariantFontFamily.tsx +25 -0
- package/src/usecases/VariantsOrder.tsx +117 -0
- package/src/usecases/ZIndex.tsx +155 -0
- package/src/usecases/helpers.tsx +44 -0
- package/src/usecases/index.native.ts +122 -0
- package/src/usecases/index.ts +3 -0
- package/src/usecases/index.web.ts +177 -0
- package/tests/AnimatePresenceEnterExit.animated.test.tsx +176 -0
- package/tests/AnimatedByProp.animated.test.tsx +138 -0
- package/tests/AnimationBehavior.animated.test.tsx +543 -0
- package/tests/AnimationTiming.animated.test.tsx +195 -0
- package/tests/AnimationsWithMediaQueries.animated.test.tsx +154 -0
- package/tests/BuildAButton.test.tsx +87 -0
- package/tests/ButtonCircular.test.tsx +17 -0
- package/tests/ButtonCustom.test.tsx +17 -0
- package/tests/ButtonIconColor.test.tsx +23 -0
- package/tests/ButtonUnstyled.test.tsx +56 -0
- package/tests/ClickDuringEnter.animated.test.tsx +174 -0
- package/tests/ColorTokenFallback.test.tsx +45 -0
- package/tests/DOMNodeAPIs.test.tsx +161 -0
- package/tests/DialogFocusScope.animated.test.tsx +309 -0
- package/tests/DialogNested.test.tsx +128 -0
- package/tests/DialogOpenControlled.test.tsx +42 -0
- package/tests/DialogPointerEvents.animated.test.tsx +108 -0
- package/tests/DialogScoped.test.tsx +137 -0
- package/tests/DialogSheetAdapt.test.tsx +68 -0
- package/tests/DialogSheetAdaptResize.test.tsx +161 -0
- package/tests/DismissLayerStacking.test.tsx +292 -0
- package/tests/DriverDisableAnimationProps.animated.test.tsx +157 -0
- package/tests/ExitCompletion.animated.test.tsx +425 -0
- package/tests/ExitTimingCheck.animated.test.ts +34 -0
- package/tests/FocusVisibleButton.test.tsx +41 -0
- package/tests/FocusVisibleButtonPointerFocus.test.tsx +23 -0
- package/tests/FocusVisibleButtonPointerFocusWithFocusStyle.test.tsx +40 -0
- package/tests/FocusWithinStyle.animated.test.tsx +66 -0
- package/tests/FocusWithinStyle.test.tsx +60 -0
- package/tests/FormButtonType.test.tsx +42 -0
- package/tests/GlobalScopedTriggerIsolation.test.tsx +89 -0
- package/tests/GroupHoverMobile.test.tsx +52 -0
- package/tests/GroupPressInVariant.test.tsx +82 -0
- package/tests/GroupProp.test.tsx +30 -0
- package/tests/GroupPseudoVariantOverride.test.tsx +57 -0
- package/tests/GroupUseCases.test.tsx +111 -0
- package/tests/GuiSiteMotion.test.ts +481 -0
- package/tests/HeightMediaQueryOverride.test.tsx +112 -0
- package/tests/InputAutoFocusAfterMenu.test.tsx +55 -0
- package/tests/InputAutoFocusStyled.test.tsx +22 -0
- package/tests/ListItem.test.tsx +129 -0
- package/tests/MediaQueriesV5.test.tsx +113 -0
- package/tests/MediaQueryGtMd.test.tsx +84 -0
- package/tests/MenuAboveDialog.test.tsx +108 -0
- package/tests/MenuAccessibility.test.tsx +346 -0
- package/tests/MenuAnimatePosition.animated.test.tsx +57 -0
- package/tests/MenuArrowAnimatePresence.animated.test.tsx +71 -0
- package/tests/MenuAsChildPosition.test.tsx +16 -0
- package/tests/MenuAutoResize.test.tsx +54 -0
- package/tests/MenuFocusLeave.test.tsx +181 -0
- package/tests/MenuHighlight.test.tsx +165 -0
- package/tests/MenuHoverKeyboardBugs.test.tsx +252 -0
- package/tests/MenuItemFocus.test.tsx +59 -0
- package/tests/MenuItemPseudoOverride.test.tsx +231 -0
- package/tests/MenuMultiTrigger.test.tsx +101 -0
- package/tests/MenuOverflow.test.tsx +93 -0
- package/tests/MenuStayInFrame.test.tsx +102 -0
- package/tests/MenuSubKeyboardFocus.test.tsx +220 -0
- package/tests/MenuSubLeftSafePolygon.test.tsx +88 -0
- package/tests/MenuSubNestedPosition.test.tsx +48 -0
- package/tests/MenuSubSafePolygon.test.tsx +97 -0
- package/tests/MenuSubStyled.test.tsx +40 -0
- package/tests/MenuTheme.test.tsx +34 -0
- package/tests/MenuUnstyled.test.tsx +56 -0
- package/tests/MultiDriverAnimation.test.tsx +207 -0
- package/tests/NewInputBasic.test.tsx +50 -0
- package/tests/NewInputEvents.test.tsx +55 -0
- package/tests/OnLayout.test.tsx +163 -0
- package/tests/OnLayoutScale.test.tsx +100 -0
- package/tests/OnLayoutStress.test.tsx +304 -0
- package/tests/ParagraphSpanFontInheritance.test.tsx +73 -0
- package/tests/PointerEvents.test.tsx +123 -0
- package/tests/Popover.animated.test.tsx +234 -0
- package/tests/PopoverAndMenuMultiTrigger.test.tsx +184 -0
- package/tests/PopoverAnimatePosition.animated.test.tsx +51 -0
- package/tests/PopoverClickDuringEnter.animated.test.tsx +197 -0
- package/tests/PopoverFocusScope.test.tsx +242 -0
- package/tests/PopoverHoverable.test.tsx +383 -0
- package/tests/PopoverHoverableDisableClick.test.tsx +106 -0
- package/tests/PopoverHoverableRapid.test.tsx +129 -0
- package/tests/PopoverHoverableReposition.test.tsx +111 -0
- package/tests/PopoverHoverableScoped.animated.test.tsx +103 -0
- package/tests/PopoverHoverableStress.test.tsx +169 -0
- package/tests/PopoverInitialPosition.animated.test.tsx +82 -0
- package/tests/PopoverMiddlewareSkipRegression.animated.test.tsx +221 -0
- package/tests/PopoverScoped.test.tsx +128 -0
- package/tests/PopoverScopedPositionGlitch.animated.test.tsx +184 -0
- package/tests/PopoverTriggerIsolation.test.tsx +62 -0
- package/tests/PseudoTransition.animated.test.tsx +319 -0
- package/tests/RawAnimatedValue.test.tsx +147 -0
- package/tests/RemoveScroll.test.tsx +223 -0
- package/tests/RenderProp.test.tsx +293 -0
- package/tests/ScrollViewRef.test.tsx +39 -0
- package/tests/SelectClickHold.test.tsx +147 -0
- package/tests/SelectFocusScope.test.tsx +176 -0
- package/tests/SelectInnerPositioning.test.tsx +82 -0
- package/tests/SelectKeyboardNav.test.tsx +173 -0
- package/tests/SelectPositioning.test.tsx +56 -0
- package/tests/SelectTypeahead.test.tsx +63 -0
- package/tests/Shadows.test.tsx +14 -0
- package/tests/SheetAnimation.animated.test.tsx +413 -0
- package/tests/SheetDrag.animated.test.tsx +223 -0
- package/tests/SheetDragResist.animated.test.tsx +393 -0
- package/tests/SheetOnAnimationComplete.animated.test.tsx +62 -0
- package/tests/SheetScrollLock.animated.test.tsx +287 -0
- package/tests/SheetScrollableDrag.animated.test.tsx +1264 -0
- package/tests/SheetSnapPointsFit.animated.test.tsx +259 -0
- package/tests/ShorthandVariables.test.tsx +44 -0
- package/tests/SpinnerCustomColors.test.tsx +67 -0
- package/tests/StackZIndex.test.tsx +51 -0
- package/tests/StressPagePerf.test.tsx +76 -0
- package/tests/StylePlatform.test.tsx +38 -0
- package/tests/StyleProp.test.tsx +20 -0
- package/tests/StyledAnchor.test.tsx +17 -0
- package/tests/StyledButtonTheme.test.tsx +22 -0
- package/tests/StyledButtonVariantPseudo.test.tsx +20 -0
- package/tests/StyledButtonVariantPseudoMerge.animated.test.tsx +33 -0
- package/tests/StyledCheckboxTheme.test.tsx +16 -0
- package/tests/StyledContextColor.test.tsx +119 -0
- package/tests/StyledContextTokens.test.tsx +56 -0
- package/tests/StyledHOCNamed.test.tsx +16 -0
- package/tests/StyledHtml.test.tsx +161 -0
- package/tests/StyledIconColor.test.tsx +32 -0
- package/tests/StyledInputFocusStyle.test.tsx +19 -0
- package/tests/StyledInputOnFocus.test.tsx +27 -0
- package/tests/StyledMediaQueryMerge.test.tsx +66 -0
- package/tests/StyledRNW.test.tsx +17 -0
- package/tests/StyledStyleableInputOnFocus.test.tsx +27 -0
- package/tests/StyledStyleableInputVariant.test.tsx +22 -0
- package/tests/StyledStyledStyleableInputOnFocus.test.tsx +27 -0
- package/tests/StyledVariantTextColor.test.tsx +24 -0
- package/tests/StyledViewOnFocus.test.tsx +27 -0
- package/tests/TabHoverAnimation.animated.test.tsx +468 -0
- package/tests/TabHoverPositionSmooth.animated.test.tsx +129 -0
- package/tests/TextNestedInheritance.test.tsx +93 -0
- package/tests/ThemeChange.test.tsx +70 -0
- package/tests/ThemeComponentResolution.test.tsx +82 -0
- package/tests/ThemeConditionalName.test.tsx +34 -0
- package/tests/ThemeMediaAnimation.test.tsx +65 -0
- package/tests/ThemeNested.test.tsx +141 -0
- package/tests/ThemeReset.test.tsx +63 -0
- package/tests/ThemeShallow.test.tsx +95 -0
- package/tests/Toast.test.tsx +106 -0
- package/tests/ToggleGroup.test.tsx +61 -0
- package/tests/ToggleGroupActiveProps.test.tsx +38 -0
- package/tests/ToggleGroupXGroup.test.tsx +172 -0
- package/tests/TooltipAnimation.animated.test.tsx +260 -0
- package/tests/TooltipEnterInterrupt.animated.test.tsx +76 -0
- package/tests/TooltipGlobalPattern.animated.test.tsx +208 -0
- package/tests/TooltipGroup.animated.test.tsx +79 -0
- package/tests/TooltipMultiTrigger.test.tsx +116 -0
- package/tests/TooltipPositionJump.animated.test.tsx +229 -0
- package/tests/TooltipPositionJumpNotes.md +219 -0
- package/tests/TooltipRapidSwitch.animated.test.tsx +399 -0
- package/tests/TooltipTriggerInline.test.tsx +65 -0
- package/tests/TransformMediaQueryMerge.test.tsx +104 -0
- package/tests/TransitionEnterExit.animated.test.tsx +311 -0
- package/tests/UseTheme.test.tsx +16 -0
- package/tests/V5ThemeBuilderOutput.test.tsx +164 -0
- package/tests/VariantFontFamily.test.tsx +11 -0
- package/tests/VariantsOrder.test.tsx +53 -0
- package/tests/_debug_position.mjs +52 -0
- package/tests/test-utils.ts +106 -0
- package/tests/utils.tsx +54 -0
- package/tsconfig.json +45 -0
- package/vite-env.d.ts +1 -0
- package/vite.config.ts +14 -0
- package/webpack.config.js +139 -0
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/test'
|
|
2
|
+
import { setupPage } from './test-utils'
|
|
3
|
+
|
|
4
|
+
test.use({
|
|
5
|
+
viewport: { width: 600, height: 400 },
|
|
6
|
+
launchOptions: {
|
|
7
|
+
args: ['--window-position=1400,700', '--window-size=620,420'],
|
|
8
|
+
},
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
test.beforeEach(async ({ page }, testInfo) => {
|
|
12
|
+
// native driver has fundamental issues with hover/animation on web
|
|
13
|
+
test.skip(
|
|
14
|
+
testInfo.project.name === 'animated-native',
|
|
15
|
+
'Native driver does not support hover animations on web'
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
await setupPage(page, { name: 'TabHoverAnimationCase', type: 'useCase' })
|
|
19
|
+
await page.waitForTimeout(500)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
const TAB_IDS = ['tab-tab-a', 'tab-tab-b', 'tab-tab-c', 'tab-tab-d', 'tab-tab-e']
|
|
23
|
+
|
|
24
|
+
// hover a tab by moving the mouse to its center
|
|
25
|
+
async function hoverTab(page: any, tabId: string) {
|
|
26
|
+
const tab = page.locator(`[data-testid="${tabId}"]`)
|
|
27
|
+
await tab.hover()
|
|
28
|
+
await page.waitForTimeout(50)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// rapidly sweep across tabs left-to-right or right-to-left
|
|
32
|
+
async function sweepTabs(page: any, ids: string[], delayMs = 30) {
|
|
33
|
+
for (const id of ids) {
|
|
34
|
+
await page.locator(`[data-testid="${id}"]`).hover()
|
|
35
|
+
await page.waitForTimeout(delayMs)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function getGoingDirection(page: any): Promise<number> {
|
|
40
|
+
return page.locator('#going-direction').evaluate((el: HTMLElement) => {
|
|
41
|
+
return Number(el.dataset.going || '0')
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function getSlideContentInfo(page: any) {
|
|
46
|
+
return page.evaluate(() => {
|
|
47
|
+
const els = document.querySelectorAll('[data-testid="slide-content"]')
|
|
48
|
+
return Array.from(els).map((el) => ({
|
|
49
|
+
tab: (el as HTMLElement).dataset.tab,
|
|
50
|
+
going: (el as HTMLElement).dataset.going,
|
|
51
|
+
transform: getComputedStyle(el).transform,
|
|
52
|
+
opacity: getComputedStyle(el).opacity,
|
|
53
|
+
}))
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// track translateX over several rAF frames
|
|
58
|
+
async function trackTranslateX(page: any, selector: string, frames = 6) {
|
|
59
|
+
return page.evaluate(
|
|
60
|
+
({ selector, frames }) => {
|
|
61
|
+
return new Promise<number[]>((resolve) => {
|
|
62
|
+
const el = document.querySelector(selector)
|
|
63
|
+
if (!el) return resolve([])
|
|
64
|
+
const values: number[] = []
|
|
65
|
+
let count = 0
|
|
66
|
+
function tick() {
|
|
67
|
+
const style = getComputedStyle(el!)
|
|
68
|
+
const matrix = new DOMMatrix(style.transform)
|
|
69
|
+
values.push(matrix.m41)
|
|
70
|
+
count++
|
|
71
|
+
if (count < frames) {
|
|
72
|
+
requestAnimationFrame(tick)
|
|
73
|
+
} else {
|
|
74
|
+
resolve(values)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
requestAnimationFrame(tick)
|
|
78
|
+
})
|
|
79
|
+
},
|
|
80
|
+
{ selector, frames }
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// === Bug 1: AnimatePresence direction ===
|
|
85
|
+
|
|
86
|
+
test('direction: hover right sets going=1', async ({ page }) => {
|
|
87
|
+
await hoverTab(page, 'tab-tab-a')
|
|
88
|
+
await page.waitForTimeout(200)
|
|
89
|
+
await hoverTab(page, 'tab-tab-c')
|
|
90
|
+
await page.waitForTimeout(200)
|
|
91
|
+
const going = await getGoingDirection(page)
|
|
92
|
+
expect(going).toBe(1)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
test('direction: hover left sets going=-1', async ({ page }) => {
|
|
96
|
+
await hoverTab(page, 'tab-tab-d')
|
|
97
|
+
await page.waitForTimeout(200)
|
|
98
|
+
await hoverTab(page, 'tab-tab-b')
|
|
99
|
+
await page.waitForTimeout(200)
|
|
100
|
+
const going = await getGoingDirection(page)
|
|
101
|
+
expect(going).toBe(-1)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
test('direction: rapid sweep right then left preserves correct direction', async ({
|
|
105
|
+
page,
|
|
106
|
+
}) => {
|
|
107
|
+
// sweep all the way right
|
|
108
|
+
await sweepTabs(page, TAB_IDS, 30)
|
|
109
|
+
await page.waitForTimeout(100)
|
|
110
|
+
const goingAfterRight = await getGoingDirection(page)
|
|
111
|
+
expect(goingAfterRight).toBe(1)
|
|
112
|
+
|
|
113
|
+
// sweep all the way left
|
|
114
|
+
await sweepTabs(page, [...TAB_IDS].reverse(), 30)
|
|
115
|
+
await page.waitForTimeout(100)
|
|
116
|
+
const goingAfterLeft = await getGoingDirection(page)
|
|
117
|
+
expect(goingAfterLeft).toBe(-1)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
test('direction: exiting content slides opposite to entering content', async ({
|
|
121
|
+
page,
|
|
122
|
+
}) => {
|
|
123
|
+
// hover tab A and wait for it to be visible
|
|
124
|
+
await hoverTab(page, 'tab-tab-a')
|
|
125
|
+
await page.waitForTimeout(300)
|
|
126
|
+
|
|
127
|
+
// now hover tab D (to the right) - tab A should exit LEFT, tab D enters from RIGHT
|
|
128
|
+
await hoverTab(page, 'tab-tab-d')
|
|
129
|
+
|
|
130
|
+
// wait for entering element to appear (may take longer in slow CI)
|
|
131
|
+
await page.waitForTimeout(150)
|
|
132
|
+
const infos = await getSlideContentInfo(page)
|
|
133
|
+
|
|
134
|
+
// the entering one should have data-tab="Tab D"
|
|
135
|
+
const entering = infos.find((i: any) => i.tab === 'Tab D')
|
|
136
|
+
expect(entering).toBeTruthy()
|
|
137
|
+
|
|
138
|
+
// going direction should be 1 (rightward)
|
|
139
|
+
const going = await getGoingDirection(page)
|
|
140
|
+
expect(going).toBe(1)
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
test('direction: exiting element keeps original direction when going reverses', async ({
|
|
144
|
+
page,
|
|
145
|
+
}) => {
|
|
146
|
+
// hover Tab A → wait for it to appear
|
|
147
|
+
await hoverTab(page, 'tab-tab-a')
|
|
148
|
+
await page.waitForTimeout(300)
|
|
149
|
+
|
|
150
|
+
// collect console logs
|
|
151
|
+
const consoleLogs: string[] = []
|
|
152
|
+
page.on('console', (msg: any) => {
|
|
153
|
+
if (msg.text().includes('[exit-debug]')) {
|
|
154
|
+
consoleLogs.push(msg.text())
|
|
155
|
+
}
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
// hover Tab E → Tab A starts exiting LEFT (going=1, exitStyle x=-100)
|
|
159
|
+
await hoverTab(page, 'tab-tab-e')
|
|
160
|
+
await page.waitForTimeout(80) // let exit animation start
|
|
161
|
+
|
|
162
|
+
// hover Tab B → going changes to -1
|
|
163
|
+
// BUG: this should NOT change Tab A's exit direction
|
|
164
|
+
await hoverTab(page, 'tab-tab-b')
|
|
165
|
+
await page.waitForTimeout(30)
|
|
166
|
+
|
|
167
|
+
// print debug logs
|
|
168
|
+
for (const log of consoleLogs) {
|
|
169
|
+
console.log(log)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// sample Tab A's current translateX - it should be NEGATIVE (exiting left)
|
|
173
|
+
const tabAX = await page.evaluate(() => {
|
|
174
|
+
const els = Array.from(document.querySelectorAll('[data-testid="slide-content"]'))
|
|
175
|
+
for (let i = 0; i < els.length; i++) {
|
|
176
|
+
const el = els[i] as HTMLElement
|
|
177
|
+
if (el.dataset.tab === 'Tab A') {
|
|
178
|
+
const matrix = new DOMMatrix(getComputedStyle(el).transform)
|
|
179
|
+
return matrix.m41
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return null
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
// Tab A should be exiting LEFT (negative x), not RIGHT (positive x)
|
|
186
|
+
// null means it already exited (which is fine)
|
|
187
|
+
if (tabAX !== null) {
|
|
188
|
+
expect(tabAX, `Tab A x=${tabAX}, should be negative (exiting left)`).toBeLessThan(0)
|
|
189
|
+
}
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
// === Bug 2: CSS driver x animation ===
|
|
193
|
+
|
|
194
|
+
test('x animation fires during tab switch', async ({ page }) => {
|
|
195
|
+
await hoverTab(page, 'tab-tab-a')
|
|
196
|
+
await page.waitForTimeout(300)
|
|
197
|
+
|
|
198
|
+
// hover tab D - should trigger x animation
|
|
199
|
+
await hoverTab(page, 'tab-tab-d')
|
|
200
|
+
|
|
201
|
+
const values = await trackTranslateX(page, '[data-testid="slide-content"]', 6)
|
|
202
|
+
|
|
203
|
+
// at least one frame should show non-zero translateX during enter animation
|
|
204
|
+
const hasMovement = values.some((v) => Math.abs(v) > 1)
|
|
205
|
+
expect(hasMovement).toBe(true)
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
// === Bug 3: Exit completion / ghost content ===
|
|
209
|
+
|
|
210
|
+
test('exit completes: no ghost content after mouse leaves', async ({ page }) => {
|
|
211
|
+
await hoverTab(page, 'tab-tab-b')
|
|
212
|
+
await page.waitForTimeout(300)
|
|
213
|
+
|
|
214
|
+
// move mouse away
|
|
215
|
+
await page.mouse.move(0, 0)
|
|
216
|
+
await page.waitForTimeout(600)
|
|
217
|
+
|
|
218
|
+
const slideContent = page.locator('[data-testid="slide-content"]')
|
|
219
|
+
await expect(slideContent).toHaveCount(0, { timeout: 2000 })
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
test('exit completes: rapid sweep then leave has no ghosts', async ({
|
|
223
|
+
page,
|
|
224
|
+
}, testInfo) => {
|
|
225
|
+
// reanimated driver doesn't complete exit after rapid sweep (known limitation)
|
|
226
|
+
test.skip(
|
|
227
|
+
testInfo.project.name === 'animated-reanimated',
|
|
228
|
+
'reanimated exit incomplete after rapid sweep'
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
// rapidly sweep across all tabs
|
|
232
|
+
await sweepTabs(page, TAB_IDS, 30)
|
|
233
|
+
await page.waitForTimeout(100)
|
|
234
|
+
|
|
235
|
+
// move mouse away
|
|
236
|
+
await page.mouse.move(0, 0)
|
|
237
|
+
|
|
238
|
+
const slideContent = page.locator('[data-testid="slide-content"]')
|
|
239
|
+
await expect(slideContent).toHaveCount(0, { timeout: 3000 })
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
test('rapid switching: no stuck ghost elements', async ({ page }) => {
|
|
243
|
+
// rapidly sweep right
|
|
244
|
+
await sweepTabs(page, TAB_IDS, 30)
|
|
245
|
+
// settle on last tab
|
|
246
|
+
await page.waitForTimeout(400)
|
|
247
|
+
|
|
248
|
+
// should only have ONE slide-content element
|
|
249
|
+
const slideContents = page.locator('[data-testid="slide-content"]')
|
|
250
|
+
const count = await slideContents.count()
|
|
251
|
+
expect(count).toBeLessThanOrEqual(1)
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
test('rapid back-and-forth: no stuck elements', async ({ page }) => {
|
|
255
|
+
// quickly bounce between tabs
|
|
256
|
+
for (let i = 0; i < 3; i++) {
|
|
257
|
+
await sweepTabs(page, TAB_IDS, 20)
|
|
258
|
+
await sweepTabs(page, [...TAB_IDS].reverse(), 20)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// settle on tab C
|
|
262
|
+
await hoverTab(page, 'tab-tab-c')
|
|
263
|
+
await page.waitForTimeout(500)
|
|
264
|
+
|
|
265
|
+
const slideContents = page.locator('[data-testid="slide-content"]')
|
|
266
|
+
const count = await slideContents.count()
|
|
267
|
+
expect(count).toBeLessThanOrEqual(1)
|
|
268
|
+
|
|
269
|
+
// and it should show Tab C content
|
|
270
|
+
if (count === 1) {
|
|
271
|
+
const tab = await slideContents.getAttribute('data-tab')
|
|
272
|
+
expect(tab).toBe('Tab C')
|
|
273
|
+
}
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
test('exit animation runs to completion on tab switch', async ({ page }) => {
|
|
277
|
+
await hoverTab(page, 'tab-tab-c')
|
|
278
|
+
await page.waitForTimeout(300)
|
|
279
|
+
|
|
280
|
+
await hoverTab(page, 'tab-tab-a')
|
|
281
|
+
await page.waitForTimeout(500)
|
|
282
|
+
|
|
283
|
+
const slideContent = page.locator('[data-testid="slide-content"]')
|
|
284
|
+
const tab = await slideContent.getAttribute('data-tab')
|
|
285
|
+
expect(tab).toBe('Tab A')
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
// === Bug 4: Popover position tracks active tab ===
|
|
289
|
+
|
|
290
|
+
test('popover position moves when hovering different tabs', async ({ page }) => {
|
|
291
|
+
// hover tab A, record popover position
|
|
292
|
+
await hoverTab(page, 'tab-tab-a')
|
|
293
|
+
await page.waitForTimeout(400)
|
|
294
|
+
|
|
295
|
+
const content = page.locator('[data-testid="hover-content"]')
|
|
296
|
+
const box1 = await content.boundingBox()
|
|
297
|
+
|
|
298
|
+
// hover tab E (far right)
|
|
299
|
+
await hoverTab(page, 'tab-tab-e')
|
|
300
|
+
await page.waitForTimeout(400)
|
|
301
|
+
|
|
302
|
+
const box2 = await content.boundingBox()
|
|
303
|
+
|
|
304
|
+
// popover should have moved right
|
|
305
|
+
if (box1 && box2) {
|
|
306
|
+
expect(box2.x).toBeGreaterThan(box1.x)
|
|
307
|
+
} else {
|
|
308
|
+
// if popover didn't render, fail
|
|
309
|
+
expect(box1).toBeTruthy()
|
|
310
|
+
expect(box2).toBeTruthy()
|
|
311
|
+
}
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
test('popover position follows rapid sweep', async ({ page }) => {
|
|
315
|
+
// hover tab A first
|
|
316
|
+
await hoverTab(page, 'tab-tab-a')
|
|
317
|
+
await page.waitForTimeout(300)
|
|
318
|
+
|
|
319
|
+
const content = page.locator('[data-testid="hover-content"]')
|
|
320
|
+
const boxStart = await content.boundingBox()
|
|
321
|
+
|
|
322
|
+
// rapidly sweep to tab E
|
|
323
|
+
await sweepTabs(page, TAB_IDS, 40)
|
|
324
|
+
await page.waitForTimeout(400)
|
|
325
|
+
|
|
326
|
+
const boxEnd = await content.boundingBox()
|
|
327
|
+
|
|
328
|
+
if (boxStart && boxEnd) {
|
|
329
|
+
// should have moved substantially to the right
|
|
330
|
+
expect(boxEnd.x).toBeGreaterThan(boxStart.x)
|
|
331
|
+
}
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
// === Bug 5: safePolygon race condition on trigger switch ===
|
|
335
|
+
|
|
336
|
+
test('trigger switch: popover stays open when rapidly switching triggers', async ({
|
|
337
|
+
page,
|
|
338
|
+
}) => {
|
|
339
|
+
// open on tab A
|
|
340
|
+
await hoverTab(page, 'tab-tab-a')
|
|
341
|
+
await page.waitForTimeout(300)
|
|
342
|
+
|
|
343
|
+
const content = page.locator('[data-testid="hover-content"]')
|
|
344
|
+
await expect(content).toBeVisible()
|
|
345
|
+
|
|
346
|
+
// rapidly switch between triggers - popover must stay open
|
|
347
|
+
await hoverTab(page, 'tab-tab-c')
|
|
348
|
+
await page.waitForTimeout(50)
|
|
349
|
+
await hoverTab(page, 'tab-tab-e')
|
|
350
|
+
await page.waitForTimeout(50)
|
|
351
|
+
await hoverTab(page, 'tab-tab-b')
|
|
352
|
+
await page.waitForTimeout(50)
|
|
353
|
+
await hoverTab(page, 'tab-tab-d')
|
|
354
|
+
await page.waitForTimeout(200)
|
|
355
|
+
|
|
356
|
+
await expect(content).toBeVisible()
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
test('trigger switch: back-and-forth between two triggers stays open', async ({
|
|
360
|
+
page,
|
|
361
|
+
}) => {
|
|
362
|
+
await hoverTab(page, 'tab-tab-b')
|
|
363
|
+
await page.waitForTimeout(300)
|
|
364
|
+
|
|
365
|
+
const content = page.locator('[data-testid="hover-content"]')
|
|
366
|
+
await expect(content).toBeVisible()
|
|
367
|
+
|
|
368
|
+
// bounce between two triggers rapidly
|
|
369
|
+
for (let i = 0; i < 5; i++) {
|
|
370
|
+
await hoverTab(page, 'tab-tab-d')
|
|
371
|
+
await page.waitForTimeout(30)
|
|
372
|
+
await hoverTab(page, 'tab-tab-b')
|
|
373
|
+
await page.waitForTimeout(30)
|
|
374
|
+
}
|
|
375
|
+
await page.waitForTimeout(200)
|
|
376
|
+
|
|
377
|
+
await expect(content).toBeVisible()
|
|
378
|
+
})
|
|
379
|
+
|
|
380
|
+
// simulate realistic mouse movement from one tab to another with real time gaps
|
|
381
|
+
async function realisticMouseMove(
|
|
382
|
+
page: any,
|
|
383
|
+
fromId: string,
|
|
384
|
+
toId: string,
|
|
385
|
+
durationMs = 80
|
|
386
|
+
) {
|
|
387
|
+
const from = await page.locator(`[data-testid="${fromId}"]`).boundingBox()
|
|
388
|
+
const to = await page.locator(`[data-testid="${toId}"]`).boundingBox()
|
|
389
|
+
if (!from || !to) return
|
|
390
|
+
const x1 = from.x + from.width / 2,
|
|
391
|
+
y1 = from.y + from.height / 2
|
|
392
|
+
const x2 = to.x + to.width / 2,
|
|
393
|
+
y2 = to.y + to.height / 2
|
|
394
|
+
const steps = Math.max(8, Math.round(durationMs / 8))
|
|
395
|
+
const delay = Math.round(durationMs / steps)
|
|
396
|
+
for (let i = 1; i <= steps; i++) {
|
|
397
|
+
const t = i / steps
|
|
398
|
+
await page.mouse.move(x1 + (x2 - x1) * t, y1 + (y2 - y1) * t)
|
|
399
|
+
if (delay > 0) await page.waitForTimeout(delay)
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
test('trigger switch race: no close events during realistic switching', async ({
|
|
404
|
+
page,
|
|
405
|
+
}, testInfo) => {
|
|
406
|
+
// reanimated driver has different animation timing that causes brief close/reopen cycles
|
|
407
|
+
// during multi-trigger hover switching - this is a known limitation on web
|
|
408
|
+
test.skip(
|
|
409
|
+
testInfo.project.name === 'animated-reanimated',
|
|
410
|
+
'reanimated hover timing causes close events during trigger switch'
|
|
411
|
+
)
|
|
412
|
+
await page.evaluate(() => {
|
|
413
|
+
;(window as any).__popoverCloseCount = 0
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
await hoverTab(page, 'tab-tab-a')
|
|
417
|
+
await page.waitForTimeout(300)
|
|
418
|
+
const content = page.locator('[data-testid="hover-content"]')
|
|
419
|
+
await expect(content).toBeVisible({ timeout: 2000 })
|
|
420
|
+
await page.evaluate(() => {
|
|
421
|
+
;(window as any).__popoverCloseCount = 0
|
|
422
|
+
})
|
|
423
|
+
|
|
424
|
+
// realistic mouse movement between triggers
|
|
425
|
+
await realisticMouseMove(page, 'tab-tab-a', 'tab-tab-c', 80)
|
|
426
|
+
await page.waitForTimeout(50)
|
|
427
|
+
await realisticMouseMove(page, 'tab-tab-c', 'tab-tab-e', 60)
|
|
428
|
+
await page.waitForTimeout(50)
|
|
429
|
+
await realisticMouseMove(page, 'tab-tab-e', 'tab-tab-b', 100)
|
|
430
|
+
await page.waitForTimeout(200)
|
|
431
|
+
|
|
432
|
+
const closeCount = await page.evaluate(() => (window as any).__popoverCloseCount)
|
|
433
|
+
expect(closeCount, 'popover closed during trigger switching').toBe(0)
|
|
434
|
+
await expect(content).toBeVisible()
|
|
435
|
+
})
|
|
436
|
+
|
|
437
|
+
test('trigger switch race with restMs: no close events', async ({ page }) => {
|
|
438
|
+
await setupPage(page, {
|
|
439
|
+
name: 'TabHoverAnimationCase',
|
|
440
|
+
type: 'useCase',
|
|
441
|
+
searchParams: { restMs: '100' },
|
|
442
|
+
})
|
|
443
|
+
await page.waitForTimeout(500)
|
|
444
|
+
|
|
445
|
+
await page.evaluate(() => {
|
|
446
|
+
;(window as any).__popoverCloseCount = 0
|
|
447
|
+
})
|
|
448
|
+
await hoverTab(page, 'tab-tab-b')
|
|
449
|
+
await page.waitForTimeout(600)
|
|
450
|
+
const content = page.locator('[data-testid="hover-content"]')
|
|
451
|
+
await expect(content).toBeVisible({ timeout: 2000 })
|
|
452
|
+
await page.evaluate(() => {
|
|
453
|
+
;(window as any).__popoverCloseCount = 0
|
|
454
|
+
})
|
|
455
|
+
|
|
456
|
+
// back-and-forth with realistic mouse movement
|
|
457
|
+
for (let i = 0; i < 3; i++) {
|
|
458
|
+
await realisticMouseMove(page, 'tab-tab-b', 'tab-tab-d', 60)
|
|
459
|
+
await page.waitForTimeout(30)
|
|
460
|
+
await realisticMouseMove(page, 'tab-tab-d', 'tab-tab-b', 60)
|
|
461
|
+
await page.waitForTimeout(30)
|
|
462
|
+
}
|
|
463
|
+
await page.waitForTimeout(300)
|
|
464
|
+
|
|
465
|
+
const closeCount = await page.evaluate(() => (window as any).__popoverCloseCount)
|
|
466
|
+
expect(closeCount, 'popover closed during trigger switching with restMs').toBe(0)
|
|
467
|
+
await expect(content).toBeVisible()
|
|
468
|
+
})
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/test'
|
|
2
|
+
import { setupPage } from './test-utils'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Tests that popover position ANIMATES smoothly (no jumps) when switching tabs.
|
|
6
|
+
* The key bug: motion driver often JUMPS to the new position instead of animating.
|
|
7
|
+
* We detect this by sampling translateX every frame and checking for smooth interpolation.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
test.beforeEach(async ({ page }, testInfo) => {
|
|
11
|
+
test.skip(
|
|
12
|
+
testInfo.project.name === 'animated-native',
|
|
13
|
+
'Native driver does not support hover animations on web'
|
|
14
|
+
)
|
|
15
|
+
test.skip(
|
|
16
|
+
testInfo.project.name === 'animated-reanimated',
|
|
17
|
+
'Reanimated driver has larger frame jumps during rapid position changes on web'
|
|
18
|
+
)
|
|
19
|
+
await setupPage(page, { name: 'TabHoverAnimationCase', type: 'useCase' })
|
|
20
|
+
await page.waitForTimeout(500)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
// start continuous position sampling via rAF using getBoundingClientRect
|
|
24
|
+
// (avoids getComputedStyle which forces style recalc and can interfere with animations)
|
|
25
|
+
async function startSampling(page: any) {
|
|
26
|
+
await page.evaluate(() => {
|
|
27
|
+
const el = document.querySelector('[data-popper-animate-position]')
|
|
28
|
+
if (!el) return
|
|
29
|
+
const log: { t: number; x: number }[] = []
|
|
30
|
+
;(window as any).__posLog = log
|
|
31
|
+
const start = performance.now()
|
|
32
|
+
let running = true
|
|
33
|
+
;(window as any).__stopPosLog = () => {
|
|
34
|
+
running = false
|
|
35
|
+
}
|
|
36
|
+
function tick() {
|
|
37
|
+
if (!running) return
|
|
38
|
+
log.push({ t: performance.now() - start, x: el!.getBoundingClientRect().left })
|
|
39
|
+
requestAnimationFrame(tick)
|
|
40
|
+
}
|
|
41
|
+
requestAnimationFrame(tick)
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function stopSampling(page: any) {
|
|
46
|
+
return page.evaluate(() => {
|
|
47
|
+
;(window as any).__stopPosLog?.()
|
|
48
|
+
return (window as any).__posLog as { t: number; x: number }[]
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function analyzeJumps(log: { t: number; x: number }[], threshold = 30) {
|
|
53
|
+
let maxDelta = 0
|
|
54
|
+
for (let i = 1; i < log.length; i++) {
|
|
55
|
+
const delta = Math.abs(log[i].x - log[i - 1].x)
|
|
56
|
+
if (delta > maxDelta) maxDelta = delta
|
|
57
|
+
}
|
|
58
|
+
return { maxDelta }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
test('popover animates smoothly from A to E', async ({ page }) => {
|
|
62
|
+
await page.locator('[data-testid="tab-tab-a"]').hover()
|
|
63
|
+
await page.waitForTimeout(800)
|
|
64
|
+
|
|
65
|
+
await startSampling(page)
|
|
66
|
+
await page.locator('[data-testid="tab-tab-e"]').hover()
|
|
67
|
+
await page.waitForTimeout(800)
|
|
68
|
+
|
|
69
|
+
const log = await stopSampling(page)
|
|
70
|
+
const { maxDelta } = analyzeJumps(log)
|
|
71
|
+
|
|
72
|
+
// with 500ms animation, smooth movement should have max ~15px per frame at 60fps
|
|
73
|
+
// allow some tolerance but no single-frame jumps > 50px
|
|
74
|
+
expect(maxDelta, `Max single-frame jump was ${maxDelta.toFixed(1)}px`).toBeLessThan(90)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
test('popover animates smoothly during rapid sweep A→E', async ({ page }) => {
|
|
78
|
+
const tabs = ['tab-tab-a', 'tab-tab-b', 'tab-tab-c', 'tab-tab-d', 'tab-tab-e']
|
|
79
|
+
|
|
80
|
+
await page.locator('[data-testid="tab-tab-a"]').hover()
|
|
81
|
+
await page.waitForTimeout(600)
|
|
82
|
+
|
|
83
|
+
await startSampling(page)
|
|
84
|
+
|
|
85
|
+
for (const id of tabs) {
|
|
86
|
+
await page.locator(`[data-testid="${id}"]`).hover()
|
|
87
|
+
await page.waitForTimeout(60)
|
|
88
|
+
}
|
|
89
|
+
await page.waitForTimeout(800)
|
|
90
|
+
|
|
91
|
+
const log = await stopSampling(page)
|
|
92
|
+
const { maxDelta } = analyzeJumps(log)
|
|
93
|
+
|
|
94
|
+
// during rapid sweep, interruptions cause animation restarts
|
|
95
|
+
// but each restart should be from CURRENT position, not from origin
|
|
96
|
+
expect(maxDelta, `Max single-frame jump was ${maxDelta.toFixed(1)}px`).toBeLessThan(90)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test('popover animates smoothly during back-and-forth', async ({ page }) => {
|
|
100
|
+
const tabs = ['tab-tab-a', 'tab-tab-b', 'tab-tab-c', 'tab-tab-d', 'tab-tab-e']
|
|
101
|
+
|
|
102
|
+
await page.locator('[data-testid="tab-tab-a"]').hover()
|
|
103
|
+
await page.waitForTimeout(600)
|
|
104
|
+
|
|
105
|
+
await startSampling(page)
|
|
106
|
+
|
|
107
|
+
// sweep right then left then right
|
|
108
|
+
for (const id of tabs) {
|
|
109
|
+
await page.locator(`[data-testid="${id}"]`).hover()
|
|
110
|
+
await page.waitForTimeout(50)
|
|
111
|
+
}
|
|
112
|
+
for (const id of [...tabs].reverse()) {
|
|
113
|
+
await page.locator(`[data-testid="${id}"]`).hover()
|
|
114
|
+
await page.waitForTimeout(50)
|
|
115
|
+
}
|
|
116
|
+
for (const id of tabs) {
|
|
117
|
+
await page.locator(`[data-testid="${id}"]`).hover()
|
|
118
|
+
await page.waitForTimeout(50)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
await page.waitForTimeout(800)
|
|
122
|
+
|
|
123
|
+
const log = await stopSampling(page)
|
|
124
|
+
const { maxDelta } = analyzeJumps(log)
|
|
125
|
+
|
|
126
|
+
// back-and-forth is the hardest case for animation interruption
|
|
127
|
+
// but should still not have massive teleports
|
|
128
|
+
expect(maxDelta, `Max single-frame jump was ${maxDelta.toFixed(1)}px`).toBeLessThan(90)
|
|
129
|
+
})
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/test'
|
|
2
|
+
|
|
3
|
+
import { getStyles } from './utils'
|
|
4
|
+
import { setupPage } from './test-utils'
|
|
5
|
+
|
|
6
|
+
test.beforeEach(async ({ page }) => {
|
|
7
|
+
await setupPage(page, { name: 'TextNestedInheritance', type: 'useCase' })
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
test(`nested hanzo-gui Text does NOT inherit color (sets own theme color)`, async ({
|
|
11
|
+
page,
|
|
12
|
+
}) => {
|
|
13
|
+
const parentStyles = await getStyles(page.getByTestId('parent-color').first())
|
|
14
|
+
const nestedStyles = await getStyles(page.getByTestId('nested-color').first())
|
|
15
|
+
|
|
16
|
+
// parent should have blue color
|
|
17
|
+
expect(parentStyles.color).toBe('rgb(0, 0, 255)')
|
|
18
|
+
|
|
19
|
+
// nested hanzo-gui Text sets color: '$color' so should NOT be blue
|
|
20
|
+
expect(nestedStyles.color).not.toBe(parentStyles.color)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
test(`nested core Text inherits color from parent via CSS`, async ({ page }) => {
|
|
24
|
+
const parentStyles = await getStyles(page.getByTestId('parent-core-color').first())
|
|
25
|
+
const nestedStyles = await getStyles(page.getByTestId('nested-core-color').first())
|
|
26
|
+
|
|
27
|
+
// parent should have blue
|
|
28
|
+
expect(parentStyles.color).toBe('rgb(0, 0, 255)')
|
|
29
|
+
|
|
30
|
+
// core Text does not set color, so it inherits blue via CSS
|
|
31
|
+
expect(nestedStyles.color).toBe(parentStyles.color)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test(`nested text inherits whiteSpace from parent (for numberOfLines)`, async ({
|
|
35
|
+
page,
|
|
36
|
+
}) => {
|
|
37
|
+
const parentStyles = await getStyles(page.getByTestId('parent-number-of-lines').first())
|
|
38
|
+
const nestedStyles = await getStyles(
|
|
39
|
+
page.getByTestId('nested-in-number-of-lines').first()
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
// parent with numberOfLines=1 should have nowrap
|
|
43
|
+
expect(parentStyles.whiteSpace).toBe('nowrap')
|
|
44
|
+
|
|
45
|
+
// nested text should inherit nowrap via CSS
|
|
46
|
+
expect(nestedStyles.whiteSpace).toBe('nowrap')
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test(`nested text inherits explicit whiteSpace from parent`, async ({ page }) => {
|
|
50
|
+
const parentStyles = await getStyles(page.getByTestId('parent-whitespace').first())
|
|
51
|
+
const nestedStyles = await getStyles(page.getByTestId('nested-whitespace').first())
|
|
52
|
+
|
|
53
|
+
// parent should have nowrap
|
|
54
|
+
expect(parentStyles.whiteSpace).toBe('nowrap')
|
|
55
|
+
|
|
56
|
+
// nested text should inherit nowrap
|
|
57
|
+
expect(nestedStyles.whiteSpace).toBe('nowrap')
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test(`nested text inherits letterSpacing from parent`, async ({ page }) => {
|
|
61
|
+
const parentStyles = await getStyles(page.getByTestId('parent-letter-spacing').first())
|
|
62
|
+
const nestedStyles = await getStyles(page.getByTestId('nested-letter-spacing').first())
|
|
63
|
+
|
|
64
|
+
// parent should have letter-spacing of 5px
|
|
65
|
+
expect(parentStyles.letterSpacing).toBe('5px')
|
|
66
|
+
|
|
67
|
+
// nested text should inherit letter-spacing
|
|
68
|
+
expect(nestedStyles.letterSpacing).toBe('5px')
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
test(`styled nested text does NOT inherit color (sets own theme color)`, async ({
|
|
72
|
+
page,
|
|
73
|
+
}) => {
|
|
74
|
+
const parentStyles = await getStyles(page.getByTestId('parent-styled').first())
|
|
75
|
+
const nestedStyles = await getStyles(page.getByTestId('nested-styled').first())
|
|
76
|
+
|
|
77
|
+
// parent should have green color
|
|
78
|
+
expect(parentStyles.color).toBe('rgb(0, 128, 0)')
|
|
79
|
+
|
|
80
|
+
// styled Text (BoldText) also sets color: '$color', should NOT be green
|
|
81
|
+
expect(nestedStyles.color).not.toBe(parentStyles.color)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
test(`explicit color override on nested text still works`, async ({ page }) => {
|
|
85
|
+
const parentStyles = await getStyles(page.getByTestId('parent-override').first())
|
|
86
|
+
const nestedStyles = await getStyles(page.getByTestId('nested-override').first())
|
|
87
|
+
|
|
88
|
+
// parent should have purple color
|
|
89
|
+
expect(parentStyles.color).toBe('rgb(128, 0, 128)')
|
|
90
|
+
|
|
91
|
+
// nested text with explicit color should be orange
|
|
92
|
+
expect(nestedStyles.color).toBe('rgb(255, 165, 0)')
|
|
93
|
+
})
|