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:
parent
a203f67a4a
commit
77a81d577e
|
@ -31,6 +31,7 @@ const Chip = ({ item, onSelect, style }: IChip) => {
|
||||||
onPress={() => onSelect(item)}
|
onPress={() => onSelect(item)}
|
||||||
style={[styles.chip, { backgroundColor: colors.auxiliaryBackground }, style]}
|
style={[styles.chip, { backgroundColor: colors.auxiliaryBackground }, style]}
|
||||||
background={Touchable.Ripple(colors.bannerBackground)}
|
background={Touchable.Ripple(colors.bannerBackground)}
|
||||||
|
testID={`multi-select-chip-${item.value}`}
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
{item.imageUrl ? <FastImage style={styles.chipImage} source={{ uri: item.imageUrl }} /> : null}
|
{item.imageUrl ? <FastImage style={styles.chipImage} source={{ uri: item.imageUrl }} /> : null}
|
||||||
|
|
|
@ -13,13 +13,13 @@ import { CustomIcon } from '../../CustomIcon';
|
||||||
|
|
||||||
interface IItem {
|
interface IItem {
|
||||||
item: IItemData;
|
item: IItemData;
|
||||||
selected?: string;
|
selected: boolean;
|
||||||
onSelect: Function;
|
onSelect: Function;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IItems {
|
interface IItems {
|
||||||
items: IItemData[];
|
items: IItemData[];
|
||||||
selected: string[];
|
selected: IItemData[];
|
||||||
onSelect: Function;
|
onSelect: Function;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ const Items = ({ items, selected, onSelect }: IItems) => (
|
||||||
keyboardShouldPersistTaps='always'
|
keyboardShouldPersistTaps='always'
|
||||||
ItemSeparatorComponent={List.Separator}
|
ItemSeparatorComponent={List.Separator}
|
||||||
keyExtractor={keyExtractor}
|
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)} />}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -17,16 +17,16 @@ interface IMultiSelectContentProps {
|
||||||
options?: IItemData[];
|
options?: IItemData[];
|
||||||
multiselect: boolean;
|
multiselect: boolean;
|
||||||
select: React.Dispatch<any>;
|
select: React.Dispatch<any>;
|
||||||
onChange: Function;
|
onChange: ({ value }: { value: string[] }) => void;
|
||||||
setCurrentValue: React.Dispatch<React.SetStateAction<string>>;
|
setCurrentValue: React.Dispatch<React.SetStateAction<string>>;
|
||||||
onHide: Function;
|
onHide: Function;
|
||||||
selectedItems: string[];
|
selectedItems: IItemData[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MultiSelectContent = React.memo(
|
export const MultiSelectContent = React.memo(
|
||||||
({ onSearch, options, multiselect, select, onChange, setCurrentValue, onHide, selectedItems }: IMultiSelectContentProps) => {
|
({ onSearch, options, multiselect, select, onChange, setCurrentValue, onHide, selectedItems }: IMultiSelectContentProps) => {
|
||||||
const { colors } = useTheme();
|
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 [items, setItems] = useState<IItemData[] | undefined>(options);
|
||||||
const { hideActionSheet } = useActionSheet();
|
const { hideActionSheet } = useActionSheet();
|
||||||
|
|
||||||
|
@ -37,14 +37,14 @@ export const MultiSelectContent = React.memo(
|
||||||
} = item;
|
} = item;
|
||||||
if (multiselect) {
|
if (multiselect) {
|
||||||
let newSelect = [];
|
let newSelect = [];
|
||||||
if (!selected.includes(value)) {
|
if (!selected.find(s => s.value === value)) {
|
||||||
newSelect = [...selected, value];
|
newSelect = [...selected, item];
|
||||||
} else {
|
} else {
|
||||||
newSelect = selected.filter((s: any) => s !== value);
|
newSelect = selected.filter((s: any) => s.value !== value);
|
||||||
}
|
}
|
||||||
setSelected(newSelect);
|
setSelected(newSelect);
|
||||||
select(newSelect);
|
select(newSelect);
|
||||||
onChange({ value: newSelect });
|
onChange({ value: newSelect.map(s => s.value) });
|
||||||
} else {
|
} else {
|
||||||
onChange({ value });
|
onChange({ value });
|
||||||
setCurrentValue(text);
|
setCurrentValue(text);
|
||||||
|
|
|
@ -17,13 +17,21 @@ export interface IItemData {
|
||||||
imageUrl?: string;
|
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 {
|
interface IMultiSelect {
|
||||||
options?: IItemData[];
|
options?: IItemData[];
|
||||||
onChange: Function;
|
|
||||||
placeholder?: IText;
|
placeholder?: IText;
|
||||||
context?: BlockContext;
|
context?: BlockContext;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
multiselect?: boolean;
|
|
||||||
onSearch?: (keyword: string) => IItemData[] | Promise<IItemData[] | undefined>;
|
onSearch?: (keyword: string) => IItemData[] | Promise<IItemData[] | undefined>;
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
inputStyle?: TextStyle;
|
inputStyle?: TextStyle;
|
||||||
|
@ -46,9 +54,9 @@ export const MultiSelect = React.memo(
|
||||||
disabled,
|
disabled,
|
||||||
inputStyle,
|
inputStyle,
|
||||||
innerInputStyle
|
innerInputStyle
|
||||||
}: IMultiSelect) => {
|
}: IMultiSelectWithMultiSelect | IMultiSelectWithoutMultiSelect) => {
|
||||||
const { colors } = useTheme();
|
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 [currentValue, setCurrentValue] = useState('');
|
||||||
|
|
||||||
const { showActionSheet, hideActionSheet } = useActionSheet();
|
const { showActionSheet, hideActionSheet } = useActionSheet();
|
||||||
|
@ -57,7 +65,7 @@ export const MultiSelect = React.memo(
|
||||||
if (Array.isArray(values)) {
|
if (Array.isArray(values)) {
|
||||||
select(values);
|
select(values);
|
||||||
}
|
}
|
||||||
}, [values]);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (values && values.length && !multiselect) {
|
if (values && values.length && !multiselect) {
|
||||||
|
@ -95,13 +103,13 @@ export const MultiSelect = React.memo(
|
||||||
} = item;
|
} = item;
|
||||||
if (multiselect) {
|
if (multiselect) {
|
||||||
let newSelect = [];
|
let newSelect = [];
|
||||||
if (!selected.includes(value)) {
|
if (!selected.find(s => s.value === value)) {
|
||||||
newSelect = [...selected, value];
|
newSelect = [...selected, item];
|
||||||
} else {
|
} else {
|
||||||
newSelect = selected.filter((s: any) => s !== value);
|
newSelect = selected.filter((s: any) => s.value !== value);
|
||||||
}
|
}
|
||||||
select(newSelect);
|
select(newSelect);
|
||||||
onChange({ value: newSelect });
|
onChange({ value: newSelect.map(s => s.value) });
|
||||||
} else {
|
} else {
|
||||||
onChange({ value });
|
onChange({ value });
|
||||||
setCurrentValue(text);
|
setCurrentValue(text);
|
||||||
|
@ -119,12 +127,10 @@ export const MultiSelect = React.memo(
|
||||||
);
|
);
|
||||||
|
|
||||||
if (context === BlockContext.FORM) {
|
if (context === BlockContext.FORM) {
|
||||||
const items: any = options.filter((option: any) => selected.includes(option.value));
|
|
||||||
|
|
||||||
button = (
|
button = (
|
||||||
<Input onPress={onShow} loading={loading} disabled={disabled} inputStyle={inputStyle} innerInputStyle={innerInputStyle}>
|
<Input onPress={onShow} loading={loading} disabled={disabled} inputStyle={inputStyle} innerInputStyle={innerInputStyle}>
|
||||||
{items.length ? (
|
{selected.length ? (
|
||||||
<Chips items={items} onSelect={(item: any) => (disabled ? {} : onSelect(item))} />
|
<Chips items={selected} onSelect={(item: any) => (disabled ? {} : onSelect(item))} />
|
||||||
) : (
|
) : (
|
||||||
<Text style={[styles.pickerText, { color: colors.auxiliaryText }]}>{placeholder.text}</Text>
|
<Text style={[styles.pickerText, { color: colors.auxiliaryText }]}>{placeholder.text}</Text>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -220,36 +220,37 @@ export const SectionMultiSelect = () =>
|
||||||
},
|
},
|
||||||
accessory: {
|
accessory: {
|
||||||
type: 'multi_static_select',
|
type: 'multi_static_select',
|
||||||
|
appId: 'app-id',
|
||||||
|
blockId: 'block-id',
|
||||||
|
actionId: 'action-id',
|
||||||
|
initialValue: ['option_1', 'option_2'],
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
|
value: 'option_1',
|
||||||
text: {
|
text: {
|
||||||
type: 'plain_text',
|
type: 'plain_text',
|
||||||
text: 'button'
|
text: 'lorem ipsum 🚀',
|
||||||
},
|
emoji: true
|
||||||
value: 1
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
value: 'option_2',
|
||||||
text: {
|
text: {
|
||||||
type: 'plain_text',
|
type: 'plain_text',
|
||||||
text: 'opt 1'
|
text: 'lorem ipsum 🚀',
|
||||||
},
|
emoji: true
|
||||||
value: 2
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
text: {
|
|
||||||
type: 'plain_text',
|
|
||||||
text: 'opt 2'
|
|
||||||
},
|
|
||||||
value: 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: {
|
|
||||||
type: 'plain_text',
|
|
||||||
text: 'opt 3'
|
|
||||||
},
|
|
||||||
value: 4
|
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
placeholder: {
|
||||||
|
type: 'plain_text',
|
||||||
|
text: 'Select an item'
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
type: 'plain_text',
|
||||||
|
text: 'Label',
|
||||||
|
emoji: true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -138,7 +138,8 @@ class MessageParser extends UiKitParserMessage<React.ReactElement> {
|
||||||
|
|
||||||
multiStaticSelect(element: IElement, context: BlockContext) {
|
multiStaticSelect(element: IElement, context: BlockContext) {
|
||||||
const [{ loading, value }, action] = useBlockContext(element, context);
|
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) {
|
staticSelect(element: IElement, context: BlockContext) {
|
||||||
|
|
|
@ -39,7 +39,7 @@ export interface ICreateDiscussionViewSelectChannel {
|
||||||
token: string;
|
token: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
initial: object;
|
initial: object;
|
||||||
onChannelSelect: Function;
|
onChannelSelect: ({ value }: { value: any }) => void;
|
||||||
blockUnauthenticatedAccess: boolean;
|
blockUnauthenticatedAccess: boolean;
|
||||||
serverVersion: string;
|
serverVersion: string;
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ export interface ICreateDiscussionViewSelectUsers {
|
||||||
token: string;
|
token: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
selected: any[];
|
selected: any[];
|
||||||
onUserSelect: Function;
|
onUserSelect: ({ value }: { value: string[] }) => void;
|
||||||
blockUnauthenticatedAccess: boolean;
|
blockUnauthenticatedAccess: boolean;
|
||||||
serverVersion: string;
|
serverVersion: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,6 +90,11 @@ const LivechatEditView = ({ user, navigation, route, theme }: ILivechatEditViewP
|
||||||
const [tagParam, setTags] = useState(livechat?.tags || []);
|
const [tagParam, setTags] = useState(livechat?.tags || []);
|
||||||
const [tagParamSelected, setTagParamSelected] = 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(() => {
|
useEffect(() => {
|
||||||
const arr = [...tagParam, ...availableUserTags];
|
const arr = [...tagParam, ...availableUserTags];
|
||||||
const uniqueArray = arr.filter((val, i) => arr.indexOf(val) === i);
|
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>
|
<Text style={[styles.label, { color: themes[theme!].titleText }]}>{I18n.t('Tags')}</Text>
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
options={tagParam.map((tag: string) => ({ text: { text: tag }, value: tag }))}
|
options={tagOptions}
|
||||||
onChange={({ value }: { value: string[] }) => {
|
onChange={({ value }: { value: string[] }) => {
|
||||||
setTagParamSelected([...value]);
|
setTagParamSelected([...value]);
|
||||||
}}
|
}}
|
||||||
placeholder={{ text: I18n.t('Tags') }}
|
placeholder={{ text: I18n.t('Tags') }}
|
||||||
value={tagParamSelected}
|
value={tagValues}
|
||||||
context={BlockContext.FORM}
|
context={BlockContext.FORM}
|
||||||
multiselect
|
multiselect
|
||||||
disabled={!editLivechatRoomCustomFieldsPermission}
|
disabled={!editLivechatRoomCustomFieldsPermission}
|
||||||
|
|
|
@ -78,6 +78,11 @@ interface IRoomInfoEditViewProps extends IBaseScreen<ChatsStackParamList | Modal
|
||||||
deleteTeamPermission: string[];
|
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> {
|
class RoomInfoEditView extends React.Component<IRoomInfoEditViewProps, IRoomInfoEditViewState> {
|
||||||
randomValue = random(15);
|
randomValue = random(15);
|
||||||
private querySubscription: Subscription | undefined;
|
private querySubscription: Subscription | undefined;
|
||||||
|
@ -447,15 +452,16 @@ class RoomInfoEditView extends React.Component<IRoomInfoEditViewProps, IRoomInfo
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const values = Array.isArray(systemMessages)
|
||||||
|
? MESSAGE_TYPE_VALUES.filter((option: any) => systemMessages.includes(option.value))
|
||||||
|
: [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
options={MessageTypeValues.map(m => ({
|
options={MESSAGE_TYPE_VALUES}
|
||||||
value: m.value,
|
onChange={({ value }) => this.setState({ systemMessages: value })}
|
||||||
text: { text: I18n.t('Hide_type_messages', { type: I18n.t(m.text) }) }
|
|
||||||
}))}
|
|
||||||
onChange={({ value }: { value: boolean }) => this.setState({ systemMessages: value })}
|
|
||||||
placeholder={{ text: I18n.t('Hide_System_Messages') }}
|
placeholder={{ text: I18n.t('Hide_System_Messages') }}
|
||||||
value={systemMessages as string[]}
|
value={values}
|
||||||
context={BlockContext.FORM}
|
context={BlockContext.FORM}
|
||||||
multiselect
|
multiselect
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -31,6 +31,7 @@ describe('Discussion', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create discussion from NewMessageView', async () => {
|
it('should create discussion from NewMessageView', async () => {
|
||||||
|
const selectUser = 'rocket.cat';
|
||||||
await waitFor(element(by.id('rooms-list-view-create-channel')))
|
await waitFor(element(by.id('rooms-list-view-create-channel')))
|
||||||
.toExist()
|
.toExist()
|
||||||
.withTimeout(2000);
|
.withTimeout(2000);
|
||||||
|
@ -53,6 +54,30 @@ describe('Discussion', () => {
|
||||||
.withTimeout(10000);
|
.withTimeout(10000);
|
||||||
await element(by.id(`multi-select-item-${room}`)).tap();
|
await element(by.id(`multi-select-item-${room}`)).tap();
|
||||||
await element(by.id('multi-select-discussion-name')).replaceText(discussionFromNewMessage);
|
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')))
|
await waitFor(element(by.id('create-discussion-submit')))
|
||||||
.toExist()
|
.toExist()
|
||||||
.withTimeout(10000);
|
.withTimeout(10000);
|
||||||
|
|
Loading…
Reference in New Issue