1 import { renderHook } from '@testing-library/react-hooks';
3 import { RESPONSE_CODE } from '@proton/shared/lib/drive/constants';
4 import isTruthy from '@proton/utils/isTruthy';
6 import { VolumesStateProvider } from '../_volumes/useVolumesState';
7 import { useLinksActions } from './useLinksActions';
9 jest.mock('@proton/components/hooks/usePreventLeave', () => {
10 const usePreventLeave = () => {
12 preventLeave: (fn: any) => fn,
15 return usePreventLeave;
18 jest.mock('../_events/useDriveEventManager', () => {
19 const useDriveEventManager = () => {
22 volumes: () => Promise.resolve(),
31 jest.mock('./useLinksState', () => {
32 const useLinksState = () => {
36 unlockLinks: () => {},
43 jest.mock('../_crypto/useDriveCrypto', () => {
44 const useDriveCrypto = () => {
46 getPrimaryAddressKey: () => {},
47 getOwnAddressAndPrimaryKeys: () => {},
51 return useDriveCrypto;
54 jest.mock('./useLink', () => {
55 const useLink = () => {
58 getLinkPassphraseAndSessionKey: () => {},
59 getLinkPrivateKey: () => {},
60 getLinkHashKey: () => {},
67 const mockRequest = jest.fn();
68 const mockGetLinks = jest.fn();
70 const mockQueryTrashLinks = jest.fn();
71 const mockQueryRestoreLinks = jest.fn();
72 const mockQueryDeleteTrashedLinks = jest.fn();
73 const mockGetShare = jest.fn();
74 const mockGetDefaultShare = jest.fn();
76 jest.mock('./useLinks', () => {
77 const useLink = () => {
79 getLinks: mockGetLinks,
86 jest.mock('../_api/useDebouncedRequest', () => {
87 const useDebouncedRequest = () => {
90 return useDebouncedRequest;
93 jest.mock('../_shares/useShare', () => {
94 const useShare = () => {
96 getShare: mockGetShare,
102 jest.mock('../_shares/useDefaultShare', () => {
103 const useDefaultShare = () => {
105 getDefaultShare: mockGetDefaultShare,
108 return useDefaultShare;
111 const SHARE_ID_0 = 'shareId00';
112 const SHARE_ID_1 = 'shareId01';
113 const VOLUME_ID = 'volumeId00';
115 // TODO: Test suite incomplete
116 // covers operations allowing using links from multiple shares
117 describe('useLinksActions', () => {
119 current: ReturnType<typeof useLinksActions>;
123 jest.resetAllMocks();
124 mockRequest.mockImplementation((linkIds: string[]) => {
125 return Promise.resolve({
126 Responses: linkIds.map(() => ({
128 Code: RESPONSE_CODE.SUCCESS,
133 mockQueryTrashLinks.mockImplementation((shareId, parentLinkId, linkIds) => linkIds);
134 mockQueryRestoreLinks.mockImplementation((shareId, linkIds) => linkIds);
135 mockQueryDeleteTrashedLinks.mockImplementation((shareId, linkIds) => linkIds);
136 mockGetShare.mockImplementation((ac, shareId) => ({ shareId }));
137 mockGetDefaultShare.mockImplementation(() => ({ volumeId: VOLUME_ID }));
139 const wrapper = ({ children }: { children: React.ReactNode }) => (
140 <VolumesStateProvider>{children}</VolumesStateProvider>
143 const { result } = renderHook(
147 queryTrashLinks: mockQueryTrashLinks,
148 queryRestoreLinks: mockQueryRestoreLinks,
149 queryDeleteTrashedLinks: mockQueryDeleteTrashedLinks,
150 queryEmptyTrashOfShare: jest.fn(),
151 queryDeleteChildrenLinks: jest.fn(),
159 it('trashes links from different shares', async () => {
171 | └── link12 x <-- non-unique parent link id
177 const ac = new AbortController();
178 const result = await hook.current.trashLinks(ac.signal, [
179 { shareId: SHARE_ID_0, linkId: 'linkId03', parentLinkId: 'linkId01' },
180 { shareId: SHARE_ID_0, linkId: 'linkId04', parentLinkId: 'linkId01' },
181 { shareId: SHARE_ID_0, linkId: 'linkId05', parentLinkId: 'linkId02' },
182 { shareId: SHARE_ID_1, linkId: 'linkId14', parentLinkId: 'linkId11' },
183 { shareId: SHARE_ID_1, linkId: 'linkId12', parentLinkId: 'linkId01' },
186 // ensure api requests are invoked by correct groups
187 expect(mockQueryTrashLinks).toBeCalledWith(SHARE_ID_0, 'linkId01', ['linkId03', 'linkId04']);
188 expect(mockQueryTrashLinks).toBeCalledWith(SHARE_ID_0, 'linkId02', ['linkId05']);
189 expect(mockQueryTrashLinks).toBeCalledWith(SHARE_ID_1, 'linkId01', ['linkId12']);
190 expect(mockQueryTrashLinks).toBeCalledWith(SHARE_ID_1, 'linkId11', ['linkId14']);
192 // verify all requested links were processed
193 expect(result.successes.sort()).toEqual(['linkId03', 'linkId04', 'linkId05', 'linkId12', 'linkId14'].sort());
196 it('restores links from different shares', async () => {
208 // emulate partial state
209 const state: Record<string, any> = {
211 rootShareId: SHARE_ID_0,
216 rootShareId: SHARE_ID_0,
221 rootShareId: SHARE_ID_1,
227 mockGetLinks.mockImplementation(async (signal, ids: { linkId: string }[]) => {
228 return ids.map((idGroup) => state[idGroup.linkId]).filter(isTruthy);
231 const ac = new AbortController();
232 const result = await hook.current.restoreLinks(ac.signal, [
233 { shareId: SHARE_ID_0, linkId: 'linkId01' },
234 { shareId: SHARE_ID_0, linkId: 'linkId04' },
235 { shareId: SHARE_ID_1, linkId: 'linkId11' },
238 expect(mockQueryRestoreLinks).toBeCalledWith(SHARE_ID_0, [
240 'linkId04', // this link has been deleted before link linkId, thus restored last
242 expect(mockQueryRestoreLinks).toBeCalledWith(SHARE_ID_1, ['linkId11']);
244 expect(result.successes.sort()).toEqual(['linkId01', 'linkId04', 'linkId11'].sort());
247 it('deletes trashed links from different shares', async () => {
259 const ac = new AbortController();
260 const result = await hook.current.deleteTrashedLinks(ac.signal, [
261 { shareId: SHARE_ID_0, linkId: 'linkId01' },
262 { shareId: SHARE_ID_0, linkId: 'linkId04' },
263 { shareId: SHARE_ID_1, linkId: 'linkId11' },
266 expect(mockQueryDeleteTrashedLinks).toBeCalledWith(SHARE_ID_0, ['linkId01', 'linkId04']);
267 expect(mockQueryDeleteTrashedLinks).toBeCalledWith(SHARE_ID_1, ['linkId11']);
269 expect(result.successes.sort()).toEqual(['linkId01', 'linkId04', 'linkId11'].sort());