Skip to content

Commit d5f290b

Browse files
kamleshchandnanichaitanyadeorukhkarsnitin315anuraghazra
authored
feat: setup interaction testing (#1895)
Co-authored-by: Chaitanya Deorukhkar <deorukhkarchaitanya@gmail.com> Co-authored-by: Nitin Kumar <nitin.kumar@razorpay.com> Co-authored-by: Nitin Kumar <snitin315@gmail.com> Co-authored-by: anuraghazra <anurag.hazra@razorpay.com>
1 parent 468fd1c commit d5f290b

File tree

14 files changed

+1906
-450
lines changed

14 files changed

+1906
-450
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: Blade Interaction Tests
2+
3+
# Runs the action when
4+
# 1. 'Run Interaction Tests' label is added to PR
5+
# 2. Workflow is trigerred manually
6+
# 3. PR is merged to master
7+
8+
on:
9+
workflow_dispatch:
10+
pull_request:
11+
types: [labeled]
12+
push:
13+
branches:
14+
- 'master'
15+
16+
env:
17+
GITHUB_ACCESS_TOKEN: ${{ secrets.CI_BOT_TOKEN }}
18+
19+
jobs:
20+
interaction-tests:
21+
name: Run Interaction Tests
22+
runs-on: ubuntu-latest # nosemgrep: non-self-hosted-runner
23+
if: ${{ github.event_name == 'workflow_dispatch' || github.event.label.name == 'Run Interaction Tests' || github.event_name == 'push' }}
24+
steps:
25+
- name: Checkout Codebase
26+
uses: actions/checkout@v3
27+
- name: Use Node v18
28+
uses: actions/setup-node@v3
29+
with:
30+
node-version: 18.12.1
31+
- name: Setup Cache & Install Dependencies
32+
uses: ./.github/actions/install-dependencies
33+
- name: Run Interaction Tests
34+
run: |
35+
npx playwright install chromium firefox webkit --with-deps
36+
yarn test:react:interaction:ci
37+
working-directory: packages/blade

.github/workflows/blade-validate.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ jobs:
4646
node-version: 18.12.1
4747
- name: Setup Cache & Install Dependencies
4848
uses: ./.github/actions/install-dependencies
49-
- name: Run React & React Native Tests
49+
- name: Run Unit Tests
5050
run: yarn test
5151
working-directory: packages/blade
5252
env:

packages/blade/.storybook/react-native/storybook.requires.js

Lines changed: 97 additions & 92 deletions
Large diffs are not rendered by default.

packages/blade/.storybook/react/main.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const config: StorybookConfig = {
2929
'@storybook/addon-docs',
3030
'@storybook/addon-a11y',
3131
'@storybook/preset-create-react-app',
32+
'@storybook/addon-interactions'
3233
],
3334
framework: {
3435
name: '@storybook/react-webpack5',

packages/blade/.storybook/react/preview.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { INTERNAL_STORY_ADDON_PARAM } from './constants';
99
const { GlobalStyle } = global;
1010
import { DocsContainer } from '@storybook/addon-docs';
1111
import React from 'react';
12+
import { MINIMAL_VIEWPORTS } from '@storybook/addon-viewport';
1213
import './global.css';
1314

1415
export const parameters = {
@@ -27,6 +28,19 @@ export const parameters = {
2728
disable: true,
2829
},
2930
},
31+
viewport: {
32+
viewports: {
33+
...MINIMAL_VIEWPORTS,
34+
iPhone6: {
35+
name: 'iPhone 6',
36+
styles: {
37+
height: '667px',
38+
width: '375px',
39+
},
40+
type: 'mobile',
41+
},
42+
},
43+
},
3044
// on development setting it to undefined so that on 'live reload' it won't switch
3145
// to docs panel while developing the component
3246
viewMode: process.env.NODE_ENV === 'development' ? undefined : 'docs',
@@ -58,7 +72,7 @@ export const parameters = {
5872
'useTheme',
5973
],
6074
'Components',
61-
['*', 'KitchenSink'],
75+
['*', 'Interaction Tests', 'KitchenSink'],
6276
'Recipes',
6377
],
6478
},

packages/blade/package.json

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,12 @@
105105
"react-native:storybook:ios": "yarn react-native:get-stories && cross-env FRAMEWORK=REACT_NATIVE react-native run-ios",
106106
"react-native:storybook:start": "yarn react-native:get-stories && cross-env NODE_OPTIONS=--openssl-legacy-provider FRAMEWORK=REACT_NATIVE react-native start --reset-cache",
107107
"react": "yarn run react:storybook",
108-
"react:storybook": "cross-env NODE_OPTIONS=--openssl-legacy-provider FRAMEWORK=REACT storybook dev -c ./.storybook/react -p 9009",
109-
"react:storybook:build": "cross-env NODE_OPTIONS=--openssl-legacy-provider FRAMEWORK=REACT storybook build -c ./.storybook/react -o storybook-site",
108+
"react:storybook": "cross-env FRAMEWORK=REACT storybook dev -c ./.storybook/react -p 9009",
109+
"react:storybook:build": "cross-env FRAMEWORK=REACT storybook build -c ./.storybook/react -o storybook-site --quiet",
110+
"react:storybook:serve": "http-server storybook-site --port 9009 --silent",
111+
"react:storybook:serve:test": "wait-on http://127.0.0.1:9009/ && yarn test:react:interaction",
112+
"test:react:interaction": "cross-env FRAMEWORK=REACT test-storybook -c ./.storybook/react --url http://127.0.0.1:9009/",
113+
"test:react:interaction:ci": "yarn react:storybook:build && run-p react:storybook:serve react:storybook:serve:test --race",
110114
"test:react": "cross-env FRAMEWORK=REACT jest -c ./jest.web.config.js --shard=$SHARD --forceExit",
111115
"test:react-native": "cross-env FRAMEWORK=REACT_NATIVE jest -c ./jest.native.config.js --shard=$SHARD --forceExit",
112116
"start:ios": "cross-env NODE_OPTIONS=--openssl-legacy-provider run-p react-native:storybook:start react-native:storybook:ios",
@@ -135,6 +139,13 @@
135139
"tinycolor2": "1.6.0"
136140
},
137141
"devDependencies": {
142+
"http-server": "14.1.1",
143+
"wait-on": "7.2.0",
144+
"@storybook/addon-interactions": "7.6.6",
145+
"@storybook/testing-library": "0.2.2",
146+
"@storybook/test-runner": "0.16.0",
147+
"@storybook/jest": "0.2.3",
148+
"playwright": "1.40.1",
138149
"chromatic": "6.22.0",
139150
"@babel/cli": "7.23.0",
140151
"@babel/core": "7.20.2",

packages/blade/src/components/Carousel/Carousel.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ const TestimonialCard = ({
255255
);
256256
};
257257

258-
const CarouselExample = (props: Omit<CarouselProps, 'children'>): React.ReactElement => {
258+
export const CarouselExample = (props: Omit<CarouselProps, 'children'>): React.ReactElement => {
259259
const key = `${props.visibleItems}-${props.shouldAddStartEndSpacing}`;
260260
return (
261261
<Box width="100%" height={isReactNative() ? '350px' : 'auto'}>

packages/blade/src/components/Carousel/Carousel.web.tsx

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,18 @@ import type { CarouselContextProps } from './CarouselContext';
1313
import { CarouselContext } from './CarouselContext';
1414
import { getCarouselItemId } from './utils';
1515
import { CAROUSEL_AUTOPLAY_INTERVAL, componentIds } from './constants';
16-
import debounce from '~utils/lodashButBetter/debounce';
17-
import throttle from '~utils/lodashButBetter/throttle';
1816
import getIn from '~utils/lodashButBetter/get';
17+
import throttle from '~utils/lodashButBetter/throttle';
18+
import debounce from '~utils/lodashButBetter/debounce';
1919
import { Box } from '~components/Box';
2020
import BaseBox from '~components/Box/BaseBox';
21-
import { castWebType, makeMotionTime, useInterval } from '~utils';
21+
import { castWebType, makeMotionTime, useInterval, usePrevious } from '~utils';
2222
import { useId } from '~utils/useId';
2323
import { makeAccessible } from '~utils/makeAccessible';
2424
import { metaAttribute, MetaConstants } from '~utils/metaAttribute';
25-
import { useDidUpdate } from '~utils/useDidUpdate';
2625
import { useVerifyAllowedChildren } from '~utils/useVerifyAllowedChildren/useVerifyAllowedChildren';
2726
import { useTheme } from '~components/BladeProvider';
27+
import { useFirstRender } from '~utils/useFirstRender';
2828

2929
type ControlsProp = Required<
3030
Pick<
@@ -223,6 +223,29 @@ const CarouselBody = React.forwardRef<HTMLDivElement, CarouselBodyProps>(
223223
},
224224
);
225225

226+
/**
227+
* A custom hook which syncs an effect with a state
228+
* While ignoring the first render & only running the effect when the state changes
229+
*/
230+
function useSyncUpdateEffect<T>(
231+
effect: React.EffectCallback,
232+
stateToSyncWith: T,
233+
deps: React.DependencyList,
234+
) {
235+
const isFirst = useFirstRender();
236+
const prevState = usePrevious<T>(stateToSyncWith);
237+
238+
React.useEffect(() => {
239+
if (!isFirst) {
240+
// if the state is the same as the previous state
241+
// we don't want to run the effect
242+
if (prevState === stateToSyncWith) return;
243+
return effect();
244+
}
245+
// eslint-disable-next-line react-hooks/exhaustive-deps
246+
}, [stateToSyncWith, ...deps]);
247+
}
248+
226249
const Carousel = ({
227250
autoPlay,
228251
visibleItems = 1,
@@ -450,9 +473,13 @@ const Carousel = ({
450473
shouldAddStartEndSpacing,
451474
]);
452475

453-
useDidUpdate(() => {
454-
onChange?.(activeSlide);
455-
}, [activeSlide, onChange]);
476+
useSyncUpdateEffect(
477+
() => {
478+
onChange?.(activeSlide);
479+
},
480+
activeSlide,
481+
[onChange],
482+
);
456483

457484
return (
458485
<CarouselContext.Provider value={carouselContext}>

0 commit comments

Comments
 (0)