diff --git a/.maestro/tests/uikit/button-kit.yaml b/.maestro/tests/uikit/button-kit.yaml new file mode 100644 index 00000000000..409d12f6153 --- /dev/null +++ b/.maestro/tests/uikit/button-kit.yaml @@ -0,0 +1,42 @@ +appId: ${APP_ID} +name: UIKit Button Kit +onFlowStart: + - runFlow: '../../helpers/setup.yaml' +onFlowComplete: + - evalScript: ${output.utils.deleteCreatedUsers()} +tags: + - test-14 + +--- +- evalScript: ${output.user = output.utils.createUser()} +- evalScript: ${output.room = output.utils.createRandomRoom(output.user.username, output.user.password)} + +- runFlow: + file: '../../helpers/login-with-deeplink.yaml' + env: + USERNAME: ${output.user.username} + PASSWORD: ${output.user.password} + +- runFlow: + file: '../../helpers/navigate-to-room.yaml' + env: + ROOM: ${output.room.name} + +# here send /uikit-test command +- tapOn: + id: message-composer-input +- inputText: + text: '/uikit-test' +- extendedWaitUntil: + visible: + id: autocomplete-item-uikit-test + timeout: 10000 +- tapOn: + id: autocomplete-item-uikit-test +- tapOn: + id: 'message-composer-send' +- tapOn: Tap Me +- extendedWaitUntil: + visible: + text: '.*Button tap received! This reply is private to you*.' + timeout: 10000 diff --git a/.sniffler/test-map.json b/.sniffler/test-map.json index f7575879a9c..714a65fb01b 100644 --- a/.sniffler/test-map.json +++ b/.sniffler/test-map.json @@ -341,5 +341,9 @@ "app/sagas/room.js", "app/sagas/createChannel.js" ] + }, + { + "test": ".maestro/tests/uikit/button-kit.yaml", + "dependsOn": ["app/containers/UIKit/**"] } ] diff --git a/app/containers/UIKit/Actions.tsx b/app/containers/UIKit/Actions.tsx index 40f328ce812..aef47b96106 100644 --- a/app/containers/UIKit/Actions.tsx +++ b/app/containers/UIKit/Actions.tsx @@ -14,6 +14,8 @@ const styles = StyleSheet.create({ }); export const Actions = ({ blockId, appId, elements, parser }: IActions) => { + 'use no memo'; + const [showMoreVisible, setShowMoreVisible] = useState(() => elements && elements.length > 5); const shouldShowMore = elements && elements.length > 5; diff --git a/app/containers/UIKit/Button.stories.tsx b/app/containers/UIKit/Button.stories.tsx new file mode 100644 index 00000000000..b4b51d5bb74 --- /dev/null +++ b/app/containers/UIKit/Button.stories.tsx @@ -0,0 +1,20 @@ +import UIKitButton from './Button'; + +const buttonProps = { + title: 'Press me!', + type: 'primary' as const, + onPress: () => {} +}; + +export default { + title: 'UIKit/Button', + component: UIKitButton +}; + +export const PrimaryButton = () => ; + +export const SecondaryButton = () => ; + +export const LoadingButton = () => ; + +export const CustomStyleButton = () => ; diff --git a/app/containers/UIKit/Button.test.tsx b/app/containers/UIKit/Button.test.tsx new file mode 100644 index 00000000000..fcb296234d0 --- /dev/null +++ b/app/containers/UIKit/Button.test.tsx @@ -0,0 +1,59 @@ +import { fireEvent, render } from '@testing-library/react-native'; + +import UIKitButton from './Button'; +import * as stories from './Button.stories'; +import { generateSnapshots } from '../../../.rnstorybook/generateSnapshots'; + +const onPressMock = jest.fn(); + +const testProps = { + title: 'Press me!', + onPress: onPressMock +}; + +describe('UIKitButtonTests', () => { + beforeEach(() => { + onPressMock.mockClear(); + }); + + test('rendered with correct title', async () => { + const { findByText } = render(); + const buttonTitle = await findByText(testProps.title); + expect(buttonTitle).toBeTruthy(); + expect(buttonTitle.props.children).toEqual(testProps.title); + }); + + test('find button using accessibilityLabel', () => { + const { getByLabelText } = render(); + const button = getByLabelText(testProps.title); + expect(button).toBeTruthy(); + }); + + test('renders secondary variant with the same title', async () => { + const { findByText } = render(); + const buttonTitle = await findByText(testProps.title); + expect(buttonTitle).toBeTruthy(); + }); + + test('title not visible while loading', () => { + const { queryByText } = render(); + const buttonTitle = queryByText(testProps.title); + expect(buttonTitle).toBeNull(); + }); + + test('onPress is not triggered while loading', () => { + const { getByLabelText } = render(); + const button = getByLabelText(testProps.title); + fireEvent.press(button); + expect(onPressMock).not.toHaveBeenCalled(); + }); + + test('should trigger onPress function on button press', () => { + const { getByLabelText } = render(); + const button = getByLabelText(testProps.title); + fireEvent.press(button); + expect(onPressMock).toHaveBeenCalled(); + }); +}); + +generateSnapshots(stories); diff --git a/app/containers/UIKit/Button.tsx b/app/containers/UIKit/Button.tsx new file mode 100644 index 00000000000..e16a7d13d95 --- /dev/null +++ b/app/containers/UIKit/Button.tsx @@ -0,0 +1,50 @@ +import { type FC } from 'react'; +import { Pressable, StyleSheet, Text, type StyleProp, type ViewStyle } from 'react-native'; + +import { useTheme } from '../../theme'; +import sharedStyles from '../../views/Styles'; +import ActivityIndicator from '../ActivityIndicator'; + +const styles = StyleSheet.create({ + container: { + borderRadius: 4, + paddingVertical: 14, + paddingHorizontal: 16, + justifyContent: 'center' + }, + text: { + ...sharedStyles.textMedium, + ...sharedStyles.textAlignCenter + }, + pressed: { + opacity: 0.7 + } +}); + +interface IUIKitButtonProps { + title: string; + onPress: () => void; + type?: 'primary' | 'secondary'; + loading?: boolean; + style?: StyleProp; +} + +const UIKitButton: FC = ({ title, onPress, type = 'primary', loading, style }) => { + const { colors } = useTheme(); + const isPrimary = type === 'primary'; + const backgroundColor = isPrimary ? colors.buttonBackgroundPrimaryDefault : colors.buttonBackgroundSecondaryDefault; + const color = isPrimary ? colors.fontWhite : colors.fontDefault; + + return ( + [styles.container, { backgroundColor }, style, pressed && styles.pressed]}> + {loading ? : {title}} + + ); +}; + +export default UIKitButton; diff --git a/app/containers/UIKit/__snapshots__/Button.test.tsx.snap b/app/containers/UIKit/__snapshots__/Button.test.tsx.snap new file mode 100644 index 00000000000..30b67aed945 --- /dev/null +++ b/app/containers/UIKit/__snapshots__/Button.test.tsx.snap @@ -0,0 +1,279 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Story Snapshots: CustomStyleButton should match snapshot 1`] = ` + + + Press me! + + +`; + +exports[`Story Snapshots: LoadingButton should match snapshot 1`] = ` + + + +`; + +exports[`Story Snapshots: PrimaryButton should match snapshot 1`] = ` + + + Press me! + + +`; + +exports[`Story Snapshots: SecondaryButton should match snapshot 1`] = ` + + + Press me! + + +`; diff --git a/app/containers/UIKit/__snapshots__/UiKitMessage.test.tsx.snap b/app/containers/UIKit/__snapshots__/UiKitMessage.test.tsx.snap index a8678d2bfb4..db9fecd1947 100644 --- a/app/containers/UIKit/__snapshots__/UiKitMessage.test.tsx.snap +++ b/app/containers/UIKit/__snapshots__/UiKitMessage.test.tsx.snap @@ -3,63 +3,59 @@ exports[`Story Snapshots: ActionButton should match snapshot 1`] = ` [ - - Approve - + , - - Deny - + , - - Deny - + , - - Deny - + , - - Deny - + , - - Deny - + , - - Deny - + , - - button - + `; diff --git a/app/containers/UIKit/__snapshots__/UiKitModal.test.tsx.snap b/app/containers/UIKit/__snapshots__/UiKitModal.test.tsx.snap index 491ab67ca3b..9972a5823ea 100644 --- a/app/containers/UIKit/__snapshots__/UiKitModal.test.tsx.snap +++ b/app/containers/UIKit/__snapshots__/UiKitModal.test.tsx.snap @@ -1056,63 +1056,59 @@ exports[`Story Snapshots: ModalActionsWithShowMore should match snapshot 1`] = ` } />, - - Primary Action - + , - - Secondary - + , - - Danger Action - + , - - Button 4 - + , - - Button 5 - + , - - Button 6 - Hidden - + , - - Button 7 - Hidden - + , - - Button 8 - Hidden - + , - - Change - + ,