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
6 changes: 5 additions & 1 deletion package/src/components/Channel/Channel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ export type ChannelPropsWithContext = Pick<ChannelContextValue, 'channel'> &
| 'bottomInset'
| 'topInset'
| 'disableAttachmentPicker'
| 'shouldRenderAttachmentPicker'
| 'numberOfAttachmentPickerImageColumns'
| 'numberOfAttachmentImagesToLoadPerCall'
>
Expand Down Expand Up @@ -389,6 +390,7 @@ export type ChannelPropsWithContext = Pick<ChannelContextValue, 'channel'> &
const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) => {
const {
disableAttachmentPicker = !isImageMediaLibraryAvailable(),
shouldRenderAttachmentPicker = true,
additionalKeyboardAvoidingViewProps,
additionalPressableProps,
additionalTextInputProps,
Expand Down Expand Up @@ -1571,6 +1573,7 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
closePicker: handleClosePicker,
disableAttachmentPicker,
openPicker: handleOpenPicker,
shouldRenderAttachmentPicker,
topInset,
numberOfAttachmentPickerImageColumns,
attachmentPickerBottomSheetHeight,
Expand All @@ -1583,6 +1586,7 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
handleClosePicker,
disableAttachmentPicker,
handleOpenPicker,
shouldRenderAttachmentPicker,
topInset,
numberOfAttachmentPickerImageColumns,
attachmentPickerBottomSheetHeight,
Expand Down Expand Up @@ -1805,7 +1809,7 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
<AudioPlayerProvider value={audioPlayerContext}>
<NotificationAnnouncer />
<View style={{ height: '100%' }}>{children}</View>
<AttachmentPicker />
{shouldRenderAttachmentPicker ? <AttachmentPicker /> : null}
</AudioPlayerProvider>
</MessageInputProvider>
</MessageComposerProvider>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import React from 'react';

import { Text } from 'react-native';

import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react-native';
import type { Channel as ChannelType, StreamChat } from 'stream-chat';

import { OverlayProvider } from '../../../contexts';
import { useAttachmentPickerState } from '../../../hooks/useAttachmentPickerState';

import { initiateClientWithChannels } from '../../../mock-builders/api/initiateClientWithChannels';
import * as NativeHandler from '../../../native';
Expand All @@ -12,6 +15,11 @@ import { Channel } from '../../Channel/Channel';
import { Chat } from '../../Chat/Chat';
import { AttachButton } from '../components/InputButtons/AttachButton';

const SelectedPicker = () => {
const { selectedPicker } = useAttachmentPickerState();
return <Text testID='selected-picker'>{selectedPicker ?? 'none'}</Text>;
};

const renderComponent = ({
channelProps,
client,
Expand All @@ -32,6 +40,25 @@ const renderComponent = ({
);
};

const renderWithSelectedPicker = ({
channelProps,
client,
}: {
channelProps: Partial<ChannelProps>;
client: StreamChat;
}) => {
return render(
<OverlayProvider>
<Chat client={client}>
<Channel {...(channelProps as ChannelProps)}>
<AttachButton />
<SelectedPicker />
</Channel>
</Chat>
</OverlayProvider>,
);
};

describe('AttachButton', () => {
let client: StreamChat;
let channel: ChannelType;
Expand Down Expand Up @@ -211,4 +238,65 @@ describe('AttachButton', () => {
expect(queryByTestId('attachment-picker-list')).toBeNull();
});
});

it('should select the image picker when pressed', async () => {
jest.spyOn(NativeHandler, 'isImageMediaLibraryAvailable').mockImplementation(() => true);

renderWithSelectedPicker({ channelProps: { channel }, client });

await waitFor(() => {
expect(screen.queryByTestId('attach-button')).toBeTruthy();
});

act(() => {
fireEvent.press(screen.getByTestId('attach-button'));
});

await waitFor(() => {
expect(screen.getByTestId('selected-picker')).toHaveTextContent('images');
});
});

it('should not select a picker when pressed if shouldRenderAttachmentPicker is false', async () => {
jest.spyOn(NativeHandler, 'isImageMediaLibraryAvailable').mockImplementation(() => true);

renderWithSelectedPicker({
channelProps: { channel, shouldRenderAttachmentPicker: false },
client,
});

await waitFor(() => {
expect(screen.queryByTestId('attach-button')).toBeTruthy();
});

act(() => {
fireEvent.press(screen.getByTestId('attach-button'));
});

await waitFor(() => {
expect(screen.getByTestId('selected-picker')).toHaveTextContent('none');
expect(screen.queryByTestId('attachment-picker-list')).toBeNull();
});
});

it('should render the attachment picker by default', async () => {
renderWithSelectedPicker({ channelProps: { channel }, client });

await waitFor(() => {
expect(screen.queryByTestId('upload-file-touchable')).toBeTruthy();
});
});

it('should not render the attachment picker when shouldRenderAttachmentPicker is false', async () => {
renderWithSelectedPicker({
channelProps: { channel, shouldRenderAttachmentPicker: false },
client,
});

await waitFor(() => {
expect(screen.queryByTestId('attach-button')).toBeTruthy();
});

expect(screen.queryByTestId('upload-file-touchable')).toBeNull();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,20 @@ export type AttachmentPickerContextValue = Pick<
openPicker: () => void;
topInset: number;

/**
* When `true`, the native image picker is used instead of the in-app image gallery.
* The attachment picker itself (with its attachment type selection bar) is still rendered.
* To not render the attachment picker at all, set `shouldRenderAttachmentPicker` to `false`.
*/
disableAttachmentPicker?: boolean;
/**
* Whether the attachment picker should be rendered at all. Defaults to `true`.
*
* Can be set to `false` if attachment picker is not used. `openAttachmentPicker` then
* does nothing. The attach button is still shown, so it needs to either be hidden it or set
* `handleAttachButtonPress` to override what it does.
*/
shouldRenderAttachmentPicker?: boolean;
attachmentPickerStore: AttachmentPickerStore;
numberOfAttachmentPickerImageColumns?: number;
numberOfAttachmentImagesToLoadPerCall?: number;
Expand Down
15 changes: 12 additions & 3 deletions package/src/contexts/messageInputContext/MessageInputContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -221,8 +221,13 @@ export const MessageInputProvider = ({
}: PropsWithChildren<{
value: InputMessageInputContextValue;
}>) => {
const { closePicker, openPicker, attachmentPickerStore, disableAttachmentPicker } =
useAttachmentPickerContext();
const {
closePicker,
openPicker,
attachmentPickerStore,
disableAttachmentPicker,
shouldRenderAttachmentPicker = true,
} = useAttachmentPickerContext();
const { client } = useChatContext();
const channelCapabilities = useOwnCapabilitiesContext();
const [audioRecorderManager] = useState(new AudioRecorderManager());
Expand Down Expand Up @@ -371,10 +376,14 @@ export const MessageInputProvider = ({
* Function to open the attachment picker if the MediaLibary is installed.
*/
const openAttachmentPicker = useCallback(() => {
if (!shouldRenderAttachmentPicker) {
// There is no picker to open, and a selected picker would leave space reserved for it.
return;
}
dismissKeyboard();
attachmentPickerStore.setSelectedPicker('images');
openPicker();
}, [attachmentPickerStore, openPicker]);
}, [attachmentPickerStore, openPicker, shouldRenderAttachmentPicker]);

/**
* Function to close the attachment picker if the MediaLibrary is installed.
Expand Down
Loading