@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,174 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/test'
|
|
2
|
+
import { setupPage } from './test-utils'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Tests that clicking to close while an enter animation is still playing
|
|
6
|
+
* doesn't cause a visual snap-back (the element shouldn't jump back to
|
|
7
|
+
* its enterStyle values before starting the exit animation).
|
|
8
|
+
*
|
|
9
|
+
* Bug: motion driver calls controls.stop() before the mid-flight capture,
|
|
10
|
+
* causing the WAAPI animation to cancel and the element to snap back to
|
|
11
|
+
* its pre-animation inline styles for one frame.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
test.beforeEach(async ({ page }, testInfo) => {
|
|
15
|
+
test.skip(
|
|
16
|
+
testInfo.project.name === 'animated-css',
|
|
17
|
+
'CSS transitions handle mid-flight interruption differently — this tests WAAPI behavior'
|
|
18
|
+
)
|
|
19
|
+
await setupPage(page, { name: 'ClickDuringEnterCase', type: 'useCase' })
|
|
20
|
+
await page.waitForTimeout(500)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
test('no opacity snap-back when exit triggered during enter animation', async ({
|
|
24
|
+
page,
|
|
25
|
+
}) => {
|
|
26
|
+
// start per-frame opacity sampling
|
|
27
|
+
await page.evaluate(() => {
|
|
28
|
+
;(window as any).__opacityLog = [] as number[]
|
|
29
|
+
;(window as any).__rafRunning = true
|
|
30
|
+
const track = () => {
|
|
31
|
+
if (!(window as any).__rafRunning) return
|
|
32
|
+
const el = document.querySelector('[data-testid="click-enter-target"]')
|
|
33
|
+
if (el) {
|
|
34
|
+
;(window as any).__opacityLog.push(parseFloat(getComputedStyle(el).opacity))
|
|
35
|
+
}
|
|
36
|
+
requestAnimationFrame(track)
|
|
37
|
+
}
|
|
38
|
+
requestAnimationFrame(track)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
// show element (starts enter animation: opacity 0 -> 1)
|
|
42
|
+
await page.getByTestId('click-enter-show').click()
|
|
43
|
+
// wait just enough for animation to start (50-100ms into a ~300ms animation)
|
|
44
|
+
await page.waitForTimeout(80)
|
|
45
|
+
// immediately hide (triggers exit while enter is mid-flight)
|
|
46
|
+
await page.getByTestId('click-enter-hide').click()
|
|
47
|
+
// wait for exit animation to complete
|
|
48
|
+
await page.waitForTimeout(1500)
|
|
49
|
+
|
|
50
|
+
// stop sampling
|
|
51
|
+
await page.evaluate(() => {
|
|
52
|
+
;(window as any).__rafRunning = false
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
const frames: number[] = await page.evaluate(() => (window as any).__opacityLog)
|
|
56
|
+
|
|
57
|
+
// find the peak opacity (the highest opacity reached during enter)
|
|
58
|
+
const peakOpacity = Math.max(...frames)
|
|
59
|
+
const peakIndex = frames.indexOf(peakOpacity)
|
|
60
|
+
|
|
61
|
+
// after the peak, opacity should monotonically decrease (exit animation)
|
|
62
|
+
// but the bug causes a snap-back: opacity jumps DOWN then back UP then down
|
|
63
|
+
// detect this by looking for any frame after peak where opacity increases
|
|
64
|
+
// significantly compared to a previous lower value
|
|
65
|
+
|
|
66
|
+
// simpler check: look for any frame that drops significantly below its
|
|
67
|
+
// neighbors (a "dip" indicates snap-back)
|
|
68
|
+
let maxDip = 0
|
|
69
|
+
for (let i = 1; i < frames.length - 1; i++) {
|
|
70
|
+
const prev = frames[i - 1]
|
|
71
|
+
const curr = frames[i]
|
|
72
|
+
const next = frames[i + 1]
|
|
73
|
+
// a dip is when current is significantly lower than both neighbors
|
|
74
|
+
if (curr < prev && curr < next) {
|
|
75
|
+
const dip = Math.min(prev - curr, next - curr)
|
|
76
|
+
if (dip > maxDip) maxDip = dip
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// also check for any opacity increase after exit begins
|
|
81
|
+
// find the first frame where opacity starts decreasing after the peak
|
|
82
|
+
let exitStartIndex = peakIndex
|
|
83
|
+
for (let i = peakIndex + 1; i < frames.length; i++) {
|
|
84
|
+
if (frames[i] < frames[i - 1] - 0.01) {
|
|
85
|
+
exitStartIndex = i
|
|
86
|
+
break
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// after exit starts, look for any significant increase (snap-back indicator)
|
|
91
|
+
let maxIncrease = 0
|
|
92
|
+
for (let i = exitStartIndex + 1; i < frames.length; i++) {
|
|
93
|
+
const increase = frames[i] - frames[i - 1]
|
|
94
|
+
if (increase > maxIncrease) maxIncrease = increase
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
console.log('opacity frames:', JSON.stringify(frames.map((f) => +f.toFixed(3))))
|
|
98
|
+
console.log(`peak: ${peakOpacity.toFixed(3)} at frame ${peakIndex}`)
|
|
99
|
+
console.log(`max dip: ${maxDip.toFixed(3)}`)
|
|
100
|
+
console.log(`max increase after exit: ${maxIncrease.toFixed(3)}`)
|
|
101
|
+
|
|
102
|
+
// there should be no significant dip (snap-back)
|
|
103
|
+
// tolerance: 0.15 allows for minor animation interpolation variance
|
|
104
|
+
expect(
|
|
105
|
+
maxDip,
|
|
106
|
+
`Opacity had a snap-back dip of ${maxDip.toFixed(3)} - element jumped to enterStyle then back. Frames: ${JSON.stringify(frames.slice(0, 30).map((f) => +f.toFixed(2)))}`
|
|
107
|
+
).toBeLessThan(0.15)
|
|
108
|
+
|
|
109
|
+
// there should be no significant increase after exit starts
|
|
110
|
+
expect(
|
|
111
|
+
maxIncrease,
|
|
112
|
+
`Opacity increased by ${maxIncrease.toFixed(3)} after exit started - indicates snap-back. Frames: ${JSON.stringify(frames.slice(0, 30).map((f) => +f.toFixed(2)))}`
|
|
113
|
+
).toBeLessThan(0.15)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
test('no transform snap-back when exit triggered during enter animation', async ({
|
|
117
|
+
page,
|
|
118
|
+
}) => {
|
|
119
|
+
// start per-frame transform sampling
|
|
120
|
+
await page.evaluate(() => {
|
|
121
|
+
;(window as any).__transformLog = [] as { y: number; scale: number }[]
|
|
122
|
+
;(window as any).__rafRunning = true
|
|
123
|
+
const track = () => {
|
|
124
|
+
if (!(window as any).__rafRunning) return
|
|
125
|
+
const el = document.querySelector('[data-testid="click-enter-target"]')
|
|
126
|
+
if (el) {
|
|
127
|
+
const transform = getComputedStyle(el).transform
|
|
128
|
+
let y = 0
|
|
129
|
+
let scale = 1
|
|
130
|
+
if (transform && transform !== 'none') {
|
|
131
|
+
const matrix = new DOMMatrix(transform)
|
|
132
|
+
y = matrix.m42 // translateY
|
|
133
|
+
scale = matrix.a // scaleX (assuming uniform)
|
|
134
|
+
}
|
|
135
|
+
;(window as any).__transformLog.push({ y, scale })
|
|
136
|
+
}
|
|
137
|
+
requestAnimationFrame(track)
|
|
138
|
+
}
|
|
139
|
+
requestAnimationFrame(track)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
await page.getByTestId('click-enter-show').click()
|
|
143
|
+
await page.waitForTimeout(80)
|
|
144
|
+
await page.getByTestId('click-enter-hide').click()
|
|
145
|
+
await page.waitForTimeout(1500)
|
|
146
|
+
|
|
147
|
+
await page.evaluate(() => {
|
|
148
|
+
;(window as any).__rafRunning = false
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
const frames: { y: number; scale: number }[] = await page.evaluate(
|
|
152
|
+
() => (window as any).__transformLog
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
// check for Y-position jumps (snap-back to enterStyle y: -10)
|
|
156
|
+
let maxYJump = 0
|
|
157
|
+
for (let i = 1; i < frames.length; i++) {
|
|
158
|
+
const jump = Math.abs(frames[i].y - frames[i - 1].y)
|
|
159
|
+
if (jump > maxYJump) maxYJump = jump
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
console.log(
|
|
163
|
+
'Y positions:',
|
|
164
|
+
JSON.stringify(frames.slice(0, 30).map((f) => +f.y.toFixed(2)))
|
|
165
|
+
)
|
|
166
|
+
console.log(`max Y jump: ${maxYJump.toFixed(2)}`)
|
|
167
|
+
|
|
168
|
+
// with smooth animation, max per-frame jump should be small
|
|
169
|
+
// a snap-back would show a jump of 5+ pixels in one frame
|
|
170
|
+
expect(
|
|
171
|
+
maxYJump,
|
|
172
|
+
`Y position jumped ${maxYJump.toFixed(1)}px in one frame - indicates snap-back to enterStyle`
|
|
173
|
+
).toBeLessThan(5)
|
|
174
|
+
})
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/test'
|
|
2
|
+
import { setupPage } from './test-utils'
|
|
3
|
+
import { getStyles } from './utils'
|
|
4
|
+
import { TEST_IDS } from '../src/constants/test-ids'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Tests for GitHub issue #3620: Theme switching broken after v1.132.15
|
|
8
|
+
*
|
|
9
|
+
* Color tokens should act as fallbacks - if a theme defines a value for the same key,
|
|
10
|
+
* the theme value should take precedence over the color token.
|
|
11
|
+
*
|
|
12
|
+
* The bug was that Object.assign(theme, colorTokens) was overwriting theme values
|
|
13
|
+
* with color token values instead of the other way around.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
test.beforeEach(async ({ page }) => {
|
|
17
|
+
await setupPage(page, { name: 'ColorTokenFallback', type: 'useCase' })
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
test('theme value takes precedence over color token', async ({ page }) => {
|
|
21
|
+
// The light_ColorTokenTest theme defines customRed as #00ff00 (green)
|
|
22
|
+
// The color token customRed is #ff0000 (red)
|
|
23
|
+
// The theme value should win, so the background should be green
|
|
24
|
+
const square = page.locator(`#${TEST_IDS.colorTokenFallbackThemeValue}`)
|
|
25
|
+
await expect(square).toBeVisible()
|
|
26
|
+
|
|
27
|
+
const styles = await getStyles(square)
|
|
28
|
+
// rgb(0, 255, 0) is #00ff00 (green) - the theme value
|
|
29
|
+
// rgb(255, 0, 0) would be #ff0000 (red) - the color token value (wrong)
|
|
30
|
+
expect(styles.backgroundColor).toBe('rgb(0, 255, 0)')
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test('color token is used as fallback when theme does not define it', async ({
|
|
34
|
+
page,
|
|
35
|
+
}) => {
|
|
36
|
+
// The light_ColorTokenTest theme does NOT define customBlue
|
|
37
|
+
// The color token customBlue is #0000ff (blue)
|
|
38
|
+
// The color token should be used as a fallback
|
|
39
|
+
const square = page.locator(`#${TEST_IDS.colorTokenFallbackTokenValue}`)
|
|
40
|
+
await expect(square).toBeVisible()
|
|
41
|
+
|
|
42
|
+
const styles = await getStyles(square)
|
|
43
|
+
// rgb(0, 0, 255) is #0000ff (blue) - the color token fallback
|
|
44
|
+
expect(styles.backgroundColor).toBe('rgb(0, 0, 255)')
|
|
45
|
+
})
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/test'
|
|
2
|
+
import { setupPage } from './test-utils'
|
|
3
|
+
|
|
4
|
+
// integration tests for RN 0.82 DOM Node APIs via hanzo-gui component refs
|
|
5
|
+
// verifies that View/Text/ScrollView/Input refs expose the full DOM node API surface
|
|
6
|
+
|
|
7
|
+
test.beforeEach(async ({ page }) => {
|
|
8
|
+
await setupPage(page, { name: 'DOMNodeAPIs', type: 'useCase' })
|
|
9
|
+
// wait for results to render
|
|
10
|
+
await page.waitForSelector('#dom-node-results', { timeout: 5_000 })
|
|
11
|
+
await page.waitForFunction(
|
|
12
|
+
() => {
|
|
13
|
+
const results = document.getElementById('dom-node-results')
|
|
14
|
+
return results && results.children.length > 5
|
|
15
|
+
},
|
|
16
|
+
{ timeout: 5_000, polling: 50 }
|
|
17
|
+
)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
async function getResult(page, key: string): Promise<string> {
|
|
21
|
+
const el = page.locator(`[data-testid="result-${key}"]`)
|
|
22
|
+
const text = await el.textContent()
|
|
23
|
+
// format is "key=value"
|
|
24
|
+
return text?.split('=').slice(1).join('=') ?? ''
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// tree traversal
|
|
28
|
+
|
|
29
|
+
test('parentNode exists on child ref', async ({ page }) => {
|
|
30
|
+
expect(await getResult(page, 'parentNode-exists')).toBe('true')
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test('parentElement exists on child ref', async ({ page }) => {
|
|
34
|
+
expect(await getResult(page, 'parentElement-exists')).toBe('true')
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test('parent.contains(child) returns true', async ({ page }) => {
|
|
38
|
+
expect(await getResult(page, 'parent-contains-childA')).toBe('true')
|
|
39
|
+
expect(await getResult(page, 'parent-contains-childB')).toBe('true')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test('child.contains(parent) returns false', async ({ page }) => {
|
|
43
|
+
expect(await getResult(page, 'childA-contains-parent')).toBe('false')
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test('hasChildNodes returns true', async ({ page }) => {
|
|
47
|
+
expect(await getResult(page, 'hasChildNodes')).toBe('true')
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test('childNodes and children have length > 0', async ({ page }) => {
|
|
51
|
+
const childNodesLen = Number(await getResult(page, 'childNodes-length'))
|
|
52
|
+
const childrenLen = Number(await getResult(page, 'children-length'))
|
|
53
|
+
expect(childNodesLen).toBeGreaterThan(0)
|
|
54
|
+
expect(childrenLen).toBeGreaterThan(0)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
test('firstChild and lastChild exist', async ({ page }) => {
|
|
58
|
+
expect(await getResult(page, 'firstChild-exists')).toBe('true')
|
|
59
|
+
expect(await getResult(page, 'lastChild-exists')).toBe('true')
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test('firstElementChild and lastElementChild exist', async ({ page }) => {
|
|
63
|
+
expect(await getResult(page, 'firstElementChild-exists')).toBe('true')
|
|
64
|
+
expect(await getResult(page, 'lastElementChild-exists')).toBe('true')
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
// sibling traversal
|
|
68
|
+
|
|
69
|
+
test('nextSibling and previousSibling work', async ({ page }) => {
|
|
70
|
+
expect(await getResult(page, 'childA-nextSibling-exists')).toBe('true')
|
|
71
|
+
expect(await getResult(page, 'childB-previousSibling-exists')).toBe('true')
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test('nextElementSibling and previousElementSibling work', async ({ page }) => {
|
|
75
|
+
expect(await getResult(page, 'childA-nextElementSibling-exists')).toBe('true')
|
|
76
|
+
expect(await getResult(page, 'childB-previousElementSibling-exists')).toBe('true')
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
// getBoundingClientRect
|
|
80
|
+
|
|
81
|
+
test('getBoundingClientRect returns full DOMRect', async ({ page }) => {
|
|
82
|
+
expect(await getResult(page, 'rect-has-width')).toBe('true')
|
|
83
|
+
expect(await getResult(page, 'rect-has-height')).toBe('true')
|
|
84
|
+
expect(await getResult(page, 'rect-has-x')).toBe('true')
|
|
85
|
+
expect(await getResult(page, 'rect-has-y')).toBe('true')
|
|
86
|
+
expect(await getResult(page, 'rect-has-top')).toBe('true')
|
|
87
|
+
expect(await getResult(page, 'rect-has-left')).toBe('true')
|
|
88
|
+
expect(await getResult(page, 'rect-has-right')).toBe('true')
|
|
89
|
+
expect(await getResult(page, 'rect-has-bottom')).toBe('true')
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
// document access
|
|
93
|
+
|
|
94
|
+
test('ownerDocument exists and isConnected is true', async ({ page }) => {
|
|
95
|
+
expect(await getResult(page, 'ownerDocument-exists')).toBe('true')
|
|
96
|
+
expect(await getResult(page, 'isConnected')).toBe('true')
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test('getElementById via ownerDocument works', async ({ page }) => {
|
|
100
|
+
expect(await getResult(page, 'getElementById-works')).toBe('true')
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
// compareDocumentPosition
|
|
104
|
+
|
|
105
|
+
test('compareDocumentPosition indicates containment', async ({ page }) => {
|
|
106
|
+
expect(await getResult(page, 'compareDocumentPosition-contained')).toBe('true')
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
// node properties
|
|
110
|
+
|
|
111
|
+
test('nodeType is 1 (ELEMENT_NODE)', async ({ page }) => {
|
|
112
|
+
expect(await getResult(page, 'parent-nodeType')).toBe('1')
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
test('nodeName exists', async ({ page }) => {
|
|
116
|
+
expect(await getResult(page, 'parent-nodeName-exists')).toBe('true')
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
// textContent
|
|
120
|
+
|
|
121
|
+
test('textContent returns text from Text component', async ({ page }) => {
|
|
122
|
+
expect(await getResult(page, 'text-textContent')).toBe('hello dom')
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
// offset properties
|
|
126
|
+
|
|
127
|
+
test('offsetWidth and offsetHeight are positive', async ({ page }) => {
|
|
128
|
+
expect(await getResult(page, 'offsetWidth-positive')).toBe('true')
|
|
129
|
+
expect(await getResult(page, 'offsetHeight-positive')).toBe('true')
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
test('offsetParent exists', async ({ page }) => {
|
|
133
|
+
expect(await getResult(page, 'offsetParent-exists')).toBe('true')
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
// client properties
|
|
137
|
+
|
|
138
|
+
test('clientWidth and clientHeight are positive', async ({ page }) => {
|
|
139
|
+
expect(await getResult(page, 'clientWidth-positive')).toBe('true')
|
|
140
|
+
expect(await getResult(page, 'clientHeight-positive')).toBe('true')
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
// childElementCount
|
|
144
|
+
|
|
145
|
+
test('childElementCount is > 0', async ({ page }) => {
|
|
146
|
+
const count = Number(await getResult(page, 'childElementCount'))
|
|
147
|
+
expect(count).toBeGreaterThan(0)
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
// getRootNode
|
|
151
|
+
|
|
152
|
+
test('getRootNode returns a node', async ({ page }) => {
|
|
153
|
+
expect(await getResult(page, 'getRootNode-exists')).toBe('true')
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
// focus/blur
|
|
157
|
+
|
|
158
|
+
test('focus and blur work on Input ref', async ({ page }) => {
|
|
159
|
+
expect(await getResult(page, 'focus-works')).toBe('true')
|
|
160
|
+
expect(await getResult(page, 'blur-works')).toBe('true')
|
|
161
|
+
})
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/test'
|
|
2
|
+
import { setupPage } from './test-utils'
|
|
3
|
+
|
|
4
|
+
test.describe('Dialog Focus Scope', () => {
|
|
5
|
+
test.beforeEach(async ({ page }) => {
|
|
6
|
+
await setupPage(page, { name: 'DialogFocusScopeCase', type: 'useCase' })
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
test('traps focus within dialog when modal', async ({ page }) => {
|
|
10
|
+
await page.waitForLoadState('networkidle')
|
|
11
|
+
|
|
12
|
+
// Open the modal dialog
|
|
13
|
+
const trigger = page.getByTestId('modal-dialog-trigger')
|
|
14
|
+
await trigger.click()
|
|
15
|
+
|
|
16
|
+
// Wait for dialog to be visible
|
|
17
|
+
const dialogContent = page.getByTestId('modal-dialog-content')
|
|
18
|
+
await expect(dialogContent).toBeVisible({ timeout: 5000 })
|
|
19
|
+
|
|
20
|
+
// Focus should be on the first input
|
|
21
|
+
// Wait for focus trap to activate (no idle wait now)
|
|
22
|
+
await page.waitForTimeout(300)
|
|
23
|
+
|
|
24
|
+
const firstInput = dialogContent.getByTestId('first-input')
|
|
25
|
+
await expect(firstInput).toBeFocused()
|
|
26
|
+
|
|
27
|
+
// Tab through all focusable elements in order
|
|
28
|
+
await page.keyboard.press('Tab')
|
|
29
|
+
const secondInput = dialogContent.getByTestId('second-input')
|
|
30
|
+
await expect(secondInput).toBeFocused()
|
|
31
|
+
|
|
32
|
+
await page.keyboard.press('Tab')
|
|
33
|
+
const emailInput = dialogContent.getByTestId('email-input')
|
|
34
|
+
await expect(emailInput).toBeFocused()
|
|
35
|
+
|
|
36
|
+
await page.keyboard.press('Tab')
|
|
37
|
+
const countrySelect = dialogContent.getByTestId('country-select')
|
|
38
|
+
await expect(countrySelect).toBeFocused()
|
|
39
|
+
|
|
40
|
+
await page.keyboard.press('Tab')
|
|
41
|
+
const commentsTextarea = dialogContent.getByTestId('comments-textarea')
|
|
42
|
+
await expect(commentsTextarea).toBeFocused()
|
|
43
|
+
|
|
44
|
+
await page.keyboard.press('Tab')
|
|
45
|
+
const termsCheckbox = dialogContent.getByTestId('terms-checkbox')
|
|
46
|
+
await expect(termsCheckbox).toBeFocused()
|
|
47
|
+
|
|
48
|
+
await page.keyboard.press('Tab')
|
|
49
|
+
const cancelButton = dialogContent.getByTestId('cancel-button')
|
|
50
|
+
await expect(cancelButton).toBeFocused()
|
|
51
|
+
|
|
52
|
+
await page.keyboard.press('Tab')
|
|
53
|
+
const saveButton = dialogContent.getByTestId('save-button')
|
|
54
|
+
await expect(saveButton).toBeFocused()
|
|
55
|
+
|
|
56
|
+
// Tab should wrap back to first input (loop)
|
|
57
|
+
await page.keyboard.press('Tab')
|
|
58
|
+
await expect(firstInput).toBeFocused()
|
|
59
|
+
|
|
60
|
+
// Shift+Tab should go backwards and loop - from first input to last button
|
|
61
|
+
await page.keyboard.press('Shift+Tab')
|
|
62
|
+
await expect(saveButton).toBeFocused()
|
|
63
|
+
|
|
64
|
+
await page.keyboard.press('Shift+Tab')
|
|
65
|
+
await expect(cancelButton).toBeFocused()
|
|
66
|
+
|
|
67
|
+
// Close dialog with Escape key instead of clicking
|
|
68
|
+
await page.keyboard.press('Escape')
|
|
69
|
+
await expect(dialogContent).not.toBeVisible()
|
|
70
|
+
|
|
71
|
+
// Focus should return to trigger
|
|
72
|
+
await expect(trigger).toBeFocused()
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test('does not trap focus when non-modal', async ({ page }) => {
|
|
76
|
+
await page.waitForLoadState('networkidle')
|
|
77
|
+
|
|
78
|
+
// Open the non-modal dialog
|
|
79
|
+
const trigger = page.getByTestId('non-modal-dialog-trigger')
|
|
80
|
+
await trigger.click()
|
|
81
|
+
|
|
82
|
+
// Wait for dialog to be visible
|
|
83
|
+
const dialogContent = page.getByTestId('non-modal-dialog-content')
|
|
84
|
+
await expect(dialogContent).toBeVisible({ timeout: 5000 })
|
|
85
|
+
|
|
86
|
+
// Focus on the first input
|
|
87
|
+
const firstInput = dialogContent.getByTestId('first-input')
|
|
88
|
+
await firstInput.focus()
|
|
89
|
+
await expect(firstInput).toBeFocused()
|
|
90
|
+
|
|
91
|
+
// Tab should allow focus to leave the dialog
|
|
92
|
+
await page.keyboard.press('Tab')
|
|
93
|
+
await page.keyboard.press('Tab')
|
|
94
|
+
await page.keyboard.press('Tab')
|
|
95
|
+
await page.keyboard.press('Tab')
|
|
96
|
+
await page.keyboard.press('Tab') // Tab past all dialog elements
|
|
97
|
+
|
|
98
|
+
// Focus should have left the dialog
|
|
99
|
+
const focusedElement = await page.evaluate(() => document.activeElement?.tagName)
|
|
100
|
+
expect(focusedElement).toBeTruthy()
|
|
101
|
+
|
|
102
|
+
// Click outside should close non-modal dialog
|
|
103
|
+
await page.click('body', { position: { x: 10, y: 10 } })
|
|
104
|
+
await expect(dialogContent).not.toBeVisible()
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
test('modal dialogs prevent right-click dismiss but allow left-click dismiss', async ({
|
|
108
|
+
page,
|
|
109
|
+
}) => {
|
|
110
|
+
await page.waitForLoadState('networkidle')
|
|
111
|
+
|
|
112
|
+
// Open a modal dialog
|
|
113
|
+
const trigger = page.getByTestId('modal-dialog-trigger')
|
|
114
|
+
await trigger.click()
|
|
115
|
+
|
|
116
|
+
const dialogContent = page.getByTestId('modal-dialog-content')
|
|
117
|
+
await expect(dialogContent).toBeVisible({ timeout: 5000 })
|
|
118
|
+
|
|
119
|
+
// Wait for auto-focus
|
|
120
|
+
await page.waitForTimeout(300)
|
|
121
|
+
|
|
122
|
+
// Try to right-click outside the dialog - should NOT close
|
|
123
|
+
const dialogBounds = await dialogContent.boundingBox()
|
|
124
|
+
|
|
125
|
+
if (dialogBounds) {
|
|
126
|
+
// Right-click to the left of the dialog (on overlay/backdrop)
|
|
127
|
+
await page.mouse.click(dialogBounds.x - 50, dialogBounds.y + 50, {
|
|
128
|
+
button: 'right',
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Wait a bit
|
|
133
|
+
await page.waitForTimeout(500)
|
|
134
|
+
|
|
135
|
+
// Modal dialogs prevent right-click dismiss
|
|
136
|
+
await expect(dialogContent).toBeVisible()
|
|
137
|
+
|
|
138
|
+
// Now try left-click - should close
|
|
139
|
+
if (dialogBounds) {
|
|
140
|
+
await page.mouse.click(dialogBounds.x - 50, dialogBounds.y + 50, { button: 'left' })
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Wait for dialog to close
|
|
144
|
+
await page.waitForTimeout(500)
|
|
145
|
+
|
|
146
|
+
// Dialog should be closed after left-click
|
|
147
|
+
await expect(dialogContent).not.toBeVisible()
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
test('auto-focuses first element on mount', async ({ page }) => {
|
|
151
|
+
await page.waitForLoadState('networkidle')
|
|
152
|
+
|
|
153
|
+
const trigger = page.getByTestId('modal-dialog-trigger')
|
|
154
|
+
await trigger.click()
|
|
155
|
+
|
|
156
|
+
const dialogContent = page.getByTestId('modal-dialog-content')
|
|
157
|
+
await expect(dialogContent).toBeVisible({ timeout: 5000 })
|
|
158
|
+
|
|
159
|
+
// Wait for auto-focus
|
|
160
|
+
await page.waitForTimeout(300)
|
|
161
|
+
|
|
162
|
+
// First input should be focused
|
|
163
|
+
const firstInput = dialogContent.getByTestId('first-input')
|
|
164
|
+
await expect(firstInput).toBeFocused()
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
test('returns focus to trigger on close', async ({ page }) => {
|
|
168
|
+
// note: test may be flaky if animations don't complete
|
|
169
|
+
// Manual testing shows the focus return works correctly
|
|
170
|
+
await page.waitForLoadState('networkidle')
|
|
171
|
+
|
|
172
|
+
const trigger = page.getByTestId('modal-dialog-trigger')
|
|
173
|
+
await trigger.click()
|
|
174
|
+
|
|
175
|
+
const dialogContent = page.getByTestId('modal-dialog-content')
|
|
176
|
+
await expect(dialogContent).toBeVisible({ timeout: 5000 })
|
|
177
|
+
|
|
178
|
+
// Wait for animations to complete
|
|
179
|
+
await page.waitForTimeout(500)
|
|
180
|
+
|
|
181
|
+
// Click cancel button via JavaScript (since it may be outside viewport in dialog scroll container)
|
|
182
|
+
await page.evaluate(() => {
|
|
183
|
+
const button = document.querySelector(
|
|
184
|
+
'[data-testid="cancel-button"]'
|
|
185
|
+
) as HTMLElement
|
|
186
|
+
if (button) button.click()
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
// Wait for dialog to close with animation
|
|
190
|
+
await expect(dialogContent).not.toBeVisible({ timeout: 5000 })
|
|
191
|
+
|
|
192
|
+
// Focus should return to trigger
|
|
193
|
+
await expect(trigger).toBeFocused({ timeout: 5000 })
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
test('handles nested dialogs focus correctly', async ({ page }) => {
|
|
197
|
+
await page.waitForLoadState('networkidle')
|
|
198
|
+
|
|
199
|
+
// Open parent dialog
|
|
200
|
+
const parentTrigger = page.getByTestId('parent-dialog-trigger')
|
|
201
|
+
await parentTrigger.click()
|
|
202
|
+
|
|
203
|
+
const parentDialog = page.getByTestId('parent-dialog-content')
|
|
204
|
+
await expect(parentDialog).toBeVisible({ timeout: 5000 })
|
|
205
|
+
|
|
206
|
+
// Open nested dialog
|
|
207
|
+
const nestedTrigger = parentDialog.getByTestId('nested-dialog-trigger')
|
|
208
|
+
await nestedTrigger.click()
|
|
209
|
+
|
|
210
|
+
const nestedDialog = page.getByTestId('nested-dialog-content')
|
|
211
|
+
await expect(nestedDialog).toBeVisible({ timeout: 5000 })
|
|
212
|
+
|
|
213
|
+
// Focus should be trapped in nested dialog
|
|
214
|
+
await page.waitForTimeout(300)
|
|
215
|
+
const nestedInput = nestedDialog.getByTestId('nested-input')
|
|
216
|
+
await expect(nestedInput).toBeFocused()
|
|
217
|
+
|
|
218
|
+
// Tab should stay within nested dialog
|
|
219
|
+
await page.keyboard.press('Tab')
|
|
220
|
+
const nestedButton = nestedDialog.getByTestId('nested-close-button')
|
|
221
|
+
await expect(nestedButton).toBeFocused()
|
|
222
|
+
|
|
223
|
+
await page.keyboard.press('Tab')
|
|
224
|
+
await expect(nestedInput).toBeFocused() // Should loop back
|
|
225
|
+
|
|
226
|
+
// Close nested dialog
|
|
227
|
+
await nestedButton.click()
|
|
228
|
+
await expect(nestedDialog).not.toBeVisible()
|
|
229
|
+
|
|
230
|
+
// Focus should return to nested trigger in parent dialog
|
|
231
|
+
await expect(nestedTrigger).toBeFocused()
|
|
232
|
+
|
|
233
|
+
// Close parent dialog
|
|
234
|
+
const parentClose = parentDialog.getByTestId('parent-close-button')
|
|
235
|
+
await parentClose.click()
|
|
236
|
+
await expect(parentDialog).not.toBeVisible()
|
|
237
|
+
|
|
238
|
+
// Focus should return to parent trigger
|
|
239
|
+
await expect(parentTrigger).toBeFocused()
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
test('nested dialog appears above parent dialog (z-index stacking)', async ({
|
|
243
|
+
page,
|
|
244
|
+
}) => {
|
|
245
|
+
await page.waitForLoadState('networkidle')
|
|
246
|
+
|
|
247
|
+
// Open parent dialog
|
|
248
|
+
const parentTrigger = page.getByTestId('parent-dialog-trigger')
|
|
249
|
+
await parentTrigger.click()
|
|
250
|
+
|
|
251
|
+
const parentDialog = page.getByTestId('parent-dialog-content')
|
|
252
|
+
await expect(parentDialog).toBeVisible({ timeout: 5000 })
|
|
253
|
+
|
|
254
|
+
// Open nested dialog
|
|
255
|
+
const nestedTrigger = parentDialog.getByTestId('nested-dialog-trigger')
|
|
256
|
+
await nestedTrigger.click()
|
|
257
|
+
|
|
258
|
+
const nestedDialog = page.getByTestId('nested-dialog-content')
|
|
259
|
+
await expect(nestedDialog).toBeVisible({ timeout: 5000 })
|
|
260
|
+
|
|
261
|
+
// Wait for animations to complete
|
|
262
|
+
await page.waitForTimeout(500)
|
|
263
|
+
|
|
264
|
+
// Get the z-index values of both dialog portal containers
|
|
265
|
+
// GuiRoot wraps portal content in Theme > span, so z-index is on the inner span
|
|
266
|
+
const zIndexInfo = await page.evaluate(() => {
|
|
267
|
+
const portals = document.querySelectorAll('span[style*="z-index"]')
|
|
268
|
+
const zIndices: number[] = []
|
|
269
|
+
|
|
270
|
+
portals.forEach((portal) => {
|
|
271
|
+
const style = window.getComputedStyle(portal)
|
|
272
|
+
const zIndex = parseInt(style.zIndex, 10)
|
|
273
|
+
if (!isNaN(zIndex)) {
|
|
274
|
+
zIndices.push(zIndex)
|
|
275
|
+
}
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
return zIndices.sort((a, b) => a - b)
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
// Should have at least 2 different z-index values
|
|
282
|
+
expect(zIndexInfo.length).toBeGreaterThanOrEqual(2)
|
|
283
|
+
|
|
284
|
+
// The nested dialog should have a higher z-index than the parent
|
|
285
|
+
// The last z-index should be higher than the first
|
|
286
|
+
const parentZIndex = zIndexInfo[0]
|
|
287
|
+
const nestedZIndex = zIndexInfo[zIndexInfo.length - 1]
|
|
288
|
+
|
|
289
|
+
expect(nestedZIndex).toBeGreaterThan(parentZIndex)
|
|
290
|
+
|
|
291
|
+
// Additionally verify that the nested dialog is visually on top by checking
|
|
292
|
+
// what element is at the center of the nested dialog
|
|
293
|
+
const nestedBounds = await nestedDialog.boundingBox()
|
|
294
|
+
if (nestedBounds) {
|
|
295
|
+
const centerX = nestedBounds.x + nestedBounds.width / 2
|
|
296
|
+
const centerY = nestedBounds.y + nestedBounds.height / 2
|
|
297
|
+
|
|
298
|
+
const elementAtPoint = await page.evaluate(
|
|
299
|
+
({ x, y }) => {
|
|
300
|
+
const el = document.elementFromPoint(x, y)
|
|
301
|
+
return el?.closest('[data-testid="nested-dialog-content"]') !== null
|
|
302
|
+
},
|
|
303
|
+
{ x: centerX, y: centerY }
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
expect(elementAtPoint).toBe(true)
|
|
307
|
+
}
|
|
308
|
+
})
|
|
309
|
+
})
|