stream-chat-react-native-core 9.4.1-beta.1 → 9.5.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/components/ChannelDetails/components/members/ChannelAddMembers.js +7 -4
- package/lib/commonjs/components/ChannelDetails/components/members/ChannelAddMembers.js.map +1 -1
- package/lib/commonjs/components/ChannelDetails/components/members/ChannelAllMembersModal.js +2 -23
- package/lib/commonjs/components/ChannelDetails/components/members/ChannelAllMembersModal.js.map +1 -1
- package/lib/commonjs/components/ChannelDetails/components/members/ChannelMemberList.js +124 -22
- package/lib/commonjs/components/ChannelDetails/components/members/ChannelMemberList.js.map +1 -1
- package/lib/commonjs/components/ChannelDetails/components/navigation-section/PinnedMessageItem.js +1 -1
- package/lib/commonjs/components/ChannelDetails/components/navigation-section/PinnedMessageItem.js.map +1 -1
- package/lib/commonjs/components/ChannelDetails/components/navigation-section/PinnedMessageList.js +1 -1
- package/lib/commonjs/components/ChannelDetails/components/navigation-section/PinnedMessageList.js.map +1 -1
- package/lib/commonjs/components/ChannelDetails/hooks/members/index.js +0 -11
- package/lib/commonjs/components/ChannelDetails/hooks/members/index.js.map +1 -1
- package/lib/commonjs/components/UIComponents/SearchInput.js +1 -3
- package/lib/commonjs/components/UIComponents/SearchInput.js.map +1 -1
- package/lib/commonjs/contexts/channelMemberListContext/ChannelMemberListContext.js +63 -0
- package/lib/commonjs/contexts/channelMemberListContext/ChannelMemberListContext.js.map +1 -0
- package/lib/commonjs/contexts/index.js +11 -0
- package/lib/commonjs/contexts/index.js.map +1 -1
- package/lib/commonjs/contexts/themeContext/utils/theme.js +8 -0
- package/lib/commonjs/contexts/themeContext/utils/theme.js.map +1 -1
- package/lib/commonjs/i18n/ar.json +3 -1
- package/lib/commonjs/i18n/en.json +2 -0
- package/lib/commonjs/i18n/es.json +3 -1
- package/lib/commonjs/i18n/fr.json +3 -1
- package/lib/commonjs/i18n/he.json +3 -1
- package/lib/commonjs/i18n/hi.json +3 -1
- package/lib/commonjs/i18n/it.json +3 -1
- package/lib/commonjs/i18n/ja.json +3 -1
- package/lib/commonjs/i18n/ko.json +3 -1
- package/lib/commonjs/i18n/nl.json +3 -1
- package/lib/commonjs/i18n/pt-br.json +3 -1
- package/lib/commonjs/i18n/ru.json +3 -1
- package/lib/commonjs/i18n/tr.json +3 -1
- package/lib/commonjs/version.json +1 -1
- package/lib/module/components/ChannelDetails/components/members/ChannelAddMembers.js +7 -4
- package/lib/module/components/ChannelDetails/components/members/ChannelAddMembers.js.map +1 -1
- package/lib/module/components/ChannelDetails/components/members/ChannelAllMembersModal.js +2 -23
- package/lib/module/components/ChannelDetails/components/members/ChannelAllMembersModal.js.map +1 -1
- package/lib/module/components/ChannelDetails/components/members/ChannelMemberList.js +124 -22
- package/lib/module/components/ChannelDetails/components/members/ChannelMemberList.js.map +1 -1
- package/lib/module/components/ChannelDetails/components/navigation-section/PinnedMessageItem.js +1 -1
- package/lib/module/components/ChannelDetails/components/navigation-section/PinnedMessageItem.js.map +1 -1
- package/lib/module/components/ChannelDetails/components/navigation-section/PinnedMessageList.js +1 -1
- package/lib/module/components/ChannelDetails/components/navigation-section/PinnedMessageList.js.map +1 -1
- package/lib/module/components/ChannelDetails/hooks/members/index.js +0 -11
- package/lib/module/components/ChannelDetails/hooks/members/index.js.map +1 -1
- package/lib/module/components/UIComponents/SearchInput.js +1 -3
- package/lib/module/components/UIComponents/SearchInput.js.map +1 -1
- package/lib/module/contexts/channelMemberListContext/ChannelMemberListContext.js +63 -0
- package/lib/module/contexts/channelMemberListContext/ChannelMemberListContext.js.map +1 -0
- package/lib/module/contexts/index.js +11 -0
- package/lib/module/contexts/index.js.map +1 -1
- package/lib/module/contexts/themeContext/utils/theme.js +8 -0
- package/lib/module/contexts/themeContext/utils/theme.js.map +1 -1
- package/lib/module/i18n/ar.json +3 -1
- package/lib/module/i18n/en.json +2 -0
- package/lib/module/i18n/es.json +3 -1
- package/lib/module/i18n/fr.json +3 -1
- package/lib/module/i18n/he.json +3 -1
- package/lib/module/i18n/hi.json +3 -1
- package/lib/module/i18n/it.json +3 -1
- package/lib/module/i18n/ja.json +3 -1
- package/lib/module/i18n/ko.json +3 -1
- package/lib/module/i18n/nl.json +3 -1
- package/lib/module/i18n/pt-br.json +3 -1
- package/lib/module/i18n/ru.json +3 -1
- package/lib/module/i18n/tr.json +3 -1
- package/lib/module/version.json +1 -1
- package/lib/typescript/components/ChannelDetails/components/members/ChannelAddMembers.d.ts.map +1 -1
- package/lib/typescript/components/ChannelDetails/components/members/ChannelAllMembersModal.d.ts +1 -1
- package/lib/typescript/components/ChannelDetails/components/members/ChannelAllMembersModal.d.ts.map +1 -1
- package/lib/typescript/components/ChannelDetails/components/members/ChannelMemberList.d.ts +4 -2
- package/lib/typescript/components/ChannelDetails/components/members/ChannelMemberList.d.ts.map +1 -1
- package/lib/typescript/components/ChannelDetails/hooks/members/index.d.ts +0 -1
- package/lib/typescript/components/ChannelDetails/hooks/members/index.d.ts.map +1 -1
- package/lib/typescript/contexts/channelMemberListContext/ChannelMemberListContext.d.ts +21 -0
- package/lib/typescript/contexts/channelMemberListContext/ChannelMemberListContext.d.ts.map +1 -0
- package/lib/typescript/contexts/componentsContext/defaultComponents.d.ts +1 -1
- package/lib/typescript/contexts/index.d.ts +1 -0
- package/lib/typescript/contexts/index.d.ts.map +1 -1
- package/lib/typescript/contexts/themeContext/ThemeContext.d.ts +8 -0
- package/lib/typescript/contexts/themeContext/ThemeContext.d.ts.map +1 -1
- package/lib/typescript/contexts/themeContext/utils/theme.d.ts +8 -0
- package/lib/typescript/contexts/themeContext/utils/theme.d.ts.map +1 -1
- package/lib/typescript/i18n/ar.json +3 -1
- package/lib/typescript/i18n/en.json +2 -0
- package/lib/typescript/i18n/es.json +3 -1
- package/lib/typescript/i18n/fr.json +3 -1
- package/lib/typescript/i18n/he.json +3 -1
- package/lib/typescript/i18n/hi.json +3 -1
- package/lib/typescript/i18n/it.json +3 -1
- package/lib/typescript/i18n/ja.json +3 -1
- package/lib/typescript/i18n/ko.json +3 -1
- package/lib/typescript/i18n/nl.json +3 -1
- package/lib/typescript/i18n/pt-br.json +3 -1
- package/lib/typescript/i18n/ru.json +3 -1
- package/lib/typescript/i18n/tr.json +3 -1
- package/lib/typescript/utils/i18n/Streami18n.d.ts +2 -0
- package/lib/typescript/utils/i18n/Streami18n.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/ChannelDetails/__tests__/members/ChannelMemberList.test.tsx +265 -151
- package/src/components/ChannelDetails/components/members/ChannelAddMembers.tsx +10 -4
- package/src/components/ChannelDetails/components/members/ChannelAllMembersModal.tsx +4 -25
- package/src/components/ChannelDetails/components/members/ChannelMemberList.tsx +137 -23
- package/src/components/ChannelDetails/components/navigation-section/PinnedMessageItem.tsx +1 -1
- package/src/components/ChannelDetails/components/navigation-section/PinnedMessageList.tsx +1 -1
- package/src/components/ChannelDetails/hooks/members/index.ts +0 -1
- package/src/components/UIComponents/SearchInput.tsx +1 -3
- package/src/contexts/channelMemberListContext/ChannelMemberListContext.tsx +72 -0
- package/src/contexts/index.ts +1 -0
- package/src/contexts/themeContext/utils/theme.ts +16 -0
- package/src/i18n/ar.json +3 -1
- package/src/i18n/en.json +2 -0
- package/src/i18n/es.json +3 -1
- package/src/i18n/fr.json +3 -1
- package/src/i18n/he.json +3 -1
- package/src/i18n/hi.json +3 -1
- package/src/i18n/it.json +3 -1
- package/src/i18n/ja.json +3 -1
- package/src/i18n/ko.json +3 -1
- package/src/i18n/nl.json +3 -1
- package/src/i18n/pt-br.json +3 -1
- package/src/i18n/ru.json +3 -1
- package/src/i18n/tr.json +3 -1
- package/src/version.json +1 -1
- package/lib/commonjs/components/ChannelDetails/hooks/members/useChannelAllMembers.js +0 -139
- package/lib/commonjs/components/ChannelDetails/hooks/members/useChannelAllMembers.js.map +0 -1
- package/lib/module/components/ChannelDetails/hooks/members/useChannelAllMembers.js +0 -139
- package/lib/module/components/ChannelDetails/hooks/members/useChannelAllMembers.js.map +0 -1
- package/lib/typescript/components/ChannelDetails/hooks/members/useChannelAllMembers.d.ts +0 -14
- package/lib/typescript/components/ChannelDetails/hooks/members/useChannelAllMembers.d.ts.map +0 -1
- package/src/components/ChannelDetails/__tests__/members/useChannelAllMembers.test.tsx +0 -234
- package/src/components/ChannelDetails/hooks/members/useChannelAllMembers.ts +0 -131
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { ActivityIndicator, type FlatListProps as RNFlatListProps, Text } from 'react-native';
|
|
3
2
|
|
|
4
|
-
import { act, render } from '@testing-library/react-native';
|
|
5
|
-
import
|
|
3
|
+
import { act, fireEvent, render, screen } from '@testing-library/react-native';
|
|
4
|
+
import { StateStore } from 'stream-chat';
|
|
5
|
+
import type { ChannelMemberResponse, SearchSourceState } from 'stream-chat';
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
|
|
7
|
+
import {
|
|
8
|
+
ChannelDetailsContextProvider,
|
|
9
|
+
type ChannelDetailsContextValue,
|
|
10
|
+
} from '../../../../contexts/channelDetailsContext/channelDetailsContext';
|
|
9
11
|
import { WithComponents } from '../../../../contexts/componentsContext/ComponentsContext';
|
|
10
12
|
import { ThemeProvider } from '../../../../contexts/themeContext/ThemeContext';
|
|
11
13
|
import { defaultTheme } from '../../../../contexts/themeContext/utils/theme';
|
|
@@ -15,72 +17,141 @@ import { generateUser } from '../../../../mock-builders/generator/user';
|
|
|
15
17
|
import type { ChannelMemberActionsSheetProps } from '../../components/members/ChannelMemberActionsSheet';
|
|
16
18
|
import type { ChannelMemberItemProps } from '../../components/members/ChannelMemberItem';
|
|
17
19
|
import { ChannelMemberList } from '../../components/members/ChannelMemberList';
|
|
18
|
-
import { useChannelAllMembers } from '../../hooks/members/useChannelAllMembers';
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
const mockItemProbe: ChannelMemberItemProps[] = [];
|
|
22
|
+
const mockSheetProbe: ChannelMemberActionsSheetProps[] = [];
|
|
21
23
|
|
|
22
|
-
const
|
|
24
|
+
const mockChannel = { cid: 'messaging:1' };
|
|
25
|
+
let mockCurrentSearchSource: FakeSearchSource;
|
|
26
|
+
const mockProviderProbe: { channel: unknown }[] = [];
|
|
27
|
+
const mockNotificationTargetProbe: { hostId?: string; panel?: string }[] = [];
|
|
28
|
+
const mockAddNotification = jest.fn();
|
|
23
29
|
|
|
24
|
-
jest.mock('
|
|
25
|
-
|
|
30
|
+
jest.mock('../../../Notifications/hooks/useNotificationApi', () => ({
|
|
31
|
+
useNotificationApi: () => ({ addNotification: mockAddNotification }),
|
|
26
32
|
}));
|
|
27
33
|
|
|
28
|
-
jest.mock('
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
jest.mock('../../../Notifications/NotificationTargetContext', () => ({
|
|
35
|
+
NotificationTargetProvider: ({
|
|
36
|
+
children,
|
|
37
|
+
hostId,
|
|
38
|
+
panel,
|
|
39
|
+
}: {
|
|
40
|
+
children: React.ReactNode;
|
|
41
|
+
hostId?: string;
|
|
42
|
+
panel?: string;
|
|
43
|
+
}) => {
|
|
44
|
+
mockNotificationTargetProbe.push({ hostId, panel });
|
|
45
|
+
return children;
|
|
46
|
+
},
|
|
47
|
+
}));
|
|
36
48
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
});
|
|
49
|
+
jest.mock('../../../Notifications/NotificationList', () => {
|
|
50
|
+
const ReactLib = require('react');
|
|
51
|
+
const { View } = require('react-native');
|
|
52
|
+
return {
|
|
53
|
+
NotificationList: () => ReactLib.createElement(View, { testID: 'notification-list' }),
|
|
54
|
+
};
|
|
40
55
|
});
|
|
41
56
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
57
|
+
jest.mock('../../../../contexts/channelMemberListContext/ChannelMemberListContext', () => ({
|
|
58
|
+
ChannelMemberListProvider: ({
|
|
59
|
+
channel: providedChannel,
|
|
60
|
+
children,
|
|
61
|
+
}: {
|
|
62
|
+
channel: unknown;
|
|
63
|
+
children: React.ReactNode;
|
|
64
|
+
}) => {
|
|
65
|
+
mockProviderProbe.push({ channel: providedChannel });
|
|
66
|
+
return children;
|
|
67
|
+
},
|
|
68
|
+
useChannelMemberListContext: () => ({
|
|
69
|
+
channel: mockChannel,
|
|
70
|
+
searchSource: mockCurrentSearchSource,
|
|
71
|
+
}),
|
|
72
|
+
}));
|
|
48
73
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
74
|
+
jest.mock('../../../UIComponents/SearchInput', () => {
|
|
75
|
+
const ReactLib = require('react');
|
|
76
|
+
const { Text } = require('react-native');
|
|
77
|
+
return {
|
|
78
|
+
SearchInput: ({ onChangeText }: { onChangeText: (t: string) => void }) =>
|
|
79
|
+
ReactLib.createElement(
|
|
80
|
+
ReactLib.Fragment,
|
|
81
|
+
null,
|
|
82
|
+
ReactLib.createElement(
|
|
83
|
+
Text,
|
|
84
|
+
{ onPress: () => onChangeText('query'), testID: 'search-change' },
|
|
85
|
+
'change',
|
|
86
|
+
),
|
|
87
|
+
ReactLib.createElement(
|
|
88
|
+
Text,
|
|
89
|
+
{ onPress: () => onChangeText(''), testID: 'search-clear' },
|
|
90
|
+
'clear',
|
|
91
|
+
),
|
|
92
|
+
),
|
|
93
|
+
};
|
|
54
94
|
});
|
|
55
95
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
96
|
+
type FakeSearchSource = {
|
|
97
|
+
search: jest.Mock;
|
|
98
|
+
state: StateStore<
|
|
99
|
+
Pick<
|
|
100
|
+
SearchSourceState<ChannelMemberResponse>,
|
|
101
|
+
'hasNext' | 'isLoading' | 'items' | 'lastQueryError' | 'searchQuery'
|
|
102
|
+
>
|
|
103
|
+
> & { partialNext: jest.Mock };
|
|
60
104
|
};
|
|
61
105
|
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
106
|
+
const makeSearchSource = (
|
|
107
|
+
overrides: Partial<{
|
|
108
|
+
hasNext: boolean;
|
|
109
|
+
isLoading: boolean;
|
|
110
|
+
items: ChannelMemberResponse[];
|
|
111
|
+
lastQueryError: Error;
|
|
112
|
+
searchQuery: string;
|
|
113
|
+
}> = {},
|
|
114
|
+
): FakeSearchSource => {
|
|
115
|
+
const state = new StateStore({
|
|
116
|
+
hasNext: overrides.hasNext ?? false,
|
|
117
|
+
isLoading: overrides.isLoading ?? false,
|
|
118
|
+
items: overrides.items,
|
|
119
|
+
searchQuery: overrides.searchQuery ?? '',
|
|
120
|
+
...(overrides.lastQueryError ? { lastQueryError: overrides.lastQueryError } : {}),
|
|
121
|
+
});
|
|
122
|
+
// The component calls state.partialNext on search input change; spy on it.
|
|
123
|
+
jest.spyOn(state, 'partialNext');
|
|
124
|
+
return {
|
|
125
|
+
search: jest.fn(),
|
|
126
|
+
state: state as FakeSearchSource['state'],
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const MemberItemProbe = (props: ChannelMemberItemProps) => {
|
|
131
|
+
const { Text } = require('react-native');
|
|
132
|
+
mockItemProbe.push(props);
|
|
133
|
+
return (
|
|
134
|
+
<Text onPress={() => props.onPress?.(props.member)} testID={`member-${props.member.user?.id}`}>
|
|
135
|
+
{props.member.user?.id}
|
|
136
|
+
</Text>
|
|
137
|
+
);
|
|
66
138
|
};
|
|
67
139
|
|
|
68
|
-
const sheetProbeCalls: ChannelMemberActionsSheetProps[] = [];
|
|
69
140
|
const MemberActionsSheetProbe = (props: ChannelMemberActionsSheetProps) => {
|
|
70
|
-
|
|
141
|
+
const { Text } = require('react-native');
|
|
142
|
+
mockSheetProbe.push(props);
|
|
71
143
|
return <Text testID='member-actions-sheet-probe'>{props.member.user?.id ?? ''}</Text>;
|
|
72
144
|
};
|
|
73
145
|
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
render(
|
|
146
|
+
const tree = (
|
|
147
|
+
searchSource: FakeSearchSource,
|
|
148
|
+
props: {
|
|
149
|
+
additionalFlatListProps?: object;
|
|
150
|
+
onMemberPress?: (member: ChannelMemberResponse) => void;
|
|
151
|
+
} = {},
|
|
152
|
+
) => {
|
|
153
|
+
mockCurrentSearchSource = searchSource;
|
|
154
|
+
return (
|
|
84
155
|
<ThemeProvider theme={defaultTheme}>
|
|
85
156
|
<TranslationProvider
|
|
86
157
|
value={{
|
|
@@ -89,161 +160,204 @@ const renderList = ({
|
|
|
89
160
|
userLanguage: 'en',
|
|
90
161
|
}}
|
|
91
162
|
>
|
|
92
|
-
<
|
|
163
|
+
<ChannelDetailsContextProvider
|
|
93
164
|
value={
|
|
94
165
|
{
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
userID: currentUserId,
|
|
99
|
-
},
|
|
100
|
-
} as never
|
|
166
|
+
channel: mockChannel,
|
|
167
|
+
onMemberPress: props.onMemberPress,
|
|
168
|
+
} as unknown as ChannelDetailsContextValue
|
|
101
169
|
}
|
|
102
170
|
>
|
|
103
|
-
<
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
</ChannelDetailsContextProvider>
|
|
113
|
-
</ChatContext.Provider>
|
|
171
|
+
<WithComponents
|
|
172
|
+
overrides={{
|
|
173
|
+
ChannelMemberActionsSheet: MemberActionsSheetProbe,
|
|
174
|
+
ChannelMemberItem: MemberItemProbe,
|
|
175
|
+
}}
|
|
176
|
+
>
|
|
177
|
+
<ChannelMemberList additionalFlatListProps={props.additionalFlatListProps as never} />
|
|
178
|
+
</WithComponents>
|
|
179
|
+
</ChannelDetailsContextProvider>
|
|
114
180
|
</TranslationProvider>
|
|
115
|
-
</ThemeProvider
|
|
181
|
+
</ThemeProvider>
|
|
116
182
|
);
|
|
117
|
-
|
|
118
|
-
const latestListProps = () => {
|
|
119
|
-
const calls = mockFlatList.mock.calls;
|
|
120
|
-
return calls[calls.length - 1]?.[0];
|
|
121
183
|
};
|
|
122
184
|
|
|
123
185
|
describe('ChannelMemberList', () => {
|
|
124
186
|
beforeEach(() => {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
187
|
+
mockItemProbe.length = 0;
|
|
188
|
+
mockSheetProbe.length = 0;
|
|
189
|
+
mockProviderProbe.length = 0;
|
|
190
|
+
mockNotificationTargetProbe.length = 0;
|
|
129
191
|
});
|
|
130
192
|
|
|
131
193
|
afterEach(() => jest.clearAllMocks());
|
|
132
194
|
|
|
133
|
-
it('
|
|
134
|
-
|
|
195
|
+
it('wraps its content in the member list provider for the channel', () => {
|
|
196
|
+
render(tree(makeSearchSource()));
|
|
197
|
+
|
|
198
|
+
expect(mockProviderProbe).toHaveLength(1);
|
|
199
|
+
expect(mockProviderProbe[0].channel).toBe(mockChannel);
|
|
200
|
+
});
|
|
135
201
|
|
|
136
|
-
|
|
202
|
+
it('calls search when the component is created', () => {
|
|
203
|
+
const searchSource = makeSearchSource();
|
|
204
|
+
render(tree(searchSource));
|
|
137
205
|
|
|
138
|
-
expect(
|
|
139
|
-
expect(
|
|
206
|
+
expect(searchSource.search).toHaveBeenCalledTimes(1);
|
|
207
|
+
expect(searchSource.search).toHaveBeenCalledWith();
|
|
140
208
|
});
|
|
141
209
|
|
|
142
|
-
it('
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
results: [generateMember({ user: generateUser({ id: 'alice' }) })],
|
|
210
|
+
it('wires the search input to the search source callbacks', () => {
|
|
211
|
+
const searchSource = makeSearchSource({
|
|
212
|
+
items: [generateMember({ user: generateUser({ id: 'alice' }) })],
|
|
146
213
|
});
|
|
214
|
+
render(tree(searchSource));
|
|
215
|
+
searchSource.search.mockClear();
|
|
147
216
|
|
|
148
|
-
|
|
217
|
+
fireEvent.press(screen.getByTestId('search-change'));
|
|
218
|
+
expect(searchSource.state.partialNext).toHaveBeenCalledWith({ searchQuery: 'query' });
|
|
219
|
+
expect(searchSource.search).toHaveBeenCalledWith('query');
|
|
149
220
|
|
|
150
|
-
|
|
151
|
-
expect(
|
|
221
|
+
fireEvent.press(screen.getByTestId('search-clear'));
|
|
222
|
+
expect(searchSource.search).toHaveBeenCalledWith('');
|
|
152
223
|
});
|
|
153
224
|
|
|
154
|
-
it('
|
|
225
|
+
it('renders a row per member and forwards a stable keyExtractor', () => {
|
|
155
226
|
const alice = generateMember({ user: generateUser({ id: 'alice', name: 'Alice' }) });
|
|
156
227
|
const bob = generateMember({ user: generateUser({ id: 'bob', name: 'Bob' }) });
|
|
157
|
-
mockHook({ results: [alice, bob] });
|
|
158
228
|
|
|
159
|
-
|
|
229
|
+
render(tree(makeSearchSource({ items: [alice, bob] })));
|
|
160
230
|
|
|
161
|
-
|
|
162
|
-
expect(
|
|
163
|
-
|
|
164
|
-
'bob',
|
|
165
|
-
]);
|
|
166
|
-
expect(props?.keyExtractor?.(alice, 0)).toBe('alice');
|
|
231
|
+
expect(mockItemProbe.map((p) => p.member.user?.id)).toEqual(['alice', 'bob']);
|
|
232
|
+
expect(screen.getByTestId('member-alice')).toBeTruthy();
|
|
233
|
+
expect(screen.getByTestId('channel-member-list').props.keyExtractor(alice, 0)).toBe('alice');
|
|
167
234
|
});
|
|
168
235
|
|
|
169
|
-
it('
|
|
170
|
-
|
|
171
|
-
mockHook({ hasMore: true, loadMore, results: [] });
|
|
172
|
-
|
|
173
|
-
renderList();
|
|
236
|
+
it('shows the loading skeleton and keeps the search bar on the initial load', () => {
|
|
237
|
+
render(tree(makeSearchSource({ isLoading: true })));
|
|
174
238
|
|
|
175
|
-
|
|
176
|
-
expect(
|
|
177
|
-
expect(props?.onEndReached).toBe(loadMore);
|
|
239
|
+
expect(screen.getByTestId('member-list-loading-skeleton')).toBeTruthy();
|
|
240
|
+
expect(screen.getByTestId('search-change')).toBeTruthy();
|
|
178
241
|
});
|
|
179
242
|
|
|
180
|
-
it('
|
|
181
|
-
|
|
243
|
+
it('shows the empty search result and keeps the search bar when there are no results', () => {
|
|
244
|
+
render(tree(makeSearchSource({ isLoading: false, items: [] })));
|
|
182
245
|
|
|
183
|
-
|
|
246
|
+
expect(screen.getByTestId('empty-search-result')).toBeTruthy();
|
|
247
|
+
expect(screen.getByText('No members found')).toBeTruthy();
|
|
248
|
+
expect(screen.getByTestId('search-change')).toBeTruthy();
|
|
249
|
+
});
|
|
184
250
|
|
|
185
|
-
|
|
251
|
+
it('renders the loading-more indicator only while loading with existing results', () => {
|
|
252
|
+
render(
|
|
253
|
+
tree(
|
|
254
|
+
makeSearchSource({
|
|
255
|
+
isLoading: true,
|
|
256
|
+
items: [generateMember({ user: generateUser({ id: 'alice' }) })],
|
|
257
|
+
}),
|
|
258
|
+
),
|
|
259
|
+
);
|
|
260
|
+
expect(screen.UNSAFE_getByType(require('react-native').ActivityIndicator)).toBeTruthy();
|
|
186
261
|
});
|
|
187
262
|
|
|
188
|
-
it('
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
263
|
+
it('loads more via the search source when the list end is reached and there is a next page', () => {
|
|
264
|
+
const searchSource = makeSearchSource({
|
|
265
|
+
hasNext: true,
|
|
266
|
+
items: [generateMember({ user: generateUser({ id: 'alice' }) })],
|
|
267
|
+
});
|
|
268
|
+
render(tree(searchSource));
|
|
269
|
+
searchSource.search.mockClear();
|
|
195
270
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
expect(
|
|
271
|
+
const list = screen.getByTestId('channel-member-list');
|
|
272
|
+
expect(list.props.onEndReachedThreshold).toBe(0.2);
|
|
273
|
+
list.props.onEndReached();
|
|
274
|
+
expect(searchSource.search).toHaveBeenCalledTimes(1);
|
|
200
275
|
});
|
|
201
276
|
|
|
202
|
-
it('
|
|
203
|
-
|
|
277
|
+
it('does not load more when there is no next page', () => {
|
|
278
|
+
const searchSource = makeSearchSource({
|
|
279
|
+
hasNext: false,
|
|
280
|
+
items: [generateMember({ user: generateUser({ id: 'alice' }) })],
|
|
281
|
+
});
|
|
282
|
+
render(tree(searchSource));
|
|
283
|
+
searchSource.search.mockClear();
|
|
284
|
+
|
|
285
|
+
screen.getByTestId('channel-member-list').props.onEndReached();
|
|
286
|
+
expect(searchSource.search).not.toHaveBeenCalled();
|
|
287
|
+
});
|
|
204
288
|
|
|
205
|
-
|
|
289
|
+
it('forwards additionalFlatListProps to the underlying list', () => {
|
|
290
|
+
render(
|
|
291
|
+
tree(makeSearchSource({ items: [generateMember({ user: generateUser({ id: 'alice' }) })] }), {
|
|
292
|
+
additionalFlatListProps: { bounces: false, testID: 'custom-list' },
|
|
293
|
+
}),
|
|
294
|
+
);
|
|
206
295
|
|
|
207
|
-
const
|
|
208
|
-
expect(props
|
|
209
|
-
expect(
|
|
296
|
+
const list = screen.getByTestId('custom-list');
|
|
297
|
+
expect(list.props.bounces).toBe(false);
|
|
298
|
+
expect(screen.queryByTestId('channel-member-list')).toBeNull();
|
|
210
299
|
});
|
|
211
300
|
|
|
212
301
|
it('opens the per-member actions sheet on press when no onMemberPress override is provided, and closes it', () => {
|
|
213
302
|
const bob = generateMember({ user: generateUser({ id: 'bob', name: 'Bob' }) });
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
const list = renderList();
|
|
303
|
+
render(tree(makeSearchSource({ items: [bob] })));
|
|
217
304
|
|
|
218
|
-
|
|
219
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
220
|
-
render((renderItem as any)({ index: 0, item: bob, separators: {} as never }));
|
|
221
|
-
const captured = itemProbeCalls.find((p) => p.member.user?.id === 'bob');
|
|
305
|
+
expect(screen.queryByTestId('member-actions-sheet-probe')).toBeNull();
|
|
222
306
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
expect(list.getByTestId('member-actions-sheet-probe').props.children).toBe('bob');
|
|
307
|
+
fireEvent.press(screen.getByTestId('member-bob'));
|
|
308
|
+
expect(screen.getByTestId('member-actions-sheet-probe').props.children).toBe('bob');
|
|
226
309
|
|
|
227
|
-
act(() =>
|
|
228
|
-
expect(
|
|
310
|
+
act(() => mockSheetProbe[mockSheetProbe.length - 1]?.onClose?.());
|
|
311
|
+
expect(screen.queryByTestId('member-actions-sheet-probe')).toBeNull();
|
|
229
312
|
});
|
|
230
313
|
|
|
231
314
|
it('calls onMemberPress instead of opening the sheet when an override is provided', () => {
|
|
232
315
|
const alice = generateMember({ user: generateUser({ id: 'alice', name: 'Alice' }) });
|
|
233
316
|
const onMemberPress = jest.fn();
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
const list = renderList({ onMemberPress });
|
|
317
|
+
render(tree(makeSearchSource({ items: [alice] }), { onMemberPress }));
|
|
237
318
|
|
|
238
|
-
|
|
239
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
240
|
-
render((renderItem as any)({ index: 0, item: alice, separators: {} as never }));
|
|
241
|
-
const captured = itemProbeCalls.find((p) => p.member.user?.id === 'alice');
|
|
242
|
-
|
|
243
|
-
act(() => captured?.onPress?.(alice));
|
|
319
|
+
fireEvent.press(screen.getByTestId('member-alice'));
|
|
244
320
|
|
|
245
321
|
expect(onMemberPress).toHaveBeenCalledTimes(1);
|
|
246
322
|
expect(onMemberPress.mock.calls[0][0].user?.id).toBe('alice');
|
|
247
|
-
expect(
|
|
323
|
+
expect(screen.queryByTestId('member-actions-sheet-probe')).toBeNull();
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it('targets the channel-details panel with a channel-scoped notification host', () => {
|
|
327
|
+
render(tree(makeSearchSource()));
|
|
328
|
+
|
|
329
|
+
expect(mockNotificationTargetProbe).toHaveLength(1);
|
|
330
|
+
expect(mockNotificationTargetProbe[0]).toEqual({
|
|
331
|
+
hostId: 'channel-member-list:messaging:1',
|
|
332
|
+
panel: 'channel-details',
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it('renders the notification list host', () => {
|
|
337
|
+
render(tree(makeSearchSource({ items: [] })));
|
|
338
|
+
|
|
339
|
+
expect(screen.getByTestId('notification-list')).toBeTruthy();
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it('adds an error notification when the search source reports a query error', () => {
|
|
343
|
+
const lastQueryError = new Error('boom');
|
|
344
|
+
render(tree(makeSearchSource({ lastQueryError })));
|
|
345
|
+
|
|
346
|
+
expect(mockAddNotification).toHaveBeenCalledTimes(1);
|
|
347
|
+
expect(mockAddNotification).toHaveBeenCalledWith({
|
|
348
|
+
message: 'Failed to load members',
|
|
349
|
+
options: {
|
|
350
|
+
originalError: lastQueryError,
|
|
351
|
+
severity: 'error',
|
|
352
|
+
type: 'api:channel:query-members:failed',
|
|
353
|
+
},
|
|
354
|
+
origin: { context: { channel: mockChannel }, emitter: 'ChannelMemberList' },
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it('does not add a notification when there is no query error', () => {
|
|
359
|
+
render(tree(makeSearchSource({ items: [] })));
|
|
360
|
+
|
|
361
|
+
expect(mockAddNotification).not.toHaveBeenCalled();
|
|
248
362
|
});
|
|
249
363
|
});
|
|
@@ -8,6 +8,7 @@ import { UserListLoadingSkeleton } from './UserListLoadingSkeleton';
|
|
|
8
8
|
|
|
9
9
|
import { useChannelAddMembersContext } from '../../../../contexts/channelAddMembersContext/ChannelAddMembersContext';
|
|
10
10
|
import { useChannelDetailsContext } from '../../../../contexts/channelDetailsContext/channelDetailsContext';
|
|
11
|
+
import { useTheme } from '../../../../contexts/themeContext/ThemeContext';
|
|
11
12
|
import { useTranslationContext } from '../../../../contexts/translationContext/TranslationContext';
|
|
12
13
|
import { getNotificationErrorOptions } from '../../../../hooks/actions/useChannelActions';
|
|
13
14
|
import { useStateStore } from '../../../../hooks/useStateStore';
|
|
@@ -43,6 +44,11 @@ const listStateSelector = (state: SearchSourceState<UserResponse>) => {
|
|
|
43
44
|
export const ChannelAddMembers = ({ additionalFlatListProps }: ChannelAddMembersProps) => {
|
|
44
45
|
const { t } = useTranslationContext();
|
|
45
46
|
const styles = useStyles();
|
|
47
|
+
const {
|
|
48
|
+
theme: {
|
|
49
|
+
channelDetails: { addMembers },
|
|
50
|
+
},
|
|
51
|
+
} = useTheme();
|
|
46
52
|
|
|
47
53
|
const { channel } = useChannelDetailsContext();
|
|
48
54
|
const { addNotification } = useNotificationApi();
|
|
@@ -103,14 +109,14 @@ export const ChannelAddMembers = ({ additionalFlatListProps }: ChannelAddMembers
|
|
|
103
109
|
const loadingMoreIndicator = <>{loading && users && users.length > 0 && <ActivityIndicator />}</>;
|
|
104
110
|
|
|
105
111
|
return (
|
|
106
|
-
<View style={styles.container}>
|
|
112
|
+
<View style={[styles.container, addMembers.container]}>
|
|
107
113
|
<SearchInput
|
|
108
114
|
accessibilityLabel={t('a11y/Search users to add')}
|
|
109
115
|
onChangeText={(text) => searchSource.search(text)}
|
|
110
116
|
/>
|
|
111
117
|
|
|
112
118
|
<FlatList
|
|
113
|
-
contentContainerStyle={styles.listContent}
|
|
119
|
+
contentContainerStyle={[styles.listContent, addMembers.listContent]}
|
|
114
120
|
data={users}
|
|
115
121
|
keyboardDismissMode='interactive'
|
|
116
122
|
keyboardShouldPersistTaps='handled'
|
|
@@ -120,7 +126,7 @@ export const ChannelAddMembers = ({ additionalFlatListProps }: ChannelAddMembers
|
|
|
120
126
|
onEndReached={loadMore}
|
|
121
127
|
onEndReachedThreshold={0.2}
|
|
122
128
|
renderItem={renderItem}
|
|
123
|
-
style={styles.list}
|
|
129
|
+
style={[styles.list, addMembers.list]}
|
|
124
130
|
testID='channel-add-members-list'
|
|
125
131
|
{...additionalFlatListProps}
|
|
126
132
|
/>
|
|
@@ -140,7 +146,7 @@ const useStyles = () => {
|
|
|
140
146
|
},
|
|
141
147
|
listContent: {
|
|
142
148
|
flexGrow: 1,
|
|
143
|
-
paddingBottom: primitives.
|
|
149
|
+
paddingBottom: primitives.spacing3xl,
|
|
144
150
|
},
|
|
145
151
|
}),
|
|
146
152
|
[],
|
|
@@ -6,8 +6,6 @@ import { useTranslationContext } from '../../../../contexts/translationContext/T
|
|
|
6
6
|
import { useChannelMemberCount } from '../../../../hooks';
|
|
7
7
|
import { useChannelOwnCapabilities } from '../../../../hooks/useChannelOwnCapabilities';
|
|
8
8
|
import { UserAdd } from '../../../../icons/user-add';
|
|
9
|
-
import { NotificationList } from '../../../Notifications/NotificationList';
|
|
10
|
-
import { NotificationTargetProvider } from '../../../Notifications/NotificationTargetContext';
|
|
11
9
|
import { Button } from '../../../ui/Button/Button';
|
|
12
10
|
import { ChannelDetailsModal } from '../modal/Modal';
|
|
13
11
|
import { ModalHeader } from '../modal/ModalHeader';
|
|
@@ -20,7 +18,10 @@ export type ChannelAllMembersModalProps = {
|
|
|
20
18
|
|
|
21
19
|
type ChannelAllMembersModalContentProps = Omit<ChannelAllMembersModalProps, 'visible'>;
|
|
22
20
|
|
|
23
|
-
|
|
21
|
+
/**
|
|
22
|
+
* @experimental This component is experimental and is subject to change.
|
|
23
|
+
*/
|
|
24
|
+
export const ChannelAllMembersModalContent = ({
|
|
24
25
|
onAddMembersPress,
|
|
25
26
|
onClose,
|
|
26
27
|
}: ChannelAllMembersModalContentProps) => {
|
|
@@ -52,32 +53,10 @@ const ChannelAllMembersModalBody = ({
|
|
|
52
53
|
title={t('{{count}} members', { count: total })}
|
|
53
54
|
/>
|
|
54
55
|
<ChannelMemberList />
|
|
55
|
-
<NotificationList />
|
|
56
56
|
</>
|
|
57
57
|
);
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
-
/**
|
|
61
|
-
* @experimental This component is experimental and is subject to change.
|
|
62
|
-
*/
|
|
63
|
-
export const ChannelAllMembersModalContent = ({
|
|
64
|
-
onAddMembersPress,
|
|
65
|
-
onClose,
|
|
66
|
-
}: ChannelAllMembersModalContentProps) => {
|
|
67
|
-
const { channel } = useChannelDetailsContext();
|
|
68
|
-
const notificationHostId = channel?.cid ? `channel-member-list:${channel.cid}` : undefined;
|
|
69
|
-
|
|
70
|
-
if (!notificationHostId) {
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return (
|
|
75
|
-
<NotificationTargetProvider hostId={notificationHostId} panel='channel-details'>
|
|
76
|
-
<ChannelAllMembersModalBody onAddMembersPress={onAddMembersPress} onClose={onClose} />
|
|
77
|
-
</NotificationTargetProvider>
|
|
78
|
-
);
|
|
79
|
-
};
|
|
80
|
-
|
|
81
60
|
/**
|
|
82
61
|
* @experimental This component is experimental and is subject to change.
|
|
83
62
|
*/
|