diff --git a/.github/workflows/blade-interaction-tests.yml b/.github/workflows/blade-interaction-tests.yml new file mode 100644 index 00000000000..e1553cca858 --- /dev/null +++ b/.github/workflows/blade-interaction-tests.yml @@ -0,0 +1,37 @@ +name: Blade Interaction Tests + +# Runs the action when +# 1. 'Run Interaction Tests' label is added to PR +# 2. Workflow is trigerred manually +# 3. PR is merged to master + +on: + workflow_dispatch: + pull_request: + types: [labeled] + push: + branches: + - 'master' + +env: + GITHUB_ACCESS_TOKEN: ${{ secrets.CI_BOT_TOKEN }} + +jobs: + interaction-tests: + name: Run Interaction Tests + runs-on: ubuntu-latest # nosemgrep: non-self-hosted-runner + if: ${{ github.event_name == 'workflow_dispatch' || github.event.label.name == 'Run Interaction Tests' || github.event_name == 'push' }} + steps: + - name: Checkout Codebase + uses: actions/checkout@v3 + - name: Use Node v18 + uses: actions/setup-node@v3 + with: + node-version: 18.12.1 + - name: Setup Cache & Install Dependencies + uses: ./.github/actions/install-dependencies + - name: Run Interaction Tests + run: | + npx playwright install chromium firefox webkit --with-deps + yarn test:react:interaction:ci + working-directory: packages/blade diff --git a/.github/workflows/blade-validate.yml b/.github/workflows/blade-validate.yml index a87d2048a53..35d1c959482 100644 --- a/.github/workflows/blade-validate.yml +++ b/.github/workflows/blade-validate.yml @@ -46,7 +46,7 @@ jobs: node-version: 18.12.1 - name: Setup Cache & Install Dependencies uses: ./.github/actions/install-dependencies - - name: Run React & React Native Tests + - name: Run Unit Tests run: yarn test working-directory: packages/blade env: diff --git a/packages/blade/.storybook/react-native/storybook.requires.js b/packages/blade/.storybook/react-native/storybook.requires.js index b10be8d2338..343e22a6532 100644 --- a/packages/blade/.storybook/react-native/storybook.requires.js +++ b/packages/blade/.storybook/react-native/storybook.requires.js @@ -1,32 +1,37 @@ /* do not change this file, it is auto generated by storybook. */ -import { configure, addDecorator, addParameters, addArgsEnhancer } from '@storybook/react-native'; +import { + configure, + addDecorator, + addParameters, + addArgsEnhancer, +} from "@storybook/react-native"; global.STORIES = [ { - titlePrefix: '', - directory: './src', - files: '**/!(_KitchenSink)*.stories.?(ts|tsx|js|jsx)', + titlePrefix: "", + directory: "./src", + files: "**/!(_KitchenSink)*.stories.?(ts|tsx|js|jsx)", importPathMatcher: - '^\\.[\\\\/](?:src(?:\\/(?!\\.)(?:(?:(?!(?:^|\\/)\\.).)*?)\\/|\\/|$)(?:(?!(?:_KitchenSink))[^/]*?)[^/]*?\\.stories\\.(?:ts|tsx|js|jsx)?)$', + "^\\.[\\\\/](?:src(?:\\/(?!\\.)(?:(?:(?!(?:^|\\/)\\.).)*?)\\/|\\/|$)(?:(?!(?:_KitchenSink))[^/]*?)[^/]*?\\.stories\\.(?:ts|tsx|js|jsx)?)$", }, { - titlePrefix: '', - directory: './src', - files: '**/*.stories.internal.?(ts|tsx|js|jsx)', + titlePrefix: "", + directory: "./src", + files: "**/*.stories.internal.?(ts|tsx|js|jsx)", importPathMatcher: - '^\\.[\\\\/](?:src(?:\\/(?!\\.)(?:(?:(?!(?:^|\\/)\\.).)*?)\\/|\\/|$)(?!\\.)(?=.)[^/]*?\\.stories\\.internal\\.(?:ts|tsx|js|jsx)?)$', + "^\\.[\\\\/](?:src(?:\\/(?!\\.)(?:(?:(?!(?:^|\\/)\\.).)*?)\\/|\\/|$)(?!\\.)(?=.)[^/]*?\\.stories\\.internal\\.(?:ts|tsx|js|jsx)?)$", }, ]; -import '@storybook/addon-ondevice-notes/register'; -import '@storybook/addon-ondevice-controls/register'; -import '@storybook/addon-ondevice-backgrounds/register'; -import '@storybook/addon-ondevice-actions/register'; +import "@storybook/addon-ondevice-notes/register"; +import "@storybook/addon-ondevice-controls/register"; +import "@storybook/addon-ondevice-backgrounds/register"; +import "@storybook/addon-ondevice-actions/register"; -import { argsEnhancers } from '@storybook/addon-actions/dist/preview'; +import { argsEnhancers } from "@storybook/addon-actions/dist/preview"; -import { decorators, parameters } from './preview'; +import { decorators, parameters } from "./preview"; if (decorators) { decorators.forEach((decorator) => addDecorator(decorator)); @@ -42,83 +47,83 @@ try { const getStories = () => { return { - './src/components/Accordion/Accordion.stories.tsx': require('../../src/components/Accordion/Accordion.stories.tsx'), - './src/components/ActionList/ActionList.stories.tsx': require('../../src/components/ActionList/ActionList.stories.tsx'), - './src/components/Alert/Alert.stories.tsx': require('../../src/components/Alert/Alert.stories.tsx'), - './src/components/Amount/Amount.stories.tsx': require('../../src/components/Amount/Amount.stories.tsx'), - './src/components/Badge/Badge.stories.tsx': require('../../src/components/Badge/Badge.stories.tsx'), - './src/components/BaseHeaderFooter/BaseHeaderFooter.internal.stories.tsx': require('../../src/components/BaseHeaderFooter/BaseHeaderFooter.internal.stories.tsx'), - './src/components/BottomSheet/BottomSheet.stories.tsx': require('../../src/components/BottomSheet/BottomSheet.stories.tsx'), - './src/components/Box/BaseBox/BaseBox.internal.stories.tsx': require('../../src/components/Box/BaseBox/BaseBox.internal.stories.tsx'), - './src/components/Box/Box.stories.tsx': require('../../src/components/Box/Box.stories.tsx'), - './src/components/Box/styledProps/StyledProps.stories.tsx': require('../../src/components/Box/styledProps/StyledProps.stories.tsx'), - './src/components/Button/BaseButton/BaseButton.internal.stories.tsx': require('../../src/components/Button/BaseButton/BaseButton.internal.stories.tsx'), - './src/components/Button/Button/Button.stories.tsx': require('../../src/components/Button/Button/Button.stories.tsx'), - './src/components/Button/IconButton/IconButton.stories.tsx': require('../../src/components/Button/IconButton/IconButton.stories.tsx'), - './src/components/Card/Card.stories.tsx': require('../../src/components/Card/Card.stories.tsx'), - './src/components/Card/CardInteractive.stories.tsx': require('../../src/components/Card/CardInteractive.stories.tsx'), - './src/components/Carousel/Carousel.stories.tsx': require('../../src/components/Carousel/Carousel.stories.tsx'), - './src/components/Checkbox/Checkbox.stories.tsx': require('../../src/components/Checkbox/Checkbox.stories.tsx'), - './src/components/Checkbox/CheckboxGroup.stories.tsx': require('../../src/components/Checkbox/CheckboxGroup.stories.tsx'), - './src/components/Chip/Chip.stories.tsx': require('../../src/components/Chip/Chip.stories.tsx'), - './src/components/Chip/ChipGroup.stories.tsx': require('../../src/components/Chip/ChipGroup.stories.tsx'), - './src/components/Collapsible/Collapsible.stories.tsx': require('../../src/components/Collapsible/Collapsible.stories.tsx'), - './src/components/Counter/Counter.stories.tsx': require('../../src/components/Counter/Counter.stories.tsx'), - './src/components/Divider/Divider.stories.tsx': require('../../src/components/Divider/Divider.stories.tsx'), - './src/components/Dropdown/docs/DropdownWithAutoComplete.stories.tsx': require('../../src/components/Dropdown/docs/DropdownWithAutoComplete.stories.tsx'), - './src/components/Dropdown/docs/DropdownWithButton.stories.tsx': require('../../src/components/Dropdown/docs/DropdownWithButton.stories.tsx'), - './src/components/Dropdown/docs/DropdownWithSelect.stories.tsx': require('../../src/components/Dropdown/docs/DropdownWithSelect.stories.tsx'), - './src/components/Icons/Icons.stories.tsx': require('../../src/components/Icons/Icons.stories.tsx'), - './src/components/Indicator/Indicator.stories.tsx': require('../../src/components/Indicator/Indicator.stories.tsx'), - './src/components/Input/BaseInput/BaseInput.stories.tsx': require('../../src/components/Input/BaseInput/BaseInput.stories.tsx'), - './src/components/Input/DropdownInputTriggers/AutoComplete.stories.tsx': require('../../src/components/Input/DropdownInputTriggers/AutoComplete.stories.tsx'), - './src/components/Input/DropdownInputTriggers/SelectInput.stories.tsx': require('../../src/components/Input/DropdownInputTriggers/SelectInput.stories.tsx'), - './src/components/Input/OTPInput/OTPInput.stories.tsx': require('../../src/components/Input/OTPInput/OTPInput.stories.tsx'), - './src/components/Input/PasswordInput/PasswordInput.stories.tsx': require('../../src/components/Input/PasswordInput/PasswordInput.stories.tsx'), - './src/components/Input/TextArea/TextArea.stories.tsx': require('../../src/components/Input/TextArea/TextArea.stories.tsx'), - './src/components/Input/TextInput/TextInput.stories.tsx': require('../../src/components/Input/TextInput/TextInput.stories.tsx'), - './src/components/Link/BaseLink/BaseLink.stories.tsx': require('../../src/components/Link/BaseLink/BaseLink.stories.tsx'), - './src/components/Link/Link/Link.stories.tsx': require('../../src/components/Link/Link/Link.stories.tsx'), - './src/components/List/List.stories.tsx': require('../../src/components/List/List.stories.tsx'), - './src/components/Modal/docs/ModalExamples.stories.tsx': require('../../src/components/Modal/docs/ModalExamples.stories.tsx'), - './src/components/Modal/docs/SimpleModal.stories.tsx': require('../../src/components/Modal/docs/SimpleModal.stories.tsx'), - './src/components/Popover/Popover.stories.tsx': require('../../src/components/Popover/Popover.stories.tsx'), - './src/components/ProgressBar/ProgressBar.stories.tsx': require('../../src/components/ProgressBar/ProgressBar.stories.tsx'), - './src/components/Radio/Radio.stories.tsx': require('../../src/components/Radio/Radio.stories.tsx'), - './src/components/Skeleton/Skeleton.stories.tsx': require('../../src/components/Skeleton/Skeleton.stories.tsx'), - './src/components/SkipNav/SkipNav.stories.tsx': require('../../src/components/SkipNav/SkipNav.stories.tsx'), - './src/components/Spinner/BaseSpinner/BaseSpinner.stories.tsx': require('../../src/components/Spinner/BaseSpinner/BaseSpinner.stories.tsx'), - './src/components/Spinner/Spinner/Spinner.stories.tsx': require('../../src/components/Spinner/Spinner/Spinner.stories.tsx'), - './src/components/SpotlightPopoverTour/docs/Tour.stories.tsx': require('../../src/components/SpotlightPopoverTour/docs/Tour.stories.tsx'), - './src/components/Switch/Switch.stories.tsx': require('../../src/components/Switch/Switch.stories.tsx'), - './src/components/Table/docs/APIStories/TableAPI.stories.tsx': require('../../src/components/Table/docs/APIStories/TableAPI.stories.tsx'), - './src/components/Table/docs/APIStories/TableBodyAPI.stories.tsx': require('../../src/components/Table/docs/APIStories/TableBodyAPI.stories.tsx'), - './src/components/Table/docs/APIStories/TableCellAPI.stories.tsx': require('../../src/components/Table/docs/APIStories/TableCellAPI.stories.tsx'), - './src/components/Table/docs/APIStories/TableFooterAPI.stories.tsx': require('../../src/components/Table/docs/APIStories/TableFooterAPI.stories.tsx'), - './src/components/Table/docs/APIStories/TableFooterCellAPI.stories.tsx': require('../../src/components/Table/docs/APIStories/TableFooterCellAPI.stories.tsx'), - './src/components/Table/docs/APIStories/TableFooterRowAPI.stories.tsx': require('../../src/components/Table/docs/APIStories/TableFooterRowAPI.stories.tsx'), - './src/components/Table/docs/APIStories/TableHeaderAPI.stories.tsx': require('../../src/components/Table/docs/APIStories/TableHeaderAPI.stories.tsx'), - './src/components/Table/docs/APIStories/TableHeaderCellAPI.stories.tsx': require('../../src/components/Table/docs/APIStories/TableHeaderCellAPI.stories.tsx'), - './src/components/Table/docs/APIStories/TableHeaderRowAPI.stories.tsx': require('../../src/components/Table/docs/APIStories/TableHeaderRowAPI.stories.tsx'), - './src/components/Table/docs/APIStories/TablePaginationAPI.stories.tsx': require('../../src/components/Table/docs/APIStories/TablePaginationAPI.stories.tsx'), - './src/components/Table/docs/APIStories/TableRowAPI.stories.tsx': require('../../src/components/Table/docs/APIStories/TableRowAPI.stories.tsx'), - './src/components/Table/docs/APIStories/TableToolbarActionsAPI.stories.tsx': require('../../src/components/Table/docs/APIStories/TableToolbarActionsAPI.stories.tsx'), - './src/components/Table/docs/APIStories/TableToolbarAPI.stories.tsx': require('../../src/components/Table/docs/APIStories/TableToolbarAPI.stories.tsx'), - './src/components/Table/docs/BasicTable.stories.tsx': require('../../src/components/Table/docs/BasicTable.stories.tsx'), - './src/components/Table/docs/TableExamples.stories.tsx': require('../../src/components/Table/docs/TableExamples.stories.tsx'), - './src/components/Tabs/Tabs.stories.tsx': require('../../src/components/Tabs/Tabs.stories.tsx'), - './src/components/Tag/Tag.stories.tsx': require('../../src/components/Tag/Tag.stories.tsx'), - './src/components/Tooltip/Tooltip.stories.tsx': require('../../src/components/Tooltip/Tooltip.stories.tsx'), - './src/components/Typography/BaseText/BaseText.stories.tsx': require('../../src/components/Typography/BaseText/BaseText.stories.tsx'), - './src/components/Typography/Code/Code.stories.tsx': require('../../src/components/Typography/Code/Code.stories.tsx'), - './src/components/Typography/Display/Display.stories.tsx': require('../../src/components/Typography/Display/Display.stories.tsx'), - './src/components/Typography/Heading/Heading.stories.tsx': require('../../src/components/Typography/Heading/Heading.stories.tsx'), - './src/components/Typography/Text/Text.stories.tsx': require('../../src/components/Typography/Text/Text.stories.tsx'), - './src/components/Typography/Title/Title.stories.tsx': require('../../src/components/Typography/Title/Title.stories.tsx'), - './src/components/VisuallyHidden/VisuallyHidden.stories.tsx': require('../../src/components/VisuallyHidden/VisuallyHidden.stories.tsx'), - './src/storybook-recipes/AccessibilityInterop/AccessibilityInteropDemo.stories.tsx': require('../../src/storybook-recipes/AccessibilityInterop/AccessibilityInteropDemo.stories.tsx'), - './src/storybook-recipes/SimpleDashboard.stories.tsx': require('../../src/storybook-recipes/SimpleDashboard.stories.tsx'), - './src/storybook-recipes/SimpleForm.stories.tsx': require('../../src/storybook-recipes/SimpleForm.stories.tsx'), + "./src/components/Accordion/Accordion.stories.tsx": require("../../src/components/Accordion/Accordion.stories.tsx"), + "./src/components/ActionList/ActionList.stories.tsx": require("../../src/components/ActionList/ActionList.stories.tsx"), + "./src/components/Alert/Alert.stories.tsx": require("../../src/components/Alert/Alert.stories.tsx"), + "./src/components/Amount/Amount.stories.tsx": require("../../src/components/Amount/Amount.stories.tsx"), + "./src/components/Badge/Badge.stories.tsx": require("../../src/components/Badge/Badge.stories.tsx"), + "./src/components/BaseHeaderFooter/BaseHeaderFooter.internal.stories.tsx": require("../../src/components/BaseHeaderFooter/BaseHeaderFooter.internal.stories.tsx"), + "./src/components/BottomSheet/BottomSheet.stories.tsx": require("../../src/components/BottomSheet/BottomSheet.stories.tsx"), + "./src/components/Box/BaseBox/BaseBox.internal.stories.tsx": require("../../src/components/Box/BaseBox/BaseBox.internal.stories.tsx"), + "./src/components/Box/Box.stories.tsx": require("../../src/components/Box/Box.stories.tsx"), + "./src/components/Box/styledProps/StyledProps.stories.tsx": require("../../src/components/Box/styledProps/StyledProps.stories.tsx"), + "./src/components/Button/BaseButton/BaseButton.internal.stories.tsx": require("../../src/components/Button/BaseButton/BaseButton.internal.stories.tsx"), + "./src/components/Button/Button/Button.stories.tsx": require("../../src/components/Button/Button/Button.stories.tsx"), + "./src/components/Button/IconButton/IconButton.stories.tsx": require("../../src/components/Button/IconButton/IconButton.stories.tsx"), + "./src/components/Card/Card.stories.tsx": require("../../src/components/Card/Card.stories.tsx"), + "./src/components/Card/CardInteractive.stories.tsx": require("../../src/components/Card/CardInteractive.stories.tsx"), + "./src/components/Carousel/Carousel.stories.tsx": require("../../src/components/Carousel/Carousel.stories.tsx"), + "./src/components/Checkbox/Checkbox.stories.tsx": require("../../src/components/Checkbox/Checkbox.stories.tsx"), + "./src/components/Checkbox/CheckboxGroup.stories.tsx": require("../../src/components/Checkbox/CheckboxGroup.stories.tsx"), + "./src/components/Chip/Chip.stories.tsx": require("../../src/components/Chip/Chip.stories.tsx"), + "./src/components/Chip/ChipGroup.stories.tsx": require("../../src/components/Chip/ChipGroup.stories.tsx"), + "./src/components/Collapsible/Collapsible.stories.tsx": require("../../src/components/Collapsible/Collapsible.stories.tsx"), + "./src/components/Counter/Counter.stories.tsx": require("../../src/components/Counter/Counter.stories.tsx"), + "./src/components/Divider/Divider.stories.tsx": require("../../src/components/Divider/Divider.stories.tsx"), + "./src/components/Dropdown/docs/DropdownWithAutoComplete.stories.tsx": require("../../src/components/Dropdown/docs/DropdownWithAutoComplete.stories.tsx"), + "./src/components/Dropdown/docs/DropdownWithButton.stories.tsx": require("../../src/components/Dropdown/docs/DropdownWithButton.stories.tsx"), + "./src/components/Dropdown/docs/DropdownWithSelect.stories.tsx": require("../../src/components/Dropdown/docs/DropdownWithSelect.stories.tsx"), + "./src/components/Icons/Icons.stories.tsx": require("../../src/components/Icons/Icons.stories.tsx"), + "./src/components/Indicator/Indicator.stories.tsx": require("../../src/components/Indicator/Indicator.stories.tsx"), + "./src/components/Input/BaseInput/BaseInput.stories.tsx": require("../../src/components/Input/BaseInput/BaseInput.stories.tsx"), + "./src/components/Input/DropdownInputTriggers/AutoComplete.stories.tsx": require("../../src/components/Input/DropdownInputTriggers/AutoComplete.stories.tsx"), + "./src/components/Input/DropdownInputTriggers/SelectInput.stories.tsx": require("../../src/components/Input/DropdownInputTriggers/SelectInput.stories.tsx"), + "./src/components/Input/OTPInput/OTPInput.stories.tsx": require("../../src/components/Input/OTPInput/OTPInput.stories.tsx"), + "./src/components/Input/PasswordInput/PasswordInput.stories.tsx": require("../../src/components/Input/PasswordInput/PasswordInput.stories.tsx"), + "./src/components/Input/TextArea/TextArea.stories.tsx": require("../../src/components/Input/TextArea/TextArea.stories.tsx"), + "./src/components/Input/TextInput/TextInput.stories.tsx": require("../../src/components/Input/TextInput/TextInput.stories.tsx"), + "./src/components/Link/BaseLink/BaseLink.stories.tsx": require("../../src/components/Link/BaseLink/BaseLink.stories.tsx"), + "./src/components/Link/Link/Link.stories.tsx": require("../../src/components/Link/Link/Link.stories.tsx"), + "./src/components/List/List.stories.tsx": require("../../src/components/List/List.stories.tsx"), + "./src/components/Modal/docs/ModalExamples.stories.tsx": require("../../src/components/Modal/docs/ModalExamples.stories.tsx"), + "./src/components/Modal/docs/SimpleModal.stories.tsx": require("../../src/components/Modal/docs/SimpleModal.stories.tsx"), + "./src/components/Popover/Popover.stories.tsx": require("../../src/components/Popover/Popover.stories.tsx"), + "./src/components/ProgressBar/ProgressBar.stories.tsx": require("../../src/components/ProgressBar/ProgressBar.stories.tsx"), + "./src/components/Radio/Radio.stories.tsx": require("../../src/components/Radio/Radio.stories.tsx"), + "./src/components/Skeleton/Skeleton.stories.tsx": require("../../src/components/Skeleton/Skeleton.stories.tsx"), + "./src/components/SkipNav/SkipNav.stories.tsx": require("../../src/components/SkipNav/SkipNav.stories.tsx"), + "./src/components/Spinner/BaseSpinner/BaseSpinner.stories.tsx": require("../../src/components/Spinner/BaseSpinner/BaseSpinner.stories.tsx"), + "./src/components/Spinner/Spinner/Spinner.stories.tsx": require("../../src/components/Spinner/Spinner/Spinner.stories.tsx"), + "./src/components/SpotlightPopoverTour/docs/Tour.stories.tsx": require("../../src/components/SpotlightPopoverTour/docs/Tour.stories.tsx"), + "./src/components/Switch/Switch.stories.tsx": require("../../src/components/Switch/Switch.stories.tsx"), + "./src/components/Table/docs/APIStories/TableAPI.stories.tsx": require("../../src/components/Table/docs/APIStories/TableAPI.stories.tsx"), + "./src/components/Table/docs/APIStories/TableBodyAPI.stories.tsx": require("../../src/components/Table/docs/APIStories/TableBodyAPI.stories.tsx"), + "./src/components/Table/docs/APIStories/TableCellAPI.stories.tsx": require("../../src/components/Table/docs/APIStories/TableCellAPI.stories.tsx"), + "./src/components/Table/docs/APIStories/TableFooterAPI.stories.tsx": require("../../src/components/Table/docs/APIStories/TableFooterAPI.stories.tsx"), + "./src/components/Table/docs/APIStories/TableFooterCellAPI.stories.tsx": require("../../src/components/Table/docs/APIStories/TableFooterCellAPI.stories.tsx"), + "./src/components/Table/docs/APIStories/TableFooterRowAPI.stories.tsx": require("../../src/components/Table/docs/APIStories/TableFooterRowAPI.stories.tsx"), + "./src/components/Table/docs/APIStories/TableHeaderAPI.stories.tsx": require("../../src/components/Table/docs/APIStories/TableHeaderAPI.stories.tsx"), + "./src/components/Table/docs/APIStories/TableHeaderCellAPI.stories.tsx": require("../../src/components/Table/docs/APIStories/TableHeaderCellAPI.stories.tsx"), + "./src/components/Table/docs/APIStories/TableHeaderRowAPI.stories.tsx": require("../../src/components/Table/docs/APIStories/TableHeaderRowAPI.stories.tsx"), + "./src/components/Table/docs/APIStories/TablePaginationAPI.stories.tsx": require("../../src/components/Table/docs/APIStories/TablePaginationAPI.stories.tsx"), + "./src/components/Table/docs/APIStories/TableRowAPI.stories.tsx": require("../../src/components/Table/docs/APIStories/TableRowAPI.stories.tsx"), + "./src/components/Table/docs/APIStories/TableToolbarActionsAPI.stories.tsx": require("../../src/components/Table/docs/APIStories/TableToolbarActionsAPI.stories.tsx"), + "./src/components/Table/docs/APIStories/TableToolbarAPI.stories.tsx": require("../../src/components/Table/docs/APIStories/TableToolbarAPI.stories.tsx"), + "./src/components/Table/docs/BasicTable.stories.tsx": require("../../src/components/Table/docs/BasicTable.stories.tsx"), + "./src/components/Table/docs/TableExamples.stories.tsx": require("../../src/components/Table/docs/TableExamples.stories.tsx"), + "./src/components/Tabs/Tabs.stories.tsx": require("../../src/components/Tabs/Tabs.stories.tsx"), + "./src/components/Tag/Tag.stories.tsx": require("../../src/components/Tag/Tag.stories.tsx"), + "./src/components/Tooltip/Tooltip.stories.tsx": require("../../src/components/Tooltip/Tooltip.stories.tsx"), + "./src/components/Typography/BaseText/BaseText.stories.tsx": require("../../src/components/Typography/BaseText/BaseText.stories.tsx"), + "./src/components/Typography/Code/Code.stories.tsx": require("../../src/components/Typography/Code/Code.stories.tsx"), + "./src/components/Typography/Display/Display.stories.tsx": require("../../src/components/Typography/Display/Display.stories.tsx"), + "./src/components/Typography/Heading/Heading.stories.tsx": require("../../src/components/Typography/Heading/Heading.stories.tsx"), + "./src/components/Typography/Text/Text.stories.tsx": require("../../src/components/Typography/Text/Text.stories.tsx"), + "./src/components/Typography/Title/Title.stories.tsx": require("../../src/components/Typography/Title/Title.stories.tsx"), + "./src/components/VisuallyHidden/VisuallyHidden.stories.tsx": require("../../src/components/VisuallyHidden/VisuallyHidden.stories.tsx"), + "./src/storybook-recipes/AccessibilityInterop/AccessibilityInteropDemo.stories.tsx": require("../../src/storybook-recipes/AccessibilityInterop/AccessibilityInteropDemo.stories.tsx"), + "./src/storybook-recipes/SimpleDashboard.stories.tsx": require("../../src/storybook-recipes/SimpleDashboard.stories.tsx"), + "./src/storybook-recipes/SimpleForm.stories.tsx": require("../../src/storybook-recipes/SimpleForm.stories.tsx"), }; }; diff --git a/packages/blade/.storybook/react/main.ts b/packages/blade/.storybook/react/main.ts index 7460200550f..f108ec91156 100644 --- a/packages/blade/.storybook/react/main.ts +++ b/packages/blade/.storybook/react/main.ts @@ -29,6 +29,7 @@ const config: StorybookConfig = { '@storybook/addon-docs', '@storybook/addon-a11y', '@storybook/preset-create-react-app', + '@storybook/addon-interactions' ], framework: { name: '@storybook/react-webpack5', diff --git a/packages/blade/.storybook/react/preview.tsx b/packages/blade/.storybook/react/preview.tsx index 7087b0a4e64..4072bac8c8d 100644 --- a/packages/blade/.storybook/react/preview.tsx +++ b/packages/blade/.storybook/react/preview.tsx @@ -9,6 +9,7 @@ import { INTERNAL_STORY_ADDON_PARAM } from './constants'; const { GlobalStyle } = global; import { DocsContainer } from '@storybook/addon-docs'; import React from 'react'; +import { MINIMAL_VIEWPORTS } from '@storybook/addon-viewport'; import './global.css'; export const parameters = { @@ -27,6 +28,19 @@ export const parameters = { disable: true, }, }, + viewport: { + viewports: { + ...MINIMAL_VIEWPORTS, + iPhone6: { + name: 'iPhone 6', + styles: { + height: '667px', + width: '375px', + }, + type: 'mobile', + }, + }, + }, // on development setting it to undefined so that on 'live reload' it won't switch // to docs panel while developing the component viewMode: process.env.NODE_ENV === 'development' ? undefined : 'docs', @@ -58,7 +72,7 @@ export const parameters = { 'useTheme', ], 'Components', - ['*', 'KitchenSink'], + ['*', 'Interaction Tests', 'KitchenSink'], 'Recipes', ], }, diff --git a/packages/blade/package.json b/packages/blade/package.json index 93aad2824e1..82770302dfe 100644 --- a/packages/blade/package.json +++ b/packages/blade/package.json @@ -105,8 +105,12 @@ "react-native:storybook:ios": "yarn react-native:get-stories && cross-env FRAMEWORK=REACT_NATIVE react-native run-ios", "react-native:storybook:start": "yarn react-native:get-stories && cross-env NODE_OPTIONS=--openssl-legacy-provider FRAMEWORK=REACT_NATIVE react-native start --reset-cache", "react": "yarn run react:storybook", - "react:storybook": "cross-env NODE_OPTIONS=--openssl-legacy-provider FRAMEWORK=REACT storybook dev -c ./.storybook/react -p 9009", - "react:storybook:build": "cross-env NODE_OPTIONS=--openssl-legacy-provider FRAMEWORK=REACT storybook build -c ./.storybook/react -o storybook-site", + "react:storybook": "cross-env FRAMEWORK=REACT storybook dev -c ./.storybook/react -p 9009", + "react:storybook:build": "cross-env FRAMEWORK=REACT storybook build -c ./.storybook/react -o storybook-site --quiet", + "react:storybook:serve": "http-server storybook-site --port 9009 --silent", + "react:storybook:serve:test": "wait-on http://127.0.0.1:9009/ && yarn test:react:interaction", + "test:react:interaction": "cross-env FRAMEWORK=REACT test-storybook -c ./.storybook/react --url http://127.0.0.1:9009/", + "test:react:interaction:ci": "yarn react:storybook:build && run-p react:storybook:serve react:storybook:serve:test --race", "test:react": "cross-env FRAMEWORK=REACT jest -c ./jest.web.config.js --shard=$SHARD --forceExit", "test:react-native": "cross-env FRAMEWORK=REACT_NATIVE jest -c ./jest.native.config.js --shard=$SHARD --forceExit", "start:ios": "cross-env NODE_OPTIONS=--openssl-legacy-provider run-p react-native:storybook:start react-native:storybook:ios", @@ -135,6 +139,13 @@ "tinycolor2": "1.6.0" }, "devDependencies": { + "http-server": "14.1.1", + "wait-on": "7.2.0", + "@storybook/addon-interactions": "7.6.6", + "@storybook/testing-library": "0.2.2", + "@storybook/test-runner": "0.16.0", + "@storybook/jest": "0.2.3", + "playwright": "1.40.1", "chromatic": "6.22.0", "@babel/cli": "7.23.0", "@babel/core": "7.20.2", diff --git a/packages/blade/src/components/Carousel/Carousel.stories.tsx b/packages/blade/src/components/Carousel/Carousel.stories.tsx index c388c02f325..a1fd743830e 100644 --- a/packages/blade/src/components/Carousel/Carousel.stories.tsx +++ b/packages/blade/src/components/Carousel/Carousel.stories.tsx @@ -255,7 +255,7 @@ const TestimonialCard = ({ ); }; -const CarouselExample = (props: Omit): React.ReactElement => { +export const CarouselExample = (props: Omit): React.ReactElement => { const key = `${props.visibleItems}-${props.shouldAddStartEndSpacing}`; return ( diff --git a/packages/blade/src/components/Carousel/Carousel.web.tsx b/packages/blade/src/components/Carousel/Carousel.web.tsx index 20e5af048b4..2384872bad4 100644 --- a/packages/blade/src/components/Carousel/Carousel.web.tsx +++ b/packages/blade/src/components/Carousel/Carousel.web.tsx @@ -13,18 +13,18 @@ import type { CarouselContextProps } from './CarouselContext'; import { CarouselContext } from './CarouselContext'; import { getCarouselItemId } from './utils'; import { CAROUSEL_AUTOPLAY_INTERVAL, componentIds } from './constants'; -import debounce from '~utils/lodashButBetter/debounce'; -import throttle from '~utils/lodashButBetter/throttle'; import getIn from '~utils/lodashButBetter/get'; +import throttle from '~utils/lodashButBetter/throttle'; +import debounce from '~utils/lodashButBetter/debounce'; import { Box } from '~components/Box'; import BaseBox from '~components/Box/BaseBox'; -import { castWebType, makeMotionTime, useInterval } from '~utils'; +import { castWebType, makeMotionTime, useInterval, usePrevious } from '~utils'; import { useId } from '~utils/useId'; import { makeAccessible } from '~utils/makeAccessible'; import { metaAttribute, MetaConstants } from '~utils/metaAttribute'; -import { useDidUpdate } from '~utils/useDidUpdate'; import { useVerifyAllowedChildren } from '~utils/useVerifyAllowedChildren/useVerifyAllowedChildren'; import { useTheme } from '~components/BladeProvider'; +import { useFirstRender } from '~utils/useFirstRender'; type ControlsProp = Required< Pick< @@ -223,6 +223,29 @@ const CarouselBody = React.forwardRef( }, ); +/** + * A custom hook which syncs an effect with a state + * While ignoring the first render & only running the effect when the state changes + */ +function useSyncUpdateEffect( + effect: React.EffectCallback, + stateToSyncWith: T, + deps: React.DependencyList, +) { + const isFirst = useFirstRender(); + const prevState = usePrevious(stateToSyncWith); + + React.useEffect(() => { + if (!isFirst) { + // if the state is the same as the previous state + // we don't want to run the effect + if (prevState === stateToSyncWith) return; + return effect(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [stateToSyncWith, ...deps]); +} + const Carousel = ({ autoPlay, visibleItems = 1, @@ -450,9 +473,13 @@ const Carousel = ({ shouldAddStartEndSpacing, ]); - useDidUpdate(() => { - onChange?.(activeSlide); - }, [activeSlide, onChange]); + useSyncUpdateEffect( + () => { + onChange?.(activeSlide); + }, + activeSlide, + [onChange], + ); return ( diff --git a/packages/blade/src/components/Carousel/__tests__/Carousel.test.stories.tsx b/packages/blade/src/components/Carousel/__tests__/Carousel.test.stories.tsx new file mode 100644 index 00000000000..692fdc4d13f --- /dev/null +++ b/packages/blade/src/components/Carousel/__tests__/Carousel.test.stories.tsx @@ -0,0 +1,205 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import type { StoryFn } from '@storybook/react'; +import { within, userEvent } from '@storybook/testing-library'; +import { expect, jest } from '@storybook/jest'; +import type { Mock } from 'jest-mock'; +import React from 'react'; +import type { CarouselProps } from '../'; +import { Carousel as CarouselComponent } from '../'; +import { CarouselExample } from '../Carousel.stories'; +import { Box } from '~components/Box'; + +const sleep = (ms: number): Promise => new Promise((resolve) => setTimeout(resolve, ms)); + +let onChange: Mock | null = null; + +const BasicCarousel = (props: CarouselProps): React.ReactElement => ( + + + +); + +export const TestCarouselOnChange: StoryFn = ( + props, +): React.ReactElement => { + onChange = jest.fn(); + return ; +}; + +TestCarouselOnChange.play = async ({ canvasElement }) => { + const { getByRole, getByText } = within(canvasElement); + const nextButton = getByRole('button', { name: 'Next Slide' }); + const previousButton = getByRole('button', { name: 'Previous Slide' }); + await userEvent.click(nextButton); + await sleep(1000); + await expect(onChange).toBeCalledWith(1); + await userEvent.click(previousButton); + await sleep(1000); + await expect(onChange).toBeCalledWith(0); + getByText(/Single Flow To Collect And Disburse Payments/)?.scrollIntoView({ behavior: 'smooth' }); + await sleep(1000); + await expect(onChange).toBeCalledWith(3); + await expect(onChange).toBeCalledTimes(3); +}; + +export const TestIndicatorButton: StoryFn = ( + props, +): React.ReactElement => { + onChange = jest.fn(); + return ; +}; + +TestIndicatorButton.play = async ({ canvasElement }) => { + const { getByLabelText } = within(canvasElement); + const indicatorButton = getByLabelText('Slide 7'); + await userEvent.click(indicatorButton); + await sleep(1000); + await expect(onChange).toBeCalledWith(6); + await expect(onChange).toBeCalledTimes(1); +}; + +export const TestStartOverAfterStartEnd: StoryFn = ( + props, +): React.ReactElement => { + onChange = jest.fn(); + return ; +}; + +TestStartOverAfterStartEnd.play = async ({ canvasElement }) => { + const { getByRole } = within(canvasElement); + const nextButton = getByRole('button', { name: 'Next Slide' }); + const previousButton = getByRole('button', { name: 'Previous Slide' }); + await userEvent.click(previousButton); + await sleep(1000); + await expect(onChange).toBeCalledWith(6); + await userEvent.click(nextButton); + await sleep(1000); + await expect(onChange).toBeCalledWith(0); + await expect(onChange).toBeCalledTimes(2); +}; + +export const TestAutoPlay: StoryFn = (props): React.ReactElement => { + onChange = jest.fn(); + return ; +}; + +TestAutoPlay.play = async ({ canvasElement }) => { + const { getByRole } = within(canvasElement); + await sleep(8000); + await expect(onChange).toBeCalledWith(1); + await expect(getByRole('tab', { selected: true })).toHaveAccessibleName('Slide 3'); + await expect(onChange).toBeCalledTimes(1); +}; + +export const TestAutofit: StoryFn = (props): React.ReactElement => { + onChange = jest.fn(); + return ( + + ); +}; + +TestAutofit.play = async ({ canvasElement }) => { + await sleep(1000); + const { getByLabelText, queryByRole } = within(canvasElement); + const lastIndicatorButton = getByLabelText('Slide 7'); + await expect(onChange).not.toBeCalled(); + await userEvent.click(lastIndicatorButton); + await sleep(1000); + const nextButton = queryByRole('button', { name: 'Next Slide' }); + await expect(nextButton).toBeNull(); + const firstIndicatorButton = getByLabelText('Slide 1'); + await userEvent.click(firstIndicatorButton); + await sleep(1000); + const previousButton = queryByRole('button', { name: 'Previous Slide' }); + await expect(previousButton).toBeNull(); + await expect(onChange).toBeCalledTimes(2); +}; + +export const TestAutoPlayPause: StoryFn = (props): React.ReactElement => { + onChange = jest.fn(); + return ; +}; + +TestAutoPlayPause.play = async ({ canvasElement }) => { + const { getByText } = within(canvasElement); + const slide = getByText(/Acquire Customers From New Customer Segments/); + await userEvent.hover(slide); + await sleep(7000); + await expect(onChange).not.toHaveBeenCalled(); +}; + +export const TestVisibleItemsOnMobile: StoryFn = ( + props, +): React.ReactElement => { + onChange = jest.fn(); + return ; +}; + +TestVisibleItemsOnMobile.parameters = { + viewport: { + defaultViewport: 'iPhone6', + }, +}; + +TestVisibleItemsOnMobile.play = async ({ canvasElement }) => { + // on mobile regardless of the visible items prop we always show 1 item + const { getByRole } = within(canvasElement); + const nextButton = getByRole('button', { name: 'Next Slide' }); + await userEvent.click(nextButton); + await sleep(1000); + await expect(onChange).toBeCalledWith(1); +}; + +// Test for onChange fires multiple times on parent component update +// https://github.com/razorpay/blade/issues/1863 +const multipleOnChange = jest.fn(); +export const TestOnChangeParentUpdate: StoryFn = ( + props, +): React.ReactElement => { + const [, setCount] = React.useState(0); + + React.useEffect(() => { + const intervalId = setInterval(() => { + setCount((prev) => prev++); + }, 100); + + return () => clearInterval(intervalId); + }, []); + + return ; +}; + +TestOnChangeParentUpdate.play = async ({ canvasElement }) => { + const { getByRole } = within(canvasElement); + await expect(multipleOnChange).not.toBeCalled(); + const nextButton = getByRole('button', { name: 'Next Slide' }); + const previousButton = getByRole('button', { name: 'Previous Slide' }); + await userEvent.click(nextButton); + await sleep(1000); + await expect(multipleOnChange).toBeCalledWith(1); + await userEvent.click(previousButton); + await sleep(1000); + await expect(multipleOnChange).toBeCalledWith(0); + await expect(multipleOnChange).toBeCalledTimes(2); +}; + +export default { + title: 'Components/Interaction Tests/Carousel', + component: CarouselComponent, + parameters: { + controls: { + disable: true, + }, + a11y: { disable: true }, + essentials: { disable: true }, + actions: { disable: true }, + }, +}; diff --git a/packages/blade/src/components/Carousel/__tests__/Carousel.web.test.tsx b/packages/blade/src/components/Carousel/__tests__/Carousel.web.test.tsx index d5456886606..be99c081557 100644 --- a/packages/blade/src/components/Carousel/__tests__/Carousel.web.test.tsx +++ b/packages/blade/src/components/Carousel/__tests__/Carousel.web.test.tsx @@ -1,6 +1,5 @@ /* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */ /* eslint-disable @typescript-eslint/require-await */ -import { fireEvent, act } from '@testing-library/react'; import { mockViewport } from 'jsdom-testing-mocks'; import { Carousel } from '../Carousel'; import { CarouselItem } from '../CarouselItem'; @@ -43,107 +42,9 @@ afterAll(() => { }); describe('', () => { - it('should go to next/previous slide', () => { + it('should render number of indicators basis visibleItems prop', () => { const onChange = jest.fn(); - const { getByRole, queryAllByRole, queryAllByTestId } = renderWithTheme( - - - - - - - - - - - - - - , - ); - expect(queryAllByTestId('active-slide')[0]).toHaveTextContent('0'); - - const nextButton = getByRole('button', { name: 'Next Slide' }); - const previousButton = getByRole('button', { name: 'Previous Slide' }); - fireEvent.click(nextButton); - fireEvent.click(nextButton); - - expect(nextButton).toBeInTheDocument(); - expect(previousButton).toBeInTheDocument(); - - expect(onChange).toHaveBeenLastCalledWith(2); - - fireEvent.click(previousButton); - fireEvent.click(previousButton); - - expect(onChange).toHaveBeenLastCalledWith(0); - - expect(queryAllByRole('tab').length).toBe(4); - expect(onChange).toBeCalledTimes(4); - }); - - it('should go to specific slide when clicking on indicator button', () => { - const onChange = jest.fn(); - const { getByRole, queryAllByTestId } = renderWithTheme( - - - - - - - - - - - - - - , - ); - expect(queryAllByTestId('active-slide')[0]).toHaveTextContent('0'); - - const indicatorButton = getByRole('tab', { name: 'Slide 3' }); - fireEvent.click(indicatorButton); - - expect(onChange).toHaveBeenLastCalledWith(2); - expect(onChange).toBeCalledTimes(1); - }); - - it('should wrap around when reaching start or end slide', () => { - const onChange = jest.fn(); - const { getByRole, queryAllByTestId } = renderWithTheme( - - - - - - - - - - - - - - , - ); - expect(queryAllByTestId('active-slide')[0]).toHaveTextContent('0'); - - const nextButton = getByRole('button', { name: 'Next Slide' }); - const previousButton = getByRole('button', { name: 'Previous Slide' }); - fireEvent.click(previousButton); - - expect(onChange).toHaveBeenLastCalledWith(3); - - fireEvent.click(nextButton); - expect(onChange).toHaveBeenLastCalledWith(0); - - expect(onChange).toBeCalledTimes(2); - }); - - it('should work with visibleItems prop', () => { - const onChange = jest.fn(); - const { getByRole, queryAllByRole, queryAllByTestId } = renderWithTheme( + const { queryAllByRole, queryAllByTestId } = renderWithTheme( @@ -164,183 +65,8 @@ describe('', () => { ); expect(queryAllByTestId('active-slide')[0]).toHaveTextContent('0'); - const nextButton = getByRole('button', { name: 'Next Slide' }); - const previousButton = getByRole('button', { name: 'Previous Slide' }); - fireEvent.click(nextButton); - - expect(onChange).toHaveBeenLastCalledWith(1); - - fireEvent.click(previousButton); - - expect(onChange).toHaveBeenLastCalledWith(0); - // assert indicator button count expect(queryAllByRole('tab').length).toBe(3); - expect(onChange).toBeCalledTimes(2); - }); - - it('should auto play', async () => { - jest.useFakeTimers(); - const onChange = jest.fn(); - const { queryAllByTestId } = renderWithTheme( - - - - - - - - - - - , - ); - expect(queryAllByTestId('active-slide')[0]).toHaveTextContent('0'); - - await act(async () => { - jest.advanceTimersByTime(6000); - }); - - expect(onChange).toHaveBeenLastCalledWith(1); - - await act(async () => { - jest.advanceTimersByTime(6000); - }); - - expect(onChange).toHaveBeenLastCalledWith(2); - - await act(async () => { - jest.advanceTimersByTime(6000); - }); - - expect(onChange).toHaveBeenLastCalledWith(0); - expect(onChange).toBeCalledTimes(3); - }); - - it('should not auto play when mouse is over', async () => { - jest.useFakeTimers(); - const onChange = jest.fn(); - const { getByLabelText, queryAllByTestId } = renderWithTheme( - - - - - - - - - - - , - ); - expect(queryAllByTestId('active-slide')[0]).toHaveTextContent('0'); - - await act(async () => { - jest.advanceTimersByTime(6000); - }); - - expect(onChange).toHaveBeenLastCalledWith(1); - - // hover over carousel - fireEvent.mouseEnter(getByLabelText('My Carousel')); - - await act(async () => { - jest.advanceTimersByTime(6000); - }); - - expect(onChange).toHaveBeenLastCalledWith(1); - - // hover out of carousel - fireEvent.mouseLeave(getByLabelText('My Carousel')); - - await act(async () => { - jest.advanceTimersByTime(6000); - }); - - expect(onChange).toHaveBeenLastCalledWith(2); - expect(onChange).toBeCalledTimes(2); - }); - - it('should not auto play when focus is inside carousel', async () => { - jest.useFakeTimers(); - const onChange = jest.fn(); - const { getByLabelText } = renderWithTheme( - - - - - - - - - - - , - ); - - await act(async () => { - jest.advanceTimersByTime(6000); - }); - - expect(onChange).toHaveBeenLastCalledWith(1); - - // hover over carousel - fireEvent.focusIn(getByLabelText('My Carousel')); - - await act(async () => { - jest.advanceTimersByTime(6000); - }); - - expect(onChange).toHaveBeenLastCalledWith(1); - - // hover out of carousel - fireEvent.focusOut(getByLabelText('My Carousel')); - - await act(async () => { - jest.advanceTimersByTime(6000); - }); - - expect(onChange).toHaveBeenLastCalledWith(2); - expect(onChange).toBeCalledTimes(2); - }); - - test('when visibleItems:autofit & navigationButtonPosition:side then next / previous buttons should be removed on reaching start/end slide', () => { - const onChange = jest.fn(); - const { queryByRole, queryAllByTestId } = renderWithTheme( - - - - - - - - - - - - - - , - ); - expect(queryAllByTestId('active-slide')[0]).toHaveTextContent('0'); - - const nextButton = queryByRole('button', { name: 'Next Slide' }); - - expect(queryByRole('button', { name: 'Next Slide' })).toBeInTheDocument(); - expect(queryByRole('button', { name: 'Previous Slide' })).not.toBeInTheDocument(); - - fireEvent.click(nextButton!); - - expect(onChange).toHaveBeenLastCalledWith(1); - - fireEvent.click(nextButton!); - fireEvent.click(nextButton!); - - expect(onChange).toHaveBeenLastCalledWith(3); - - expect(queryByRole('button', { name: 'Next Slide' })).not.toBeInTheDocument(); - expect(queryByRole('button', { name: 'Previous Slide' })).toBeInTheDocument(); - expect(onChange).toBeCalledTimes(3); }); test('when visibleItems:autofit & shouldAddStartEndSpacing is undefined then we hide the indicators since they are unnecessary', () => { @@ -451,6 +177,7 @@ describe('Carousel Snapshots', () => { expect(container).toMatchSnapshot(); }); + // add this it('should not render overlay on mobile devices', () => { const viewport = mockViewport({ width: '320px', height: '568px' }); diff --git a/packages/blade/src/components/Carousel/__tests__/__snapshots__/Carousel.web.test.tsx.snap b/packages/blade/src/components/Carousel/__tests__/__snapshots__/Carousel.web.test.tsx.snap index 408502c57b7..382ffedfdc7 100644 --- a/packages/blade/src/components/Carousel/__tests__/__snapshots__/Carousel.web.test.tsx.snap +++ b/packages/blade/src/components/Carousel/__tests__/__snapshots__/Carousel.web.test.tsx.snap @@ -290,7 +290,7 @@ exports[`Carousel Snapshots should not render overlay on mobile devices 1`] = ` class=" c4" data-blade-component="base-box" data-slide-index="0" - id="carousel-52-carousel-item-0" + id="carousel-15-carousel-item-0" role="tabpanel" >
@@ -307,7 +307,7 @@ exports[`Carousel Snapshots should not render overlay on mobile devices 1`] = ` class=" c4" data-blade-component="base-box" data-slide-index="1" - id="carousel-52-carousel-item-1" + id="carousel-15-carousel-item-1" role="tabpanel" >
@@ -358,7 +358,7 @@ exports[`Carousel Snapshots should not render overlay on mobile devices 1`] = ` role="tablist" >