Skip to content

Commit 6ebfa49

Browse files
authored
Merge pull request Expensify#90381 from situchan/fix-89538
[No QA] fix storybook issues
2 parents 6e1c1df + 2a6e00a commit 6ebfa49

12 files changed

Lines changed: 128 additions & 68 deletions

.storybook/main.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type {StorybookConfig} from 'storybook/internal/types';
1+
import type {StorybookConfig} from '@storybook/react-webpack5';
22

33
const main: StorybookConfig = {
44
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
@@ -15,6 +15,9 @@ const main: StorybookConfig = {
1515
options: {},
1616
},
1717
docs: {},
18+
typescript: {
19+
reactDocgen: false,
20+
},
1821
};
1922

2023
export default main;

.storybook/preview.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import {SafeAreaProvider} from 'react-native-safe-area-context';
55
import type {Parameters} from 'storybook/internal/types';
66
import EnvironmentProvider from '@components/EnvironmentContextProvider';
77
import OnyxListItemProvider from '@components/OnyxListItemProvider';
8+
import ScreenWrapperStatusContext from '@components/ScreenWrapper/ScreenWrapperStatusContext';
89
import {SearchContextProvider} from '@components/Search/SearchContext';
10+
import colors from '@styles/theme/colors';
911
import ComposeProviders from '@src/components/ComposeProviders';
1012
import HTMLEngineProvider from '@src/components/HTMLEngineProvider';
1113
import {LocaleContextProvider} from '@src/components/LocaleContextProvider';
@@ -35,7 +37,9 @@ const decorators = [
3537
SearchContextProvider,
3638
]}
3739
>
38-
<Story />
40+
<ScreenWrapperStatusContext.Provider value={{didScreenTransitionEnd: true, isSafeAreaTopPaddingApplied: false, isSafeAreaBottomPaddingApplied: false}}>
41+
<Story />
42+
</ScreenWrapperStatusContext.Provider>
3943
</ComposeProviders>
4044
),
4145
];
@@ -46,6 +50,16 @@ const parameters: Parameters = {
4650
color: /(background|color)$/i,
4751
},
4852
},
53+
backgrounds: {
54+
options: {
55+
dark: {name: 'Dark', value: colors.productDark100},
56+
light: {name: 'Light', value: colors.productLight100},
57+
},
58+
},
59+
};
60+
61+
const initialGlobals = {
62+
backgrounds: {value: 'dark'},
4963
};
5064

51-
export {decorators, parameters};
65+
export {decorators, parameters, initialGlobals};

__mocks__/@react-navigation/native/index.ts

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type * as ReactNavigation from '@react-navigation/native';
2+
import {useEffect} from 'react';
23
import createAddListenerMock from '../../../tests/utils/createAddListenerMock';
34

45
const isJestEnv = process.env.NODE_ENV === 'test';
@@ -16,15 +17,14 @@ const {triggerTransitionEnd, addListener} = isJestEnv
1617
addListener: () => {},
1718
};
1819

19-
const useNavigation = isJestEnv
20-
? realReactNavigation.useNavigation
21-
: {
22-
navigate: isJestEnv ? jest.fn() : () => {},
23-
getState: () => ({
24-
routes: [],
25-
}),
26-
addListener,
27-
};
20+
const navigationMock = {
21+
navigate: () => {},
22+
getState: () => ({routes: []}),
23+
isFocused: () => true,
24+
addListener,
25+
};
26+
27+
const useNavigation = isJestEnv ? realReactNavigation.useNavigation : () => navigationMock;
2828

2929
type NativeNavigationMock = typeof ReactNavigation & {
3030
triggerTransitionEnd: () => void;
@@ -42,7 +42,12 @@ const useLinkProps = isJestEnv ? realReactNavigation.useLinkProps : () => null;
4242
const useLinkTo = isJestEnv ? realReactNavigation.useLinkTo : () => null;
4343
const useScrollToTop = isJestEnv ? realReactNavigation.useScrollToTop : () => null;
4444
const useRoute = isJestEnv ? realReactNavigation.useRoute : () => ({params: {}});
45-
const useFocusEffect = isJestEnv ? realReactNavigation.useFocusEffect : (callback: () => void) => callback();
45+
// Run callback in useEffect (like real useFocusEffect), not synchronously during render
46+
const useFocusEffect = isJestEnv
47+
? realReactNavigation.useFocusEffect
48+
: (callback: () => (() => void) | void) => {
49+
useEffect(() => callback(), [callback]);
50+
};
4651
const usePreventRemove = isJestEnv ? jest.fn() : () => {};
4752
const useNavigationState = isJestEnv ? realReactNavigation.useNavigationState : () => {};
4853

src/components/Search/SearchContext.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {useNavigation} from '@react-navigation/core';
1+
import {useNavigation} from '@react-navigation/native';
22
import type {NavigationState} from '@react-navigation/routers';
33
import React, {useContext, useEffect, useRef, useState} from 'react';
44
// We need direct access to useOnyx from react-native-onyx to avoid circular dependencies in SearchContext

src/stories/AddressSearch.stories.tsx

100644100755
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ function Template(props: AddressSearchProps) {
2525
const [value, setValue] = useState<string | number | Address | StreetValue>('');
2626
return (
2727
<AddressSearch
28+
inputID="addressLine1"
2829
value={value as string}
2930
onInputChange={(inputValue) => setValue(inputValue)}
3031
{...props}

src/stories/Button.stories.tsx

100644100755
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type {Meta, StoryFn} from '@storybook/react-webpack5';
2-
import React, {useCallback, useState} from 'react';
2+
import React, {useState} from 'react';
33
import {View} from 'react-native';
44
import type {ButtonProps} from '@components/Button';
55
import Button from '@components/Button';
@@ -26,11 +26,11 @@ function Template(props: ButtonProps) {
2626
const Default: ButtonStory = Template.bind({});
2727
const Loading: ButtonStory = Template.bind({});
2828
function PressOnEnter(props: ButtonProps) {
29-
const [text, setText] = useState('');
30-
const onPress = useCallback(() => {
29+
const [text, setText] = useState(props.text);
30+
const onPress = () => {
3131
setText('Button Pressed!');
32-
setTimeout(() => setText(''), 500);
33-
}, []);
32+
setTimeout(() => setText(props.text), 500);
33+
};
3434
return (
3535
<Button
3636
{...props}

src/stories/EReceipt.stories.tsx

100644100755
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ const transactionData = {
3535
amount: 1000,
3636
currency: 'USD',
3737
cardID: 5,
38+
cardName: 'Expensify Test Card',
3839
merchant: 'United Airlines',
3940
mccGroup: 'Commuter',
4041
created: '2023-07-24 13:46:20',
@@ -155,8 +156,8 @@ const transactionData = {
155156

156157
Onyx.mergeCollection(ONYXKEYS.COLLECTION.TRANSACTION, transactionData);
157158
Onyx.merge('cardList', {
158-
4: {bank: 'Expensify Card', lastFourPAN: '1000'},
159-
5: {bank: 'Expensify Card', lastFourPAN: '4444'},
159+
4: {cardName: 'Test Card', lastFourPAN: '1000', fundID: '123'},
160+
5: {bank: 'Expensify Card', lastFourPAN: '4444', fundID: '456'},
160161
});
161162

162163
/**

src/stories/Form.stories.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import Picker from '@components/Picker';
1313
import StateSelector from '@components/StateSelector';
1414
import Text from '@components/Text';
1515
import TextInput from '@components/TextInput';
16+
import useLocalize from '@hooks/useLocalize';
1617
import {isRequiredFulfilled} from '@libs/ValidationUtils';
1718
import {clearErrors, setDraftValues, setErrors, setIsLoading} from '@userActions/FormActions';
1819
import CONST from '@src/CONST';
@@ -63,6 +64,8 @@ const story: Meta<typeof FormProvider> = {
6364
};
6465

6566
function Template(props: FormProviderProps & FormProviderOnyxProps) {
67+
const {translate} = useLocalize();
68+
6669
// Form consumes data from Onyx, so we initialize Onyx with the necessary data here
6770
setIsLoading(props.formID, !!props.formState?.isLoading);
6871
setDraftValues(props.formID, props.draftValues);
@@ -98,7 +101,7 @@ function Template(props: FormProviderProps & FormProviderOnyxProps) {
98101
label="Street"
99102
inputID="street"
100103
containerStyles={defaultStyles.mt4}
101-
hint="common.noPO"
104+
hint={translate('common.noPO')}
102105
/>
103106
<InputWrapper
104107
InputComponent={DatePicker}

src/stories/MoneyRequestReportPreview.stories.tsx

100644100755
Lines changed: 67 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
1+
import type {ListRenderItem} from '@shopify/flash-list';
12
import type {StoryFn} from '@storybook/react-webpack5';
2-
import React from 'react';
3-
import type {ListRenderItem} from 'react-native';
3+
import React, {useRef, useState} from 'react';
4+
import type {LayoutChangeEvent} from 'react-native';
45
import {View} from 'react-native';
56
import MoneyRequestReportPreviewContent from '@components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent';
67
import type {MoneyRequestReportPreviewContentProps} from '@components/ReportActionItem/MoneyRequestReportPreview/types';
78
import TransactionPreviewContent from '@components/ReportActionItem/TransactionPreview/TransactionPreviewContent';
89
import ThemeProvider from '@components/ThemeProvider';
910
import ThemeStylesProvider from '@components/ThemeStylesContextProvider';
10-
// eslint-disable-next-line no-restricted-imports
11-
import getMoneyRequestReportPreviewStyle from '@styles/utils/getMoneyRequestReportPreviewStyle';
12-
// eslint-disable-next-line no-restricted-imports
13-
import sizing from '@styles/utils/sizing';
11+
import useStyleUtils from '@hooks/useStyleUtils';
12+
import useThemeStyles from '@hooks/useThemeStyles';
1413
import CONST from '@src/CONST';
1514
import SCREENS from '@src/SCREENS';
1615
import type {Transaction} from '@src/types/onyx';
@@ -34,30 +33,6 @@ const mockTransactionsBig = Array.from({length: 12}).map((item, index) => {
3433
return {...transactionR14932, transactionID: `${transactionR14932.transactionID}${index}`};
3534
});
3635

37-
const mockRenderItem: ListRenderItem<Transaction> = ({item}) => (
38-
<TransactionPreviewContent
39-
action={actionR14932}
40-
isWhisper={false}
41-
isHovered={false}
42-
chatReport={chatReportR14932}
43-
personalDetails={personalDetails}
44-
report={iouReportR14932}
45-
transaction={item}
46-
transactionRawAmount={item.amount}
47-
violations={item.errors ? violationsR14932 : []}
48-
offlineWithFeedbackOnClose={() => undefined}
49-
navigateToReviewFields={() => undefined}
50-
isBillSplit={false}
51-
areThereDuplicates={false}
52-
sessionAccountID={11111111}
53-
walletTermsErrors={undefined}
54-
routeName={SCREENS.TRANSACTION_DUPLICATE.REVIEW}
55-
shouldHideOnDelete={false}
56-
transactionPreviewWidth={303}
57-
containerStyles={[sizing.h100]}
58-
/>
59-
);
60-
6136
type MoneyRequestReportPreviewStory = StoryFn<typeof MoneyRequestReportPreviewContent>;
6237

6338
export default {
@@ -123,7 +98,6 @@ export default {
12398
violations: violationsR14932,
12499
invoiceReceiverPersonalDetail: undefined,
125100
invoiceReceiverPolicy: undefined,
126-
renderTransactionItem: mockRenderItem,
127101
},
128102
parameters: {
129103
useLightTheme: true,
@@ -133,17 +107,76 @@ export default {
133107
function Template(props: MoneyRequestReportPreviewContentProps, {parameters}: {parameters: {useLightTheme?: boolean; transactionsBig?: boolean}}) {
134108
const theme = parameters.useLightTheme ? CONST.THEME.LIGHT : CONST.THEME.DARK;
135109
const transactions = parameters.transactionsBig ? mockTransactionsBig : props.transactions;
136-
const reportPreviewStyle = getMoneyRequestReportPreviewStyle(false, transactions.length, 400, 400);
110+
111+
const widthsRef = useRef<{currentWidth: number | null; currentWrapperWidth: number | null}>({currentWidth: null, currentWrapperWidth: null});
112+
const [widths, setWidths] = useState({currentWidth: 0, currentWrapperWidth: 0});
113+
114+
const updateWidths = () => {
115+
const {currentWidth, currentWrapperWidth} = widthsRef.current;
116+
117+
if (currentWidth && currentWrapperWidth) {
118+
setWidths({currentWidth, currentWrapperWidth});
119+
}
120+
};
121+
122+
const onCarouselLayout = (e: LayoutChangeEvent) => {
123+
const newWidth = e.nativeEvent.layout.width;
124+
if (widthsRef.current.currentWidth !== newWidth) {
125+
widthsRef.current.currentWidth = newWidth;
126+
updateWidths();
127+
}
128+
};
129+
130+
const onWrapperLayout = (e: LayoutChangeEvent) => {
131+
const newWrapperWidth = e.nativeEvent.layout.width;
132+
if (widthsRef.current.currentWrapperWidth !== newWrapperWidth) {
133+
widthsRef.current.currentWrapperWidth = newWrapperWidth;
134+
updateWidths();
135+
}
136+
};
137+
138+
const StyleUtils = useStyleUtils();
139+
const styles = useThemeStyles();
140+
const reportPreviewStyles = StyleUtils.getMoneyRequestReportPreviewStyle(false, transactions.length, widths.currentWidth, widths.currentWrapperWidth);
141+
const transactionPreviewContainerStyles = [styles.h100, reportPreviewStyles.transactionPreviewCarouselStyle];
142+
143+
const renderItem: ListRenderItem<Transaction> = ({item}) => (
144+
<TransactionPreviewContent
145+
action={actionR14932}
146+
isWhisper={false}
147+
isHovered={false}
148+
chatReport={chatReportR14932}
149+
personalDetails={personalDetails}
150+
report={iouReportR14932}
151+
transaction={item}
152+
transactionRawAmount={item.amount}
153+
violations={item.errors ? violationsR14932 : []}
154+
offlineWithFeedbackOnClose={() => undefined}
155+
navigateToReviewFields={() => undefined}
156+
isBillSplit={false}
157+
areThereDuplicates={false}
158+
sessionAccountID={11111111}
159+
walletTermsErrors={undefined}
160+
routeName={SCREENS.TRANSACTION_DUPLICATE.REVIEW}
161+
shouldHideOnDelete={false}
162+
transactionPreviewWidth={reportPreviewStyles.transactionPreviewCarouselStyle.width}
163+
containerStyles={transactionPreviewContainerStyles}
164+
/>
165+
);
137166

138167
return (
139168
<ThemeProvider theme={theme}>
140169
<ThemeStylesProvider>
141170
<View style={{maxWidth: '100%'}}>
142171
<MoneyRequestReportPreviewContent
143172
{...props}
144-
reportPreviewStyles={reportPreviewStyle}
145-
containerStyles={[reportPreviewStyle.componentStyle, props.containerStyles]}
173+
reportPreviewStyles={reportPreviewStyles}
174+
containerStyles={[reportPreviewStyles.componentStyle, props.containerStyles]}
146175
transactions={transactions}
176+
onCarouselLayout={onCarouselLayout}
177+
onWrapperLayout={onWrapperLayout}
178+
currentWidth={widths.currentWidth}
179+
renderTransactionItem={renderItem}
147180
/>
148181
</View>
149182
</ThemeStylesProvider>

src/stories/PromotedActionBar.stories.tsx

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ const story = {
1414
component: PromotedActionsBar,
1515
};
1616

17-
type PromotedActionWithText = Omit<PromotedAction, 'translationKey'> & {text: string};
18-
type PromotedActionsBarPropsWithText = Omit<PromotedActionsBarProps, 'promotedActions'> & {promotedActions: PromotedActionWithText[]};
17+
type PromotedActionsBarPropsWithText = Omit<PromotedActionsBarProps, 'promotedActions'> & {promotedActions: PromotedAction[]};
1918
type StoryType = typeof Template & {args?: Partial<PromotedActionsBarPropsWithText>};
2019

2120
function Template(args: PromotedActionsBarProps) {
@@ -30,29 +29,29 @@ const promotedActions = [
3029
{
3130
key: 'join',
3231
icon: 'CommentBubbles',
33-
text: 'Join',
32+
translationKey: 'common.message',
3433
onSelected: () => {},
3534
},
3635
{
3736
key: 'pin',
3837
icon: 'Pin',
39-
text: 'Pin',
38+
translationKey: 'common.pin',
4039
onSelected: () => {},
4140
},
4241
{
4342
key: 'share',
4443
icon: 'QrCode',
45-
text: 'Share',
44+
translationKey: 'common.share',
4645
onSelected: () => {},
4746
},
48-
] satisfies PromotedActionWithText[];
47+
] satisfies PromotedAction[];
4948

5049
const defaultPromotedAction = {
5150
key: '',
5251
icon: 'ChatBubbles',
53-
text: '',
52+
translationKey: 'common.join',
5453
onSelected: () => {},
55-
} satisfies PromotedActionWithText;
54+
} satisfies PromotedAction;
5655

5756
// Arguments can be passed to the component by binding
5857
// See: https://storybook.js.org/docs/react/writing-stories/introduction#using-args

0 commit comments

Comments
 (0)