fix: selected items on a multiselect change when do a new search (#5145)

* fix: selected items on a multiselect change when do a new search

* fix livechateditview

* minor tweak at const

* update uikit storyshot and uikit handle the multiStaticSelect

* add e2e test

* minor tweak
This commit is contained in:
Reinaldo Neto 2023-08-03 16:37:14 -03:00 committed by GitHub
parent a203f67a4a
commit 77a81d577e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 100 additions and 55 deletions

View File

@ -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 ? <FastImage style={styles.chipImage} source={{ uri: item.imageUrl }} /> : null}

View File

@ -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 }) => <Item item={item} onSelect={onSelect} selected={selected.find(s => s === item.value)} />}
renderItem={({ item }) => <Item item={item} onSelect={onSelect} selected={!!selected.find(s => s.value === item.value)} />}
/>
);

View File

@ -17,16 +17,16 @@ interface IMultiSelectContentProps {
options?: IItemData[];
multiselect: boolean;
select: React.Dispatch<any>;
onChange: Function;
onChange: ({ value }: { value: string[] }) => void;
setCurrentValue: React.Dispatch<React.SetStateAction<string>>;
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<string[]>(Array.isArray(selectedItems) ? selectedItems : []);
const [selected, setSelected] = useState<IItemData[]>(Array.isArray(selectedItems) ? selectedItems : []);
const [items, setItems] = useState<IItemData[] | undefined>(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);

View File

@ -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<IItemData[] | undefined>;
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<string[]>(Array.isArray(values) ? values : []);
const [selected, select] = useState<IItemData[]>(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 = (
<Input onPress={onShow} loading={loading} disabled={disabled} inputStyle={inputStyle} innerInputStyle={innerInputStyle}>
{items.length ? (
<Chips items={items} onSelect={(item: any) => (disabled ? {} : onSelect(item))} />
{selected.length ? (
<Chips items={selected} onSelect={(item: any) => (disabled ? {} : onSelect(item))} />
) : (
<Text style={[styles.pickerText, { color: colors.auxiliaryText }]}>{placeholder.text}</Text>
)}

View File

@ -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
}
}
}
]);

View File

@ -138,7 +138,8 @@ class MessageParser extends UiKitParserMessage<React.ReactElement> {
multiStaticSelect(element: IElement, context: BlockContext) {
const [{ loading, value }, action] = useBlockContext(element, context);
return <MultiSelect {...element} value={value} onChange={action} context={context} loading={loading} multiselect />;
const valueFiltered = element.options?.filter(option => value.includes(option.value));
return <MultiSelect {...element} value={valueFiltered} onChange={action} context={context} loading={loading} multiselect />;
}
staticSelect(element: IElement, context: BlockContext) {

View File

@ -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;
}

View File

@ -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
<Text style={[styles.label, { color: themes[theme!].titleText }]}>{I18n.t('Tags')}</Text>
<MultiSelect
options={tagParam.map((tag: string) => ({ 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}

View File

@ -78,6 +78,11 @@ interface IRoomInfoEditViewProps extends IBaseScreen<ChatsStackParamList | Modal
deleteTeamPermission: string[];
}
const MESSAGE_TYPE_VALUES = MessageTypeValues.map(m => ({
value: m.value,
text: { text: I18n.t('Hide_type_messages', { type: I18n.t(m.text) }) }
}));
class RoomInfoEditView extends React.Component<IRoomInfoEditViewProps, IRoomInfoEditViewState> {
randomValue = random(15);
private querySubscription: Subscription | undefined;
@ -447,15 +452,16 @@ class RoomInfoEditView extends React.Component<IRoomInfoEditViewProps, IRoomInfo
return null;
}
const values = Array.isArray(systemMessages)
? MESSAGE_TYPE_VALUES.filter((option: any) => systemMessages.includes(option.value))
: [];
return (
<MultiSelect
options={MessageTypeValues.map(m => ({
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
/>

View File

@ -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);