@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,208 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/test'
|
|
2
|
+
import { setupPage } from './test-utils'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Global tooltip pattern: single scoped tooltip with triggers in opposite corners.
|
|
6
|
+
*
|
|
7
|
+
* Two behaviors to verify:
|
|
8
|
+
* 1. While open, switching triggers → smooth position animation
|
|
9
|
+
* 2. Close at A, reopen at B → snap position (animateOnly: []), then animate in
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
test.describe('Tooltip Global Pattern', () => {
|
|
13
|
+
test.beforeEach(async ({ page }, testInfo) => {
|
|
14
|
+
const driver = (testInfo.project?.metadata as any)?.animationDriver
|
|
15
|
+
if (driver !== 'motion') {
|
|
16
|
+
test.skip()
|
|
17
|
+
return
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
await setupPage(page, { name: 'TooltipGlobalPatternCase', type: 'useCase' })
|
|
21
|
+
await page.waitForLoadState('networkidle')
|
|
22
|
+
await page.waitForTimeout(500)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
test('while open: smoothly animates position when switching triggers', async ({
|
|
26
|
+
page,
|
|
27
|
+
}) => {
|
|
28
|
+
const triggerTL = page.locator('[data-testid="trigger-tl"]')
|
|
29
|
+
const triggerBR = page.locator('[data-testid="trigger-br"]')
|
|
30
|
+
const content = page.locator('[data-testid="global-tip-content"]')
|
|
31
|
+
|
|
32
|
+
const tlBox = await triggerTL.boundingBox()
|
|
33
|
+
const brBox = await triggerBR.boundingBox()
|
|
34
|
+
expect(tlBox).toBeTruthy()
|
|
35
|
+
expect(brBox).toBeTruthy()
|
|
36
|
+
|
|
37
|
+
const tlCenter = {
|
|
38
|
+
x: tlBox!.x + tlBox!.width / 2,
|
|
39
|
+
y: tlBox!.y + tlBox!.height / 2,
|
|
40
|
+
}
|
|
41
|
+
const brCenter = {
|
|
42
|
+
x: brBox!.x + brBox!.width / 2,
|
|
43
|
+
y: brBox!.y + brBox!.height / 2,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// hover top-left trigger to open
|
|
47
|
+
await page.mouse.move(tlCenter.x, tlCenter.y)
|
|
48
|
+
await page.waitForTimeout(100)
|
|
49
|
+
await page.mouse.move(tlCenter.x + 1, tlCenter.y + 1)
|
|
50
|
+
await page.waitForTimeout(800)
|
|
51
|
+
await expect(content).toBeVisible({ timeout: 5000 })
|
|
52
|
+
await page.waitForTimeout(300)
|
|
53
|
+
|
|
54
|
+
const posA = await content.evaluate((el) => {
|
|
55
|
+
const rect = el.getBoundingClientRect()
|
|
56
|
+
return { x: rect.left, y: rect.top }
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
// track positions
|
|
60
|
+
await page.evaluate(() => {
|
|
61
|
+
;(window as any).__positions = []
|
|
62
|
+
const track = () => {
|
|
63
|
+
const el = document.querySelector('[data-testid="global-tip-content"]')
|
|
64
|
+
if (el) {
|
|
65
|
+
const rect = el.getBoundingClientRect()
|
|
66
|
+
;(window as any).__positions.push({
|
|
67
|
+
x: rect.left,
|
|
68
|
+
y: rect.top,
|
|
69
|
+
time: Date.now(),
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
;(window as any).__rafId = requestAnimationFrame(track)
|
|
73
|
+
}
|
|
74
|
+
;(window as any).__rafId = requestAnimationFrame(track)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
// switch to bottom-right (tooltip stays open via triggerElements)
|
|
78
|
+
await page.mouse.move(brCenter.x, brCenter.y)
|
|
79
|
+
await page.waitForTimeout(100)
|
|
80
|
+
await page.mouse.move(brCenter.x + 1, brCenter.y + 1)
|
|
81
|
+
await page.waitForTimeout(400)
|
|
82
|
+
|
|
83
|
+
await page.evaluate(() => cancelAnimationFrame((window as any).__rafId))
|
|
84
|
+
|
|
85
|
+
const posB = await content.evaluate((el) => {
|
|
86
|
+
const rect = el.getBoundingClientRect()
|
|
87
|
+
return { x: rect.left, y: rect.top }
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
const positions: { x: number; y: number }[] = await page.evaluate(
|
|
91
|
+
() => (window as any).__positions
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
const xDiff = Math.abs(posB.x - posA.x)
|
|
95
|
+
expect(xDiff).toBeGreaterThan(100)
|
|
96
|
+
|
|
97
|
+
const minX = Math.min(posA.x, posB.x)
|
|
98
|
+
const maxX = Math.max(posA.x, posB.x)
|
|
99
|
+
const margin = xDiff * 0.15
|
|
100
|
+
|
|
101
|
+
const intermediatePositions = positions.filter(
|
|
102
|
+
(p) => p.x > minX + margin && p.x < maxX - margin
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
expect(
|
|
106
|
+
intermediatePositions.length,
|
|
107
|
+
`Should smoothly animate while open. ` +
|
|
108
|
+
`posA.x=${posA.x.toFixed(1)}, posB.x=${posB.x.toFixed(1)}, ` +
|
|
109
|
+
`intermediate=${intermediatePositions.length}/${positions.length}`
|
|
110
|
+
).toBeGreaterThan(2)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
test('close then reopen: snaps position, does not slide from old position', async ({
|
|
114
|
+
page,
|
|
115
|
+
}) => {
|
|
116
|
+
const triggerTL = page.locator('[data-testid="trigger-tl"]')
|
|
117
|
+
const triggerBR = page.locator('[data-testid="trigger-br"]')
|
|
118
|
+
const content = page.locator('[data-testid="global-tip-content"]')
|
|
119
|
+
|
|
120
|
+
const tlBox = await triggerTL.boundingBox()
|
|
121
|
+
const brBox = await triggerBR.boundingBox()
|
|
122
|
+
expect(tlBox).toBeTruthy()
|
|
123
|
+
expect(brBox).toBeTruthy()
|
|
124
|
+
|
|
125
|
+
const tlCenter = {
|
|
126
|
+
x: tlBox!.x + tlBox!.width / 2,
|
|
127
|
+
y: tlBox!.y + tlBox!.height / 2,
|
|
128
|
+
}
|
|
129
|
+
const brCenter = {
|
|
130
|
+
x: brBox!.x + brBox!.width / 2,
|
|
131
|
+
y: brBox!.y + brBox!.height / 2,
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// open at top-left
|
|
135
|
+
await page.mouse.move(tlCenter.x, tlCenter.y)
|
|
136
|
+
await page.waitForTimeout(100)
|
|
137
|
+
await page.mouse.move(tlCenter.x + 1, tlCenter.y + 1)
|
|
138
|
+
await page.waitForTimeout(800)
|
|
139
|
+
await expect(content).toBeVisible({ timeout: 5000 })
|
|
140
|
+
await page.waitForTimeout(300)
|
|
141
|
+
|
|
142
|
+
const posA = await content.evaluate((el) => {
|
|
143
|
+
const rect = el.getBoundingClientRect()
|
|
144
|
+
return { x: rect.left, y: rect.top }
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
// close: move mouse far away
|
|
148
|
+
await page.mouse.move(400, 300)
|
|
149
|
+
await page.waitForTimeout(1000)
|
|
150
|
+
await expect(content).not.toBeVisible({ timeout: 5000 })
|
|
151
|
+
|
|
152
|
+
// start tracking
|
|
153
|
+
await page.evaluate(() => {
|
|
154
|
+
;(window as any).__positions = []
|
|
155
|
+
const track = () => {
|
|
156
|
+
const el = document.querySelector('[data-testid="global-tip-content"]')
|
|
157
|
+
if (el) {
|
|
158
|
+
const rect = el.getBoundingClientRect()
|
|
159
|
+
const style = getComputedStyle(el)
|
|
160
|
+
;(window as any).__positions.push({
|
|
161
|
+
x: rect.left,
|
|
162
|
+
y: rect.top,
|
|
163
|
+
opacity: parseFloat(style.opacity),
|
|
164
|
+
time: Date.now(),
|
|
165
|
+
})
|
|
166
|
+
}
|
|
167
|
+
;(window as any).__rafId = requestAnimationFrame(track)
|
|
168
|
+
}
|
|
169
|
+
;(window as any).__rafId = requestAnimationFrame(track)
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
// reopen at bottom-right
|
|
173
|
+
await page.mouse.move(brCenter.x, brCenter.y)
|
|
174
|
+
await page.waitForTimeout(100)
|
|
175
|
+
await page.mouse.move(brCenter.x + 1, brCenter.y + 1)
|
|
176
|
+
await page.waitForTimeout(1000)
|
|
177
|
+
await expect(content).toBeVisible({ timeout: 5000 })
|
|
178
|
+
|
|
179
|
+
await page.evaluate(() => cancelAnimationFrame((window as any).__rafId))
|
|
180
|
+
|
|
181
|
+
const posB = await content.evaluate((el) => {
|
|
182
|
+
const rect = el.getBoundingClientRect()
|
|
183
|
+
return { x: rect.left, y: rect.top }
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
const positions: { x: number; y: number; opacity: number }[] = await page.evaluate(
|
|
187
|
+
() => (window as any).__positions
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
const totalDistance = Math.sqrt((posB.x - posA.x) ** 2 + (posB.y - posA.y) ** 2)
|
|
191
|
+
expect(totalDistance).toBeGreaterThan(200)
|
|
192
|
+
|
|
193
|
+
// no VISIBLE frame should be in the "middle zone" between A and B
|
|
194
|
+
// the tooltip should snap to B's position, not slide from A
|
|
195
|
+
const threshold = totalDistance * 0.25
|
|
196
|
+
const middlePositions = positions.filter((p) => {
|
|
197
|
+
if (p.opacity < 0.1) return false
|
|
198
|
+
const distFromA = Math.sqrt((p.x - posA.x) ** 2 + (p.y - posA.y) ** 2)
|
|
199
|
+
const distFromB = Math.sqrt((p.x - posB.x) ** 2 + (p.y - posB.y) ** 2)
|
|
200
|
+
return distFromA > threshold && distFromB > threshold
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
expect(
|
|
204
|
+
middlePositions.length,
|
|
205
|
+
`Close→reopen should snap, not slide. Found ${middlePositions.length} frames in middle zone`
|
|
206
|
+
).toBeLessThanOrEqual(2)
|
|
207
|
+
})
|
|
208
|
+
})
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { expect, test, type Page } from '@playwright/test'
|
|
2
|
+
import { setupPage } from './test-utils'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* TooltipGroup Tests
|
|
6
|
+
*
|
|
7
|
+
* TooltipGroup enables grouped delay behavior:
|
|
8
|
+
* - First tooltip in group: waits for full delay
|
|
9
|
+
* - Subsequent tooltips: show immediately while within the group's timeout window
|
|
10
|
+
*
|
|
11
|
+
* This is implemented via FloatingDelayGroup from @hanzogui/floating.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
async function tooltipVisible(page: Page, testId: string): Promise<boolean> {
|
|
15
|
+
return page.evaluate((id) => !!document.querySelector(`[data-testid="${id}"]`), testId)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
test.describe('TooltipGroup', () => {
|
|
19
|
+
test.beforeEach(async ({ page }) => {
|
|
20
|
+
await setupPage(page, { name: 'TooltipGroupCase', type: 'useCase' })
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
test('subsequent grouped tooltips skip delay', async ({ page }) => {
|
|
24
|
+
// Config: 1000ms open delay, 500ms timeout
|
|
25
|
+
const DELAY = 1000
|
|
26
|
+
|
|
27
|
+
// Show first tooltip (with delay)
|
|
28
|
+
await page.getByTestId('tooltip-trigger-1').hover()
|
|
29
|
+
await page.waitForTimeout(DELAY + 200)
|
|
30
|
+
expect(await tooltipVisible(page, 'tooltip-content-1')).toBe(true)
|
|
31
|
+
|
|
32
|
+
// Move to second tooltip - should appear quickly (no full delay)
|
|
33
|
+
await page.getByTestId('tooltip-trigger-2').hover()
|
|
34
|
+
await page.waitForTimeout(300)
|
|
35
|
+
|
|
36
|
+
expect(
|
|
37
|
+
await tooltipVisible(page, 'tooltip-content-2'),
|
|
38
|
+
'Second grouped tooltip should appear without waiting for full delay'
|
|
39
|
+
).toBe(true)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test('first grouped tooltip respects delay', async ({ page }) => {
|
|
43
|
+
const DELAY = 1000
|
|
44
|
+
|
|
45
|
+
await page.getByTestId('tooltip-trigger-1').hover()
|
|
46
|
+
|
|
47
|
+
// Should NOT appear before delay
|
|
48
|
+
await page.waitForTimeout(200)
|
|
49
|
+
expect(
|
|
50
|
+
await tooltipVisible(page, 'tooltip-content-1'),
|
|
51
|
+
'Should not appear before delay'
|
|
52
|
+
).toBe(false)
|
|
53
|
+
|
|
54
|
+
// Should appear after delay
|
|
55
|
+
await page.waitForTimeout(DELAY)
|
|
56
|
+
expect(
|
|
57
|
+
await tooltipVisible(page, 'tooltip-content-1'),
|
|
58
|
+
'Should appear after delay'
|
|
59
|
+
).toBe(true)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test('standalone tooltips always have delay', async ({ page }) => {
|
|
63
|
+
const DELAY = 1000
|
|
64
|
+
|
|
65
|
+
// Show standalone A
|
|
66
|
+
await page.getByTestId('tooltip-trigger-standalone-a').hover()
|
|
67
|
+
await page.waitForTimeout(DELAY + 200)
|
|
68
|
+
expect(await tooltipVisible(page, 'tooltip-content-standalone-a')).toBe(true)
|
|
69
|
+
|
|
70
|
+
// Move to standalone B - should NOT appear quickly (has its own delay)
|
|
71
|
+
await page.getByTestId('tooltip-trigger-standalone-b').hover()
|
|
72
|
+
await page.waitForTimeout(300)
|
|
73
|
+
|
|
74
|
+
expect(
|
|
75
|
+
await tooltipVisible(page, 'tooltip-content-standalone-b'),
|
|
76
|
+
'Standalone tooltips should always wait for their delay'
|
|
77
|
+
).toBe(false)
|
|
78
|
+
})
|
|
79
|
+
})
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/test'
|
|
2
|
+
import { getBoundingRect, setupPage } from './test-utils'
|
|
3
|
+
|
|
4
|
+
test.describe('Tooltip multi-trigger rapid hover', () => {
|
|
5
|
+
test.beforeEach(async ({ page }) => {
|
|
6
|
+
await setupPage(page, {
|
|
7
|
+
name: 'TooltipMultiTriggerCase',
|
|
8
|
+
type: 'useCase',
|
|
9
|
+
})
|
|
10
|
+
await page.waitForLoadState('networkidle')
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
test('rapid sweep: tooltip stays open and tracks final trigger', async ({ page }) => {
|
|
14
|
+
const content = page.locator('#tip-content')
|
|
15
|
+
|
|
16
|
+
// hover first trigger to open
|
|
17
|
+
await page.locator('#tip-trigger-a').hover()
|
|
18
|
+
await page.waitForTimeout(200)
|
|
19
|
+
await expect(content).toBeVisible({ timeout: 3000 })
|
|
20
|
+
|
|
21
|
+
// sweep through triggers with gaps
|
|
22
|
+
const ids = ['a', 'b', 'c']
|
|
23
|
+
for (let i = 0; i < ids.length - 1; i++) {
|
|
24
|
+
const curr = page.locator(`#tip-trigger-${ids[i]}`)
|
|
25
|
+
const next = page.locator(`#tip-trigger-${ids[i + 1]}`)
|
|
26
|
+
const currBox = await curr.boundingBox()
|
|
27
|
+
const nextBox = await next.boundingBox()
|
|
28
|
+
if (currBox && nextBox) {
|
|
29
|
+
// move to gap between triggers
|
|
30
|
+
const gapX =
|
|
31
|
+
currBox.x + currBox.width + (nextBox.x - (currBox.x + currBox.width)) / 2
|
|
32
|
+
const gapY = currBox.y + currBox.height / 2
|
|
33
|
+
await page.mouse.move(gapX, gapY)
|
|
34
|
+
await page.waitForTimeout(30)
|
|
35
|
+
// move to next trigger
|
|
36
|
+
await page.mouse.move(
|
|
37
|
+
nextBox.x + nextBox.width / 2,
|
|
38
|
+
nextBox.y + nextBox.height / 2
|
|
39
|
+
)
|
|
40
|
+
await page.waitForTimeout(20)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
await page.waitForTimeout(200)
|
|
45
|
+
await expect(content).toBeVisible({ timeout: 2000 })
|
|
46
|
+
|
|
47
|
+
const label = page.locator('#tip-label')
|
|
48
|
+
await expect(label).toContainText('Expert', { timeout: 2000 })
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
test('arrow stays aligned with hovered trigger after switch', async ({ page }) => {
|
|
52
|
+
const content = page.locator('#tip-content')
|
|
53
|
+
|
|
54
|
+
// hover first trigger to open
|
|
55
|
+
await page.locator('#tip-trigger-a').hover()
|
|
56
|
+
await page.waitForTimeout(200)
|
|
57
|
+
await expect(content).toBeVisible({ timeout: 3000 })
|
|
58
|
+
|
|
59
|
+
// verify arrow is near trigger A
|
|
60
|
+
const arrowA = await getBoundingRect(page, '#tip-arrow')
|
|
61
|
+
const triggerARect = await getBoundingRect(page, '#tip-trigger-a')
|
|
62
|
+
expect(arrowA).toBeTruthy()
|
|
63
|
+
const arrowACX = arrowA!.x + arrowA!.width / 2
|
|
64
|
+
const triggerACX = triggerARect!.x + triggerARect!.width / 2
|
|
65
|
+
// arrow should be within 30px of trigger center
|
|
66
|
+
expect(Math.abs(arrowACX - triggerACX)).toBeLessThan(30)
|
|
67
|
+
|
|
68
|
+
// quickly jump to trigger C (2 steps = very fast mouse move)
|
|
69
|
+
const triggerC = page.locator('#tip-trigger-c')
|
|
70
|
+
const cBox = await triggerC.boundingBox()
|
|
71
|
+
await page.mouse.move(cBox!.x + cBox!.width / 2, cBox!.y + cBox!.height / 2, {
|
|
72
|
+
steps: 2,
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
// wait for position to settle (animatePosition transition)
|
|
76
|
+
await page.waitForTimeout(500)
|
|
77
|
+
await expect(content).toBeVisible({ timeout: 2000 })
|
|
78
|
+
|
|
79
|
+
// arrow should now be near trigger C, not still near trigger A
|
|
80
|
+
const arrowC = await getBoundingRect(page, '#tip-arrow')
|
|
81
|
+
const triggerCRect = await getBoundingRect(page, '#tip-trigger-c')
|
|
82
|
+
const contentRect = await getBoundingRect(page, '#tip-content')
|
|
83
|
+
expect(arrowC).toBeTruthy()
|
|
84
|
+
expect(contentRect).toBeTruthy()
|
|
85
|
+
|
|
86
|
+
const arrowCCX = arrowC!.x + arrowC!.width / 2
|
|
87
|
+
const triggerCCX = triggerCRect!.x + triggerCRect!.width / 2
|
|
88
|
+
const contentCCX = contentRect!.x + contentRect!.width / 2
|
|
89
|
+
|
|
90
|
+
// arrow center should be within content bounds
|
|
91
|
+
expect(arrowCCX).toBeGreaterThanOrEqual(contentRect!.x)
|
|
92
|
+
expect(arrowCCX).toBeLessThanOrEqual(contentRect!.x + contentRect!.width)
|
|
93
|
+
|
|
94
|
+
// arrow should be reasonably near trigger C (not still at A)
|
|
95
|
+
// with offset=20 and animatePosition, arrow should track the trigger
|
|
96
|
+
expect(
|
|
97
|
+
Math.abs(arrowCCX - triggerCCX),
|
|
98
|
+
`arrow center (${arrowCCX}) should be near trigger C center (${triggerCCX}), content center at ${contentCCX}`
|
|
99
|
+
).toBeLessThan(40)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
test('tooltip closes when cursor leaves all triggers', async ({ page }) => {
|
|
103
|
+
const content = page.locator('#tip-content')
|
|
104
|
+
|
|
105
|
+
// hover to open
|
|
106
|
+
await page.locator('#tip-trigger-b').hover()
|
|
107
|
+
await page.waitForTimeout(200)
|
|
108
|
+
await expect(content).toBeVisible({ timeout: 3000 })
|
|
109
|
+
|
|
110
|
+
// move cursor far away from triggers
|
|
111
|
+
await page.mouse.move(10, 10)
|
|
112
|
+
await page.waitForTimeout(500)
|
|
113
|
+
|
|
114
|
+
await expect(content).not.toBeVisible({ timeout: 3000 })
|
|
115
|
+
})
|
|
116
|
+
})
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { expect, test, type Page } from '@playwright/test'
|
|
2
|
+
import { setupPage } from './test-utils'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* TOOLTIP POSITION JUMP TEST (Motion Driver Only)
|
|
6
|
+
*
|
|
7
|
+
* Bug: When using animatePosition with a scoped tooltip,
|
|
8
|
+
* rapidly moving between triggers causes the tooltip to JUMP to wrong position
|
|
9
|
+
* (often near origin/top-left) before animating back.
|
|
10
|
+
*
|
|
11
|
+
* Key: Must actually MOVE the mouse across, not just call hover() which teleports.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
test.describe('Tooltip Position Jump', () => {
|
|
15
|
+
test.beforeEach(async ({ page }, testInfo) => {
|
|
16
|
+
// only test with motion driver - this is where the bug manifests
|
|
17
|
+
const driver = (testInfo.project?.metadata as any)?.animationDriver
|
|
18
|
+
if (driver !== 'motion') {
|
|
19
|
+
test.skip()
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
await setupPage(page, { name: 'TooltipPositionJumpCase', type: 'useCase' })
|
|
24
|
+
await page.waitForTimeout(500)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
test('scoped tooltip should not jump when moving quickly between triggers', async ({
|
|
28
|
+
page,
|
|
29
|
+
}) => {
|
|
30
|
+
// get button positions
|
|
31
|
+
const hireBtn = page.locator('[data-testid="tooltip-trigger-hire"]')
|
|
32
|
+
const bentoBtn = page.locator('[data-testid="tooltip-trigger-bento"]')
|
|
33
|
+
const takeoutBtn = page.locator('[data-testid="tooltip-trigger-takeout"]')
|
|
34
|
+
|
|
35
|
+
const hireBox = await hireBtn.boundingBox()
|
|
36
|
+
const bentoBox = await bentoBtn.boundingBox()
|
|
37
|
+
const takeoutBox = await takeoutBtn.boundingBox()
|
|
38
|
+
|
|
39
|
+
if (!hireBox || !bentoBox || !takeoutBox) {
|
|
40
|
+
throw new Error('Could not find button positions')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// inject position tracking script
|
|
44
|
+
await page.evaluate(() => {
|
|
45
|
+
;(window as any).__tooltipPositions = []
|
|
46
|
+
;(window as any).__trackTooltip = () => {
|
|
47
|
+
const el = document.querySelector(
|
|
48
|
+
'[data-testid="tooltip-jump-content"]'
|
|
49
|
+
) as HTMLElement
|
|
50
|
+
if (!el) return null
|
|
51
|
+
|
|
52
|
+
const style = getComputedStyle(el)
|
|
53
|
+
const transform = style.transform
|
|
54
|
+
if (!transform || transform === 'none') return null
|
|
55
|
+
|
|
56
|
+
const match = transform.match(
|
|
57
|
+
/matrix\([^,]+,\s*[^,]+,\s*[^,]+,\s*[^,]+,\s*([^,]+),\s*([^)]+)\)/
|
|
58
|
+
)
|
|
59
|
+
if (!match) return null
|
|
60
|
+
|
|
61
|
+
const pos = { x: parseFloat(match[1]), y: parseFloat(match[2]), time: Date.now() }
|
|
62
|
+
;(window as any).__tooltipPositions.push(pos)
|
|
63
|
+
return pos
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// start tracking at 60fps
|
|
67
|
+
const track = () => {
|
|
68
|
+
;(window as any).__trackTooltip()
|
|
69
|
+
requestAnimationFrame(track)
|
|
70
|
+
}
|
|
71
|
+
requestAnimationFrame(track)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
// step 1: hover on rightmost button (HIRE) and wait for tooltip to fully appear
|
|
75
|
+
const hireCenter = {
|
|
76
|
+
x: hireBox.x + hireBox.width / 2,
|
|
77
|
+
y: hireBox.y + hireBox.height / 2,
|
|
78
|
+
}
|
|
79
|
+
await page.mouse.move(hireCenter.x, hireCenter.y)
|
|
80
|
+
await page.waitForTimeout(800)
|
|
81
|
+
|
|
82
|
+
// step 2: move mouse QUICKLY across to leftmost button (TAKEOUT)
|
|
83
|
+
// must actually traverse the path, not teleport
|
|
84
|
+
const takeoutCenter = {
|
|
85
|
+
x: takeoutBox.x + takeoutBox.width / 2,
|
|
86
|
+
y: takeoutBox.y + takeoutBox.height / 2,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// do fast sweep: HIRE -> TAKEOUT in ~100ms total
|
|
90
|
+
const steps = 10
|
|
91
|
+
const totalTime = 100
|
|
92
|
+
const stepDelay = totalTime / steps
|
|
93
|
+
|
|
94
|
+
for (let i = 1; i <= steps; i++) {
|
|
95
|
+
const t = i / steps
|
|
96
|
+
const x = hireCenter.x + (takeoutCenter.x - hireCenter.x) * t
|
|
97
|
+
const y = hireCenter.y + (takeoutCenter.y - hireCenter.y) * t
|
|
98
|
+
await page.mouse.move(x, y)
|
|
99
|
+
await page.waitForTimeout(stepDelay)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
await page.waitForTimeout(300)
|
|
103
|
+
|
|
104
|
+
// analyze for jumps
|
|
105
|
+
const positions = await page.evaluate(() => (window as any).__tooltipPositions)
|
|
106
|
+
const jumps: any[] = []
|
|
107
|
+
|
|
108
|
+
for (let i = 1; i < positions.length; i++) {
|
|
109
|
+
const prev = positions[i - 1]
|
|
110
|
+
const curr = positions[i]
|
|
111
|
+
const dx = Math.abs(curr.x - prev.x)
|
|
112
|
+
const dy = Math.abs(curr.y - prev.y)
|
|
113
|
+
const delta = Math.sqrt(dx * dx + dy * dy)
|
|
114
|
+
const timeDelta = curr.time - prev.time
|
|
115
|
+
|
|
116
|
+
// jump: large delta in short time, going toward origin
|
|
117
|
+
if (timeDelta < 50 && delta > 100) {
|
|
118
|
+
const jumpingLeft = curr.x < prev.x - 100
|
|
119
|
+
const jumpingUp = curr.y < prev.y - 50
|
|
120
|
+
const jumpToNearOrigin = curr.x < 200 && curr.y < 200 && prev.x > 300
|
|
121
|
+
|
|
122
|
+
if (jumpingLeft || jumpingUp || jumpToNearOrigin) {
|
|
123
|
+
jumps.push({ from: prev, to: curr, delta: Math.round(delta) })
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
console.log(`Collected ${positions.length} positions, found ${jumps.length} jumps`)
|
|
129
|
+
if (jumps.length > 0) {
|
|
130
|
+
console.log('Jumps:', JSON.stringify(jumps, null, 2))
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
expect(jumps.length, `Detected ${jumps.length} position jumps!`).toBe(0)
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
test('rapid back-and-forth should not cause jumps', async ({ page }) => {
|
|
137
|
+
const hireBtn = page.locator('[data-testid="tooltip-trigger-hire"]')
|
|
138
|
+
const takeoutBtn = page.locator('[data-testid="tooltip-trigger-takeout"]')
|
|
139
|
+
|
|
140
|
+
const hireBox = await hireBtn.boundingBox()
|
|
141
|
+
const takeoutBox = await takeoutBtn.boundingBox()
|
|
142
|
+
|
|
143
|
+
if (!hireBox || !takeoutBox) {
|
|
144
|
+
throw new Error('Could not find button positions')
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// inject tracking
|
|
148
|
+
await page.evaluate(() => {
|
|
149
|
+
;(window as any).__tooltipPositions = []
|
|
150
|
+
const track = () => {
|
|
151
|
+
const el = document.querySelector(
|
|
152
|
+
'[data-testid="tooltip-jump-content"]'
|
|
153
|
+
) as HTMLElement
|
|
154
|
+
if (el) {
|
|
155
|
+
const transform = getComputedStyle(el).transform
|
|
156
|
+
if (transform && transform !== 'none') {
|
|
157
|
+
const match = transform.match(
|
|
158
|
+
/matrix\([^,]+,\s*[^,]+,\s*[^,]+,\s*[^,]+,\s*([^,]+),\s*([^)]+)\)/
|
|
159
|
+
)
|
|
160
|
+
if (match) {
|
|
161
|
+
;(window as any).__tooltipPositions.push({
|
|
162
|
+
x: parseFloat(match[1]),
|
|
163
|
+
y: parseFloat(match[2]),
|
|
164
|
+
time: Date.now(),
|
|
165
|
+
})
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
requestAnimationFrame(track)
|
|
170
|
+
}
|
|
171
|
+
requestAnimationFrame(track)
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
const hireCenter = {
|
|
175
|
+
x: hireBox.x + hireBox.width / 2,
|
|
176
|
+
y: hireBox.y + hireBox.height / 2,
|
|
177
|
+
}
|
|
178
|
+
const takeoutCenter = {
|
|
179
|
+
x: takeoutBox.x + takeoutBox.width / 2,
|
|
180
|
+
y: takeoutBox.y + takeoutBox.height / 2,
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// hover HIRE first
|
|
184
|
+
await page.mouse.move(hireCenter.x, hireCenter.y)
|
|
185
|
+
await page.waitForTimeout(600)
|
|
186
|
+
|
|
187
|
+
// do 3 rapid back-and-forth sweeps
|
|
188
|
+
for (let sweep = 0; sweep < 3; sweep++) {
|
|
189
|
+
for (let i = 0; i <= 8; i++) {
|
|
190
|
+
const t = i / 8
|
|
191
|
+
await page.mouse.move(
|
|
192
|
+
hireCenter.x + (takeoutCenter.x - hireCenter.x) * t,
|
|
193
|
+
hireCenter.y + (takeoutCenter.y - hireCenter.y) * t
|
|
194
|
+
)
|
|
195
|
+
await page.waitForTimeout(10)
|
|
196
|
+
}
|
|
197
|
+
for (let i = 0; i <= 8; i++) {
|
|
198
|
+
const t = i / 8
|
|
199
|
+
await page.mouse.move(
|
|
200
|
+
takeoutCenter.x + (hireCenter.x - takeoutCenter.x) * t,
|
|
201
|
+
takeoutCenter.y + (hireCenter.y - takeoutCenter.y) * t
|
|
202
|
+
)
|
|
203
|
+
await page.waitForTimeout(10)
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
await page.waitForTimeout(300)
|
|
208
|
+
|
|
209
|
+
const positions = await page.evaluate(() => (window as any).__tooltipPositions)
|
|
210
|
+
const jumps: any[] = []
|
|
211
|
+
|
|
212
|
+
for (let i = 1; i < positions.length; i++) {
|
|
213
|
+
const prev = positions[i - 1]
|
|
214
|
+
const curr = positions[i]
|
|
215
|
+
const delta = Math.sqrt((curr.x - prev.x) ** 2 + (curr.y - prev.y) ** 2)
|
|
216
|
+
const timeDelta = curr.time - prev.time
|
|
217
|
+
|
|
218
|
+
if (timeDelta < 50 && delta > 100) {
|
|
219
|
+
const jumpingTowardOrigin = curr.x < prev.x - 50 && curr.x < 200
|
|
220
|
+
if (jumpingTowardOrigin) {
|
|
221
|
+
jumps.push({ from: prev, to: curr, delta: Math.round(delta) })
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
console.log(`Found ${jumps.length} jumps in back-and-forth test`)
|
|
227
|
+
expect(jumps.length).toBe(0)
|
|
228
|
+
})
|
|
229
|
+
})
|