diff --git a/app/containers/UIKit/MultiSelect/Chips.tsx b/app/containers/UIKit/MultiSelect/Chips.tsx index 30584885c..ac10c932d 100644 --- a/app/containers/UIKit/MultiSelect/Chips.tsx +++ b/app/containers/UIKit/MultiSelect/Chips.tsx @@ -31,6 +31,7 @@ const Chip = ({ item, onSelect, style }: IChip) => { onPress={() => onSelect(item)} style={[styles.chip, { backgroundColor: colors.auxiliaryBackground }, style]} background={Touchable.Ripple(colors.bannerBackground)} + testID={`multi-select-chip-${item.value}`} > <> {item.imageUrl ? : null} diff --git a/app/containers/UIKit/MultiSelect/Items.tsx b/app/containers/UIKit/MultiSelect/Items.tsx index 8c8b07c1e..9f8f9cf26 100644 --- a/app/containers/UIKit/MultiSelect/Items.tsx +++ b/app/containers/UIKit/MultiSelect/Items.tsx @@ -13,13 +13,13 @@ import { CustomIcon } from '../../CustomIcon'; interface IItem { item: IItemData; - selected?: string; + selected: boolean; onSelect: Function; } interface IItems { items: IItemData[]; - selected: string[]; + selected: IItemData[]; onSelect: Function; } @@ -54,7 +54,7 @@ const Items = ({ items, selected, onSelect }: IItems) => ( keyboardShouldPersistTaps='always' ItemSeparatorComponent={List.Separator} keyExtractor={keyExtractor} - renderItem={({ item }) => s === item.value)} />} + renderItem={({ item }) => s.value === item.value)} />} /> ); diff --git a/app/containers/UIKit/MultiSelect/MultiSelectContent.tsx b/app/containers/UIKit/MultiSelect/MultiSelectContent.tsx index 9a9bd632b..270163692 100644 --- a/app/containers/UIKit/MultiSelect/MultiSelectContent.tsx +++ b/app/containers/UIKit/MultiSelect/MultiSelectContent.tsx @@ -17,16 +17,16 @@ interface IMultiSelectContentProps { options?: IItemData[]; multiselect: boolean; select: React.Dispatch; - onChange: Function; + onChange: ({ value }: { value: string[] }) => void; setCurrentValue: React.Dispatch>; onHide: Function; - selectedItems: string[]; + selectedItems: IItemData[]; } export const MultiSelectContent = React.memo( ({ onSearch, options, multiselect, select, onChange, setCurrentValue, onHide, selectedItems }: IMultiSelectContentProps) => { const { colors } = useTheme(); - const [selected, setSelected] = useState(Array.isArray(selectedItems) ? selectedItems : []); + const [selected, setSelected] = useState(Array.isArray(selectedItems) ? selectedItems : []); const [items, setItems] = useState(options); const { hideActionSheet } = useActionSheet(); @@ -37,14 +37,14 @@ export const MultiSelectContent = React.memo( } = item; if (multiselect) { let newSelect = []; - if (!selected.includes(value)) { - newSelect = [...selected, value]; + if (!selected.find(s => s.value === value)) { + newSelect = [...selected, item]; } else { - newSelect = selected.filter((s: any) => s !== value); + newSelect = selected.filter((s: any) => s.value !== value); } setSelected(newSelect); select(newSelect); - onChange({ value: newSelect }); + onChange({ value: newSelect.map(s => s.value) }); } else { onChange({ value }); setCurrentValue(text); diff --git a/app/containers/UIKit/MultiSelect/index.tsx b/app/containers/UIKit/MultiSelect/index.tsx index 20e9e0975..7808683a0 100644 --- a/app/containers/UIKit/MultiSelect/index.tsx +++ b/app/containers/UIKit/MultiSelect/index.tsx @@ -17,13 +17,21 @@ export interface IItemData { imageUrl?: string; } +interface IMultiSelectWithMultiSelect extends IMultiSelect { + multiselect: true; + onChange: ({ value }: { value: string[] }) => void; +} + +interface IMultiSelectWithoutMultiSelect extends IMultiSelect { + multiselect?: false; + onChange: ({ value }: { value: any }) => void; +} + interface IMultiSelect { options?: IItemData[]; - onChange: Function; placeholder?: IText; context?: BlockContext; loading?: boolean; - multiselect?: boolean; onSearch?: (keyword: string) => IItemData[] | Promise; onClose?: () => void; inputStyle?: TextStyle; @@ -46,9 +54,9 @@ export const MultiSelect = React.memo( disabled, inputStyle, innerInputStyle - }: IMultiSelect) => { + }: IMultiSelectWithMultiSelect | IMultiSelectWithoutMultiSelect) => { const { colors } = useTheme(); - const [selected, select] = useState(Array.isArray(values) ? values : []); + const [selected, select] = useState(Array.isArray(values) ? values : []); const [currentValue, setCurrentValue] = useState(''); const { showActionSheet, hideActionSheet } = useActionSheet(); @@ -57,7 +65,7 @@ export const MultiSelect = React.memo( if (Array.isArray(values)) { select(values); } - }, [values]); + }, []); useEffect(() => { if (values && values.length && !multiselect) { @@ -95,13 +103,13 @@ export const MultiSelect = React.memo( } = item; if (multiselect) { let newSelect = []; - if (!selected.includes(value)) { - newSelect = [...selected, value]; + if (!selected.find(s => s.value === value)) { + newSelect = [...selected, item]; } else { - newSelect = selected.filter((s: any) => s !== value); + newSelect = selected.filter((s: any) => s.value !== value); } select(newSelect); - onChange({ value: newSelect }); + onChange({ value: newSelect.map(s => s.value) }); } else { onChange({ value }); setCurrentValue(text); @@ -119,12 +127,10 @@ export const MultiSelect = React.memo( ); if (context === BlockContext.FORM) { - const items: any = options.filter((option: any) => selected.includes(option.value)); - button = ( - {items.length ? ( - (disabled ? {} : onSelect(item))} /> + {selected.length ? ( + (disabled ? {} : onSelect(item))} /> ) : ( {placeholder.text} )} diff --git a/app/containers/UIKit/UiKitMessage.stories.tsx b/app/containers/UIKit/UiKitMessage.stories.tsx index ecb5d7bcd..696732047 100644 --- a/app/containers/UIKit/UiKitMessage.stories.tsx +++ b/app/containers/UIKit/UiKitMessage.stories.tsx @@ -220,36 +220,37 @@ export const SectionMultiSelect = () => }, accessory: { type: 'multi_static_select', + appId: 'app-id', + blockId: 'block-id', + actionId: 'action-id', + initialValue: ['option_1', 'option_2'], options: [ { + value: 'option_1', text: { type: 'plain_text', - text: 'button' - }, - value: 1 + text: 'lorem ipsum 🚀', + emoji: true + } }, { + value: 'option_2', text: { type: 'plain_text', - text: 'opt 1' - }, - value: 2 - }, - { - text: { - type: 'plain_text', - text: 'opt 2' - }, - value: 3 - }, - { - text: { - type: 'plain_text', - text: 'opt 3' - }, - value: 4 + text: 'lorem ipsum 🚀', + emoji: true + } } - ] + ], + placeholder: { + type: 'plain_text', + text: 'Select an item' + }, + label: { + type: 'plain_text', + text: 'Label', + emoji: true + } } } ]); diff --git a/app/containers/UIKit/index.tsx b/app/containers/UIKit/index.tsx index 6dbd87f28..d03efec1c 100644 --- a/app/containers/UIKit/index.tsx +++ b/app/containers/UIKit/index.tsx @@ -138,7 +138,8 @@ class MessageParser extends UiKitParserMessage { multiStaticSelect(element: IElement, context: BlockContext) { const [{ loading, value }, action] = useBlockContext(element, context); - return ; + const valueFiltered = element.options?.filter(option => value.includes(option.value)); + return ; } staticSelect(element: IElement, context: BlockContext) { diff --git a/app/views/CreateDiscussionView/interfaces.ts b/app/views/CreateDiscussionView/interfaces.ts index f80b2b3c5..47163cfb3 100644 --- a/app/views/CreateDiscussionView/interfaces.ts +++ b/app/views/CreateDiscussionView/interfaces.ts @@ -39,7 +39,7 @@ export interface ICreateDiscussionViewSelectChannel { token: string; userId: string; initial: object; - onChannelSelect: Function; + onChannelSelect: ({ value }: { value: any }) => void; blockUnauthenticatedAccess: boolean; serverVersion: string; } @@ -49,7 +49,7 @@ export interface ICreateDiscussionViewSelectUsers { token: string; userId: string; selected: any[]; - onUserSelect: Function; + onUserSelect: ({ value }: { value: string[] }) => void; blockUnauthenticatedAccess: boolean; serverVersion: string; } diff --git a/app/views/LivechatEditView.tsx b/app/views/LivechatEditView.tsx index f06c7eca3..aad596a01 100644 --- a/app/views/LivechatEditView.tsx +++ b/app/views/LivechatEditView.tsx @@ -90,6 +90,11 @@ const LivechatEditView = ({ user, navigation, route, theme }: ILivechatEditViewP const [tagParam, setTags] = useState(livechat?.tags || []); const [tagParamSelected, setTagParamSelected] = useState(livechat?.tags || []); + const tagOptions = tagParam.map((tag: string) => ({ text: { text: tag }, value: tag })); + const tagValues = Array.isArray(tagParamSelected) + ? tagOptions.filter((option: any) => tagParamSelected.includes(option.value)) + : []; + useEffect(() => { const arr = [...tagParam, ...availableUserTags]; const uniqueArray = arr.filter((val, i) => arr.indexOf(val) === i); @@ -254,12 +259,12 @@ const LivechatEditView = ({ user, navigation, route, theme }: ILivechatEditViewP {I18n.t('Tags')} ({ text: { text: tag }, value: tag }))} + options={tagOptions} onChange={({ value }: { value: string[] }) => { setTagParamSelected([...value]); }} placeholder={{ text: I18n.t('Tags') }} - value={tagParamSelected} + value={tagValues} context={BlockContext.FORM} multiselect disabled={!editLivechatRoomCustomFieldsPermission} diff --git a/app/views/RoomInfoEditView/index.tsx b/app/views/RoomInfoEditView/index.tsx index 93e54630f..2132a382e 100644 --- a/app/views/RoomInfoEditView/index.tsx +++ b/app/views/RoomInfoEditView/index.tsx @@ -78,6 +78,11 @@ interface IRoomInfoEditViewProps extends IBaseScreen ({ + value: m.value, + text: { text: I18n.t('Hide_type_messages', { type: I18n.t(m.text) }) } +})); + class RoomInfoEditView extends React.Component { randomValue = random(15); private querySubscription: Subscription | undefined; @@ -447,15 +452,16 @@ class RoomInfoEditView extends React.Component systemMessages.includes(option.value)) + : []; + return ( ({ - value: m.value, - text: { text: I18n.t('Hide_type_messages', { type: I18n.t(m.text) }) } - }))} - onChange={({ value }: { value: boolean }) => this.setState({ systemMessages: value })} + options={MESSAGE_TYPE_VALUES} + onChange={({ value }) => this.setState({ systemMessages: value })} placeholder={{ text: I18n.t('Hide_System_Messages') }} - value={systemMessages as string[]} + value={values} context={BlockContext.FORM} multiselect /> diff --git a/e2e/tests/room/04-discussion.spec.ts b/e2e/tests/room/04-discussion.spec.ts index 434316739..5360ab693 100644 --- a/e2e/tests/room/04-discussion.spec.ts +++ b/e2e/tests/room/04-discussion.spec.ts @@ -31,6 +31,7 @@ describe('Discussion', () => { }); it('should create discussion from NewMessageView', async () => { + const selectUser = 'rocket.cat'; await waitFor(element(by.id('rooms-list-view-create-channel'))) .toExist() .withTimeout(2000); @@ -53,6 +54,30 @@ describe('Discussion', () => { .withTimeout(10000); await element(by.id(`multi-select-item-${room}`)).tap(); await element(by.id('multi-select-discussion-name')).replaceText(discussionFromNewMessage); + await element(by[textMatcher]('Select users...')).tap(); + await element(by.id('multi-select-search')).replaceText(`${selectUser}`); + await waitFor(element(by.id(`multi-select-item-${selectUser}`))) + .toExist() + .withTimeout(10000); + await element(by.id(`multi-select-item-${selectUser}`)).tap(); + await sleep(300); + // checking if the chip was placed properly + await waitFor(element(by.id(`multi-select-chip-${selectUser}`))) + .toExist() + .withTimeout(10000); + // should keep the same chip even when the user does a new research + await element(by.id('multi-select-search')).replaceText(`user`); + await waitFor(element(by.id(`multi-select-item-${selectUser}`))) + .not.toExist() + .withTimeout(10000); + await waitFor(element(by.id(`multi-select-chip-${selectUser}`))) + .toExist() + .withTimeout(10000); + await sleep(500); + await element(by.id('multi-select-search')).tapReturnKey(); + await sleep(500); + // removing the rocket.cat from the users + await element(by.id(`multi-select-chip-${selectUser}`)).tap(); await waitFor(element(by.id('create-discussion-submit'))) .toExist() .withTimeout(10000);