@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,106 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/test'
|
|
2
|
+
import { setupPage } from './test-utils'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Tests the disableClick prop on Popover.Trigger.
|
|
6
|
+
*
|
|
7
|
+
* Bug: when a hoverable popover is open and the user clicks the trigger,
|
|
8
|
+
* the browser fires pointerdown then click. If onPressIn calls setOpen(false),
|
|
9
|
+
* React re-renders between the two events. The built-in onPress handler then
|
|
10
|
+
* reads the updated open=false and toggles it back to true, causing a
|
|
11
|
+
* close → re-open → close oscillation (visible as a flash/glitch).
|
|
12
|
+
*
|
|
13
|
+
* Fix: disableClick prop suppresses the built-in click toggle on the trigger,
|
|
14
|
+
* allowing the user to control open/close entirely through their own handlers.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
test.beforeEach(async ({ page }) => {
|
|
18
|
+
await setupPage(page, {
|
|
19
|
+
name: 'PopoverHoverableDisableClickCase',
|
|
20
|
+
type: 'useCase',
|
|
21
|
+
})
|
|
22
|
+
await page.waitForLoadState('networkidle')
|
|
23
|
+
await page.waitForTimeout(500)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test('disableClick: hover opens, click dismisses without re-open oscillation', async ({
|
|
27
|
+
page,
|
|
28
|
+
}) => {
|
|
29
|
+
const trigger = page.locator('#disableclick-trigger')
|
|
30
|
+
const content = page.locator('#disableclick-content')
|
|
31
|
+
|
|
32
|
+
await expect(content).not.toBeVisible()
|
|
33
|
+
|
|
34
|
+
// hover to open
|
|
35
|
+
await trigger.hover()
|
|
36
|
+
await page.waitForTimeout(300)
|
|
37
|
+
await expect(content).toBeVisible({ timeout: 2000 })
|
|
38
|
+
|
|
39
|
+
// click to dismiss (onPressIn sets open=false)
|
|
40
|
+
await trigger.click()
|
|
41
|
+
await page.waitForTimeout(500)
|
|
42
|
+
|
|
43
|
+
// popover should be closed — NOT re-opened by the built-in onPress toggle
|
|
44
|
+
await expect(content).not.toBeVisible({ timeout: 2000 })
|
|
45
|
+
|
|
46
|
+
// check the log for oscillation: if we see onOpenChange: true AFTER
|
|
47
|
+
// onPressIn: dismissing, that's the bug
|
|
48
|
+
const logEntries = await page.evaluate(() => {
|
|
49
|
+
const logEl = document.getElementById('open-state-log')
|
|
50
|
+
if (!logEl) return []
|
|
51
|
+
return Array.from(logEl.querySelectorAll('span')).map((el) => el.textContent || '')
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
console.log('State log:', logEntries)
|
|
55
|
+
|
|
56
|
+
// after the dismiss, there should be no onOpenChange: true
|
|
57
|
+
const dismissIdx = logEntries.findIndex((e) => e.includes('dismissing'))
|
|
58
|
+
if (dismissIdx >= 0) {
|
|
59
|
+
const afterDismiss = logEntries.slice(dismissIdx + 1)
|
|
60
|
+
const reOpened = afterDismiss.some((e) => e.includes('onOpenChange: true'))
|
|
61
|
+
expect(reOpened, 'Popover re-opened after dismiss (state oscillation bug)').toBe(
|
|
62
|
+
false
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test('disableClick: hover opens, move away closes, re-hover re-opens', async ({
|
|
68
|
+
page,
|
|
69
|
+
}) => {
|
|
70
|
+
const trigger = page.locator('#disableclick-trigger')
|
|
71
|
+
const content = page.locator('#disableclick-content')
|
|
72
|
+
|
|
73
|
+
await expect(content).not.toBeVisible()
|
|
74
|
+
|
|
75
|
+
// hover to open
|
|
76
|
+
await trigger.hover()
|
|
77
|
+
await page.waitForTimeout(300)
|
|
78
|
+
await expect(content).toBeVisible({ timeout: 2000 })
|
|
79
|
+
|
|
80
|
+
// move away to close
|
|
81
|
+
await page.mouse.move(10, 10)
|
|
82
|
+
await page.waitForTimeout(800)
|
|
83
|
+
await expect(content).not.toBeVisible({ timeout: 2000 })
|
|
84
|
+
|
|
85
|
+
// hover again should re-open (hover still works with disableClick)
|
|
86
|
+
await trigger.hover()
|
|
87
|
+
await page.waitForTimeout(300)
|
|
88
|
+
await expect(content).toBeVisible({ timeout: 2000 })
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
test('normal trigger: click toggles popover open and closed', async ({ page }) => {
|
|
92
|
+
const trigger = page.locator('#withclick-trigger')
|
|
93
|
+
const content = page.locator('#withclick-content')
|
|
94
|
+
|
|
95
|
+
await expect(content).not.toBeVisible()
|
|
96
|
+
|
|
97
|
+
// click should open (normal trigger without disableClick)
|
|
98
|
+
await trigger.click()
|
|
99
|
+
await page.waitForTimeout(500)
|
|
100
|
+
await expect(content).toBeVisible({ timeout: 2000 })
|
|
101
|
+
|
|
102
|
+
// click again should close
|
|
103
|
+
await trigger.click()
|
|
104
|
+
await page.waitForTimeout(500)
|
|
105
|
+
await expect(content).not.toBeVisible({ timeout: 2000 })
|
|
106
|
+
})
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/test'
|
|
2
|
+
import { getBoundingRect, setupPage } from './test-utils'
|
|
3
|
+
|
|
4
|
+
// bug: rapidly sweeping across many side-by-side hoverable triggers with short
|
|
5
|
+
// restMs can cause the popover to get "stuck" on a past trigger or close
|
|
6
|
+
// unexpectedly. the root cause is a race between safePolygon closing during the
|
|
7
|
+
// gap between triggers and stale openRef preventing re-open.
|
|
8
|
+
|
|
9
|
+
test.describe('Popover hoverable rapid trigger switching', () => {
|
|
10
|
+
test.beforeEach(async ({ page }) => {
|
|
11
|
+
await setupPage(page, {
|
|
12
|
+
name: 'PopoverHoverableRapidCase',
|
|
13
|
+
type: 'useCase',
|
|
14
|
+
searchParams: { animationDriver: 'css' },
|
|
15
|
+
})
|
|
16
|
+
await page.waitForLoadState('networkidle')
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
test('rapid sweep: popover stays open and tracks final trigger', async ({ page }) => {
|
|
20
|
+
const content = page.locator('#rapid-content')
|
|
21
|
+
|
|
22
|
+
// hover first trigger to open
|
|
23
|
+
const triggerA = page.locator('#rapid-trigger-a')
|
|
24
|
+
await triggerA.hover()
|
|
25
|
+
await page.waitForTimeout(150) // restMs is 60ms + buffer
|
|
26
|
+
await expect(content).toBeVisible({ timeout: 2000 })
|
|
27
|
+
|
|
28
|
+
// move through gaps between triggers to expose the race condition:
|
|
29
|
+
// when the mouse is in the gap, onTriggerRef is false and safePolygon
|
|
30
|
+
// can fire a close. then when entering the next trigger, stale openRef
|
|
31
|
+
// prevents re-open.
|
|
32
|
+
const ids = ['a', 'b', 'c', 'd', 'e', 'f']
|
|
33
|
+
for (let i = 0; i < ids.length - 1; i++) {
|
|
34
|
+
const curr = page.locator(`#rapid-trigger-${ids[i]}`)
|
|
35
|
+
const next = page.locator(`#rapid-trigger-${ids[i + 1]}`)
|
|
36
|
+
const currBox = await curr.boundingBox()
|
|
37
|
+
const nextBox = await next.boundingBox()
|
|
38
|
+
if (currBox && nextBox) {
|
|
39
|
+
// move to the gap between current and next trigger
|
|
40
|
+
const gapX =
|
|
41
|
+
currBox.x + currBox.width + (nextBox.x - (currBox.x + currBox.width)) / 2
|
|
42
|
+
const gapY = currBox.y + currBox.height / 2
|
|
43
|
+
await page.mouse.move(gapX, gapY)
|
|
44
|
+
// wait in the gap for safePolygon's document mousemove handler to fire
|
|
45
|
+
// the mouse is now between triggers (onTriggerRef is false)
|
|
46
|
+
await page.waitForTimeout(30)
|
|
47
|
+
// now move to the next trigger
|
|
48
|
+
await page.mouse.move(
|
|
49
|
+
nextBox.x + nextBox.width / 2,
|
|
50
|
+
nextBox.y + nextBox.height / 2
|
|
51
|
+
)
|
|
52
|
+
await page.waitForTimeout(20)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// rest on the final trigger
|
|
57
|
+
await page.waitForTimeout(200)
|
|
58
|
+
|
|
59
|
+
// popover should still be visible (not closed/stuck)
|
|
60
|
+
await expect(content).toBeVisible({ timeout: 2000 })
|
|
61
|
+
|
|
62
|
+
// the panel label should show the final trigger
|
|
63
|
+
const label = page.locator('#rapid-panel-label')
|
|
64
|
+
await expect(label).toHaveText('Panel F', { timeout: 2000 })
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test('rapid back-and-forth: popover stays open', async ({ page }) => {
|
|
68
|
+
const content = page.locator('#rapid-content')
|
|
69
|
+
|
|
70
|
+
// open on trigger c
|
|
71
|
+
const triggerC = page.locator('#rapid-trigger-c')
|
|
72
|
+
await triggerC.hover()
|
|
73
|
+
await page.waitForTimeout(150)
|
|
74
|
+
await expect(content).toBeVisible({ timeout: 2000 })
|
|
75
|
+
|
|
76
|
+
// sweep back and forth quickly with abrupt jumps
|
|
77
|
+
const sweep = ['d', 'e', 'f', 'e', 'd', 'c', 'b', 'a', 'b', 'c', 'd', 'e', 'f']
|
|
78
|
+
for (const id of sweep) {
|
|
79
|
+
const trigger = page.locator(`#rapid-trigger-${id}`)
|
|
80
|
+
const box = await trigger.boundingBox()
|
|
81
|
+
if (box) {
|
|
82
|
+
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2, {
|
|
83
|
+
steps: 1,
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
await page.waitForTimeout(5)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
await page.waitForTimeout(200)
|
|
90
|
+
|
|
91
|
+
// should still be open and tracking final trigger
|
|
92
|
+
await expect(content).toBeVisible({ timeout: 2000 })
|
|
93
|
+
const label = page.locator('#rapid-panel-label')
|
|
94
|
+
await expect(label).toHaveText('Panel F', { timeout: 2000 })
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
test('rapid sweep then rest: content positioned near final trigger', async ({
|
|
98
|
+
page,
|
|
99
|
+
}) => {
|
|
100
|
+
const content = page.locator('#rapid-content')
|
|
101
|
+
|
|
102
|
+
// open on first trigger
|
|
103
|
+
await page.locator('#rapid-trigger-a').hover()
|
|
104
|
+
await page.waitForTimeout(150)
|
|
105
|
+
await expect(content).toBeVisible({ timeout: 2000 })
|
|
106
|
+
|
|
107
|
+
// quickly move to last trigger
|
|
108
|
+
const triggerF = page.locator('#rapid-trigger-f')
|
|
109
|
+
const box = await triggerF.boundingBox()
|
|
110
|
+
if (box) {
|
|
111
|
+
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2, {
|
|
112
|
+
steps: 3,
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
await page.waitForTimeout(200)
|
|
116
|
+
|
|
117
|
+
await expect(content).toBeVisible({ timeout: 2000 })
|
|
118
|
+
|
|
119
|
+
// content should be near trigger F, not stuck at trigger A
|
|
120
|
+
const triggerFRect = await getBoundingRect(page, '#rapid-trigger-f')
|
|
121
|
+
const triggerARect = await getBoundingRect(page, '#rapid-trigger-a')
|
|
122
|
+
const contentRect = await getBoundingRect(page, '#rapid-content')
|
|
123
|
+
|
|
124
|
+
// content x should be closer to trigger F than to trigger A
|
|
125
|
+
const distToF = Math.abs(contentRect!.x - triggerFRect!.x)
|
|
126
|
+
const distToA = Math.abs(contentRect!.x - triggerARect!.x)
|
|
127
|
+
expect(distToF).toBeLessThan(distToA)
|
|
128
|
+
})
|
|
129
|
+
})
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/test'
|
|
2
|
+
import { setupPage } from './test-utils'
|
|
3
|
+
|
|
4
|
+
// regression: when a scoped hoverable popover with animatePosition fully
|
|
5
|
+
// closes then reopens on a DIFFERENT trigger, it should appear at the new
|
|
6
|
+
// trigger's position — not animate/slide from the old trigger's position.
|
|
7
|
+
|
|
8
|
+
test.describe('Popover hoverable reposition after close', () => {
|
|
9
|
+
test.beforeEach(async ({ page }) => {
|
|
10
|
+
await setupPage(page, {
|
|
11
|
+
name: 'PopoverHoverableScopedCase',
|
|
12
|
+
type: 'useCase',
|
|
13
|
+
searchParams: { animationDriver: 'css' },
|
|
14
|
+
})
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
test('close then reopen on different trigger: no slide from old position', async ({
|
|
18
|
+
page,
|
|
19
|
+
}) => {
|
|
20
|
+
const content = page.locator('#nav-content')
|
|
21
|
+
|
|
22
|
+
// hover "about" trigger to open
|
|
23
|
+
const aboutTrigger = page.locator('#nav-trigger-about')
|
|
24
|
+
await aboutTrigger.hover()
|
|
25
|
+
await page.waitForTimeout(500)
|
|
26
|
+
await expect(content).toBeVisible()
|
|
27
|
+
|
|
28
|
+
// record content position when anchored to "about"
|
|
29
|
+
const aboutContentBox = await content.boundingBox()
|
|
30
|
+
expect(aboutContentBox).toBeTruthy()
|
|
31
|
+
|
|
32
|
+
// move mouse far away to fully close
|
|
33
|
+
await page.mouse.move(10, 600, { steps: 2 })
|
|
34
|
+
await page.waitForTimeout(800) // wait for exit animation (500ms transition)
|
|
35
|
+
await expect(content).not.toBeVisible()
|
|
36
|
+
|
|
37
|
+
// now hover "contact" trigger (rightmost, far from "about")
|
|
38
|
+
const contactTrigger = page.locator('#nav-trigger-contact')
|
|
39
|
+
const contactBox = await contactTrigger.boundingBox()
|
|
40
|
+
expect(contactBox).toBeTruthy()
|
|
41
|
+
await contactTrigger.hover()
|
|
42
|
+
await page.waitForTimeout(500)
|
|
43
|
+
await expect(content).toBeVisible()
|
|
44
|
+
|
|
45
|
+
// record content position when anchored to "contact"
|
|
46
|
+
const contactContentBox = await content.boundingBox()
|
|
47
|
+
expect(contactContentBox).toBeTruthy()
|
|
48
|
+
|
|
49
|
+
// the content x should be near "contact" trigger, NOT near "about" trigger.
|
|
50
|
+
// if the bug is present, content would start near aboutContentBox.x and
|
|
51
|
+
// slide to contactContentBox.x — we catch this by checking the FIRST frame.
|
|
52
|
+
// since we waited 500ms (full transition), the final position should be correct
|
|
53
|
+
// either way, but let's verify the position is near the contact trigger.
|
|
54
|
+
const contactTriggerCenter = contactBox!.x + contactBox!.width / 2
|
|
55
|
+
const contentCenter = contactContentBox!.x + contactContentBox!.width / 2
|
|
56
|
+
|
|
57
|
+
// content should be roughly centered on the contact trigger
|
|
58
|
+
expect(Math.abs(contactTriggerCenter - contentCenter)).toBeLessThan(
|
|
59
|
+
contactContentBox!.width / 2 + 50
|
|
60
|
+
)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('close then reopen on different trigger: initial position is correct (no animation from old)', async ({
|
|
64
|
+
page,
|
|
65
|
+
}) => {
|
|
66
|
+
const content = page.locator('#nav-content')
|
|
67
|
+
|
|
68
|
+
// hover "about" trigger
|
|
69
|
+
const aboutTrigger = page.locator('#nav-trigger-about')
|
|
70
|
+
await aboutTrigger.hover()
|
|
71
|
+
await page.waitForTimeout(500)
|
|
72
|
+
await expect(content).toBeVisible()
|
|
73
|
+
|
|
74
|
+
const aboutContentBox = await content.boundingBox()
|
|
75
|
+
|
|
76
|
+
// fully close
|
|
77
|
+
await page.mouse.move(10, 600, { steps: 2 })
|
|
78
|
+
await page.waitForTimeout(800)
|
|
79
|
+
await expect(content).not.toBeVisible()
|
|
80
|
+
|
|
81
|
+
// hover "contact" trigger
|
|
82
|
+
const contactTrigger = page.locator('#nav-trigger-contact')
|
|
83
|
+
await contactTrigger.hover()
|
|
84
|
+
|
|
85
|
+
// check position EARLY — before the full 500ms transition
|
|
86
|
+
// if there's a slide-from-old-position bug, the early frame shows the old x
|
|
87
|
+
await page.waitForTimeout(100) // just enough for popover to appear
|
|
88
|
+
await expect(content).toBeVisible()
|
|
89
|
+
|
|
90
|
+
const earlyBox = await content.boundingBox()
|
|
91
|
+
expect(earlyBox).toBeTruthy()
|
|
92
|
+
|
|
93
|
+
// wait for full settle
|
|
94
|
+
await page.waitForTimeout(500)
|
|
95
|
+
const finalBox = await content.boundingBox()
|
|
96
|
+
expect(finalBox).toBeTruthy()
|
|
97
|
+
|
|
98
|
+
// the early position should be very close to the final position
|
|
99
|
+
// (no sliding from old trigger). allow tolerance for enter animation y offset.
|
|
100
|
+
const xDrift = Math.abs(earlyBox!.x - finalBox!.x)
|
|
101
|
+
expect(xDrift).toBeLessThan(20) // should be ~0 if no slide
|
|
102
|
+
|
|
103
|
+
// also verify it's NOT near the "about" position
|
|
104
|
+
if (aboutContentBox) {
|
|
105
|
+
const distFromAbout = Math.abs(earlyBox!.x - aboutContentBox.x)
|
|
106
|
+
const distFromFinal = Math.abs(earlyBox!.x - finalBox!.x)
|
|
107
|
+
// early box should be closer to final (contact) than to about
|
|
108
|
+
expect(distFromFinal).toBeLessThan(distFromAbout + 10)
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
})
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/test'
|
|
2
|
+
import { setupPage } from './test-utils'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Scoped multi-trigger popover with animatePosition:
|
|
6
|
+
* when switching between triggers, the popover content should smoothly
|
|
7
|
+
* animate its position — NOT snap instantly.
|
|
8
|
+
*/
|
|
9
|
+
test.describe('Popover hoverable scoped position animation', () => {
|
|
10
|
+
test.beforeEach(async ({ page }, testInfo) => {
|
|
11
|
+
const driver = (testInfo.project?.metadata as any)?.animationDriver
|
|
12
|
+
// animatePosition works via CSS transitions — only test on css/motion drivers
|
|
13
|
+
if (driver !== 'css' && driver !== 'motion') {
|
|
14
|
+
test.skip()
|
|
15
|
+
return
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
await setupPage(page, {
|
|
19
|
+
name: 'PopoverHoverableScopedCase',
|
|
20
|
+
type: 'useCase',
|
|
21
|
+
searchParams: { animationDriver: driver },
|
|
22
|
+
})
|
|
23
|
+
await page.waitForLoadState('networkidle')
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test('position smoothly animates when switching between triggers', async ({ page }) => {
|
|
27
|
+
const aboutTrigger = page.locator('#nav-trigger-about')
|
|
28
|
+
const contactTrigger = page.locator('#nav-trigger-contact')
|
|
29
|
+
const content = page.locator('#nav-content')
|
|
30
|
+
|
|
31
|
+
// hover about to open popover
|
|
32
|
+
await aboutTrigger.hover()
|
|
33
|
+
await page.waitForTimeout(450)
|
|
34
|
+
await expect(content).toBeVisible({ timeout: 3000 })
|
|
35
|
+
|
|
36
|
+
// wait for enter animation to settle
|
|
37
|
+
await page.waitForTimeout(600)
|
|
38
|
+
|
|
39
|
+
// get initial content X position (anchored near "about")
|
|
40
|
+
const posA = await content.evaluate((el) => {
|
|
41
|
+
const rect = el.getBoundingClientRect()
|
|
42
|
+
return { x: rect.left, y: rect.top }
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
// start tracking positions at 60fps
|
|
46
|
+
await page.evaluate(() => {
|
|
47
|
+
;(window as any).__xPositions = []
|
|
48
|
+
const track = () => {
|
|
49
|
+
const el = document.getElementById('nav-content')
|
|
50
|
+
if (el) {
|
|
51
|
+
const rect = el.getBoundingClientRect()
|
|
52
|
+
;(window as any).__xPositions.push({
|
|
53
|
+
x: rect.left,
|
|
54
|
+
time: Date.now(),
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
;(window as any).__trackRaf = requestAnimationFrame(track)
|
|
58
|
+
}
|
|
59
|
+
;(window as any).__trackRaf = requestAnimationFrame(track)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
// switch to contact trigger (rightmost)
|
|
63
|
+
await contactTrigger.hover()
|
|
64
|
+
|
|
65
|
+
// wait for the 500ms position transition to complete
|
|
66
|
+
await page.waitForTimeout(700)
|
|
67
|
+
|
|
68
|
+
// stop tracking
|
|
69
|
+
await page.evaluate(() => cancelAnimationFrame((window as any).__trackRaf))
|
|
70
|
+
|
|
71
|
+
// get final content X position (should be anchored near "contact")
|
|
72
|
+
const posB = await content.evaluate((el) => {
|
|
73
|
+
const rect = el.getBoundingClientRect()
|
|
74
|
+
return { x: rect.left, y: rect.top }
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
const positions: { x: number; time: number }[] = await page.evaluate(
|
|
78
|
+
() => (window as any).__xPositions
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
// the two positions should be different (contact is to the right of about)
|
|
82
|
+
const xDiff = Math.abs(posB.x - posA.x)
|
|
83
|
+
expect(xDiff).toBeGreaterThan(20)
|
|
84
|
+
|
|
85
|
+
// check for intermediate positions: if animatePosition works correctly,
|
|
86
|
+
// we should see X values between posA.x and posB.x during the transition.
|
|
87
|
+
// if it snaps, all positions will be either at posA.x or posB.x.
|
|
88
|
+
const minX = Math.min(posA.x, posB.x)
|
|
89
|
+
const maxX = Math.max(posA.x, posB.x)
|
|
90
|
+
const margin = xDiff * 0.15
|
|
91
|
+
|
|
92
|
+
const intermediatePositions = positions.filter((p) => {
|
|
93
|
+
return p.x > minX + margin && p.x < maxX - margin
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
expect(
|
|
97
|
+
intermediatePositions.length,
|
|
98
|
+
`Position should animate smoothly between triggers. ` +
|
|
99
|
+
`posA.x=${posA.x.toFixed(1)}, posB.x=${posB.x.toFixed(1)}, ` +
|
|
100
|
+
`found ${intermediatePositions.length} intermediate frames out of ${positions.length} total`
|
|
101
|
+
).toBeGreaterThan(2)
|
|
102
|
+
})
|
|
103
|
+
})
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/test'
|
|
2
|
+
import { setupPage } from './test-utils'
|
|
3
|
+
|
|
4
|
+
// stress tests for scoped hoverable popover with multiple triggers.
|
|
5
|
+
// hammers the interaction between safePolygon, multi-trigger switching,
|
|
6
|
+
// content interaction, diagonal movement, and open/close cycles.
|
|
7
|
+
|
|
8
|
+
test.describe('Popover hoverable stress', () => {
|
|
9
|
+
test.beforeEach(async ({ page }) => {
|
|
10
|
+
await setupPage(page, {
|
|
11
|
+
name: 'PopoverHoverableRapidCase',
|
|
12
|
+
type: 'useCase',
|
|
13
|
+
searchParams: { animationDriver: 'css' },
|
|
14
|
+
})
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
test('trigger → content → different trigger keeps popover open', async ({ page }) => {
|
|
18
|
+
const content = page.locator('#rapid-content')
|
|
19
|
+
const triggerA = page.locator('#rapid-trigger-a')
|
|
20
|
+
const triggerD = page.locator('#rapid-trigger-d')
|
|
21
|
+
|
|
22
|
+
// open on A
|
|
23
|
+
await triggerA.hover()
|
|
24
|
+
await page.waitForTimeout(150)
|
|
25
|
+
await expect(content).toBeVisible()
|
|
26
|
+
|
|
27
|
+
// move into the content
|
|
28
|
+
const cBox = await content.boundingBox()
|
|
29
|
+
await page.mouse.move(cBox!.x + cBox!.width / 2, cBox!.y + cBox!.height / 2)
|
|
30
|
+
await page.waitForTimeout(100)
|
|
31
|
+
await expect(content).toBeVisible()
|
|
32
|
+
|
|
33
|
+
// move from content back up to trigger D (skipping B, C)
|
|
34
|
+
const dBox = await triggerD.boundingBox()
|
|
35
|
+
await page.mouse.move(dBox!.x + dBox!.width / 2, dBox!.y + dBox!.height / 2, {
|
|
36
|
+
steps: 5,
|
|
37
|
+
})
|
|
38
|
+
await page.waitForTimeout(150)
|
|
39
|
+
|
|
40
|
+
await expect(content).toBeVisible()
|
|
41
|
+
await expect(page.locator('#rapid-panel-label')).toHaveText('Panel D')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('diagonal movement through safe polygon keeps popover open', async ({ page }) => {
|
|
45
|
+
const content = page.locator('#rapid-content')
|
|
46
|
+
const triggerA = page.locator('#rapid-trigger-a')
|
|
47
|
+
|
|
48
|
+
// open on A
|
|
49
|
+
await triggerA.hover()
|
|
50
|
+
await page.waitForTimeout(150)
|
|
51
|
+
await expect(content).toBeVisible()
|
|
52
|
+
|
|
53
|
+
// get positions for diagonal sweep from trigger toward content corner
|
|
54
|
+
const aBox = await triggerA.boundingBox()
|
|
55
|
+
const cBox = await content.boundingBox()
|
|
56
|
+
|
|
57
|
+
// move diagonally from trigger bottom-right toward content top-left
|
|
58
|
+
const steps = 8
|
|
59
|
+
for (let i = 1; i <= steps; i++) {
|
|
60
|
+
const t = i / steps
|
|
61
|
+
const x = aBox!.x + aBox!.width * (1 - t * 0.3) // drift slightly left
|
|
62
|
+
const y = aBox!.y + aBox!.height + (cBox!.y - aBox!.y - aBox!.height) * t
|
|
63
|
+
await page.mouse.move(x, y)
|
|
64
|
+
await page.waitForTimeout(15)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// land on content
|
|
68
|
+
await page.mouse.move(cBox!.x + cBox!.width / 2, cBox!.y + 10)
|
|
69
|
+
await page.waitForTimeout(100)
|
|
70
|
+
await expect(content).toBeVisible()
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test('mouse leaving to empty space closes popover', async ({ page }) => {
|
|
74
|
+
const content = page.locator('#rapid-content')
|
|
75
|
+
const triggerC = page.locator('#rapid-trigger-c')
|
|
76
|
+
|
|
77
|
+
await triggerC.hover()
|
|
78
|
+
await page.waitForTimeout(150)
|
|
79
|
+
await expect(content).toBeVisible()
|
|
80
|
+
|
|
81
|
+
// move mouse far away from both trigger and content
|
|
82
|
+
await page.mouse.move(10, 10, { steps: 2 })
|
|
83
|
+
await page.waitForTimeout(300)
|
|
84
|
+
await expect(content).not.toBeVisible()
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
test('repeated open/close cycles work reliably', async ({ page }) => {
|
|
88
|
+
const content = page.locator('#rapid-content')
|
|
89
|
+
const triggerB = page.locator('#rapid-trigger-b')
|
|
90
|
+
|
|
91
|
+
for (let cycle = 0; cycle < 3; cycle++) {
|
|
92
|
+
// open
|
|
93
|
+
await triggerB.hover()
|
|
94
|
+
await page.waitForTimeout(150)
|
|
95
|
+
await expect(content).toBeVisible()
|
|
96
|
+
|
|
97
|
+
// close by moving away
|
|
98
|
+
await page.mouse.move(10, 10, { steps: 2 })
|
|
99
|
+
await page.waitForTimeout(300)
|
|
100
|
+
await expect(content).not.toBeVisible()
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
test('fast sweep then slow traverse through gap', async ({ page }) => {
|
|
105
|
+
const content = page.locator('#rapid-content')
|
|
106
|
+
const triggerA = page.locator('#rapid-trigger-a')
|
|
107
|
+
|
|
108
|
+
// open on A
|
|
109
|
+
await triggerA.hover()
|
|
110
|
+
await page.waitForTimeout(150)
|
|
111
|
+
await expect(content).toBeVisible()
|
|
112
|
+
|
|
113
|
+
// fast sweep to E
|
|
114
|
+
const triggerE = page.locator('#rapid-trigger-e')
|
|
115
|
+
const eBox = await triggerE.boundingBox()
|
|
116
|
+
await page.mouse.move(eBox!.x + eBox!.width / 2, eBox!.y + eBox!.height / 2, {
|
|
117
|
+
steps: 2,
|
|
118
|
+
})
|
|
119
|
+
await page.waitForTimeout(100)
|
|
120
|
+
|
|
121
|
+
// now SLOWLY move down through the gap to content
|
|
122
|
+
const cBox = await content.boundingBox()
|
|
123
|
+
const startY = eBox!.y + eBox!.height
|
|
124
|
+
const endY = cBox!.y + 10
|
|
125
|
+
const slowSteps = 12
|
|
126
|
+
for (let i = 1; i <= slowSteps; i++) {
|
|
127
|
+
const t = i / slowSteps
|
|
128
|
+
await page.mouse.move(eBox!.x + eBox!.width / 2, startY + (endY - startY) * t)
|
|
129
|
+
await page.waitForTimeout(30)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
await expect(content).toBeVisible()
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
// skipped in CI: runner is too slow for the tight timing in this sweep+enter sequence
|
|
136
|
+
test.skip(!!process.env.CI, 'flaky on slow CI runners')
|
|
137
|
+
test('sweep all triggers then enter content from last', async ({ page }) => {
|
|
138
|
+
const content = page.locator('#rapid-content')
|
|
139
|
+
const ids = ['a', 'b', 'c', 'd', 'e', 'f']
|
|
140
|
+
|
|
141
|
+
// open on first
|
|
142
|
+
await page.locator('#rapid-trigger-a').hover()
|
|
143
|
+
await page.waitForTimeout(150)
|
|
144
|
+
await expect(content).toBeVisible()
|
|
145
|
+
|
|
146
|
+
// sweep through all triggers
|
|
147
|
+
for (const id of ids.slice(1)) {
|
|
148
|
+
const el = page.locator(`#rapid-trigger-${id}`)
|
|
149
|
+
const box = await el.boundingBox()
|
|
150
|
+
await page.mouse.move(box!.x + box!.width / 2, box!.y + box!.height / 2, {
|
|
151
|
+
steps: 2,
|
|
152
|
+
})
|
|
153
|
+
await page.waitForTimeout(20)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// rest on F
|
|
157
|
+
await page.waitForTimeout(100)
|
|
158
|
+
await expect(content).toBeVisible()
|
|
159
|
+
await expect(page.locator('#rapid-panel-label')).toHaveText('Panel F')
|
|
160
|
+
|
|
161
|
+
// move into content from F
|
|
162
|
+
const cBox = await content.boundingBox()
|
|
163
|
+
await page.mouse.move(cBox!.x + cBox!.width / 2, cBox!.y + cBox!.height / 2, {
|
|
164
|
+
steps: 5,
|
|
165
|
+
})
|
|
166
|
+
await page.waitForTimeout(100)
|
|
167
|
+
await expect(content).toBeVisible()
|
|
168
|
+
})
|
|
169
|
+
})
|