Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions app/constants/colors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ export const themes: any = {
previewTintColor: '#ffffff',
backdropOpacity: 0.3,
attachmentLoadingOpacity: 0.7,
collapsibleQuoteBorder: '#CBCED1',
collapsibleChevron: '#6C727A',
...mentions
},
dark: {
Expand Down Expand Up @@ -114,6 +116,8 @@ export const themes: any = {
previewTintColor: '#ffffff',
backdropOpacity: 0.9,
attachmentLoadingOpacity: 0.3,
collapsibleQuoteBorder: '#CBCED1',
collapsibleChevron: '#6C727A',
...mentions
},
black: {
Expand Down Expand Up @@ -162,6 +166,8 @@ export const themes: any = {
previewTintColor: '#ffffff',
backdropOpacity: 0.9,
attachmentLoadingOpacity: 0.3,
collapsibleQuoteBorder: '#CBCED1',
collapsibleChevron: '#6C727A',
...mentions
}
};
12 changes: 12 additions & 0 deletions app/containers/message/Attachments.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Reply from './Reply';
import Button from '../Button';
import styles from './styles';
import MessageContext from './Context';
import CollapsibleQuote from './Components/CollapsibleQuote';

const AttachedActions = ({ attachment, theme }: IMessageAttachedActions) => {
const { onAnswerButtonPress } = useContext(MessageContext);
Expand Down Expand Up @@ -51,6 +52,17 @@ const Attachments = React.memo(
if (file.actions && file.actions.length > 0) {
return <AttachedActions attachment={file} theme={theme} />;
}
if (file.collapsed)
Comment thread
dnlsilva marked this conversation as resolved.
Outdated
return (
<CollapsibleQuote
key={index}
index={index}
attachment={file}
timeFormat={timeFormat}
getCustomEmoji={getCustomEmoji}
theme={theme}
Comment thread
dnlsilva marked this conversation as resolved.
Outdated
/>
);

return (
<Reply
Expand Down
91 changes: 91 additions & 0 deletions app/containers/message/Components/CollapsibleQuote.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { fireEvent, render, within } from '@testing-library/react-native';
Comment thread
dnlsilva marked this conversation as resolved.
import React from 'react';

import MessageContext from '../Context';
import CollapsibleQuote from './CollapsibleQuote';

// For some reason a general mock didn't work, I have to do a search
jest.mock('react-native-mmkv-storage', () => ({
Loader: jest.fn().mockImplementation(() => ({
setProcessingMode: jest.fn().mockImplementation(() => ({
withEncryption: jest.fn().mockImplementation(() => ({
initialize: jest.fn()
}))
}))
})),
create: jest.fn(),
MODES: { MULTI_PROCESS: '' }
}));

const attachment = {
ts: '1970-01-01T00:00:00.000Z',
title: 'Engineering (9 today)',
fields: [
{
title: 'Out Today:\n',
value:
'Ricardo Mellu, 1 day, until Fri Mar 11\nLoma, 1 day, until Fri Mar 11\nAnitta, 3 hours\nDiego Carlitos, 19 days, until Fri Mar 11\nGabriel Vasconcelos, 5 days, until Fri Mar 11\nJorge Leite, 1 day, until Fri Mar 11\nKevin Aleman, 1 day, until Fri Mar 11\nPierre, 1 day, until Fri Mar 11\nTiago Evangelista Pinto, 1 day, until Fri Mar 11'
}
],
attachments: []
};

const mockFn = jest.fn();

const Render = () => (
<MessageContext.Provider
value={{
onLongPress: () => {},
user: { username: 'Marcos' }
}}>
<CollapsibleQuote key={0} index={0} attachment={attachment} getCustomEmoji={mockFn} timeFormat='LT' theme='light' />
</MessageContext.Provider>
);

describe('CollapsibleQuote', () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should avoid using testId imo.
This is one mistake I made a lot while writing Detox tests.
It's a lot easier to write tests, of course, but that's not the reality.
This article is pretty good about it (: https://kentcdodds.com/blog/making-your-ui-tests-resilient-to-change

test('rendered', async () => {
const { findByTestId } = render(<Render />);
const collapsibleQuoteTouchable = await findByTestId('collapsibleQuoteTouchable');
expect(collapsibleQuoteTouchable).toBeTruthy();
});

test('title exists and is correct', async () => {
const { findByTestId } = render(<Render />);
const collapsibleQuoteTitle = await findByTestId('collapsibleQuoteTitle');
expect(collapsibleQuoteTitle).toBeTruthy();
expect(collapsibleQuoteTitle.props.children).toEqual(attachment.title);
});

test('fields render title correctly', async () => {
const collapsibleQuote = render(<Render />);
const collapsibleQuoteTouchable = await collapsibleQuote.findByTestId('collapsibleQuoteTouchable');
// open
fireEvent.press(collapsibleQuoteTouchable);
const open = within(collapsibleQuoteTouchable);
const fieldTitleOpen = await open.findByTestId('collapsibleQuoteTouchableFieldTitle');
expect(fieldTitleOpen).toBeTruthy();
expect(fieldTitleOpen.props.children).toEqual(attachment.fields[0].title);
// close
fireEvent.press(collapsibleQuoteTouchable);
collapsibleQuote.rerender(<Render />);
const close = within(collapsibleQuoteTouchable);
const fieldTitleClosed = close.queryByTestId('collapsibleQuoteTouchableFieldTitle');
expect(fieldTitleClosed).toBeNull();
});

test('fields render fields correctly', async () => {
const collapsibleQuote = render(<Render />);
const collapsibleQuoteTouchable = await collapsibleQuote.findByTestId('collapsibleQuoteTouchable');
// open
fireEvent.press(collapsibleQuoteTouchable);
const open = within(collapsibleQuoteTouchable);
const fieldValueOpen = open.getByLabelText(attachment.fields[0].value.split('\n')[0]);
expect(fieldValueOpen).toBeTruthy();
// close
fireEvent.press(collapsibleQuoteTouchable);
collapsibleQuote.rerender(<Render />);
const close = within(collapsibleQuoteTouchable);
const fieldValueClosed = close.queryByTestId(attachment.fields[0].value.split('\n')[0]);
expect(fieldValueClosed).toBeNull();
});
});
183 changes: 183 additions & 0 deletions app/containers/message/Components/CollapsibleQuote.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import { transparentize } from 'color2k';
import { dequal } from 'dequal';
import React, { useContext, useState } from 'react';
import { StyleSheet, Text, View } from 'react-native';

import { themes } from '../../../constants/colors';
import { IAttachment } from '../../../definitions/IAttachment';
import { TGetCustomEmoji } from '../../../definitions/IEmoji';
import { CustomIcon } from '../../../lib/Icons';
import sharedStyles from '../../../views/Styles';
import Markdown from '../../markdown';
import MessageContext from '../Context';
import Touchable from '../Touchable';

const styles = StyleSheet.create({
button: {
Comment thread
dnlsilva marked this conversation as resolved.
flex: 1,
flexDirection: 'row',
alignItems: 'center',
marginTop: 6,
alignSelf: 'flex-start',
borderWidth: 1,
borderRadius: 4
},
attachmentContainer: {
flex: 1,
borderRadius: 4,
flexDirection: 'column',
padding: 8
},
authorContainer: {
flex: 1,
flexDirection: 'row',
alignItems: 'center'
},
fieldsContainer: {
flex: 1,
flexWrap: 'wrap',
flexDirection: 'row'
},
fieldContainer: {
flexDirection: 'column',
padding: 10
},
fieldTitle: {
fontSize: 15,
...sharedStyles.textBold
},
marginTop: {
marginTop: 4
},
marginBottom: {
marginBottom: 4
},
title: {
flex: 1,
fontSize: 16,
...sharedStyles.textMedium
},
touchableContainer: { flex: 1, flexDirection: 'row' },
Comment thread
dnlsilva marked this conversation as resolved.
Outdated
markdownFontSize: { fontSize: 15 },
iconContainer: { width: 20, height: 20, right: 8, top: 6, justifyContent: 'center', alignItems: 'center' }
});

interface IMessageFields {
attachment: IAttachment;
theme: string;
getCustomEmoji: TGetCustomEmoji;
}

interface IMessageReply {
attachment: IAttachment;
timeFormat?: string;
index: number;
theme: string;
getCustomEmoji: TGetCustomEmoji;
}

const Fields = React.memo(
({ attachment, theme, getCustomEmoji }: IMessageFields) => {
if (!attachment.fields) {
return null;
}

const { baseUrl, user } = useContext(MessageContext);
return (
<View style={styles.fieldsContainer}>
{attachment.fields.map(field => (
<View key={field.title} style={[styles.fieldContainer, { width: field.short ? '50%' : '100%' }]}>
<Text testID='collapsibleQuoteTouchableFieldTitle' style={[styles.fieldTitle, { color: themes[theme].bodyText }]}>
{field.title}
</Text>
<Markdown
msg={field?.value || ''}
baseUrl={baseUrl}
username={user.username}
getCustomEmoji={getCustomEmoji}
theme={theme}
style={[styles.markdownFontSize]}
/>
</View>
))}
</View>
);
},
(prevProps, nextProps) =>
dequal(prevProps.attachment.fields, nextProps.attachment.fields) && prevProps.theme === nextProps.theme
);

const CollapsibleQuote = React.memo(
({ attachment, index, getCustomEmoji, theme }: IMessageReply) => {
Comment thread
dnlsilva marked this conversation as resolved.
Outdated
const [open, setOpen] = useState(false);
if (!attachment) {
return null;
}

const onPress = () => {
setOpen(!open);
};

let {
borderColor,
chatComponentBackground: backgroundColor,
collapsibleQuoteBorder,
collapsibleChevron,
headerTintColor
} = themes[theme];

try {
if (attachment.color) {
backgroundColor = transparentize(attachment.color, 0.8);
borderColor = attachment.color;
collapsibleQuoteBorder = attachment.color;
collapsibleChevron = attachment.color;
headerTintColor = headerTintColor;
}
} catch (e) {
// fallback to default
}

return (
<>
<Touchable
Comment thread
dnlsilva marked this conversation as resolved.
testID='collapsibleQuoteTouchable'
onPress={onPress}
style={[
styles.button,
index > 0 && styles.marginTop,
attachment.description && styles.marginBottom,
{
backgroundColor,
borderLeftColor: collapsibleQuoteBorder,
borderTopColor: borderColor,
borderRightColor: borderColor,
borderBottomColor: borderColor,
borderLeftWidth: 2
}
]}
background={Touchable.Ripple(themes[theme].bannerBackground)}>
<View style={styles.touchableContainer}>
<View style={styles.attachmentContainer}>
<View style={styles.authorContainer}>
<Text testID='collapsibleQuoteTitle' style={[styles.title, { color: headerTintColor }]}>
{attachment.title}
</Text>
</View>
{open && <Fields attachment={attachment} getCustomEmoji={getCustomEmoji} theme={theme} />}
</View>
<View style={styles.iconContainer}>
<CustomIcon name={open ? 'chevron-up' : 'chevron-down'} size={22} color={collapsibleChevron} />
</View>
</View>
</Touchable>
</>
);
},
(prevProps, nextProps) => dequal(prevProps.attachment, nextProps.attachment) && prevProps.theme === nextProps.theme
);

CollapsibleQuote.displayName = 'CollapsibleQuote';
Fields.displayName = 'CollapsibleQuoteFields';

export default CollapsibleQuote;
7 changes: 4 additions & 3 deletions app/definitions/IAttachment.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { IUser } from './IUser';

export interface IAttachment {
ts: string | Date;
ts?: string | Date;
title: string;
type: string;
description: string;
type?: string;
description?: string;
title_link?: string;
image_url?: string;
image_type?: string;
Expand All @@ -24,6 +24,7 @@ export interface IAttachment {
author_link?: string;
color?: string;
thumb_url?: string;
attachments?: any[];
}

export interface IServerAttachment {
Expand Down
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@
"@rocket.chat/eslint-config": "^0.4.0",
"@storybook/addon-storyshots": "5.3.21",
"@storybook/react-native": "5.3.25",
"@testing-library/jest-native": "^4.0.4",
"@testing-library/react-native": "^9.0.0",
"@types/bytebuffer": "^5.0.43",
"@types/ejson": "^2.1.3",
Expand Down Expand Up @@ -205,7 +206,10 @@
},
"transform": {
"^.+\\.js$": "<rootDir>/node_modules/react-native/jest/preprocessor.js"
}
},
"setupFilesAfterEnv": [
"@testing-library/jest-native/extend-expect"
]
},
"snyk": true,
"engines": {
Expand Down
Loading