Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions package/jest-setup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ jest.mock('react-native/Libraries/Components/RefreshControl/RefreshControl', ()
__esModule: true,
default: require('./__mocks__/RefreshControlMock'),
}));
jest.mock('react-native/Libraries/Utilities/Appearance', () => ({
addChangeListener: () => ({ remove: () => {} }),
getColorScheme: () => 'light',
setColorScheme: () => {},
}));

jest.mock('@shopify/flash-list', () => ({
FlashList: undefined,
Expand Down
2 changes: 1 addition & 1 deletion package/src/components/Attachment/Attachment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ const useAudioAttachmentStyles = () => {
: semantics.chatBorderOnChatIncoming,
},
durationText: {
color: semantics.chatTextIncoming,
color: isMyMessage ? semantics.chatTextOutgoing : semantics.chatTextIncoming,
fontWeight: primitives.typographyFontWeightSemiBold,
},
leftContainer: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ const useStyles = () => {
justifyContent: 'center',
alignItems: 'center',
borderWidth: 1,
borderColor: semantics.chatBorderOnChatIncoming,
borderColor: semantics.controlPlaybackToggleBorder,
},
});
}, [semantics]);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React from 'react';
import { StyleSheet } from 'react-native';

import { render, screen } from '@testing-library/react-native';

import { mergeThemes, ThemeProvider } from '../../../../contexts/themeContext/ThemeContext';
import { PlayPauseButton } from '../PlayPauseButton';

describe('PlayPauseButton', () => {
const lightTheme = mergeThemes({ scheme: 'light' });

const renderButton = (containerStyle?: Record<string, unknown>) =>
render(
<ThemeProvider>
<PlayPauseButton
containerStyle={containerStyle}
isPlaying={false}
onPress={jest.fn()}
testID='play-pause-button'
/>
</ThemeProvider>,
);

const borderColorOf = () => {
const style = screen.getByTestId('play-pause-button').props.style;
return StyleSheet.flatten(typeof style === 'function' ? style({ pressed: false }) : style)
?.borderColor;
};

// This control also renders in the composer's attachment previews, which are
// not a message bubble - so it must not reach for a chat side token.
it('defaults to the neutral playback toggle border, not a chat bubble border', () => {
renderButton();

expect(borderColorOf()).toBe(lightTheme.semantics.controlPlaybackToggleBorder);
expect(borderColorOf()).not.toBe(lightTheme.semantics.chatBorderOnChatIncoming);
});

it('lets in-bubble callers override the border with the resolved chat side', () => {
renderButton({ borderColor: lightTheme.semantics.chatBorderOnChatOutgoing });

expect(borderColorOf()).toBe(lightTheme.semantics.chatBorderOnChatOutgoing);
});
});
12 changes: 4 additions & 8 deletions package/src/components/Attachment/UrlPreview/URLPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,6 @@ const URLPreviewWithContext = (props: URLPreviewPropsWithContext) => {

const { icons } = useComponentsContext();

const {
theme: { semantics },
} = useTheme();

const { image_url, og_scrape_url, text, thumb_url, title, type } = attachment;

const {
Expand Down Expand Up @@ -156,7 +152,7 @@ const URLPreviewWithContext = (props: URLPreviewPropsWithContext) => {
</Text>
) : null}
<View style={[styles.linkPreview, linkPreview, stylesProp.linkPreview]}>
<icons.Link height={12} width={12} stroke={semantics.chatTextIncoming} />
<icons.Link height={12} width={12} stroke={styles.linkPreviewText.color} />
<Text
numberOfLines={1}
style={[styles.linkPreviewText, linkPreviewText, stylesProp.linkPreviewText]}
Expand Down Expand Up @@ -267,13 +263,13 @@ const useStyles = () => {
padding: primitives.spacingSm,
},
title: {
color: semantics.chatTextIncoming,
color: isMyMessage ? semantics.chatTextOutgoing : semantics.chatTextIncoming,
fontSize: primitives.typographyFontSizeSm,
fontWeight: primitives.typographyFontWeightSemiBold,
lineHeight: primitives.typographyLineHeightTight,
},
description: {
color: semantics.chatTextIncoming,
color: isMyMessage ? semantics.chatTextOutgoing : semantics.chatTextIncoming,
fontSize: primitives.typographyFontSizeXs,
fontWeight: primitives.typographyFontWeightRegular,
lineHeight: primitives.typographyLineHeightTight,
Expand All @@ -284,7 +280,7 @@ const useStyles = () => {
gap: primitives.spacingXxs,
},
linkPreviewText: {
color: semantics.chatTextIncoming,
color: isMyMessage ? semantics.chatTextOutgoing : semantics.chatTextIncoming,
fontSize: primitives.typographyFontSizeXs,
fontWeight: primitives.typographyFontWeightRegular,
lineHeight: primitives.typographyLineHeightTight,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,6 @@ const URLPreviewCompactWithContext = (props: URLPreviewCompactPropsWithContext)

const { icons } = useComponentsContext();

const {
theme: { semantics },
} = useTheme();

const { image_url, og_scrape_url, text, thumb_url, title: titleText, type } = attachment;

const {
Expand Down Expand Up @@ -158,7 +154,7 @@ const URLPreviewCompactWithContext = (props: URLPreviewCompactPropsWithContext)
</Text>
) : null}
<View style={[styles.linkPreview, linkPreview, stylesProp.linkPreview]}>
<icons.Link height={12} width={12} stroke={semantics.chatTextIncoming} />
<icons.Link height={12} width={12} stroke={styles.linkPreviewText.color} />
<Text
numberOfLines={1}
style={[styles.linkPreviewText, linkPreviewText, stylesProp.linkPreviewText]}
Expand Down Expand Up @@ -271,13 +267,13 @@ const useStyles = () => {
flexShrink: 1,
},
title: {
color: semantics.chatTextIncoming,
color: isMyMessage ? semantics.chatTextOutgoing : semantics.chatTextIncoming,
fontSize: primitives.typographyFontSizeSm,
fontWeight: primitives.typographyFontWeightSemiBold,
lineHeight: primitives.typographyLineHeightTight,
},
description: {
color: semantics.chatTextIncoming,
color: isMyMessage ? semantics.chatTextOutgoing : semantics.chatTextIncoming,
fontSize: primitives.typographyFontSizeXs,
fontWeight: primitives.typographyFontWeightRegular,
lineHeight: primitives.typographyLineHeightTight,
Expand All @@ -288,7 +284,7 @@ const useStyles = () => {
gap: primitives.spacingXxs,
},
linkPreviewText: {
color: semantics.chatTextIncoming,
color: isMyMessage ? semantics.chatTextOutgoing : semantics.chatTextIncoming,
fontSize: primitives.typographyFontSizeXs,
fontWeight: primitives.typographyFontWeightRegular,
lineHeight: primitives.typographyLineHeightTight,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import React from 'react';
import { StyleSheet } from 'react-native';

import { cleanup, render, screen, waitFor } from '@testing-library/react-native';

import type { MessageContextValue } from '../../../../contexts/messageContext/MessageContext';
import { MessageProvider } from '../../../../contexts/messageContext/MessageContext';
import type { MessagesContextValue } from '../../../../contexts/messagesContext/MessagesContext';
import { MessagesProvider } from '../../../../contexts/messagesContext/MessagesContext';
import { mergeThemes, ThemeProvider } from '../../../../contexts/themeContext/ThemeContext';
import { generateCardAttachment } from '../../../../mock-builders/generator/attachment';
import { generateMessage } from '../../../../mock-builders/generator/message';
import { URLPreview } from '../URLPreview';
import { URLPreviewCompact } from '../URLPreviewCompact';

const lightTheme = mergeThemes({ scheme: 'light' });

/**
* Both URL preview variants resolve every text colour from the same
* incoming/outgoing pair, so the assertions are shared.
*/
describe.each([
['URLPreview', URLPreview],
['URLPreviewCompact', URLPreviewCompact],
])('%s chat text side', (_name, Component) => {
const renderPreview = (messageContextValue: Partial<MessageContextValue> = {}) => {
const attachment = generateCardAttachment({ title: 'A title', text: 'A description' });

return render(
<ThemeProvider>
<MessagesProvider value={{} as unknown as MessagesContextValue}>
<MessageProvider
value={
{
message: generateMessage(),
...messageContextValue,
} as unknown as MessageContextValue
}
>
<Component attachment={attachment} />
</MessageProvider>
</MessagesProvider>
</ThemeProvider>,
);
};

const colorOf = (text: string) => StyleSheet.flatten(screen.getByText(text).props.style)?.color;

afterEach(cleanup);

it('uses the outgoing chat text colour on our own message', async () => {
renderPreview({ isMyMessage: true });

await waitFor(() => {
expect(colorOf('A title')).toBe(lightTheme.semantics.chatTextOutgoing);
});
expect(colorOf('A description')).toBe(lightTheme.semantics.chatTextOutgoing);
});

it("uses the incoming chat text colour on another user's message", async () => {
renderPreview({ isMyMessage: false });

await waitFor(() => {
expect(colorOf('A title')).toBe(lightTheme.semantics.chatTextIncoming);
});
expect(colorOf('A description')).toBe(lightTheme.semantics.chatTextIncoming);
});

it('uses the outgoing attachment background on our own message', async () => {
renderPreview({ isMyMessage: true });

await waitFor(() => {
expect(StyleSheet.flatten(screen.getByTestId('card-attachment').props.style)).toEqual(
expect.objectContaining({
backgroundColor: lightTheme.semantics.chatBgAttachmentOutgoing,
}),
);
});
});

it('strokes the link icon with the same colour as the link text', async () => {
renderPreview({ isMyMessage: true });

// The URL text and its leading icon must not disagree on the side.
await waitFor(() => {
expect(screen.getByTestId('card-attachment')).toBeTruthy();
});

const linkIcon = screen.UNSAFE_getByProps({ stroke: lightTheme.semantics.chatTextOutgoing });
expect(linkIcon).toBeTruthy();
});
});
48 changes: 48 additions & 0 deletions package/src/components/Attachment/__tests__/Attachment.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,54 @@ describe('Attachment', () => {
isSoundPackageAvailable.mockReturnValue(false);
});

it('uses the outgoing audio player background on our own message', async () => {
const { isSoundPackageAvailable } = require('../../../native');
isSoundPackageAvailable.mockReturnValue(true);
const attachment = generateAudioAttachment({ duration: 10, waveform_data: [0.2, 0.6] });
const message = generateMessage({ attachments: [attachment, generateAudioAttachment()] });

const { getByLabelText } = render(
getAttachmentComponent(
{ attachment },
{ isMyMessage: true, message, messageHasOnlySingleAttachment: false },
),
);

await waitFor(() => {
const style = StyleSheet.flatten(getByLabelText('audio-attachment-preview').props.style);
expect(style.backgroundColor).toBe(lightTheme.semantics.chatBgAttachmentOutgoing);
});
isSoundPackageAvailable.mockReturnValue(false);
});

it('resolves the audio duration label colour from the message side', async () => {
const { isSoundPackageAvailable } = require('../../../native');
isSoundPackageAvailable.mockReturnValue(true);
const attachment = generateAudioAttachment({ duration: 10, waveform_data: [0.2, 0.6] });
const message = generateMessage({ attachments: [attachment] });

const { getByLabelText } = render(
getAttachmentComponent(
{ attachment },
{ isMyMessage: true, message, messageHasOnlySingleAttachment: false },
),
);

// NOTE: `StableDurationLabel` applies its own `visibleStyle` last, which
// always sets `color` from the playback state - so this asserts the wiring
// reaching the label, not the final rendered pixel. See the reserve label.
await waitFor(() => {
expect(getByLabelText('Progress Duration').props.style).toEqual(
expect.arrayContaining([
expect.arrayContaining([
expect.objectContaining({ color: lightTheme.semantics.chatTextOutgoing }),
]),
]),
);
});
isSoundPackageAvailable.mockReturnValue(false);
});

it('should render UrlPreview component if attachment has title_link or og_scrape_url', async () => {
const attachment = generateImageAttachment({
og_scrape_url: uuidv4(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,6 @@ const MessageItemViewWithContext = (props: MessageItemViewPropsWithContext) => {

const {
isMessageErrorType,
isMessageReceivedOrErrorType,
isMessageTypeDeleted,
isVeryLastMessage,
messageGroupedSingleOrBottom,
Expand Down Expand Up @@ -200,7 +199,7 @@ const MessageItemViewWithContext = (props: MessageItemViewPropsWithContext) => {
backgroundColor = 'transparent';
} else if (hasStandaloneGiphyOrImgur) {
backgroundColor = 'transparent';
} else if (isMessageReceivedOrErrorType) {
} else if (!isMyMessage) {
backgroundColor = semantics.chatBgIncoming;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React from 'react';
import { Text } from 'react-native';
import { StyleSheet, Text } from 'react-native';

import { cleanup, render, waitFor } from '@testing-library/react-native';

import { WithComponents } from '../../../../contexts/componentsContext/ComponentsContext';
import { OverlayProvider } from '../../../../contexts/overlayContext/OverlayProvider';
import { ThemeProvider } from '../../../../contexts/themeContext/ThemeContext';
import { mergeThemes, ThemeProvider } from '../../../../contexts/themeContext/ThemeContext';
import { defaultTheme } from '../../../../contexts/themeContext/utils/theme';
import { getOrCreateChannelApi } from '../../../../mock-builders/api/getOrCreateChannel';
import { useMockedApis } from '../../../../mock-builders/api/useMockedApis';
Expand All @@ -22,6 +22,8 @@ import { MessageList } from '../../../MessageList/MessageList';
import { MessageTextContainer } from '../MessageTextContainer';

describe('MessageTextContainer', () => {
const lightTheme = mergeThemes({ scheme: 'light' });

afterEach(cleanup);

it('should render message text container', async () => {
Expand Down Expand Up @@ -110,4 +112,20 @@ describe('MessageTextContainer', () => {
expect(getByText(message.i18n.no_text)).toBeTruthy();
});
});

it('renders our own message text with the outgoing chat text colour', async () => {
const message = generateMessage({ user: generateStaticUser(1) });

const { getByText } = render(
<ThemeProvider>
<MessageTextContainer isMyMessage message={message} />
</ThemeProvider>,
);

await waitFor(() => {
expect(StyleSheet.flatten(getByText(message.text as string).props.style)?.color).toBe(
lightTheme.semantics.chatTextOutgoing,
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ exports[`MessageAuthor should render message author 1`] = `
"width": 32,
},
{
"backgroundColor": "#006970",
"backgroundColor": "#a9e4ea",
},
{
"borderColor": "rgba(255, 255, 255, 0.2)",
"borderColor": "rgba(26, 27, 37, 0.1)",
"borderWidth": 1,
},
undefined,
Expand Down
Loading