@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,425 @@
|
|
|
1
|
+
import { expect, test, type Page } from '@playwright/test'
|
|
2
|
+
import { setupPage } from './test-utils'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* EXIT COMPLETION INVARIANT TESTS
|
|
6
|
+
*
|
|
7
|
+
* Tests that sendExitComplete is called exactly once per exit cycle,
|
|
8
|
+
* at the correct time (after animations complete), and handles edge cases
|
|
9
|
+
* like rapid toggling, interruptions, and zero-duration animations.
|
|
10
|
+
*
|
|
11
|
+
* These tests catch bugs in exit completion tracking across all animation drivers.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
interface ExitTrackingData {
|
|
15
|
+
counts: Record<string, number>
|
|
16
|
+
times: Record<string, number[]>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function getExitTrackingData(page: Page): Promise<ExitTrackingData> {
|
|
20
|
+
return page.evaluate(() => ({
|
|
21
|
+
counts: (window as any).__exitCompletionCounts || {},
|
|
22
|
+
times: (window as any).__exitCompletionTimes || {},
|
|
23
|
+
}))
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function resetExitTracking(page: Page): Promise<void> {
|
|
27
|
+
await page.evaluate(() => {
|
|
28
|
+
;(window as any).__resetExitTracking?.()
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function waitForExitComplete(
|
|
33
|
+
page: Page,
|
|
34
|
+
scenarioId: string,
|
|
35
|
+
timeout = 2000
|
|
36
|
+
): Promise<number> {
|
|
37
|
+
const startTime = Date.now()
|
|
38
|
+
while (Date.now() - startTime < timeout) {
|
|
39
|
+
const data = await getExitTrackingData(page)
|
|
40
|
+
if (data.counts[scenarioId] && data.counts[scenarioId] > 0) {
|
|
41
|
+
return data.counts[scenarioId]
|
|
42
|
+
}
|
|
43
|
+
await page.waitForTimeout(50)
|
|
44
|
+
}
|
|
45
|
+
// return count even if zero (timed out)
|
|
46
|
+
const data = await getExitTrackingData(page)
|
|
47
|
+
return data.counts[scenarioId] || 0
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function expectStableCompletionCount(
|
|
51
|
+
page: Page,
|
|
52
|
+
scenarioId: string,
|
|
53
|
+
expectedCount: number,
|
|
54
|
+
settleMs = 300
|
|
55
|
+
): Promise<void> {
|
|
56
|
+
await page.waitForTimeout(settleMs)
|
|
57
|
+
const data = await getExitTrackingData(page)
|
|
58
|
+
expect(
|
|
59
|
+
data.counts[scenarioId] || 0,
|
|
60
|
+
`${scenarioId} should remain at ${expectedCount} completion(s)`
|
|
61
|
+
).toBe(expectedCount)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function elementExists(page: Page, testId: string): Promise<boolean> {
|
|
65
|
+
return page.evaluate((id) => !!document.querySelector(`[data-testid="${id}"]`), testId)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
test.beforeEach(async ({ page }) => {
|
|
69
|
+
await setupPage(page, { name: 'ExitCompletionCase', type: 'useCase' })
|
|
70
|
+
await page.waitForTimeout(500) // let initial render settle
|
|
71
|
+
await resetExitTracking(page)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test.describe('Basic Exit Completion', () => {
|
|
75
|
+
test('scenario 01: basic exit completes exactly once', async ({ page }) => {
|
|
76
|
+
await page.getByTestId('exit-01-trigger').click()
|
|
77
|
+
|
|
78
|
+
// wait for exit to complete
|
|
79
|
+
const count = await waitForExitComplete(page, '01-basic-exit')
|
|
80
|
+
|
|
81
|
+
expect(count, 'Exit should complete exactly once').toBe(1)
|
|
82
|
+
await expectStableCompletionCount(page, '01-basic-exit', 1, 400)
|
|
83
|
+
expect(await elementExists(page, 'exit-01-target'), 'Element should be gone').toBe(
|
|
84
|
+
false
|
|
85
|
+
)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
test('scenario 02: zero duration exit completes exactly once', async ({ page }) => {
|
|
89
|
+
await page.getByTestId('exit-02-trigger').click()
|
|
90
|
+
|
|
91
|
+
// zero duration should still complete once
|
|
92
|
+
const count = await waitForExitComplete(page, '02-zero-duration', 500)
|
|
93
|
+
|
|
94
|
+
expect(count, 'Zero duration exit should complete exactly once').toBe(1)
|
|
95
|
+
await expectStableCompletionCount(page, '02-zero-duration', 1, 200)
|
|
96
|
+
expect(await elementExists(page, 'exit-02-target'), 'Element should be gone').toBe(
|
|
97
|
+
false
|
|
98
|
+
)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
test('scenario 03: very short duration (30ms) completes exactly once', async ({
|
|
102
|
+
page,
|
|
103
|
+
}) => {
|
|
104
|
+
await page.getByTestId('exit-03-trigger').click()
|
|
105
|
+
|
|
106
|
+
const count = await waitForExitComplete(page, '03-short-duration', 2000)
|
|
107
|
+
|
|
108
|
+
expect(count, 'Short duration exit should complete exactly once').toBe(1)
|
|
109
|
+
await expectStableCompletionCount(page, '03-short-duration', 1, 400)
|
|
110
|
+
expect(await elementExists(page, 'exit-03-target'), 'Element should be gone').toBe(
|
|
111
|
+
false
|
|
112
|
+
)
|
|
113
|
+
})
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
test.describe('Duplicate Completion Guards', () => {
|
|
117
|
+
test('scenario 04: rapid toggle does not cause duplicate completions', async ({
|
|
118
|
+
page,
|
|
119
|
+
}) => {
|
|
120
|
+
// this test catches finding #4/#5: duplicate completions on re-renders
|
|
121
|
+
await page.getByTestId('exit-04-trigger').click()
|
|
122
|
+
|
|
123
|
+
// wait for the sequence to complete (off -> on -> off)
|
|
124
|
+
// CI can be slow, give plenty of time for animations
|
|
125
|
+
await page.waitForTimeout(1500)
|
|
126
|
+
|
|
127
|
+
const data = await getExitTrackingData(page)
|
|
128
|
+
const count = data.counts['04-rapid-toggle'] || 0
|
|
129
|
+
|
|
130
|
+
// first exit is interrupted; only final exit should complete
|
|
131
|
+
expect(count, 'Rapid toggle should complete exactly once for the final exit').toBe(1)
|
|
132
|
+
expect(await elementExists(page, 'exit-04-target'), 'Element should be gone').toBe(
|
|
133
|
+
false
|
|
134
|
+
)
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
test('scenario 05: re-renders during exit do not cause duplicate completions', async ({
|
|
138
|
+
page,
|
|
139
|
+
}) => {
|
|
140
|
+
// this test catches finding #4: flush restart causing duplicate completions
|
|
141
|
+
await page.getByTestId('exit-05-trigger').click()
|
|
142
|
+
|
|
143
|
+
// wait for exit + re-renders to complete
|
|
144
|
+
// CI can be slow, give plenty of time
|
|
145
|
+
await page.waitForTimeout(1500)
|
|
146
|
+
|
|
147
|
+
const data = await getExitTrackingData(page)
|
|
148
|
+
const count = data.counts['05-rerender-during-exit'] || 0
|
|
149
|
+
|
|
150
|
+
expect(count, 'Re-renders during exit should not cause duplicate completions').toBe(1)
|
|
151
|
+
expect(await elementExists(page, 'exit-05-target'), 'Element should be gone').toBe(
|
|
152
|
+
false
|
|
153
|
+
)
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
test('scenario 06: multiple children exiting fires completion once', async ({
|
|
157
|
+
page,
|
|
158
|
+
}) => {
|
|
159
|
+
await page.getByTestId('exit-06-trigger').click()
|
|
160
|
+
|
|
161
|
+
// wait for all 3 children to exit
|
|
162
|
+
await page.waitForTimeout(800)
|
|
163
|
+
|
|
164
|
+
const data = await getExitTrackingData(page)
|
|
165
|
+
const count = data.counts['06-multiple-children'] || 0
|
|
166
|
+
|
|
167
|
+
// AnimatePresence should fire onExitComplete once after ALL children exit
|
|
168
|
+
expect(count, 'Multiple children should trigger one completion').toBe(1)
|
|
169
|
+
expect(await elementExists(page, 'exit-06-target-1'), 'Child 1 should be gone').toBe(
|
|
170
|
+
false
|
|
171
|
+
)
|
|
172
|
+
expect(await elementExists(page, 'exit-06-target-2'), 'Child 2 should be gone').toBe(
|
|
173
|
+
false
|
|
174
|
+
)
|
|
175
|
+
expect(await elementExists(page, 'exit-06-target-3'), 'Child 3 should be gone').toBe(
|
|
176
|
+
false
|
|
177
|
+
)
|
|
178
|
+
})
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
test.describe('Timing Validation', () => {
|
|
182
|
+
test('scenario 07: long animation (500ms) completes after animation finishes', async ({
|
|
183
|
+
page,
|
|
184
|
+
}) => {
|
|
185
|
+
await page.getByTestId('exit-07-trigger').click()
|
|
186
|
+
|
|
187
|
+
const startTime = Date.now()
|
|
188
|
+
await waitForExitComplete(page, '07-long-animation', 2000)
|
|
189
|
+
const elapsed = Date.now() - startTime
|
|
190
|
+
|
|
191
|
+
const data = await getExitTrackingData(page)
|
|
192
|
+
const completionTime = data.times['07-long-animation']?.[0] || 0
|
|
193
|
+
|
|
194
|
+
// completion should happen after ~500ms, not immediately
|
|
195
|
+
// allow some tolerance for CI variance
|
|
196
|
+
expect(
|
|
197
|
+
completionTime,
|
|
198
|
+
'Exit should complete after animation (>350ms)'
|
|
199
|
+
).toBeGreaterThan(350)
|
|
200
|
+
expect(data.counts['07-long-animation'], 'Should complete exactly once').toBe(1)
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
test('scenario 08: interrupted exit completes at most once', async ({ page }) => {
|
|
204
|
+
// this test catches finding #2: canceled animations counted as complete
|
|
205
|
+
await page.getByTestId('exit-08-trigger').click()
|
|
206
|
+
|
|
207
|
+
// wait for the full sequence: exit -> interrupt -> exit again
|
|
208
|
+
await page.waitForTimeout(1200)
|
|
209
|
+
|
|
210
|
+
const data = await getExitTrackingData(page)
|
|
211
|
+
const count = data.counts['08-interrupted'] || 0
|
|
212
|
+
|
|
213
|
+
// the interrupted exit should not count as complete:
|
|
214
|
+
// only the final successful exit should complete
|
|
215
|
+
expect(count, 'Interrupted exit should complete exactly once').toBe(1)
|
|
216
|
+
|
|
217
|
+
const completionTime = data.times['08-interrupted']?.[0] || 0
|
|
218
|
+
expect(
|
|
219
|
+
completionTime,
|
|
220
|
+
'Interrupted scenario should not complete early (final completion should be late)'
|
|
221
|
+
).toBeGreaterThan(350)
|
|
222
|
+
|
|
223
|
+
// final state should be hidden
|
|
224
|
+
const status = await page.getByTestId('exit-08-status').textContent()
|
|
225
|
+
expect(status).toBe('hidden')
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
test('scenario 09: stress test with many cancel/restart cycles', async ({ page }) => {
|
|
229
|
+
await page.getByTestId('exit-09-trigger').click()
|
|
230
|
+
|
|
231
|
+
// wait for the stress test sequence to complete
|
|
232
|
+
await page.waitForTimeout(1500)
|
|
233
|
+
|
|
234
|
+
const data = await getExitTrackingData(page)
|
|
235
|
+
const count = data.counts['09-cancel-restart'] || 0
|
|
236
|
+
|
|
237
|
+
// all interim exits are interrupted; only the final exit should complete
|
|
238
|
+
expect(count, 'Stress test should complete exactly once').toBe(1)
|
|
239
|
+
|
|
240
|
+
// final state should be hidden
|
|
241
|
+
const status = await page.getByTestId('exit-09-status').textContent()
|
|
242
|
+
expect(status).toBe('hidden')
|
|
243
|
+
})
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
test.describe('Per-Property Exit', () => {
|
|
247
|
+
test('scenario 10: per-property exit waits for longest animation', async ({
|
|
248
|
+
page,
|
|
249
|
+
}, testInfo) => {
|
|
250
|
+
// motion driver can't differentiate transform sub-keys (scale becomes transform)
|
|
251
|
+
// so per-property timing for scale vs opacity doesn't work
|
|
252
|
+
const driver = (testInfo.project?.metadata as any)?.animationDriver
|
|
253
|
+
if (driver === 'motion') {
|
|
254
|
+
test.skip()
|
|
255
|
+
return
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// this test catches finding #1: emitter-driven keys not tracked
|
|
259
|
+
// opacity=100ms, scale=500ms - should wait for scale
|
|
260
|
+
await page.getByTestId('exit-10-trigger').click()
|
|
261
|
+
|
|
262
|
+
await waitForExitComplete(page, '10-per-property', 2000)
|
|
263
|
+
|
|
264
|
+
const data = await getExitTrackingData(page)
|
|
265
|
+
const completionTime = data.times['10-per-property']?.[0] || 0
|
|
266
|
+
|
|
267
|
+
// should wait for the slow scale animation (500ms), not just opacity (100ms)
|
|
268
|
+
// allow tolerance for CI
|
|
269
|
+
expect(
|
|
270
|
+
completionTime,
|
|
271
|
+
'Per-property exit should wait for longest animation (>350ms)'
|
|
272
|
+
).toBeGreaterThan(350)
|
|
273
|
+
expect(data.counts['10-per-property'], 'Should complete exactly once').toBe(1)
|
|
274
|
+
await expectStableCompletionCount(page, '10-per-property', 1, 350)
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
test('scenario 11: mixed duration exit completes after slowest property', async ({
|
|
278
|
+
page,
|
|
279
|
+
}, testInfo) => {
|
|
280
|
+
// motion driver can't differentiate transform sub-keys
|
|
281
|
+
const driver = (testInfo.project?.metadata as any)?.animationDriver
|
|
282
|
+
if (driver === 'motion') {
|
|
283
|
+
test.skip()
|
|
284
|
+
return
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// opacity=100ms, scale/y=400ms
|
|
288
|
+
await page.getByTestId('exit-11-trigger').click()
|
|
289
|
+
|
|
290
|
+
await waitForExitComplete(page, '11-mixed-duration', 2000)
|
|
291
|
+
|
|
292
|
+
const data = await getExitTrackingData(page)
|
|
293
|
+
const completionTime = data.times['11-mixed-duration']?.[0] || 0
|
|
294
|
+
|
|
295
|
+
// should wait for 400ms animations, not 100ms opacity
|
|
296
|
+
expect(
|
|
297
|
+
completionTime,
|
|
298
|
+
'Mixed duration should wait for slowest property (>300ms)'
|
|
299
|
+
).toBeGreaterThan(300)
|
|
300
|
+
expect(data.counts['11-mixed-duration'], 'Should complete exactly once').toBe(1)
|
|
301
|
+
await expectStableCompletionCount(page, '11-mixed-duration', 1, 350)
|
|
302
|
+
})
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
test.describe('Element removal timing', () => {
|
|
306
|
+
test('element should exist during exit animation', async ({ page }) => {
|
|
307
|
+
// native driver has timing issues on web - animations complete too quickly
|
|
308
|
+
const driver = (test.info().project?.metadata as any)?.animationDriver
|
|
309
|
+
test.skip(driver === 'native', 'native driver has animation timing issues on web')
|
|
310
|
+
|
|
311
|
+
// verify element stays in DOM during exit animation
|
|
312
|
+
await page.getByTestId('exit-07-trigger').click()
|
|
313
|
+
|
|
314
|
+
// immediately after triggering, element should still exist
|
|
315
|
+
await page.waitForTimeout(50)
|
|
316
|
+
expect(
|
|
317
|
+
await elementExists(page, 'exit-07-target'),
|
|
318
|
+
'Element should exist during exit animation'
|
|
319
|
+
).toBe(true)
|
|
320
|
+
|
|
321
|
+
// at 200ms into 500ms animation, should still exist
|
|
322
|
+
await page.waitForTimeout(150)
|
|
323
|
+
expect(
|
|
324
|
+
await elementExists(page, 'exit-07-target'),
|
|
325
|
+
'Element should still exist at 200ms into 500ms animation'
|
|
326
|
+
).toBe(true)
|
|
327
|
+
|
|
328
|
+
// after animation completes, should be gone
|
|
329
|
+
await page.waitForTimeout(500)
|
|
330
|
+
expect(
|
|
331
|
+
await elementExists(page, 'exit-07-target'),
|
|
332
|
+
'Element should be gone after animation completes'
|
|
333
|
+
).toBe(false)
|
|
334
|
+
})
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
test.describe('AnimateOnly & Transform Sub-Keys', () => {
|
|
338
|
+
test('scenario 51: animateOnly excludes scale from pending set', async ({
|
|
339
|
+
page,
|
|
340
|
+
}, testInfo) => {
|
|
341
|
+
// this test uses CSS-style duration strings which only CSS driver supports
|
|
342
|
+
const driver = (testInfo.project?.metadata as any)?.animationDriver
|
|
343
|
+
if (driver !== 'css') {
|
|
344
|
+
test.skip()
|
|
345
|
+
return
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// animateOnly=['opacity'] with scale=500ms, opacity=100ms
|
|
349
|
+
// should complete based on opacity timing (~100ms), not scale
|
|
350
|
+
await page.getByTestId('exit-51-trigger').click()
|
|
351
|
+
|
|
352
|
+
await waitForExitComplete(page, '51-animateonly-exclusion', 2000)
|
|
353
|
+
|
|
354
|
+
const data = await getExitTrackingData(page)
|
|
355
|
+
const completionTime = data.times['51-animateonly-exclusion']?.[0] || 0
|
|
356
|
+
|
|
357
|
+
// should complete after opacity (100ms) finishes, not wait for excluded scale (500ms)
|
|
358
|
+
// allow tolerance but should NOT wait 400-500ms
|
|
359
|
+
expect(
|
|
360
|
+
completionTime,
|
|
361
|
+
'AnimateOnly should exclude scale - complete around opacity time (not 400+ms)'
|
|
362
|
+
).toBeLessThan(400)
|
|
363
|
+
expect(data.counts['51-animateonly-exclusion'], 'Should complete exactly once').toBe(
|
|
364
|
+
1
|
|
365
|
+
)
|
|
366
|
+
await expectStableCompletionCount(page, '51-animateonly-exclusion', 1, 350)
|
|
367
|
+
expect(await elementExists(page, 'exit-51-target'), 'Element should be gone').toBe(
|
|
368
|
+
false
|
|
369
|
+
)
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
test('scenario 53: transform sub-keys with different durations wait for longest', async ({
|
|
373
|
+
page,
|
|
374
|
+
}, testInfo) => {
|
|
375
|
+
// this test uses CSS-style duration strings ('100ms', '500ms') which only CSS driver supports
|
|
376
|
+
const driver = (testInfo.project?.metadata as any)?.animationDriver
|
|
377
|
+
if (driver !== 'css') {
|
|
378
|
+
test.skip()
|
|
379
|
+
return
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// scale=100ms, y=500ms - should wait for y
|
|
383
|
+
await page.getByTestId('exit-53-trigger').click()
|
|
384
|
+
|
|
385
|
+
await waitForExitComplete(page, '53-transform-subkeys', 2000)
|
|
386
|
+
|
|
387
|
+
const data = await getExitTrackingData(page)
|
|
388
|
+
const completionTime = data.times['53-transform-subkeys']?.[0] || 0
|
|
389
|
+
|
|
390
|
+
// should wait for the slower y animation (500ms), not just scale (100ms)
|
|
391
|
+
expect(
|
|
392
|
+
completionTime,
|
|
393
|
+
'Transform sub-keys should wait for longest (y=500ms, should be >350ms)'
|
|
394
|
+
).toBeGreaterThan(350)
|
|
395
|
+
expect(data.counts['53-transform-subkeys'], 'Should complete exactly once').toBe(1)
|
|
396
|
+
await expectStableCompletionCount(page, '53-transform-subkeys', 1, 350)
|
|
397
|
+
expect(await elementExists(page, 'exit-53-target'), 'Element should be gone').toBe(
|
|
398
|
+
false
|
|
399
|
+
)
|
|
400
|
+
})
|
|
401
|
+
|
|
402
|
+
test('scenario 55: zero animatable props completes immediately', async ({ page }) => {
|
|
403
|
+
// animateOnly=[] means nothing should animate, immediate completion
|
|
404
|
+
await page.getByTestId('exit-55-trigger').click()
|
|
405
|
+
|
|
406
|
+
const startTime = Date.now()
|
|
407
|
+
await waitForExitComplete(page, '55-zero-animatable', 1000)
|
|
408
|
+
const elapsed = Date.now() - startTime
|
|
409
|
+
|
|
410
|
+
const data = await getExitTrackingData(page)
|
|
411
|
+
const completionTime = data.times['55-zero-animatable']?.[0] || 0
|
|
412
|
+
|
|
413
|
+
// should complete almost immediately since no animations
|
|
414
|
+
// 300ms threshold to account for CI overhead (actual should be <10ms)
|
|
415
|
+
expect(
|
|
416
|
+
completionTime,
|
|
417
|
+
'Zero animatable props should complete immediately (<300ms)'
|
|
418
|
+
).toBeLessThan(300)
|
|
419
|
+
expect(data.counts['55-zero-animatable'], 'Should complete exactly once').toBe(1)
|
|
420
|
+
await expectStableCompletionCount(page, '55-zero-animatable', 1, 200)
|
|
421
|
+
expect(await elementExists(page, 'exit-55-target'), 'Element should be gone').toBe(
|
|
422
|
+
false
|
|
423
|
+
)
|
|
424
|
+
})
|
|
425
|
+
})
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { test, expect } from '@playwright/test'
|
|
2
|
+
import { setupPage } from './test-utils'
|
|
3
|
+
|
|
4
|
+
test('exit animation holds element during tab switch', async ({ page }) => {
|
|
5
|
+
// native driver has hover/animation issues on web
|
|
6
|
+
const driver = (test.info().project?.metadata as any)?.animationDriver
|
|
7
|
+
test.skip(driver === 'native', 'native driver has element detection issues on web')
|
|
8
|
+
|
|
9
|
+
await setupPage(page, { name: 'TabHoverAnimationCase', type: 'useCase' })
|
|
10
|
+
await page.waitForTimeout(500)
|
|
11
|
+
|
|
12
|
+
await page.locator('[data-testid="tab-tab-a"]').hover()
|
|
13
|
+
await page.waitForTimeout(400)
|
|
14
|
+
|
|
15
|
+
const content = page.locator('[data-testid="slide-content"]')
|
|
16
|
+
expect(await content.count()).toBe(1)
|
|
17
|
+
|
|
18
|
+
await page.locator('[data-testid="tab-tab-d"]').hover()
|
|
19
|
+
|
|
20
|
+
await page.waitForTimeout(60)
|
|
21
|
+
const countAt60 = await content.count()
|
|
22
|
+
|
|
23
|
+
const elemsAt60 = await page.evaluate(() =>
|
|
24
|
+
Array.from(document.querySelectorAll('[data-testid="slide-content"]')).map((el) => ({
|
|
25
|
+
tab: (el as HTMLElement).dataset.tab,
|
|
26
|
+
opacity: getComputedStyle(el).opacity,
|
|
27
|
+
}))
|
|
28
|
+
)
|
|
29
|
+
console.log(`At 60ms - count: ${countAt60}, elements: ${JSON.stringify(elemsAt60)}`)
|
|
30
|
+
|
|
31
|
+
// If exit animation works: 2 elements (A exiting + D entering)
|
|
32
|
+
// If exit is immediate: 1 element (only D)
|
|
33
|
+
expect(countAt60).toBe(2)
|
|
34
|
+
})
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/test'
|
|
2
|
+
import { setupPage } from './test-utils'
|
|
3
|
+
|
|
4
|
+
test.beforeEach(async ({ page }) => {
|
|
5
|
+
await setupPage(page, { name: 'FocusVisibleButton', type: 'useCase' })
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
test(`button + focusVisibleStyle`, async ({ page }) => {
|
|
9
|
+
const button = page.locator('#focus-visible-button')
|
|
10
|
+
|
|
11
|
+
// Ensure the button is visible and ready
|
|
12
|
+
await button.waitFor({ state: 'visible' })
|
|
13
|
+
|
|
14
|
+
// Click the button first to ensure it's interactable, then focus via keyboard
|
|
15
|
+
await button.click()
|
|
16
|
+
await page.keyboard.press('Tab')
|
|
17
|
+
await page.keyboard.press('Shift+Tab')
|
|
18
|
+
|
|
19
|
+
// Wait for focus to be applied and styles to update
|
|
20
|
+
await page.waitForTimeout(100)
|
|
21
|
+
|
|
22
|
+
// Verify the button is focused
|
|
23
|
+
const isFocused = await button.evaluate((el) => {
|
|
24
|
+
return document.activeElement === el
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
expect(isFocused).toBe(true)
|
|
28
|
+
|
|
29
|
+
// Check if focus-visible pseudo-class is applied
|
|
30
|
+
const hasFocusVisible = await button.evaluate((el) => {
|
|
31
|
+
return el.matches(':focus-visible')
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
// Get computed styles
|
|
35
|
+
const styles = await button.evaluate((el) => {
|
|
36
|
+
return window.getComputedStyle(el)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
expect(hasFocusVisible).toBe(true)
|
|
40
|
+
expect(styles.borderWidth).toBe(`2px`)
|
|
41
|
+
})
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/test'
|
|
2
|
+
import { setupPage } from './test-utils'
|
|
3
|
+
|
|
4
|
+
test.beforeEach(async ({ page }) => {
|
|
5
|
+
await setupPage(page, { name: 'FocusVisibleButtonPointer', type: 'useCase' })
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
test(`button + focusVisibleStyle + non keyboard focus`, async ({ page }) => {
|
|
9
|
+
const button = page.locator('#focus-visible-button')
|
|
10
|
+
|
|
11
|
+
const box = await button.boundingBox()
|
|
12
|
+
await page.mouse.move(box!.x + box!.width / 2, box!.y + box!.height / 2)
|
|
13
|
+
await page.mouse.down({
|
|
14
|
+
button: 'left',
|
|
15
|
+
clickCount: 1,
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
const styles = await button.evaluate((el) => {
|
|
19
|
+
return window.getComputedStyle(el)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
expect(styles.borderWidth).toBe(`1px`)
|
|
23
|
+
})
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/test'
|
|
2
|
+
import { setupPage } from './test-utils'
|
|
3
|
+
|
|
4
|
+
test.beforeEach(async ({ page }) => {
|
|
5
|
+
await setupPage(page, { name: 'FocusVisibleButtonWithFocusStyle', type: 'useCase' })
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
test(`button + focusVisibleStyle + focusVisible`, async ({ page }) => {
|
|
9
|
+
const button = page.locator('#focus-visible-button')
|
|
10
|
+
|
|
11
|
+
const beforeFocusStyles = await button.evaluate((el) => {
|
|
12
|
+
return window.getComputedStyle(el)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
expect(beforeFocusStyles.borderWidth).toBe(`1px`)
|
|
16
|
+
|
|
17
|
+
await page.keyboard.press('Tab')
|
|
18
|
+
|
|
19
|
+
const stylesAfterKeyboardFocus = await button.evaluate((el) => {
|
|
20
|
+
return window.getComputedStyle(el)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
expect(stylesAfterKeyboardFocus.borderWidth).toBe(`3px`)
|
|
24
|
+
|
|
25
|
+
await button.blur()
|
|
26
|
+
|
|
27
|
+
const box = await button.boundingBox()
|
|
28
|
+
await page.mouse.move(box!.x + box!.width / 2, box!.y + box!.height / 2)
|
|
29
|
+
await page.mouse.down({
|
|
30
|
+
button: 'left',
|
|
31
|
+
clickCount: 1,
|
|
32
|
+
})
|
|
33
|
+
await page.mouse.up()
|
|
34
|
+
|
|
35
|
+
const stylesAfterMouseFocus = await button.evaluate((el) => {
|
|
36
|
+
return window.getComputedStyle(el)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
expect(stylesAfterMouseFocus.borderWidth).toBe(`2px`)
|
|
40
|
+
})
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/test'
|
|
2
|
+
import { setupPage } from './test-utils'
|
|
3
|
+
|
|
4
|
+
test.beforeEach(async ({ page }) => {
|
|
5
|
+
await setupPage(page, { name: 'FocusWithinCase', type: 'useCase' })
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
test('animated focusWithinStyle applies on focus', async ({ page }, testInfo) => {
|
|
9
|
+
// native driver uses RN Animated API which can't animate CSS border colors on web
|
|
10
|
+
test.skip(
|
|
11
|
+
testInfo.project.name === 'animated-native',
|
|
12
|
+
'Native driver cannot animate CSS properties on web'
|
|
13
|
+
)
|
|
14
|
+
const input = page.locator('[data-testid="animated-input"]')
|
|
15
|
+
const parent = page.locator('[data-testid="animated-parent"]')
|
|
16
|
+
|
|
17
|
+
await input.waitFor({ state: 'visible' })
|
|
18
|
+
await input.focus()
|
|
19
|
+
// reanimated spring needs more time to settle than motion
|
|
20
|
+
await page.waitForTimeout(1500)
|
|
21
|
+
|
|
22
|
+
const borderColor = await parent.evaluate((el) => getComputedStyle(el).borderColor)
|
|
23
|
+
expect(borderColor).toBe('rgb(0, 128, 0)')
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test('animated focusWithinStyle removes on blur', async ({ page }, testInfo) => {
|
|
27
|
+
test.skip(
|
|
28
|
+
testInfo.project.name === 'animated-native',
|
|
29
|
+
'Native driver cannot animate CSS properties on web'
|
|
30
|
+
)
|
|
31
|
+
const input = page.locator('[data-testid="animated-input"]')
|
|
32
|
+
const parent = page.locator('[data-testid="animated-parent"]')
|
|
33
|
+
|
|
34
|
+
await input.waitFor({ state: 'visible' })
|
|
35
|
+
await input.focus()
|
|
36
|
+
await page.waitForTimeout(1500)
|
|
37
|
+
await input.blur()
|
|
38
|
+
await page.waitForTimeout(1500)
|
|
39
|
+
|
|
40
|
+
const borderColor = await parent.evaluate((el) => getComputedStyle(el).borderColor)
|
|
41
|
+
expect(borderColor).not.toBe('rgb(0, 128, 0)')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('animated focusWithinStyle does not cause React re-render (avoidReRenders)', async ({
|
|
45
|
+
page,
|
|
46
|
+
}, testInfo) => {
|
|
47
|
+
// native driver doesn't support avoidReRenders
|
|
48
|
+
test.skip(
|
|
49
|
+
testInfo.project.name === 'animated-native',
|
|
50
|
+
'Native driver does not support avoidReRenders'
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
const input = page.locator('[data-testid="animated-input"]')
|
|
54
|
+
const renders = page.locator('[data-testid="animated-renders"]')
|
|
55
|
+
|
|
56
|
+
await input.waitFor({ state: 'visible' })
|
|
57
|
+
const before = await renders.textContent()
|
|
58
|
+
|
|
59
|
+
await input.focus()
|
|
60
|
+
await page.waitForTimeout(300)
|
|
61
|
+
await input.blur()
|
|
62
|
+
await page.waitForTimeout(300)
|
|
63
|
+
|
|
64
|
+
const after = await renders.textContent()
|
|
65
|
+
expect(after).toBe(before)
|
|
66
|
+
})
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/test'
|
|
2
|
+
import { setupPage } from './test-utils'
|
|
3
|
+
|
|
4
|
+
test.beforeEach(async ({ page }) => {
|
|
5
|
+
await setupPage(page, { name: 'FocusWithinCase', type: 'useCase' })
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
test('focusWithinStyle applies via direct prop', async ({ page }) => {
|
|
9
|
+
const input = page.locator('[data-testid="direct-input"]')
|
|
10
|
+
const parent = page.locator('[data-testid="direct-parent"]')
|
|
11
|
+
|
|
12
|
+
await input.waitFor({ state: 'visible' })
|
|
13
|
+
await input.focus()
|
|
14
|
+
await page.waitForTimeout(100)
|
|
15
|
+
|
|
16
|
+
const borderColor = await parent.evaluate((el) => getComputedStyle(el).borderColor)
|
|
17
|
+
expect(borderColor).toBe('rgb(255, 0, 0)')
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
test('focusWithinStyle applies via styled() component', async ({ page }) => {
|
|
21
|
+
const input = page.locator('[data-testid="styled-input"]')
|
|
22
|
+
const parent = page.locator('[data-testid="styled-parent"]')
|
|
23
|
+
|
|
24
|
+
await input.waitFor({ state: 'visible' })
|
|
25
|
+
await input.focus()
|
|
26
|
+
await page.waitForTimeout(100)
|
|
27
|
+
|
|
28
|
+
const borderColor = await parent.evaluate((el) => getComputedStyle(el).borderColor)
|
|
29
|
+
expect(borderColor).toBe('rgb(0, 0, 255)')
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test('focusWithinStyle removes on blur', async ({ page }) => {
|
|
33
|
+
const input = page.locator('[data-testid="direct-input"]')
|
|
34
|
+
const parent = page.locator('[data-testid="direct-parent"]')
|
|
35
|
+
|
|
36
|
+
await input.waitFor({ state: 'visible' })
|
|
37
|
+
await input.focus()
|
|
38
|
+
await page.waitForTimeout(100)
|
|
39
|
+
await input.blur()
|
|
40
|
+
await page.waitForTimeout(100)
|
|
41
|
+
|
|
42
|
+
const borderColor = await parent.evaluate((el) => getComputedStyle(el).borderColor)
|
|
43
|
+
expect(borderColor).not.toBe('rgb(255, 0, 0)')
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test('plain focusWithinStyle does not re-render parent', async ({ page }) => {
|
|
47
|
+
const input = page.locator('[data-testid="direct-input"]')
|
|
48
|
+
const renders = page.locator('[data-testid="direct-renders"]')
|
|
49
|
+
|
|
50
|
+
await input.waitFor({ state: 'visible' })
|
|
51
|
+
const before = await renders.textContent()
|
|
52
|
+
|
|
53
|
+
await input.focus()
|
|
54
|
+
await page.waitForTimeout(200)
|
|
55
|
+
await input.blur()
|
|
56
|
+
await page.waitForTimeout(200)
|
|
57
|
+
|
|
58
|
+
const after = await renders.textContent()
|
|
59
|
+
expect(after).toBe(before)
|
|
60
|
+
})
|