Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions src/contexts/ClipboardProvider.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,19 +82,25 @@ const TestComponent = (): JSX.Element => {
{Array.from(clipboardData.cutItemIds).join(',')}
</div>
<div data-testid="source-folder-id">
{clipboardData.sourceFolderId ?? 'none'}
{clipboardData.sourceFolderIds && clipboardData.sourceFolderIds.size > 0
? Array.from(clipboardData.sourceFolderIds).join(',')
: 'none'}
</div>
<div data-testid="modal-visible">
{moveValidationModal.isVisible.toString()}
</div>
<div data-testid="modal-type">{moveValidationModal.type ?? 'none'}</div>

<button
onClick={(): void => copyFiles([mockFile1, mockFile2], 'source-folder')}
onClick={(): void =>
copyFiles([mockFile1, mockFile2], new Set(['source-folder']))
}
>
Copy Files
</button>
<button onClick={(): void => cutFiles([mockFile1], 'source-folder')}>
<button
onClick={(): void => cutFiles([mockFile1], new Set(['source-folder']))}
>
Cut Files
</button>
<button onClick={clearClipboard}>Clear Clipboard</button>
Expand Down Expand Up @@ -166,7 +172,7 @@ describe('ClipboardProvider', () => {
result.current.copyFiles([mockFile1])
})

expect(result.current.clipboardData.sourceFolderId).toBeNull()
expect(result.current.clipboardData.sourceFolderIds).toBeNull()
})
})

Expand Down
34 changes: 20 additions & 14 deletions src/contexts/ClipboardProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import React, {

import { IOCozyFile } from 'cozy-client/types/types'

const OPERATION_CUT = 'cut' as const
const OPERATION_COPY = 'copy' as const
export const OPERATION_CUT = 'cut' as const
export const OPERATION_COPY = 'copy' as const

interface MoveValidationModal {
isVisible: boolean
Expand All @@ -25,14 +25,14 @@ interface ClipboardState {
operation: typeof OPERATION_COPY | typeof OPERATION_CUT | null
timestamp: number | null
cutItemIds: Set<string>
sourceFolderId: string | null
sourceFolderIds: Set<string> | null
moveValidationModal: MoveValidationModal
}

interface ClipboardContextValue {
clipboardData: ClipboardState
copyFiles: (files: IOCozyFile[], sourceFolderId?: string) => void
cutFiles: (files: IOCozyFile[], sourceFolderId?: string) => void
copyFiles: (files: IOCozyFile[], sourceFolderIds?: Set<string>) => void
cutFiles: (files: IOCozyFile[], sourceFolderIds?: Set<string>) => void
clearClipboard: () => void
hasClipboardData: boolean
isItemCut: (itemId: string) => boolean
Expand All @@ -56,11 +56,14 @@ const HIDE_SHARING_MODAL = 'HIDE_SHARING_MODAL'
type ClipboardAction =
| {
type: typeof COPY_FILES
payload: { files: IOCozyFile[]; sourceFolderId?: string }
payload: { files: IOCozyFile[]; sourceFolderIds?: Set<string> }
}
| {
type: typeof CUT_FILES
payload: { files: IOCozyFile[]; sourceFolderId?: string }
payload: {
files: IOCozyFile[]
sourceFolderIds?: Set<string>
}
}
| { type: typeof CLEAR_CLIPBOARD }
| {
Expand All @@ -80,7 +83,7 @@ const initialState: ClipboardState = {
operation: null,
timestamp: null,
cutItemIds: new Set(),
sourceFolderId: null,
sourceFolderIds: new Set(),
moveValidationModal: {
isVisible: false,
type: null,
Expand All @@ -103,7 +106,7 @@ const clipboardReducer = (
operation: OPERATION_COPY,
timestamp: Date.now(),
cutItemIds: new Set(),
sourceFolderId: action.payload.sourceFolderId ?? null
sourceFolderIds: action.payload.sourceFolderIds ?? null
}
case CUT_FILES:
return {
Expand All @@ -112,7 +115,7 @@ const clipboardReducer = (
operation: OPERATION_CUT,
timestamp: Date.now(),
cutItemIds: new Set(action.payload.files.map(file => file._id)),
sourceFolderId: action.payload.sourceFolderId ?? null
sourceFolderIds: action.payload.sourceFolderIds ?? null
}
case CLEAR_CLIPBOARD:
return {
Expand Down Expand Up @@ -154,15 +157,18 @@ const ClipboardProvider: React.FC<ClipboardProviderProps> = ({ children }) => {
const [state, dispatch] = useReducer(clipboardReducer, initialState)

const copyFiles = useCallback(
(files: IOCozyFile[], sourceFolderId?: string) => {
dispatch({ type: COPY_FILES, payload: { files, sourceFolderId } })
(files: IOCozyFile[], sourceFolderIds?: Set<string>) => {
dispatch({ type: COPY_FILES, payload: { files, sourceFolderIds } })
},
[]
)

const cutFiles = useCallback(
(files: IOCozyFile[], sourceFolderId?: string) => {
dispatch({ type: CUT_FILES, payload: { files, sourceFolderId } })
(files: IOCozyFile[], sourceFolderIds?: Set<string>) => {
dispatch({
type: CUT_FILES,
payload: { files, sourceFolderIds }
})
},
[]
)
Expand Down
90 changes: 61 additions & 29 deletions src/hooks/useKeyboardShortcuts.spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,11 @@ import { isEditableTarget, normalizeKey } from './helpers'
import { useKeyboardShortcuts } from './useKeyboardShortcuts.tsx'

import { isMacOS } from '@/components/pushClient'
import { useClipboardContext } from '@/contexts/ClipboardProvider'
import {
OPERATION_COPY,
OPERATION_CUT,
useClipboardContext
} from '@/contexts/ClipboardProvider'
import { useDisplayedFolder } from '@/hooks'
import { startRenamingAsync } from '@/modules/drive/rename'
import { useNextcloudCurrentFolder } from '@/modules/nextcloud/hooks/useNextcloudCurrentFolder'
Expand Down Expand Up @@ -93,15 +97,11 @@ describe('useKeyboardShortcuts', () => {
}

beforeEach(() => {
mockDispatch = jest.fn()
mockShowAlert = jest.fn()
mockT = jest.fn((key, options) =>
options?.count ? `${key}_${options.count}` : key
)

flag.mockImplementation(flagName => {
if (flagName === 'drive.keyboard-shortcuts.enabled') return true
return false
mockT = jest.fn((key, options) => {
if (options && options.count !== undefined) {
return `${key}_${options.count}`
}
return key
})
mockCopyFiles = jest.fn()
mockCutFiles = jest.fn()
Expand All @@ -111,6 +111,8 @@ describe('useKeyboardShortcuts', () => {
mockHideSelectionBar = jest.fn()
mockShowMoveValidationModal = jest.fn()
mockOnPaste = jest.fn()
mockDispatch = jest.fn()
mockShowAlert = jest.fn()

mockClient = {
save: jest.fn(),
Expand All @@ -124,8 +126,18 @@ describe('useKeyboardShortcuts', () => {
}

mockSelectedItems = [
{ _id: 'file1', name: 'test1.txt', type: 'file' },
{ _id: 'file2', name: 'test2.txt', type: 'file' }
{
_id: 'file1',
name: 'test1.txt',
type: 'file',
dir_id: 'parent-folder-1'
},
{
_id: 'file2',
name: 'test2.txt',
type: 'file',
dir_id: 'parent-folder-2'
}
]

mockItems = [
Expand All @@ -143,8 +155,8 @@ describe('useKeyboardShortcuts', () => {
useClipboardContext.mockReturnValue({
clipboardData: {
files: [{ _id: 'clipboard-file', name: 'clipboard.txt' }],
operation: 'copy',
sourceFolderId: 'source-folder-id'
operation: OPERATION_COPY,
sourceFolderIds: new Set(['source-folder-id'])
},
copyFiles: mockCopyFiles,
cutFiles: mockCutFiles,
Expand Down Expand Up @@ -177,10 +189,25 @@ describe('useKeyboardShortcuts', () => {
})
isMacOS.mockReturnValue(false)
handlePasteOperation.mockResolvedValue([
{ success: true, file: { _id: 'pasted-file' }, operation: 'copy' }
{ success: true, file: { _id: 'pasted-file' }, operation: OPERATION_COPY }
])

jest.clearAllMocks()
flag.mockImplementation(flagName => {
if (flagName === 'drive.keyboard-shortcuts.enabled') return true
return false
})

mockCopyFiles.mockClear()
mockCutFiles.mockClear()
mockClearClipboard.mockClear()
mockSelectAll.mockClear()
mockClearSelection.mockClear()
mockHideSelectionBar.mockClear()
mockShowMoveValidationModal.mockClear()
mockOnPaste.mockClear()
mockDispatch.mockClear()
mockShowAlert.mockClear()
handlePasteOperation.mockClear()
})

describe('Copy Operations (Ctrl+C / Cmd+C)', () => {
Expand Down Expand Up @@ -208,7 +235,7 @@ describe('useKeyboardShortcuts', () => {

expect(mockCopyFiles).toHaveBeenCalledWith(
mockSelectedItems,
mockCurrentFolder._id
new Set(['parent-folder-1', 'parent-folder-2'])
)
expect(mockShowAlert).toHaveBeenCalledWith({
message: 'alert.items_copied_2',
Expand Down Expand Up @@ -242,7 +269,7 @@ describe('useKeyboardShortcuts', () => {
expect(mockCopyFiles).not.toHaveBeenCalled()
expect(mockShowAlert).toHaveBeenCalledWith({
message: 'alert.copy_not_allowed',
severity: 'info'
severity: 'secondary'
})
})

Expand Down Expand Up @@ -272,7 +299,7 @@ describe('useKeyboardShortcuts', () => {

expect(mockCopyFiles).toHaveBeenCalledWith(
mockSelectedItems.filter(item => item.type === 'file'),
mockCurrentFolder._id
new Set(['parent-folder-1', 'parent-folder-2'])
)
})
})
Expand Down Expand Up @@ -301,7 +328,7 @@ describe('useKeyboardShortcuts', () => {

expect(mockCutFiles).toHaveBeenCalledWith(
mockSelectedItems,
mockCurrentFolder._id
new Set(['parent-folder-1', 'parent-folder-2'])
)
expect(mockShowAlert).toHaveBeenCalledWith({
message: 'alert.items_cut_2',
Expand Down Expand Up @@ -338,7 +365,7 @@ describe('useKeyboardShortcuts', () => {
expect(handlePasteOperation).toHaveBeenCalledWith(
mockClient,
[{ _id: 'clipboard-file', name: 'clipboard.txt' }],
'copy',
OPERATION_COPY,
mockCurrentFolder,
{
showAlert: mockShowAlert,
Expand All @@ -358,8 +385,8 @@ describe('useKeyboardShortcuts', () => {
useClipboardContext.mockReturnValue({
clipboardData: {
files: [{ _id: 'clipboard-file', name: 'clipboard.txt' }],
operation: 'cut',
sourceFolderId: 'source-folder-id'
operation: OPERATION_CUT,
sourceFolderIds: new Set(['source-folder-id'])
},
copyFiles: mockCopyFiles,
cutFiles: mockCutFiles,
Expand Down Expand Up @@ -395,9 +422,14 @@ describe('useKeyboardShortcuts', () => {
it('should skip paste when cutting and pasting in same folder', async () => {
useClipboardContext.mockReturnValue({
clipboardData: {
files: [{ _id: 'clipboard-file', name: 'clipboard.txt' }],
operation: 'cut',
sourceFolderId: mockCurrentFolder._id
files: [
{
_id: 'clipboard-file',
name: 'clipboard.txt'
}
],
operation: OPERATION_CUT,
sourceFolderIds: new Set(['current-folder-id'])
},
copyFiles: mockCopyFiles,
cutFiles: mockCutFiles,
Expand Down Expand Up @@ -427,11 +459,11 @@ describe('useKeyboardShortcuts', () => {
document.dispatchEvent(event)
})

expect(handlePasteOperation).not.toHaveBeenCalled()
expect(mockShowAlert).toHaveBeenCalledWith({
message: 'alert.paste_same_folder_skipped',
severity: 'info'
severity: 'secondary'
})
expect(handlePasteOperation).not.toHaveBeenCalled()
})
})

Expand Down Expand Up @@ -462,7 +494,7 @@ describe('useKeyboardShortcuts', () => {
expect(handlePasteOperation).toHaveBeenCalledWith(
mockClient,
expect.any(Array),
'copy',
OPERATION_COPY,
mockCurrentFolder,
expect.objectContaining({
showMoveValidationModal: mockShowMoveValidationModal,
Expand Down
Loading
Loading