@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,82 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/test'
|
|
2
|
+
import { setupPage } from './test-utils'
|
|
3
|
+
|
|
4
|
+
// regression: popover content should appear near its trigger, not fly in
|
|
5
|
+
// from the top-left corner. when floating-ui resets isPositioned on close
|
|
6
|
+
// but x/y retain stale values, the hide logic must still prevent the
|
|
7
|
+
// content from being visible at the wrong position.
|
|
8
|
+
|
|
9
|
+
// native animation driver has a pre-existing initial position bug
|
|
10
|
+
const driverName = process.env.HANZO_GUI_TEST_ANIMATION_DRIVER || ''
|
|
11
|
+
test.skip(
|
|
12
|
+
driverName === 'native',
|
|
13
|
+
'native driver has pre-existing initial position issue'
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
test.describe('Popover initial position', () => {
|
|
17
|
+
test('hoverable popover appears near trigger, not at top-left', async ({ page }) => {
|
|
18
|
+
await setupPage(page, {
|
|
19
|
+
name: 'PopoverHoverableScopedCase',
|
|
20
|
+
type: 'useCase',
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const content = page.locator('#nav-content')
|
|
24
|
+
const trigger = page.locator('#nav-trigger-about')
|
|
25
|
+
const triggerBox = await trigger.boundingBox()
|
|
26
|
+
expect(triggerBox).toBeTruthy()
|
|
27
|
+
|
|
28
|
+
// hover trigger to open
|
|
29
|
+
await trigger.hover()
|
|
30
|
+
await page.waitForTimeout(500)
|
|
31
|
+
await expect(content).toBeVisible()
|
|
32
|
+
|
|
33
|
+
const contentBox = await content.boundingBox()
|
|
34
|
+
expect(contentBox).toBeTruthy()
|
|
35
|
+
|
|
36
|
+
// content should be near the trigger, not at the page origin
|
|
37
|
+
// popover is placed below trigger, so content.y should be close to trigger bottom
|
|
38
|
+
const triggerBottom = triggerBox!.y + triggerBox!.height
|
|
39
|
+
expect(contentBox!.y).toBeGreaterThan(triggerBottom - 20)
|
|
40
|
+
expect(contentBox!.y).toBeLessThan(triggerBottom + 100)
|
|
41
|
+
|
|
42
|
+
// content x should be in the general area of the trigger, not at 0
|
|
43
|
+
expect(contentBox!.x).toBeGreaterThan(triggerBox!.x - contentBox!.width)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test('popover does not briefly appear at origin on reopen', async ({ page }) => {
|
|
47
|
+
await setupPage(page, {
|
|
48
|
+
name: 'PopoverHoverableScopedCase',
|
|
49
|
+
type: 'useCase',
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
const content = page.locator('#nav-content')
|
|
53
|
+
const trigger = page.locator('#nav-trigger-contact')
|
|
54
|
+
|
|
55
|
+
// open
|
|
56
|
+
await trigger.hover()
|
|
57
|
+
await page.waitForTimeout(500)
|
|
58
|
+
await expect(content).toBeVisible()
|
|
59
|
+
|
|
60
|
+
// close fully
|
|
61
|
+
await page.mouse.move(10, 600, { steps: 2 })
|
|
62
|
+
await page.waitForTimeout(800)
|
|
63
|
+
await expect(content).not.toBeVisible()
|
|
64
|
+
|
|
65
|
+
// reopen on same trigger and check position EARLY
|
|
66
|
+
await trigger.hover()
|
|
67
|
+
await page.waitForTimeout(100)
|
|
68
|
+
|
|
69
|
+
const earlyBox = await content.boundingBox()
|
|
70
|
+
if (earlyBox) {
|
|
71
|
+
const triggerBox = await trigger.boundingBox()
|
|
72
|
+
// if content is visible, it must NOT be at the top-left corner
|
|
73
|
+
// (the fly-from-origin bug shows content starting near 0,0)
|
|
74
|
+
const distFromOrigin = Math.sqrt(earlyBox.x ** 2 + earlyBox.y ** 2)
|
|
75
|
+
const triggerDist = Math.sqrt(
|
|
76
|
+
(earlyBox.x - triggerBox!.x) ** 2 + (earlyBox.y - triggerBox!.y) ** 2
|
|
77
|
+
)
|
|
78
|
+
// content should be closer to trigger than to the page origin
|
|
79
|
+
expect(triggerDist).toBeLessThan(distFromOrigin + 50)
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
})
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/test'
|
|
2
|
+
import { setupPage } from './test-utils'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Regression test for: skipping middleware when closed causes position/arrow
|
|
6
|
+
* glitches on reopen. When middleware is set to [] while closed, computePosition
|
|
7
|
+
* runs without offset/arrow/transformOrigin, producing wrong cached position.
|
|
8
|
+
* On reopen, the animation starts from that wrong position.
|
|
9
|
+
*/
|
|
10
|
+
test.describe('Popover middleware skip regression', () => {
|
|
11
|
+
test.beforeEach(async ({ page }, testInfo) => {
|
|
12
|
+
const driver = (testInfo.project?.metadata as any)?.animationDriver
|
|
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 is stable across close/reopen cycles at same trigger', async ({
|
|
27
|
+
page,
|
|
28
|
+
}) => {
|
|
29
|
+
const about = page.locator('#nav-trigger-about')
|
|
30
|
+
const content = page.locator('#nav-content')
|
|
31
|
+
|
|
32
|
+
// open at "about"
|
|
33
|
+
await about.hover()
|
|
34
|
+
await page.waitForTimeout(500)
|
|
35
|
+
await expect(content).toBeVisible({ timeout: 3000 })
|
|
36
|
+
await page.waitForTimeout(700)
|
|
37
|
+
|
|
38
|
+
// record settled position
|
|
39
|
+
const pos1 = await content.evaluate((el) => {
|
|
40
|
+
const r = el.getBoundingClientRect()
|
|
41
|
+
return { x: r.left, y: r.top }
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
// close
|
|
45
|
+
await page.mouse.move(0, 0)
|
|
46
|
+
await page.waitForTimeout(600)
|
|
47
|
+
|
|
48
|
+
// reopen at same trigger
|
|
49
|
+
await about.hover()
|
|
50
|
+
await page.waitForTimeout(500)
|
|
51
|
+
await expect(content).toBeVisible({ timeout: 3000 })
|
|
52
|
+
await page.waitForTimeout(700)
|
|
53
|
+
|
|
54
|
+
// record settled position again
|
|
55
|
+
const pos2 = await content.evaluate((el) => {
|
|
56
|
+
const r = el.getBoundingClientRect()
|
|
57
|
+
return { x: r.left, y: r.top }
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
// positions should match (same trigger, same content)
|
|
61
|
+
expect(
|
|
62
|
+
Math.abs(pos2.x - pos1.x),
|
|
63
|
+
`X position shifted by ${(pos2.x - pos1.x).toFixed(1)}px after close/reopen`
|
|
64
|
+
).toBeLessThan(2)
|
|
65
|
+
expect(
|
|
66
|
+
Math.abs(pos2.y - pos1.y),
|
|
67
|
+
`Y position shifted by ${(pos2.y - pos1.y).toFixed(1)}px after close/reopen`
|
|
68
|
+
).toBeLessThan(2)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
test('reopen animation starts near final position, not from origin', async ({
|
|
72
|
+
page,
|
|
73
|
+
}) => {
|
|
74
|
+
const about = page.locator('#nav-trigger-about')
|
|
75
|
+
const content = page.locator('#nav-content')
|
|
76
|
+
|
|
77
|
+
// first open to establish position
|
|
78
|
+
await about.hover()
|
|
79
|
+
await page.waitForTimeout(500)
|
|
80
|
+
await expect(content).toBeVisible({ timeout: 3000 })
|
|
81
|
+
await page.waitForTimeout(700)
|
|
82
|
+
|
|
83
|
+
const settledPos = await content.evaluate((el) => {
|
|
84
|
+
const r = el.getBoundingClientRect()
|
|
85
|
+
return { x: r.left, y: r.top }
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
// close
|
|
89
|
+
await page.mouse.move(0, 0)
|
|
90
|
+
await page.waitForTimeout(600)
|
|
91
|
+
|
|
92
|
+
// start tracking positions immediately, then reopen
|
|
93
|
+
await page.evaluate(() => {
|
|
94
|
+
;(window as any).__reopenPositions = []
|
|
95
|
+
;(window as any).__trackReopen = true
|
|
96
|
+
const track = () => {
|
|
97
|
+
if (!(window as any).__trackReopen) return
|
|
98
|
+
const el = document.getElementById('nav-content')
|
|
99
|
+
if (el) {
|
|
100
|
+
const style = window.getComputedStyle(el)
|
|
101
|
+
if (parseFloat(style.opacity) > 0.05) {
|
|
102
|
+
const r = el.getBoundingClientRect()
|
|
103
|
+
;(window as any).__reopenPositions.push({
|
|
104
|
+
x: r.left,
|
|
105
|
+
y: r.top,
|
|
106
|
+
opacity: style.opacity,
|
|
107
|
+
time: Date.now(),
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
requestAnimationFrame(track)
|
|
112
|
+
}
|
|
113
|
+
requestAnimationFrame(track)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
// reopen
|
|
117
|
+
await about.hover()
|
|
118
|
+
await page.waitForTimeout(500)
|
|
119
|
+
await expect(content).toBeVisible({ timeout: 3000 })
|
|
120
|
+
await page.waitForTimeout(700)
|
|
121
|
+
|
|
122
|
+
await page.evaluate(() => {
|
|
123
|
+
;(window as any).__trackReopen = false
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
const positions: { x: number; y: number; opacity: string; time: number }[] =
|
|
127
|
+
await page.evaluate(() => (window as any).__reopenPositions)
|
|
128
|
+
|
|
129
|
+
if (positions.length === 0) return
|
|
130
|
+
|
|
131
|
+
// the FIRST visible frame should be near the settled position, not at (0,0)
|
|
132
|
+
// or some other wrong location caused by missing middleware
|
|
133
|
+
const firstFrame = positions[0]
|
|
134
|
+
const distFromSettled = Math.sqrt(
|
|
135
|
+
(firstFrame.x - settledPos.x) ** 2 + (firstFrame.y - settledPos.y) ** 2
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
// allow up to 50px for enter animation movement (enterStyle y: -6)
|
|
139
|
+
// but reject positions that are hundreds of pixels away (middleware bug)
|
|
140
|
+
expect(
|
|
141
|
+
distFromSettled,
|
|
142
|
+
`First visible frame at (${firstFrame.x.toFixed(0)}, ${firstFrame.y.toFixed(0)}) ` +
|
|
143
|
+
`is ${distFromSettled.toFixed(0)}px from settled position ` +
|
|
144
|
+
`(${settledPos.x.toFixed(0)}, ${settledPos.y.toFixed(0)})`
|
|
145
|
+
).toBeLessThan(50)
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
test('switching triggers: no position jump on entry', async ({ page }) => {
|
|
149
|
+
const about = page.locator('#nav-trigger-about')
|
|
150
|
+
const contact = page.locator('#nav-trigger-contact')
|
|
151
|
+
const content = page.locator('#nav-content')
|
|
152
|
+
|
|
153
|
+
// open on about first
|
|
154
|
+
await about.hover()
|
|
155
|
+
await page.waitForTimeout(500)
|
|
156
|
+
await expect(content).toBeVisible({ timeout: 3000 })
|
|
157
|
+
await page.waitForTimeout(700)
|
|
158
|
+
|
|
159
|
+
// close
|
|
160
|
+
await page.mouse.move(0, 0)
|
|
161
|
+
await page.waitForTimeout(600)
|
|
162
|
+
|
|
163
|
+
// track positions during reopen at contact
|
|
164
|
+
await page.evaluate(() => {
|
|
165
|
+
;(window as any).__switchPositions = []
|
|
166
|
+
;(window as any).__trackSwitch = true
|
|
167
|
+
const track = () => {
|
|
168
|
+
if (!(window as any).__trackSwitch) return
|
|
169
|
+
const el = document.getElementById('nav-content')
|
|
170
|
+
if (el) {
|
|
171
|
+
const style = window.getComputedStyle(el)
|
|
172
|
+
if (parseFloat(style.opacity) > 0.05) {
|
|
173
|
+
const r = el.getBoundingClientRect()
|
|
174
|
+
;(window as any).__switchPositions.push({
|
|
175
|
+
x: r.left,
|
|
176
|
+
y: r.top,
|
|
177
|
+
time: Date.now(),
|
|
178
|
+
})
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
requestAnimationFrame(track)
|
|
182
|
+
}
|
|
183
|
+
requestAnimationFrame(track)
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
// open on contact
|
|
187
|
+
await contact.hover()
|
|
188
|
+
await page.waitForTimeout(500)
|
|
189
|
+
await expect(content).toBeVisible({ timeout: 3000 })
|
|
190
|
+
await page.waitForTimeout(700)
|
|
191
|
+
|
|
192
|
+
await page.evaluate(() => {
|
|
193
|
+
;(window as any).__trackSwitch = false
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
const contactPos = await content.evaluate((el) => {
|
|
197
|
+
const r = el.getBoundingClientRect()
|
|
198
|
+
return { x: r.left, y: r.top }
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
const positions: { x: number; y: number; time: number }[] = await page.evaluate(
|
|
202
|
+
() => (window as any).__switchPositions
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
if (positions.length < 3) return
|
|
206
|
+
|
|
207
|
+
// check that no frame during entry is wildly far from final position
|
|
208
|
+
// (would indicate middleware was missing during position computation)
|
|
209
|
+
const wildFrames = positions.filter((p) => {
|
|
210
|
+
const dist = Math.sqrt((p.x - contactPos.x) ** 2 + (p.y - contactPos.y) ** 2)
|
|
211
|
+
return dist > 100 // more than 100px from where it should end up
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
expect(
|
|
215
|
+
wildFrames.length,
|
|
216
|
+
`${wildFrames.length} frames were >100px from final position. ` +
|
|
217
|
+
`First wild frame: (${wildFrames[0]?.x.toFixed(0)}, ${wildFrames[0]?.y.toFixed(0)}), ` +
|
|
218
|
+
`final: (${contactPos.x.toFixed(0)}, ${contactPos.y.toFixed(0)})`
|
|
219
|
+
).toBe(0)
|
|
220
|
+
})
|
|
221
|
+
})
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/test'
|
|
2
|
+
import { setupPage } from './test-utils'
|
|
3
|
+
|
|
4
|
+
test('scoped popovers work', async ({ page }) => {
|
|
5
|
+
await setupPage(page, { name: 'PopoverScopedCase', type: 'useCase' })
|
|
6
|
+
|
|
7
|
+
// Wait for page to load
|
|
8
|
+
await page.waitForLoadState('networkidle')
|
|
9
|
+
|
|
10
|
+
async function testPopoverScoped(name: string) {
|
|
11
|
+
const trigger = page.getByTestId(name + '-trigger')
|
|
12
|
+
const content = page.getByTestId(name + '-popover-content')
|
|
13
|
+
const closeButton = page.getByTestId('popover-close')
|
|
14
|
+
|
|
15
|
+
// Check initial state
|
|
16
|
+
await expect(trigger).toBeVisible()
|
|
17
|
+
await expect(content).not.toBeVisible()
|
|
18
|
+
|
|
19
|
+
// Click trigger to open popover
|
|
20
|
+
await trigger.click()
|
|
21
|
+
|
|
22
|
+
// Wait for content to be visible
|
|
23
|
+
await expect(content).toBeVisible({ timeout: 5000 })
|
|
24
|
+
|
|
25
|
+
// Click close button
|
|
26
|
+
await closeButton.click()
|
|
27
|
+
|
|
28
|
+
// Verify popover is closed
|
|
29
|
+
await expect(content).not.toBeVisible()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
await testPopoverScoped('plain')
|
|
33
|
+
await testPopoverScoped('a')
|
|
34
|
+
await testPopoverScoped('b')
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test('popover scopes are isolated', async ({ page }) => {
|
|
38
|
+
await setupPage(page, { name: 'PopoverScopedCase', type: 'useCase' })
|
|
39
|
+
|
|
40
|
+
// Wait for page to load
|
|
41
|
+
await page.waitForLoadState('networkidle')
|
|
42
|
+
|
|
43
|
+
const plainTrigger = page.getByTestId('plain-trigger')
|
|
44
|
+
const aTrigger = page.getByTestId('a-trigger')
|
|
45
|
+
const bTrigger = page.getByTestId('b-trigger')
|
|
46
|
+
|
|
47
|
+
const plainContent = page.getByTestId('plain-popover-content')
|
|
48
|
+
const aContent = page.getByTestId('a-popover-content')
|
|
49
|
+
const bContent = page.getByTestId('b-popover-content')
|
|
50
|
+
|
|
51
|
+
// Open popover A
|
|
52
|
+
await aTrigger.click()
|
|
53
|
+
await expect(aContent).toBeVisible({ timeout: 5000 })
|
|
54
|
+
|
|
55
|
+
// Verify other popovers are not visible
|
|
56
|
+
await expect(plainContent).not.toBeVisible()
|
|
57
|
+
await expect(bContent).not.toBeVisible()
|
|
58
|
+
|
|
59
|
+
// Close popover A
|
|
60
|
+
await aContent.getByTestId('popover-close').click()
|
|
61
|
+
await expect(aContent).not.toBeVisible()
|
|
62
|
+
|
|
63
|
+
// Open popover B
|
|
64
|
+
await bTrigger.click()
|
|
65
|
+
await expect(bContent).toBeVisible({ timeout: 5000 })
|
|
66
|
+
|
|
67
|
+
// Verify other popovers are not visible
|
|
68
|
+
await expect(plainContent).not.toBeVisible()
|
|
69
|
+
await expect(aContent).not.toBeVisible()
|
|
70
|
+
|
|
71
|
+
// Close popover B
|
|
72
|
+
await bContent.getByTestId('popover-close').click()
|
|
73
|
+
await expect(bContent).not.toBeVisible()
|
|
74
|
+
|
|
75
|
+
// Open plain popover
|
|
76
|
+
await plainTrigger.click()
|
|
77
|
+
await expect(plainContent).toBeVisible({ timeout: 5000 })
|
|
78
|
+
|
|
79
|
+
// Verify other popovers are not visible
|
|
80
|
+
await expect(aContent).not.toBeVisible()
|
|
81
|
+
await expect(bContent).not.toBeVisible()
|
|
82
|
+
|
|
83
|
+
// Close plain popover
|
|
84
|
+
await plainContent.getByTestId('popover-close').click()
|
|
85
|
+
await expect(plainContent).not.toBeVisible()
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
test('scoped popovers adapt to sheets', async ({ page }) => {
|
|
89
|
+
await setupPage(page, {
|
|
90
|
+
name: 'PopoverScopedCase',
|
|
91
|
+
type: 'useCase',
|
|
92
|
+
adapt: true,
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
// Wait for page to load
|
|
96
|
+
await page.waitForLoadState('networkidle')
|
|
97
|
+
|
|
98
|
+
async function testPopoverAdapted(name: string) {
|
|
99
|
+
const trigger = page.getByTestId(`${name}-trigger`)
|
|
100
|
+
const popoverContent = page.getByTestId(`${name}-popover-content`)
|
|
101
|
+
|
|
102
|
+
// Click trigger to open sheet
|
|
103
|
+
await trigger.click()
|
|
104
|
+
|
|
105
|
+
const sheetContents = page.getByTestId(`${name}-sheet-contents`).first()
|
|
106
|
+
|
|
107
|
+
// Wait for sheet to be visible and open
|
|
108
|
+
await expect(sheetContents).toBeVisible({ timeout: 5000 })
|
|
109
|
+
await expect(sheetContents).toHaveAttribute('data-state', 'open', { timeout: 5000 })
|
|
110
|
+
|
|
111
|
+
// Check that popover content is inside sheet
|
|
112
|
+
await expect(sheetContents.locator(popoverContent)).toBeVisible()
|
|
113
|
+
|
|
114
|
+
const closeButton = sheetContents.getByTestId('popover-close')
|
|
115
|
+
// Click close button
|
|
116
|
+
await closeButton.click()
|
|
117
|
+
|
|
118
|
+
// Wait for sheet to close by checking data-state attribute
|
|
119
|
+
await expect(sheetContents).toHaveAttribute('data-state', 'closed', { timeout: 5000 })
|
|
120
|
+
|
|
121
|
+
// Verify sheet is visually off-screen
|
|
122
|
+
await expect(sheetContents).not.toBeInViewport({ ratio: 0.5 })
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
await testPopoverAdapted('plain')
|
|
126
|
+
await testPopoverAdapted('a')
|
|
127
|
+
await testPopoverAdapted('b')
|
|
128
|
+
})
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/test'
|
|
2
|
+
import { setupPage } from './test-utils'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Tests for position glitches when opening/closing scoped popovers
|
|
6
|
+
* across multiple targets with animatePosition enabled.
|
|
7
|
+
*
|
|
8
|
+
* Tracks frame-by-frame positions to detect:
|
|
9
|
+
* - Jumps to (0,0) or far-off positions during enter/exit
|
|
10
|
+
* - Arrow misalignment
|
|
11
|
+
* - Position instability after multiple open/close cycles
|
|
12
|
+
*/
|
|
13
|
+
test.describe('Popover scoped position glitch detection', () => {
|
|
14
|
+
test.beforeEach(async ({ page }, testInfo) => {
|
|
15
|
+
const driver = (testInfo.project?.metadata as any)?.animationDriver
|
|
16
|
+
if (driver !== 'css' && driver !== 'motion') {
|
|
17
|
+
test.skip()
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
await setupPage(page, {
|
|
22
|
+
name: 'PopoverHoverableScopedCase',
|
|
23
|
+
type: 'useCase',
|
|
24
|
+
searchParams: { animationDriver: driver },
|
|
25
|
+
})
|
|
26
|
+
await page.waitForLoadState('networkidle')
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('no far-off positions during open/close across targets', async ({ page }) => {
|
|
30
|
+
const about = page.locator('#nav-trigger-about')
|
|
31
|
+
const blog = page.locator('#nav-trigger-blog')
|
|
32
|
+
const contact = page.locator('#nav-trigger-contact')
|
|
33
|
+
const content = page.locator('#nav-content')
|
|
34
|
+
|
|
35
|
+
// get trigger positions for reference
|
|
36
|
+
const triggerBounds = await page.evaluate(() => {
|
|
37
|
+
const triggers = ['about', 'blog', 'contact'].map((id) => {
|
|
38
|
+
const el = document.getElementById(`nav-trigger-${id}`)!
|
|
39
|
+
const r = el.getBoundingClientRect()
|
|
40
|
+
return { id, x: r.left, y: r.top, right: r.right, bottom: r.bottom }
|
|
41
|
+
})
|
|
42
|
+
return triggers
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
// reasonable bounds: content should be within viewport, near the triggers
|
|
46
|
+
const viewportWidth = await page.evaluate(() => window.innerWidth)
|
|
47
|
+
const viewportHeight = await page.evaluate(() => window.innerHeight)
|
|
48
|
+
const triggerMinX = Math.min(...triggerBounds.map((t) => t.x))
|
|
49
|
+
const triggerMaxRight = Math.max(...triggerBounds.map((t) => t.right))
|
|
50
|
+
const triggerBottom = Math.max(...triggerBounds.map((t) => t.bottom))
|
|
51
|
+
|
|
52
|
+
// start position tracker
|
|
53
|
+
await page.evaluate(() => {
|
|
54
|
+
;(window as any).__posLog = []
|
|
55
|
+
;(window as any).__trackPositions = true
|
|
56
|
+
const track = () => {
|
|
57
|
+
if (!(window as any).__trackPositions) return
|
|
58
|
+
const el = document.getElementById('nav-content')
|
|
59
|
+
if (el) {
|
|
60
|
+
const r = el.getBoundingClientRect()
|
|
61
|
+
const style = window.getComputedStyle(el)
|
|
62
|
+
;(window as any).__posLog.push({
|
|
63
|
+
x: r.left,
|
|
64
|
+
y: r.top,
|
|
65
|
+
w: r.width,
|
|
66
|
+
h: r.height,
|
|
67
|
+
opacity: style.opacity,
|
|
68
|
+
transform: style.transform,
|
|
69
|
+
time: Date.now(),
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
requestAnimationFrame(track)
|
|
73
|
+
}
|
|
74
|
+
requestAnimationFrame(track)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
// cycle 1: open on about, wait, close
|
|
78
|
+
await about.hover()
|
|
79
|
+
await page.waitForTimeout(500)
|
|
80
|
+
await expect(content).toBeVisible({ timeout: 3000 })
|
|
81
|
+
await page.waitForTimeout(600)
|
|
82
|
+
await page.mouse.move(0, 0)
|
|
83
|
+
await page.waitForTimeout(500)
|
|
84
|
+
|
|
85
|
+
// cycle 2: open on contact
|
|
86
|
+
await contact.hover()
|
|
87
|
+
await page.waitForTimeout(500)
|
|
88
|
+
await expect(content).toBeVisible({ timeout: 3000 })
|
|
89
|
+
await page.waitForTimeout(600)
|
|
90
|
+
await page.mouse.move(0, 0)
|
|
91
|
+
await page.waitForTimeout(500)
|
|
92
|
+
|
|
93
|
+
// cycle 3: open on blog
|
|
94
|
+
await blog.hover()
|
|
95
|
+
await page.waitForTimeout(500)
|
|
96
|
+
await expect(content).toBeVisible({ timeout: 3000 })
|
|
97
|
+
await page.waitForTimeout(600)
|
|
98
|
+
|
|
99
|
+
// cycle 4: switch directly to about (no close in between)
|
|
100
|
+
await about.hover()
|
|
101
|
+
await page.waitForTimeout(700)
|
|
102
|
+
|
|
103
|
+
// cycle 5: switch directly to contact
|
|
104
|
+
await contact.hover()
|
|
105
|
+
await page.waitForTimeout(700)
|
|
106
|
+
|
|
107
|
+
// stop tracking
|
|
108
|
+
await page.evaluate(() => {
|
|
109
|
+
;(window as any).__trackPositions = false
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
const positions: {
|
|
113
|
+
x: number
|
|
114
|
+
y: number
|
|
115
|
+
w: number
|
|
116
|
+
h: number
|
|
117
|
+
opacity: string
|
|
118
|
+
transform: string
|
|
119
|
+
time: number
|
|
120
|
+
}[] = await page.evaluate(() => (window as any).__posLog)
|
|
121
|
+
|
|
122
|
+
// filter to frames where content is visible (opacity > 0)
|
|
123
|
+
const visibleFrames = positions.filter((p) => parseFloat(p.opacity) > 0.1)
|
|
124
|
+
|
|
125
|
+
// check: no frame should have content at a far-off position
|
|
126
|
+
// "far off" = more than 200px outside the trigger area
|
|
127
|
+
const badFrames = visibleFrames.filter((p) => {
|
|
128
|
+
const tooFarLeft = p.x < triggerMinX - 200
|
|
129
|
+
const tooFarRight = p.x > triggerMaxRight + 200
|
|
130
|
+
const tooFarUp = p.y < 0
|
|
131
|
+
const tooFarDown = p.y > viewportHeight + 100
|
|
132
|
+
const atOrigin = p.x === 0 && p.y === 0
|
|
133
|
+
return tooFarLeft || tooFarRight || tooFarUp || tooFarDown || atOrigin
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
if (badFrames.length > 0) {
|
|
137
|
+
console.log('bad frames:', JSON.stringify(badFrames.slice(0, 5), null, 2))
|
|
138
|
+
console.log(
|
|
139
|
+
'trigger area:',
|
|
140
|
+
JSON.stringify({ triggerMinX, triggerMaxRight, triggerBottom })
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
expect(
|
|
145
|
+
badFrames.length,
|
|
146
|
+
`Found ${badFrames.length} frames with far-off positions. ` +
|
|
147
|
+
`First bad: x=${badFrames[0]?.x}, y=${badFrames[0]?.y}`
|
|
148
|
+
).toBe(0)
|
|
149
|
+
|
|
150
|
+
// check: no sudden large jumps between consecutive visible frames
|
|
151
|
+
const jumpThreshold = 150 // px
|
|
152
|
+
const jumps: { from: (typeof visibleFrames)[0]; to: (typeof visibleFrames)[0] }[] = []
|
|
153
|
+
for (let i = 1; i < visibleFrames.length; i++) {
|
|
154
|
+
const dx = Math.abs(visibleFrames[i].x - visibleFrames[i - 1].x)
|
|
155
|
+
const dy = Math.abs(visibleFrames[i].y - visibleFrames[i - 1].y)
|
|
156
|
+
if (dx > jumpThreshold || dy > jumpThreshold) {
|
|
157
|
+
jumps.push({ from: visibleFrames[i - 1], to: visibleFrames[i] })
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (jumps.length > 0) {
|
|
162
|
+
console.log(
|
|
163
|
+
'position jumps:',
|
|
164
|
+
JSON.stringify(
|
|
165
|
+
jumps.slice(0, 3).map((j) => ({
|
|
166
|
+
from: { x: j.from.x.toFixed(1), y: j.from.y.toFixed(1) },
|
|
167
|
+
to: { x: j.to.x.toFixed(1), y: j.to.y.toFixed(1) },
|
|
168
|
+
dx: Math.abs(j.to.x - j.from.x).toFixed(1),
|
|
169
|
+
dy: Math.abs(j.to.y - j.from.y).toFixed(1),
|
|
170
|
+
})),
|
|
171
|
+
null,
|
|
172
|
+
2
|
|
173
|
+
)
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// allow some jumps (close→reopen at different target is expected)
|
|
178
|
+
// but there shouldn't be jumps DURING an animation
|
|
179
|
+
expect(
|
|
180
|
+
jumps.length,
|
|
181
|
+
`Found ${jumps.length} large position jumps (>${jumpThreshold}px)`
|
|
182
|
+
).toBeLessThanOrEqual(4) // at most one jump per close→reopen transition
|
|
183
|
+
})
|
|
184
|
+
})
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/test'
|
|
2
|
+
import { setupPage } from './test-utils'
|
|
3
|
+
|
|
4
|
+
// helper to extract render count from text like "renders: 1"
|
|
5
|
+
function parseRenderCount(text: string | null): number {
|
|
6
|
+
if (!text) return 0
|
|
7
|
+
const match = text.match(/renders:\s*(\d+)/)
|
|
8
|
+
return match ? Number(match[1]) : 0
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
test.describe('Popover Trigger Render Isolation', () => {
|
|
12
|
+
test.beforeEach(async ({ page }) => {
|
|
13
|
+
await setupPage(page, { name: 'PopoverTriggerIsolationCase', type: 'useCase' })
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
test('only the active trigger re-renders when popover opens', async ({ page }) => {
|
|
17
|
+
await page.waitForLoadState('networkidle')
|
|
18
|
+
|
|
19
|
+
// get initial render counts
|
|
20
|
+
const getRenderCounts = async () => {
|
|
21
|
+
return {
|
|
22
|
+
trigger1: parseRenderCount(
|
|
23
|
+
await page.getByTestId('isolated-trigger-1-render-count').textContent()
|
|
24
|
+
),
|
|
25
|
+
trigger2: parseRenderCount(
|
|
26
|
+
await page.getByTestId('isolated-trigger-2-render-count').textContent()
|
|
27
|
+
),
|
|
28
|
+
trigger3: parseRenderCount(
|
|
29
|
+
await page.getByTestId('isolated-trigger-3-render-count').textContent()
|
|
30
|
+
),
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const initialCounts = await getRenderCounts()
|
|
35
|
+
|
|
36
|
+
// click trigger1 to open popover
|
|
37
|
+
await page.getByTestId('isolated-trigger-1').click()
|
|
38
|
+
|
|
39
|
+
// wait for popover to open
|
|
40
|
+
const content = page.getByTestId('isolated-popover-content')
|
|
41
|
+
await expect(content).toBeVisible({ timeout: 5000 })
|
|
42
|
+
|
|
43
|
+
// check render counts after opening
|
|
44
|
+
const afterOpenCounts = await getRenderCounts()
|
|
45
|
+
|
|
46
|
+
// trigger1 (active) may re-render to update aria-expanded
|
|
47
|
+
// but trigger2 and trigger3 (inactive) should NOT re-render
|
|
48
|
+
expect(afterOpenCounts.trigger2).toBe(initialCounts.trigger2)
|
|
49
|
+
expect(afterOpenCounts.trigger3).toBe(initialCounts.trigger3)
|
|
50
|
+
|
|
51
|
+
// close the popover
|
|
52
|
+
await page.getByTestId('isolated-close').click()
|
|
53
|
+
await expect(content).not.toBeVisible({ timeout: 5000 })
|
|
54
|
+
|
|
55
|
+
// check render counts after closing
|
|
56
|
+
const afterCloseCounts = await getRenderCounts()
|
|
57
|
+
|
|
58
|
+
// inactive triggers should still not have re-rendered
|
|
59
|
+
expect(afterCloseCounts.trigger2).toBe(initialCounts.trigger2)
|
|
60
|
+
expect(afterCloseCounts.trigger3).toBe(initialCounts.trigger3)
|
|
61
|
+
})
|
|
62
|
+
})
|