Skip to content

Commit c77457e

Browse files
committed
feat(recents): show progress bar while shared-drive files load
Shared-drive and federated files are fetched from the stack via the dataproxy and arrive after the local files, so the recents list silently grows once it already looks settled. Surface that background fetch with a progress bar under the header once a list is on screen.
1 parent 5dbf8ae commit c77457e

2 files changed

Lines changed: 117 additions & 0 deletions

File tree

src/modules/views/Recent/index.jsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
} from 'cozy-sharing'
1010
import { makeActions } from 'cozy-ui/transpiled/react/ActionsMenu/Actions'
1111
import { Content } from 'cozy-ui/transpiled/react/Layout'
12+
import LinearProgress from 'cozy-ui/transpiled/react/LinearProgress'
1213

1314
import FolderView from '../Folder/FolderView'
1415
import FolderViewBody from '../Folder/FolderViewBody'
@@ -51,6 +52,11 @@ export const RecentView = () => {
5152

5253
const recentsResult = useRecentFiles()
5354

55+
// Shared-drive and federated files arrive from the dataproxy seconds after
56+
// the local files; surface that background fetch once a list is on screen.
57+
const isFetchingMore =
58+
recentsResult?.fetchStatus === 'loading' && recentsResult?.data?.length > 0
59+
5460
useKeyboardShortcuts({
5561
client: base.client,
5662
items: recentsResult?.data || [],
@@ -103,6 +109,7 @@ export const RecentView = () => {
103109
<Breadcrumb path={[{ name: base.t('breadcrumb.title_recent') }]} />
104110
<Toolbar canUpload={false} canCreateFolder={false} />
105111
</FolderViewHeader>
112+
{isFetchingMore && <LinearProgress />}
106113
{flag('drive.virtualization.enabled') && !base.isMobile ? (
107114
<FolderViewBodyVz
108115
actions={actions}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { render } from '@testing-library/react'
2+
import React from 'react'
3+
4+
import { useSharingContext } from 'cozy-sharing'
5+
6+
import RecentViewWithProvider from './index'
7+
import { generateFileFixtures } from '../testUtils'
8+
import AppLike from 'test/components/AppLike'
9+
import { setupStoreAndClient } from 'test/setup'
10+
11+
jest.mock('react-router-dom', () => ({
12+
...jest.requireActual('react-router-dom'),
13+
useNavigate: () => jest.fn()
14+
}))
15+
16+
jest.mock('components/pushClient', () => ({
17+
isMacOS: jest.fn(() => false),
18+
isIOS: jest.fn(() => false),
19+
isLinux: jest.fn(() => false),
20+
isAndroid: jest.fn(() => false)
21+
}))
22+
jest.mock('components/pushClient/Banner', () => () => <div>Banner</div>)
23+
jest.mock('cozy-client/dist/hooks/useQuery', () =>
24+
jest.fn(() => ({
25+
fetchStatus: '',
26+
data: []
27+
}))
28+
)
29+
jest.mock('cozy-keys-lib', () => ({
30+
useVaultClient: jest.fn()
31+
}))
32+
jest.mock('components/useHead', () => jest.fn())
33+
jest.mock('cozy-sharing', () => ({
34+
__esModule: true,
35+
...jest.requireActual('cozy-sharing'),
36+
useSharingContext: jest.fn()
37+
}))
38+
jest.mock('cozy-dataproxy-lib', () => ({
39+
useDataProxy: jest.fn(() => ({ dataProxyServicesAvailable: false }))
40+
}))
41+
jest.mock('@/hooks/useRecentFiles', () => ({
42+
__esModule: true,
43+
default: jest.fn()
44+
}))
45+
46+
const mockUseRecentFiles = require('@/hooks/useRecentFiles').default
47+
48+
useSharingContext.mockReturnValue({ byDocId: [] })
49+
50+
const renderRecentView = recentsResult => {
51+
const { store, client } = setupStoreAndClient()
52+
client.plugins.realtime = { subscribe: jest.fn(), unsubscribe: jest.fn() }
53+
client.query = jest.fn().mockReturnValue({ data: [] })
54+
client.stackClient.fetchJSON = jest
55+
.fn()
56+
.mockReturnValue({ data: [], rows: [] })
57+
58+
mockUseRecentFiles.mockReturnValue(recentsResult)
59+
60+
return render(
61+
<AppLike client={client} store={store}>
62+
<RecentViewWithProvider />
63+
</AppLike>
64+
)
65+
}
66+
67+
const someFiles = () =>
68+
generateFileFixtures({
69+
nbFiles: 2,
70+
path: '/test',
71+
dir_id: '123',
72+
updated_at: '2020-05-14T10:33:31.365224+02:00'
73+
}).map(f => ({ ...f, displayedPath: '/test' }))
74+
75+
describe('Recent View loading indicator', () => {
76+
beforeEach(() => {
77+
jest.spyOn(console, 'error').mockImplementation()
78+
jest.spyOn(console, 'warn').mockImplementation()
79+
})
80+
81+
it('shows a progress bar while shared-drive files are still loading and local files are already displayed', () => {
82+
const { queryByRole } = renderRecentView({
83+
data: someFiles(),
84+
fetchStatus: 'loading',
85+
error: null
86+
})
87+
88+
expect(queryByRole('progressbar')).not.toBeNull()
89+
})
90+
91+
it('hides the progress bar once everything is loaded', () => {
92+
const { queryByRole } = renderRecentView({
93+
data: someFiles(),
94+
fetchStatus: 'loaded',
95+
error: null
96+
})
97+
98+
expect(queryByRole('progressbar')).toBeNull()
99+
})
100+
101+
it('does not show the progress bar while loading with no list yet (the skeleton covers that case)', () => {
102+
const { queryByRole } = renderRecentView({
103+
data: [],
104+
fetchStatus: 'loading',
105+
error: null
106+
})
107+
108+
expect(queryByRole('progressbar')).toBeNull()
109+
})
110+
})

0 commit comments

Comments
 (0)