@planningcenter/chat-react-native 4.4.1-qa-staging.17 → 4.4.1-qa-staging.19
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/build/components/conversations/archived_results_footer.d.ts +6 -0
- package/build/components/conversations/archived_results_footer.d.ts.map +1 -0
- package/build/components/conversations/archived_results_footer.js +51 -0
- package/build/components/conversations/archived_results_footer.js.map +1 -0
- package/build/components/conversations/conversation_actions.d.ts.map +1 -1
- package/build/components/conversations/conversation_actions.js +241 -206
- package/build/components/conversations/conversation_actions.js.map +1 -1
- package/build/components/conversations/conversation_preview.d.ts.map +1 -1
- package/build/components/conversations/conversation_preview.js +99 -93
- package/build/components/conversations/conversation_preview.js.map +1 -1
- package/build/components/conversations/conversations.d.ts.map +1 -1
- package/build/components/conversations/conversations.js +44 -35
- package/build/components/conversations/conversations.js.map +1 -1
- package/build/components/display/filter_dropdown.d.ts +2 -1
- package/build/components/display/filter_dropdown.d.ts.map +1 -1
- package/build/components/display/filter_dropdown.js +63 -58
- package/build/components/display/filter_dropdown.js.map +1 -1
- package/build/components/display/icon_tile.d.ts +12 -0
- package/build/components/display/icon_tile.d.ts.map +1 -0
- package/build/components/display/icon_tile.js +109 -0
- package/build/components/display/icon_tile.js.map +1 -0
- package/build/components/display/index.d.ts +1 -0
- package/build/components/display/index.d.ts.map +1 -1
- package/build/components/display/index.js +1 -0
- package/build/components/display/index.js.map +1 -1
- package/build/contexts/conversations_context.d.ts.map +1 -1
- package/build/contexts/conversations_context.js +21 -18
- package/build/contexts/conversations_context.js.map +1 -1
- package/build/hooks/index.d.ts +2 -0
- package/build/hooks/index.d.ts.map +1 -1
- package/build/hooks/index.js +2 -0
- package/build/hooks/index.js.map +1 -1
- package/build/hooks/use_can_browse_archived_conversations.d.ts +2 -0
- package/build/hooks/use_can_browse_archived_conversations.d.ts.map +1 -0
- package/build/hooks/use_can_browse_archived_conversations.js +29 -0
- package/build/hooks/use_can_browse_archived_conversations.js.map +1 -0
- package/build/hooks/use_clear_conversation_filters.d.ts.map +1 -1
- package/build/hooks/use_clear_conversation_filters.js +2 -1
- package/build/hooks/use_clear_conversation_filters.js.map +1 -1
- package/build/hooks/use_conversation.d.ts.map +1 -1
- package/build/hooks/use_conversation.js +32 -24
- package/build/hooks/use_conversation.js.map +1 -1
- package/build/hooks/use_current_person.d.ts.map +1 -1
- package/build/hooks/use_current_person.js +1 -1
- package/build/hooks/use_current_person.js.map +1 -1
- package/build/hooks/use_jolt.d.ts.map +1 -1
- package/build/hooks/use_jolt.js +29 -8
- package/build/hooks/use_jolt.js.map +1 -1
- package/build/hooks/use_manual_archiving_enabled.d.ts +2 -0
- package/build/hooks/use_manual_archiving_enabled.d.ts.map +1 -0
- package/build/hooks/use_manual_archiving_enabled.js +18 -0
- package/build/hooks/use_manual_archiving_enabled.js.map +1 -0
- package/build/screens/conversation_details_screen.d.ts.map +1 -1
- package/build/screens/conversation_details_screen.js +2 -1
- package/build/screens/conversation_details_screen.js.map +1 -1
- package/build/screens/conversation_filters/components/conversation_filters.d.ts.map +1 -1
- package/build/screens/conversation_filters/components/conversation_filters.js +80 -13
- package/build/screens/conversation_filters/components/conversation_filters.js.map +1 -1
- package/build/screens/conversation_filters/components/rows.d.ts +9 -0
- package/build/screens/conversation_filters/components/rows.d.ts.map +1 -1
- package/build/screens/conversation_filters/components/rows.js +92 -1
- package/build/screens/conversation_filters/components/rows.js.map +1 -1
- package/build/screens/conversation_filters/context/conversation_filter_context.d.ts.map +1 -1
- package/build/screens/conversation_filters/context/conversation_filter_context.js +8 -4
- package/build/screens/conversation_filters/context/conversation_filter_context.js.map +1 -1
- package/build/screens/conversation_filters/filter_types.d.ts +1 -0
- package/build/screens/conversation_filters/filter_types.d.ts.map +1 -1
- package/build/screens/conversation_filters/filter_types.js +1 -0
- package/build/screens/conversation_filters/filter_types.js.map +1 -1
- package/build/screens/conversation_filters/screen_props.d.ts +1 -0
- package/build/screens/conversation_filters/screen_props.d.ts.map +1 -1
- package/build/screens/conversation_filters/screen_props.js.map +1 -1
- package/build/screens/conversation_filters_screen.d.ts +1 -0
- package/build/screens/conversation_filters_screen.d.ts.map +1 -1
- package/build/screens/conversation_filters_screen.js +75 -49
- package/build/screens/conversation_filters_screen.js.map +1 -1
- package/build/screens/conversations/components/filter_dropdown_list_header_component.d.ts +2 -0
- package/build/screens/conversations/components/filter_dropdown_list_header_component.d.ts.map +1 -0
- package/build/screens/conversations/components/filter_dropdown_list_header_component.js +580 -0
- package/build/screens/conversations/components/filter_dropdown_list_header_component.js.map +1 -0
- package/build/screens/conversations/components/list_header_component.d.ts.map +1 -1
- package/build/screens/conversations/components/list_header_component.js +95 -90
- package/build/screens/conversations/components/list_header_component.js.map +1 -1
- package/build/screens/conversations/components/search_list_header_component.d.ts.map +1 -1
- package/build/screens/conversations/components/search_list_header_component.js +100 -95
- package/build/screens/conversations/components/search_list_header_component.js.map +1 -1
- package/build/screens/conversations/conversations_screen.d.ts +1 -0
- package/build/screens/conversations/conversations_screen.d.ts.map +1 -1
- package/build/screens/conversations/conversations_screen.js +15 -2
- package/build/screens/conversations/conversations_screen.js.map +1 -1
- package/build/types/resources/person.d.ts +1 -0
- package/build/types/resources/person.d.ts.map +1 -1
- package/build/types/resources/person.js.map +1 -1
- package/build/utils/date.d.ts.map +1 -1
- package/build/utils/date.js +5 -10
- package/build/utils/date.js.map +1 -1
- package/build/utils/native_adapters/menu.d.ts +1 -0
- package/build/utils/native_adapters/menu.d.ts.map +1 -1
- package/build/utils/native_adapters/menu.js +1 -0
- package/build/utils/native_adapters/menu.js.map +1 -1
- package/build/utils/request/conversation.d.ts +2 -1
- package/build/utils/request/conversation.d.ts.map +1 -1
- package/build/utils/request/conversation.js +4 -1
- package/build/utils/request/conversation.js.map +1 -1
- package/build/vendor/font_awesome_pro_solid_icons.d.ts +1 -0
- package/build/vendor/font_awesome_pro_solid_icons.d.ts.map +1 -1
- package/build/vendor/font_awesome_pro_solid_icons.js +5 -0
- package/build/vendor/font_awesome_pro_solid_icons.js.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/hooks/use_can_browse_archived_conversations.test.tsx +98 -0
- package/src/__tests__/screens/conversation_filters/components/conversation_filters.test.tsx +3 -0
- package/src/__tests__/screens/conversation_filters_screen.test.tsx +76 -0
- package/src/components/conversations/__tests__/conversation_actions.test.tsx +67 -0
- package/src/components/conversations/__tests__/conversation_preview.test.tsx +38 -3
- package/src/components/conversations/__tests__/conversations.test.tsx +82 -0
- package/src/components/conversations/__tests__/conversations_blank_state.test.tsx +1 -0
- package/src/components/conversations/archived_results_footer.tsx +36 -0
- package/src/components/conversations/conversation_actions.tsx +27 -19
- package/src/components/conversations/conversation_preview.tsx +30 -10
- package/src/components/conversations/conversations.tsx +15 -3
- package/src/components/display/__tests__/filter_dropdown.test.tsx +9 -1
- package/src/components/display/filter_dropdown.tsx +14 -13
- package/src/components/display/icon_tile.tsx +54 -0
- package/src/components/display/index.ts +1 -0
- package/src/contexts/conversations_context.tsx +2 -1
- package/src/hooks/__tests__/use_conversation_archive.test.tsx +22 -0
- package/src/hooks/__tests__/use_jolt.test.tsx +30 -8
- package/src/hooks/index.ts +2 -0
- package/src/hooks/use_can_browse_archived_conversations.ts +17 -0
- package/src/hooks/use_clear_conversation_filters.ts +1 -0
- package/src/hooks/use_conversation.ts +4 -1
- package/src/hooks/use_current_person.ts +1 -0
- package/src/hooks/use_jolt.ts +33 -8
- package/src/hooks/use_manual_archiving_enabled.ts +10 -0
- package/src/screens/conversation_details_screen.tsx +2 -3
- package/src/screens/conversation_filters/__tests__/conversation_filters.test.tsx +101 -0
- package/src/screens/conversation_filters/components/conversation_filters.tsx +32 -2
- package/src/screens/conversation_filters/components/rows.tsx +29 -1
- package/src/screens/conversation_filters/context/conversation_filter_context.tsx +4 -0
- package/src/screens/conversation_filters/filter_types.ts +1 -0
- package/src/screens/conversation_filters/screen_props.ts +1 -0
- package/src/screens/conversation_filters_screen.tsx +20 -4
- package/src/screens/conversations/__tests__/conversations_screen.test.tsx +52 -0
- package/src/screens/conversations/components/__tests__/filter_dropdown_list_header_component.test.tsx +259 -0
- package/src/screens/conversations/components/__tests__/list_header_component.test.tsx +21 -4
- package/src/screens/conversations/components/filter_dropdown_list_header_component.tsx +286 -0
- package/src/screens/conversations/components/list_header_component.tsx +17 -12
- package/src/screens/conversations/components/search_list_header_component.tsx +17 -12
- package/src/screens/conversations/conversations_screen.tsx +15 -2
- package/src/types/resources/person.ts +1 -0
- package/src/utils/__tests__/conversation.test.ts +1 -0
- package/src/utils/__tests__/date.test.ts +4 -0
- package/src/utils/date.ts +5 -4
- package/src/utils/native_adapters/__tests__/menu.test.tsx +6 -2
- package/src/utils/native_adapters/menu.tsx +2 -0
- package/src/utils/request/conversation.ts +4 -0
- package/src/vendor/font_awesome_pro_solid_icons.ts +12 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { DefaultTheme, ThemeProvider } from '@react-navigation/native'
|
|
2
|
+
import { fireEvent, render, screen, waitFor } from '@testing-library/react-native'
|
|
3
|
+
import React from 'react'
|
|
4
|
+
import { Platform } from 'react-native'
|
|
5
|
+
import { SafeAreaProvider } from 'react-native-safe-area-context'
|
|
6
|
+
import { FilterContext } from '../../screens/conversation_filters/context/conversation_filter_context'
|
|
7
|
+
import { ConversationFiltersHeader } from '../../screens/conversation_filters_screen'
|
|
8
|
+
|
|
9
|
+
const mockGoBack = jest.fn()
|
|
10
|
+
|
|
11
|
+
jest.mock('@react-navigation/elements', () => ({
|
|
12
|
+
...jest.requireActual('@react-navigation/elements'),
|
|
13
|
+
useHeaderHeight: () => 0,
|
|
14
|
+
}))
|
|
15
|
+
jest.mock('@react-navigation/native', () => ({
|
|
16
|
+
...jest.requireActual('@react-navigation/native'),
|
|
17
|
+
useNavigation: () => ({ goBack: mockGoBack }),
|
|
18
|
+
useRoute: () => ({ params: {} }),
|
|
19
|
+
}))
|
|
20
|
+
jest.mock('../../hooks/use_filter_dropdown_enabled', () => ({
|
|
21
|
+
useFilterDropdownEnabled: () => true,
|
|
22
|
+
}))
|
|
23
|
+
|
|
24
|
+
const safeAreaMetrics = {
|
|
25
|
+
frame: { x: 0, y: 0, width: 390, height: 844 },
|
|
26
|
+
insets: { top: 0, right: 0, bottom: 0, left: 0 },
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const renderHeader = () =>
|
|
30
|
+
render(
|
|
31
|
+
<SafeAreaProvider initialMetrics={safeAreaMetrics}>
|
|
32
|
+
<ThemeProvider value={DefaultTheme}>
|
|
33
|
+
<FilterContext.Provider
|
|
34
|
+
value={{
|
|
35
|
+
params: {},
|
|
36
|
+
scrollOffset: 0,
|
|
37
|
+
setScrollOffset: jest.fn(),
|
|
38
|
+
setFormSheetHeaderHeight: jest.fn(),
|
|
39
|
+
resetFilter: jest.fn(),
|
|
40
|
+
setGroupFilter: jest.fn(),
|
|
41
|
+
setAppFilter: jest.fn(),
|
|
42
|
+
applyFilters: jest.fn(),
|
|
43
|
+
platformListPaddingBottom: 0,
|
|
44
|
+
}}
|
|
45
|
+
>
|
|
46
|
+
<ConversationFiltersHeader />
|
|
47
|
+
</FilterContext.Provider>
|
|
48
|
+
</ThemeProvider>
|
|
49
|
+
</SafeAreaProvider>
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
describe('ConversationFiltersHeader', () => {
|
|
53
|
+
afterEach(() => {
|
|
54
|
+
jest.restoreAllMocks()
|
|
55
|
+
mockGoBack.mockClear()
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('shows a back button that dismisses the sheet on Android', async () => {
|
|
59
|
+
jest.replaceProperty(Platform, 'OS', 'android')
|
|
60
|
+
|
|
61
|
+
renderHeader()
|
|
62
|
+
|
|
63
|
+
fireEvent.press(screen.getByLabelText('Back'))
|
|
64
|
+
|
|
65
|
+
await waitFor(() => expect(mockGoBack).toHaveBeenCalledTimes(1))
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('does not show a back button on iOS', () => {
|
|
69
|
+
jest.replaceProperty(Platform, 'OS', 'ios')
|
|
70
|
+
|
|
71
|
+
renderHeader()
|
|
72
|
+
|
|
73
|
+
expect(screen.getByText('More filters')).toBeTruthy()
|
|
74
|
+
expect(screen.queryByLabelText('Back')).toBeNull()
|
|
75
|
+
})
|
|
76
|
+
})
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { render } from '@testing-library/react-native'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import { Text } from 'react-native'
|
|
4
|
+
import type { ConversationResource } from '../../../types'
|
|
5
|
+
import { ConversationActions } from '../conversation_actions'
|
|
6
|
+
|
|
7
|
+
jest.mock('react-native-gesture-handler/ReanimatedSwipeable', () => {
|
|
8
|
+
const { View: MockView } = jest.requireActual('react-native')
|
|
9
|
+
return {
|
|
10
|
+
__esModule: true,
|
|
11
|
+
default: ({
|
|
12
|
+
children,
|
|
13
|
+
renderLeftActions,
|
|
14
|
+
}: {
|
|
15
|
+
children: React.ReactNode
|
|
16
|
+
renderLeftActions: () => React.ReactNode
|
|
17
|
+
}) => (
|
|
18
|
+
<MockView>
|
|
19
|
+
{renderLeftActions()}
|
|
20
|
+
{children}
|
|
21
|
+
</MockView>
|
|
22
|
+
),
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
jest.mock('../../../hooks/use_conversations_actions', () => ({
|
|
26
|
+
useConversationsMute: () => ({ muted: false, setMuted: jest.fn(), isPending: false }),
|
|
27
|
+
useConversationsMarkRead: () => ({ read: false, markRead: jest.fn(), isPending: false }),
|
|
28
|
+
}))
|
|
29
|
+
|
|
30
|
+
const createdAt = '2026-10-01T12:00:00Z'
|
|
31
|
+
|
|
32
|
+
const conversation = (overrides: Partial<ConversationResource>): ConversationResource => ({
|
|
33
|
+
type: 'Conversation',
|
|
34
|
+
id: 7,
|
|
35
|
+
title: 'Youth Group',
|
|
36
|
+
conversationPreview: null,
|
|
37
|
+
createdAt,
|
|
38
|
+
lastMessageCreatedAt: createdAt,
|
|
39
|
+
muted: false,
|
|
40
|
+
repliesDisabled: false,
|
|
41
|
+
unreadCount: 1,
|
|
42
|
+
unreadReaction: false,
|
|
43
|
+
updatedAt: createdAt,
|
|
44
|
+
...overrides,
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const renderActions = (overrides: Partial<ConversationResource>) =>
|
|
48
|
+
render(
|
|
49
|
+
<ConversationActions conversation={conversation(overrides)} onPress={jest.fn()}>
|
|
50
|
+
<Text>Youth Group</Text>
|
|
51
|
+
</ConversationActions>
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
describe('ConversationActions', () => {
|
|
55
|
+
it('offers mark read but not mute on an archived conversation', () => {
|
|
56
|
+
const view = renderActions({ disabled: true, disabledReason: 'archived' })
|
|
57
|
+
|
|
58
|
+
expect(view.getByText('Mark read')).toBeTruthy()
|
|
59
|
+
expect(view.queryByText('Silence')).toBeNull()
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('offers mute on a conversation disabled for another reason', () => {
|
|
63
|
+
const view = renderActions({ disabled: true, disabledReason: 'safety_check' })
|
|
64
|
+
|
|
65
|
+
expect(view.getByText('Silence')).toBeTruthy()
|
|
66
|
+
})
|
|
67
|
+
})
|
|
@@ -19,7 +19,10 @@ jest.mock('../conversation_actions', () => ({
|
|
|
19
19
|
|
|
20
20
|
const createdAt = '2026-03-04T15:30:00Z'
|
|
21
21
|
|
|
22
|
-
const conversation = (
|
|
22
|
+
const conversation = (
|
|
23
|
+
conversationPreview: ConversationPreviewData,
|
|
24
|
+
overrides: Partial<ConversationResource> = {}
|
|
25
|
+
): ConversationResource => ({
|
|
23
26
|
type: 'Conversation',
|
|
24
27
|
id: 7,
|
|
25
28
|
title: 'Worship Team',
|
|
@@ -31,10 +34,24 @@ const conversation = (conversationPreview: ConversationPreviewData): Conversatio
|
|
|
31
34
|
unreadCount: 0,
|
|
32
35
|
unreadReaction: false,
|
|
33
36
|
updatedAt: createdAt,
|
|
37
|
+
...overrides,
|
|
34
38
|
})
|
|
35
39
|
|
|
36
|
-
const
|
|
37
|
-
|
|
40
|
+
const messagePreview: ConversationPreviewData = {
|
|
41
|
+
kind: 'message',
|
|
42
|
+
authorName: 'Ada Lovelace',
|
|
43
|
+
value: null,
|
|
44
|
+
text: 'See you Sunday',
|
|
45
|
+
createdAt,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const renderPreview = (
|
|
49
|
+
preview: ConversationPreviewData,
|
|
50
|
+
overrides: Partial<ConversationResource> = {}
|
|
51
|
+
) =>
|
|
52
|
+
render(
|
|
53
|
+
<ConversationPreview conversation={conversation(preview, overrides)} onPress={jest.fn()} />
|
|
54
|
+
)
|
|
38
55
|
|
|
39
56
|
describe('ConversationPreview', () => {
|
|
40
57
|
it('prefixes message previews with the author', () => {
|
|
@@ -67,4 +84,22 @@ describe('ConversationPreview', () => {
|
|
|
67
84
|
)
|
|
68
85
|
).toBeTruthy()
|
|
69
86
|
})
|
|
87
|
+
|
|
88
|
+
it('marks an archived conversation with an archive icon instead of the disabled label', () => {
|
|
89
|
+
const view = renderPreview(messagePreview, { disabled: true, disabledReason: 'archived' })
|
|
90
|
+
|
|
91
|
+
expect(
|
|
92
|
+
view.getByTestId('conversation-archived-icon', { includeHiddenElements: true })
|
|
93
|
+
).toBeTruthy()
|
|
94
|
+
expect(view.getByLabelText(/^Select Worship Team, Archived,/)).toBeTruthy()
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('keeps the disabled label for a conversation disabled for another reason', () => {
|
|
98
|
+
const view = renderPreview(messagePreview, { disabled: true, disabledReason: 'safety_check' })
|
|
99
|
+
|
|
100
|
+
expect(
|
|
101
|
+
view.queryByTestId('conversation-archived-icon', { includeHiddenElements: true })
|
|
102
|
+
).toBeNull()
|
|
103
|
+
expect(view.getByLabelText(/^Select Worship Team, Disabled,/)).toBeTruthy()
|
|
104
|
+
})
|
|
70
105
|
})
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { fireEvent, render } from '@testing-library/react-native'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import { ConversationFiltersParams } from '../../../screens/conversation_filters/screen_props'
|
|
4
|
+
import { Conversations } from '../conversations'
|
|
5
|
+
|
|
6
|
+
const mockSetParams = jest.fn()
|
|
7
|
+
let mockArgs: ConversationFiltersParams = {}
|
|
8
|
+
let mockFilterDropdownEnabled = false
|
|
9
|
+
|
|
10
|
+
jest.mock('@react-navigation/native', () => ({
|
|
11
|
+
...jest.requireActual('@react-navigation/native'),
|
|
12
|
+
useNavigation: () => ({ setParams: mockSetParams, navigate: jest.fn() }),
|
|
13
|
+
}))
|
|
14
|
+
jest.mock('../../../contexts/conversations_context', () => ({
|
|
15
|
+
useConversationsContext: () => ({
|
|
16
|
+
conversations: [{ id: 1, title: 'Youth Group' }],
|
|
17
|
+
isLoading: false,
|
|
18
|
+
isFetched: true,
|
|
19
|
+
isRefetching: false,
|
|
20
|
+
isError: false,
|
|
21
|
+
error: null,
|
|
22
|
+
fetchNextPage: jest.fn(),
|
|
23
|
+
refetch: jest.fn(),
|
|
24
|
+
args: mockArgs,
|
|
25
|
+
}),
|
|
26
|
+
}))
|
|
27
|
+
jest.mock('../../../hooks/use_chat_permissions', () => ({
|
|
28
|
+
useCanCreateConversations: () => false,
|
|
29
|
+
}))
|
|
30
|
+
jest.mock('../../../hooks/use_filter_dropdown_enabled', () => ({
|
|
31
|
+
useFilterDropdownEnabled: () => mockFilterDropdownEnabled,
|
|
32
|
+
}))
|
|
33
|
+
jest.mock('../../../hooks/use_conversations_jolt_events', () => ({
|
|
34
|
+
useConversationsJoltEvents: () => {},
|
|
35
|
+
}))
|
|
36
|
+
jest.mock('../conversation_preview', () => ({
|
|
37
|
+
ConversationPreview: () => null,
|
|
38
|
+
ConversationPreviewSkeleton: () => null,
|
|
39
|
+
}))
|
|
40
|
+
jest.mock('../conversations_blank_state', () => ({
|
|
41
|
+
ConversationsBlankState: () => null,
|
|
42
|
+
}))
|
|
43
|
+
|
|
44
|
+
describe('Conversations archived footer', () => {
|
|
45
|
+
beforeEach(() => {
|
|
46
|
+
mockSetParams.mockClear()
|
|
47
|
+
mockFilterDropdownEnabled = false
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('ends the archived list with a way back to active conversations', () => {
|
|
51
|
+
mockArgs = { show_archived: true }
|
|
52
|
+
const view = render(<Conversations />)
|
|
53
|
+
|
|
54
|
+
expect(view.getByText('You’re viewing archived results.')).toBeTruthy()
|
|
55
|
+
|
|
56
|
+
fireEvent.press(view.getByLabelText('View active conversations'))
|
|
57
|
+
|
|
58
|
+
expect(mockSetParams).toHaveBeenCalledWith({
|
|
59
|
+
title: undefined,
|
|
60
|
+
chat_group_graph_id: undefined,
|
|
61
|
+
group_source_app_name: undefined,
|
|
62
|
+
show_direct_messages: undefined,
|
|
63
|
+
show_archived: undefined,
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('shows the archived footer instead of the filtered one when the dropdown is on', () => {
|
|
68
|
+
mockArgs = { show_archived: true }
|
|
69
|
+
mockFilterDropdownEnabled = true
|
|
70
|
+
const view = render(<Conversations />)
|
|
71
|
+
|
|
72
|
+
expect(view.getByLabelText('View active conversations')).toBeTruthy()
|
|
73
|
+
expect(view.queryByText('You’re viewing filtered results')).toBeNull()
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('leaves the active list without the archived footer', () => {
|
|
77
|
+
mockArgs = {}
|
|
78
|
+
const view = render(<Conversations />)
|
|
79
|
+
|
|
80
|
+
expect(view.queryByLabelText('View active conversations')).toBeNull()
|
|
81
|
+
})
|
|
82
|
+
})
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { StyleSheet, View } from 'react-native'
|
|
3
|
+
import { Text, TextInlineButton } from '../display'
|
|
4
|
+
|
|
5
|
+
interface ArchivedResultsFooterProps {
|
|
6
|
+
onViewActiveConversations: () => void
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function ArchivedResultsFooter({ onViewActiveConversations }: ArchivedResultsFooterProps) {
|
|
10
|
+
return (
|
|
11
|
+
<View style={styles.container}>
|
|
12
|
+
<Text variant="tertiary" style={styles.text}>
|
|
13
|
+
You’re viewing archived results.
|
|
14
|
+
</Text>
|
|
15
|
+
<TextInlineButton
|
|
16
|
+
variant="tertiary"
|
|
17
|
+
onPress={onViewActiveConversations}
|
|
18
|
+
accessibilityLabel="View active conversations"
|
|
19
|
+
>
|
|
20
|
+
View active conversations
|
|
21
|
+
</TextInlineButton>
|
|
22
|
+
</View>
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const styles = StyleSheet.create({
|
|
27
|
+
container: {
|
|
28
|
+
alignItems: 'center',
|
|
29
|
+
gap: 4,
|
|
30
|
+
paddingVertical: 24,
|
|
31
|
+
paddingHorizontal: 16,
|
|
32
|
+
},
|
|
33
|
+
text: {
|
|
34
|
+
textAlign: 'center',
|
|
35
|
+
},
|
|
36
|
+
})
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
useConversationsMute,
|
|
19
19
|
} from '../../hooks/use_conversations_actions'
|
|
20
20
|
import { ConversationResource } from '../../types'
|
|
21
|
-
import { platformPressedOpacityStyle } from '../../utils'
|
|
21
|
+
import { isArchivedConversation, platformPressedOpacityStyle } from '../../utils'
|
|
22
22
|
import { tokens } from '../../vendor/tapestry/tokens'
|
|
23
23
|
import { SwipeableToggleButton } from './swipeable_toggle_button'
|
|
24
24
|
|
|
@@ -53,6 +53,7 @@ export function ConversationActions({
|
|
|
53
53
|
})
|
|
54
54
|
|
|
55
55
|
const isConversationEmpty = conversation.lastMessageCreatedAt === null
|
|
56
|
+
const canMute = !isArchivedConversation(conversation)
|
|
56
57
|
|
|
57
58
|
const handleSwipeableClose = useCallback(() => {
|
|
58
59
|
setDisabled(false)
|
|
@@ -88,13 +89,16 @@ export function ConversationActions({
|
|
|
88
89
|
]
|
|
89
90
|
: []
|
|
90
91
|
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
92
|
+
const muteAccessibilityAction = canMute
|
|
93
|
+
? [
|
|
94
|
+
{
|
|
95
|
+
name: muted ? 'unmute' : 'mute',
|
|
96
|
+
label: muted ? 'Enable' : 'Silence',
|
|
97
|
+
},
|
|
98
|
+
]
|
|
99
|
+
: []
|
|
100
|
+
|
|
101
|
+
const accessibilityActions = [...muteAccessibilityAction, ...readAccessibilityAction]
|
|
98
102
|
|
|
99
103
|
const handleAccessibilityAction = useCallback(
|
|
100
104
|
(event: AccessibilityActionEvent) => {
|
|
@@ -136,6 +140,7 @@ export function ConversationActions({
|
|
|
136
140
|
renderLeftActions={() => (
|
|
137
141
|
<LeftActions
|
|
138
142
|
isConversationEmpty={isConversationEmpty}
|
|
143
|
+
canMute={canMute}
|
|
139
144
|
handleMute={handleMute}
|
|
140
145
|
handleLatestMessageUnread={handleLatestMessageUnread}
|
|
141
146
|
muted={muted}
|
|
@@ -163,6 +168,7 @@ export function ConversationActions({
|
|
|
163
168
|
|
|
164
169
|
interface LeftActionsProps {
|
|
165
170
|
isConversationEmpty: boolean
|
|
171
|
+
canMute: boolean
|
|
166
172
|
handleMute: () => void
|
|
167
173
|
handleLatestMessageUnread: () => void
|
|
168
174
|
muted: boolean
|
|
@@ -173,6 +179,7 @@ interface LeftActionsProps {
|
|
|
173
179
|
|
|
174
180
|
function LeftActions({
|
|
175
181
|
isConversationEmpty,
|
|
182
|
+
canMute,
|
|
176
183
|
handleMute,
|
|
177
184
|
handleLatestMessageUnread,
|
|
178
185
|
muted,
|
|
@@ -180,7 +187,7 @@ function LeftActions({
|
|
|
180
187
|
isMutedPending,
|
|
181
188
|
isMarkReadPending,
|
|
182
189
|
}: LeftActionsProps) {
|
|
183
|
-
const styles = useStyles()
|
|
190
|
+
const styles = useStyles({ actionButtonCount: canMute ? 2 : 1 })
|
|
184
191
|
|
|
185
192
|
const muteToggleContent = {
|
|
186
193
|
true: { iconName: 'general.bell', label: 'Enable' },
|
|
@@ -202,21 +209,22 @@ function LeftActions({
|
|
|
202
209
|
toggleContent={latestMessageUnreadToggleContent}
|
|
203
210
|
backgroundColor={tokens.fillColorInteractionSwipeDefault}
|
|
204
211
|
/>
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
+
{canMute && (
|
|
213
|
+
<SwipeableToggleButton
|
|
214
|
+
loading={isMutedPending}
|
|
215
|
+
toggled={muted}
|
|
216
|
+
onPress={handleMute}
|
|
217
|
+
toggleContent={muteToggleContent}
|
|
218
|
+
backgroundColor={tokens.fillColorInteractionSwipeSecondary}
|
|
219
|
+
/>
|
|
220
|
+
)}
|
|
212
221
|
</View>
|
|
213
222
|
)
|
|
214
223
|
}
|
|
215
224
|
|
|
216
225
|
const ACTION_BUTTON_WIDTH = 120
|
|
217
|
-
const ACTION_BUTTON_COUNT = 2
|
|
218
226
|
|
|
219
|
-
const useStyles = () => {
|
|
227
|
+
const useStyles = ({ actionButtonCount = 2 }: { actionButtonCount?: number } = {}) => {
|
|
220
228
|
const { colors } = useTheme()
|
|
221
229
|
return StyleSheet.create({
|
|
222
230
|
swipeableChildContainer: {
|
|
@@ -225,7 +233,7 @@ const useStyles = () => {
|
|
|
225
233
|
actionButtonContainer: {
|
|
226
234
|
flexDirection: 'row',
|
|
227
235
|
alignItems: 'center',
|
|
228
|
-
width: ACTION_BUTTON_WIDTH *
|
|
236
|
+
width: ACTION_BUTTON_WIDTH * actionButtonCount,
|
|
229
237
|
alignContent: 'stretch',
|
|
230
238
|
},
|
|
231
239
|
swipeableContainer: {
|
|
@@ -7,10 +7,19 @@ import {
|
|
|
7
7
|
useScalableNumberOfLines,
|
|
8
8
|
} from '../../hooks'
|
|
9
9
|
import { ConversationPreviewData, ConversationResource } from '../../types'
|
|
10
|
-
import { REACTION_EMOJIS, platformFontWeightBold } from '../../utils'
|
|
10
|
+
import { REACTION_EMOJIS, isArchivedConversation, platformFontWeightBold } from '../../utils'
|
|
11
11
|
import { formatDatePreview } from '../../utils/date'
|
|
12
|
+
import { faBoxArchive } from '../../vendor/font_awesome_pro_solid_icons'
|
|
12
13
|
import { tokens } from '../../vendor/tapestry/tokens'
|
|
13
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
AvatarGroup,
|
|
16
|
+
ConversationAvatar,
|
|
17
|
+
Heading,
|
|
18
|
+
IconTile,
|
|
19
|
+
Text,
|
|
20
|
+
Badge,
|
|
21
|
+
type BadgeProps,
|
|
22
|
+
} from '../display'
|
|
14
23
|
import { ConversationActions } from './conversation_actions'
|
|
15
24
|
import { MuteIndicator } from './mute_indicator'
|
|
16
25
|
import { UnreadCountBadge } from './unread_count_badge'
|
|
@@ -42,6 +51,7 @@ export const ConversationPreview = ({
|
|
|
42
51
|
unreadReaction,
|
|
43
52
|
} = conversation
|
|
44
53
|
|
|
54
|
+
const archived = isArchivedConversation(conversation)
|
|
45
55
|
const emptyConversation = !lastMessageCreatedAt
|
|
46
56
|
const fallbackIconName = emptyConversation ? 'people.noTextMessage' : 'general.person'
|
|
47
57
|
const hasMetaContent = lastMessageCreatedAt || unreadCount > 0 || muted
|
|
@@ -57,25 +67,30 @@ export const ConversationPreview = ({
|
|
|
57
67
|
const conversationAccessibilityLabel = getConversationAccessibilityLabel({
|
|
58
68
|
conversation,
|
|
59
69
|
showBadges,
|
|
70
|
+
archived,
|
|
60
71
|
})
|
|
61
72
|
|
|
62
73
|
return (
|
|
63
74
|
<ConversationActions
|
|
64
75
|
conversation={conversation}
|
|
65
|
-
style={[styles.previewRow, disabled && styles.disabled, style]}
|
|
76
|
+
style={[styles.previewRow, disabled && !archived && styles.disabled, style]}
|
|
66
77
|
onPress={onPress}
|
|
67
78
|
accessibilityLabel={conversationAccessibilityLabel}
|
|
68
79
|
>
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
80
|
+
{archived ? (
|
|
81
|
+
<IconTile icon={faBoxArchive} size="lg" testID="conversation-archived-icon" />
|
|
82
|
+
) : (
|
|
83
|
+
<ConversationAvatar
|
|
84
|
+
conversation={conversation}
|
|
85
|
+
size="lg"
|
|
86
|
+
fallbackIconName={fallbackIconName}
|
|
87
|
+
/>
|
|
88
|
+
)}
|
|
74
89
|
<View style={styles.conversationBody}>
|
|
75
90
|
<Heading
|
|
76
91
|
numberOfLines={numberOfLinesScaledTitle}
|
|
77
92
|
variant="h3"
|
|
78
|
-
style={[styles.title, isUnread && styles.unreadTitle]}
|
|
93
|
+
style={[styles.title, archived && styles.archivedTitle, isUnread && styles.unreadTitle]}
|
|
79
94
|
>
|
|
80
95
|
{title}
|
|
81
96
|
</Heading>
|
|
@@ -193,10 +208,12 @@ function formatConversationPreviewAccessibilityLabel(preview: ConversationPrevie
|
|
|
193
208
|
interface GetConversationAccessibilityLabelProps {
|
|
194
209
|
conversation: ConversationResource
|
|
195
210
|
showBadges: boolean
|
|
211
|
+
archived: boolean
|
|
196
212
|
}
|
|
197
213
|
const getConversationAccessibilityLabel = ({
|
|
198
214
|
conversation,
|
|
199
215
|
showBadges,
|
|
216
|
+
archived,
|
|
200
217
|
}: GetConversationAccessibilityLabelProps) => {
|
|
201
218
|
const { badges, title, conversationPreview, muted, disabled, unreadCount, unreadReaction } =
|
|
202
219
|
conversation
|
|
@@ -207,7 +224,7 @@ const getConversationAccessibilityLabel = ({
|
|
|
207
224
|
const label = {
|
|
208
225
|
title,
|
|
209
226
|
badge: hasBadges ? `for ${badges[0]?.pcoResourceType} ${badges[0]?.text || ''}` : '',
|
|
210
|
-
disabled: disabled ? 'Disabled' : '',
|
|
227
|
+
disabled: archived ? 'Archived' : disabled ? 'Disabled' : '',
|
|
211
228
|
muted: muted ? 'Muted' : '',
|
|
212
229
|
readStatus: hasUnreadMessages
|
|
213
230
|
? `${unreadCount} unread messages`
|
|
@@ -255,6 +272,9 @@ const useStyles = () => {
|
|
|
255
272
|
fontSize: 16,
|
|
256
273
|
color: colors.textColorDefaultPrimary,
|
|
257
274
|
},
|
|
275
|
+
archivedTitle: {
|
|
276
|
+
color: colors.textColorDefaultSecondary,
|
|
277
|
+
},
|
|
258
278
|
unreadTitle: {
|
|
259
279
|
fontWeight: tokens.fontWeightBold,
|
|
260
280
|
},
|
|
@@ -4,9 +4,11 @@ import { FlatList, StyleSheet, View } from 'react-native'
|
|
|
4
4
|
import { useConversationsContext } from '../../contexts/conversations_context'
|
|
5
5
|
import { useTheme } from '../../hooks'
|
|
6
6
|
import { useCanCreateConversations } from '../../hooks/use_chat_permissions'
|
|
7
|
+
import { useClearConversationFilters } from '../../hooks/use_clear_conversation_filters'
|
|
7
8
|
import { useConversationsJoltEvents } from '../../hooks/use_conversations_jolt_events'
|
|
8
9
|
import { useFilterDropdownEnabled } from '../../hooks/use_filter_dropdown_enabled'
|
|
9
10
|
import { ConversationResource } from '../../types'
|
|
11
|
+
import { ArchivedResultsFooter } from './archived_results_footer'
|
|
10
12
|
import { ConversationPreview, ConversationPreviewSkeleton } from './conversation_preview'
|
|
11
13
|
import { ConversationsBlankState } from './conversations_blank_state'
|
|
12
14
|
import { FilteredResultsFooter } from './filtered_results_footer'
|
|
@@ -29,15 +31,19 @@ export const Conversations = ({ ListHeaderComponent }: ConversationsProps) => {
|
|
|
29
31
|
isRefetching,
|
|
30
32
|
isFetched,
|
|
31
33
|
hasNextPage,
|
|
32
|
-
args: { chat_group_graph_id, group_source_app_name, show_direct_messages },
|
|
34
|
+
args: { chat_group_graph_id, group_source_app_name, show_direct_messages, show_archived },
|
|
33
35
|
} = useConversationsContext()
|
|
34
36
|
const navigation = useNavigation()
|
|
35
37
|
const canCreateConversations = useCanCreateConversations()
|
|
38
|
+
const clearFilters = useClearConversationFilters()
|
|
36
39
|
const filterDropdownEnabled = useFilterDropdownEnabled()
|
|
37
|
-
const isFilterApplied =
|
|
40
|
+
const isFilterApplied =
|
|
41
|
+
!!chat_group_graph_id || !!group_source_app_name || !!show_direct_messages || !!show_archived
|
|
42
|
+
const showArchivedResultsFooter = !!show_archived && !isLoading
|
|
38
43
|
const showFilteredResultsFooter =
|
|
39
44
|
filterDropdownEnabled &&
|
|
40
45
|
isFilterApplied &&
|
|
46
|
+
!show_archived &&
|
|
41
47
|
!hasNextPage &&
|
|
42
48
|
!isLoading &&
|
|
43
49
|
conversations.length > 0
|
|
@@ -72,7 +78,13 @@ export const Conversations = ({ ListHeaderComponent }: ConversationsProps) => {
|
|
|
72
78
|
canCreateConversations={canCreateConversations}
|
|
73
79
|
/>
|
|
74
80
|
}
|
|
75
|
-
ListFooterComponent={
|
|
81
|
+
ListFooterComponent={
|
|
82
|
+
showArchivedResultsFooter ? (
|
|
83
|
+
<ArchivedResultsFooter onViewActiveConversations={clearFilters} />
|
|
84
|
+
) : showFilteredResultsFooter ? (
|
|
85
|
+
<FilteredResultsFooter />
|
|
86
|
+
) : null
|
|
87
|
+
}
|
|
76
88
|
renderItem={({ item }) => {
|
|
77
89
|
if (item.type === 'loading') {
|
|
78
90
|
return <ConversationPreviewSkeleton />
|
|
@@ -58,7 +58,7 @@ const options = [
|
|
|
58
58
|
{ id: 'teams', label: 'Teams' },
|
|
59
59
|
]
|
|
60
60
|
|
|
61
|
-
const renderFilterDropdown = () => {
|
|
61
|
+
const renderFilterDropdown = ({ showMoreFilters }: { showMoreFilters?: boolean } = {}) => {
|
|
62
62
|
const onSelect = jest.fn()
|
|
63
63
|
const onMoreFilters = jest.fn()
|
|
64
64
|
|
|
@@ -69,6 +69,7 @@ const renderFilterDropdown = () => {
|
|
|
69
69
|
onSelect={onSelect}
|
|
70
70
|
onMoreFilters={onMoreFilters}
|
|
71
71
|
triggerLabel="Groups"
|
|
72
|
+
showMoreFilters={showMoreFilters}
|
|
72
73
|
/>
|
|
73
74
|
)
|
|
74
75
|
|
|
@@ -95,6 +96,13 @@ describe('FilterDropdown', () => {
|
|
|
95
96
|
expect(onMoreFilters).not.toHaveBeenCalled()
|
|
96
97
|
})
|
|
97
98
|
|
|
99
|
+
it('leaves More filters out when showMoreFilters is false', () => {
|
|
100
|
+
renderFilterDropdown({ showMoreFilters: false })
|
|
101
|
+
|
|
102
|
+
expect(screen.queryByRole('menuitem', { name: 'More filters' })).toBeNull()
|
|
103
|
+
expect(screen.getAllByRole('menuitem')).toHaveLength(options.length)
|
|
104
|
+
})
|
|
105
|
+
|
|
98
106
|
it('calls onMoreFilters for the More filters action', () => {
|
|
99
107
|
const { onSelect, onMoreFilters } = renderFilterDropdown()
|
|
100
108
|
|
|
@@ -34,6 +34,7 @@ export interface FilterDropdownProps {
|
|
|
34
34
|
onSelect: (id: string) => void
|
|
35
35
|
onMoreFilters: () => void
|
|
36
36
|
triggerLabel: string
|
|
37
|
+
showMoreFilters?: boolean
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
export function FilterDropdown({
|
|
@@ -42,22 +43,22 @@ export function FilterDropdown({
|
|
|
42
43
|
onSelect,
|
|
43
44
|
onMoreFilters,
|
|
44
45
|
triggerLabel,
|
|
46
|
+
showMoreFilters = true,
|
|
45
47
|
}: FilterDropdownProps) {
|
|
46
48
|
const styles = useStyles()
|
|
47
49
|
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
]
|
|
50
|
+
const optionActions: MenuAction[] = options.map(option => ({
|
|
51
|
+
id: option.id,
|
|
52
|
+
title: option.label,
|
|
53
|
+
state: option.id === selectedId ? ('on' as const) : ('off' as const),
|
|
54
|
+
}))
|
|
55
|
+
const moreFiltersAction: MenuAction = {
|
|
56
|
+
id: MORE_FILTERS_ID,
|
|
57
|
+
title: '',
|
|
58
|
+
displayInline: true,
|
|
59
|
+
subactions: [{ id: MORE_FILTERS_ID, title: MORE_FILTERS_LABEL }],
|
|
60
|
+
}
|
|
61
|
+
const actions = showMoreFilters ? [...optionActions, moreFiltersAction] : optionActions
|
|
61
62
|
|
|
62
63
|
const handlePressAction = ({ nativeEvent }: MenuActionEvent) => {
|
|
63
64
|
if (nativeEvent.event === MORE_FILTERS_ID) {
|