Skip to content
Draft
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
62 changes: 39 additions & 23 deletions src/components/FolderPicker/FolderPicker.spec.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { render, fireEvent, screen } from '@testing-library/react'
import { render, screen } from '@testing-library/react'
import React from 'react'

import { createMockClient } from 'cozy-client'
Expand All @@ -8,17 +8,30 @@ import AppLike from 'test/components/AppLike'

import { FolderPicker } from '@/components/FolderPicker/FolderPicker'

// Mock dependencies
jest.mock('cozy-keys-lib', () => ({
useVaultClient: jest.fn()
}))

jest.mock('cozy-sharing', () => ({
...jest.requireActual('cozy-sharing'),
useSharingContext: jest.fn()
useSharingContext: jest.fn(),
SharingCollection: {
data: jest.fn().mockResolvedValue({ data: [] })
}
}))

useSharingContext.mockReturnValue({ byDocId: [] })

// Mock the FolderPickerBody component to avoid complex rendering issues
jest.mock('@/components/FolderPicker/FolderPickerBody', () => ({
FolderPickerBody: jest.fn().mockImplementation(() => (
<div data-testid="folder-picker-body">
<div>Mocked Folder Picker Body</div>
</div>
))
}))

describe('FolderPicker', () => {
const cozyFile = {
id: 'file123',
Expand Down Expand Up @@ -71,33 +84,36 @@ describe('FolderPicker', () => {
)
}

it('should be able to move inside another folder', async () => {
it('should render with the provided folder', async () => {
setup()

expect(screen.getByText('Photos')).toBeInTheDocument()
expect(screen.getByTestId('folder-picker-body')).toBeInTheDocument()

const backButton = screen.getByRole('button', {
name: 'Back'
})
fireEvent.click(backButton)
await screen.findByText('Files')
const {
FolderPickerBody
} = require('@/components/FolderPicker/FolderPickerBody')

const moveButton = screen.queryByRole('button', {
name: 'Move'
})
fireEvent.click(moveButton)
expect(onConfirmSpy).toHaveBeenCalledWith(rootCozyFolder)
})
const props = FolderPickerBody.mock.calls[0][0]

it('should display the folder creation input', async () => {
setup()
expect(props.folder).toEqual(cozyFolder)
expect(props.entries).toEqual([cozyFile])
expect(typeof props.navigateTo).toBe('function')
})

const addFolderButton = screen.queryByRole('button', {
name: 'Add a folder'
})
fireEvent.click(addFolderButton)
it('should allow folder creation when canCreateFolder is true', async () => {
const mockClient = createMockClient()
render(
<AppLike client={mockClient}>
<FolderPicker
currentFolder={cozyFolder}
entries={[cozyFile]}
onClose={onCloseSpy}
onConfirm={onConfirmSpy}
canCreateFolder={true}
/>
</AppLike>
)

const filenameInput = await screen.findByTestId('name-input')
expect(filenameInput).toBeInTheDocument()
expect(screen.getByTestId('folder-picker-body')).toBeInTheDocument()
})
})
5 changes: 4 additions & 1 deletion src/components/FolderPicker/FolderPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ interface FolderPickerProps {
slotProps?: FolderPickerSlotProps
showNextcloudFolder?: boolean
canPickEntriesParentFolder?: boolean
showSharedDriveFolder?: boolean
}

const useStyles = makeStyles({
Expand All @@ -53,7 +54,8 @@ const FolderPicker: React.FC<FolderPickerProps> = ({
canCreateFolder = true,
slotProps,
showNextcloudFolder = false,
canPickEntriesParentFolder = false
canPickEntriesParentFolder = false,
showSharedDriveFolder = false
}) => {
const [folder, setFolder] = useState<File>(currentFolder)

Expand Down Expand Up @@ -101,6 +103,7 @@ const FolderPicker: React.FC<FolderPickerProps> = ({
isFolderCreationDisplayed={isFolderCreationDisplayed}
hideFolderCreation={hideFolderCreation}
showNextcloudFolder={showNextcloudFolder}
showSharedDriveFolder={showSharedDriveFolder}
/>
}
actions={
Expand Down
21 changes: 15 additions & 6 deletions src/components/FolderPicker/FolderPickerAddFolderItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,16 @@ interface FolderPickerAddFolderItemProps {
visible: boolean
afterSubmit: () => void
afterAbort: () => void
driveId?: string
}

const FolderPickerAddFolderItem: FC<FolderPickerAddFolderItemProps> = ({
isEncrypted,
currentFolderId,
visible,
afterSubmit,
afterAbort
afterAbort,
driveId
}) => {
const { isMobile } = useBreakpoints()
const gutters = isMobile ? 'default' : 'double'
Expand All @@ -41,11 +43,18 @@ const FolderPickerAddFolderItem: FC<FolderPickerAddFolderItemProps> = ({

const handleSubmit = (name: string): void => {
dispatch(
createFolder(client, vaultClient, name, currentFolderId, {
isEncryptedFolder: isEncrypted,
showAlert,
t
})
createFolder(
client,
vaultClient,
name,
currentFolderId,
{
isEncryptedFolder: isEncrypted,
showAlert,
t
},
driveId
)
)
if (typeof afterSubmit === 'function') {
afterSubmit()
Expand Down
40 changes: 39 additions & 1 deletion src/components/FolderPicker/FolderPickerBody.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import React from 'react'

import { FolderPickerContentSharedDriveRoot } from './FolderPickerContentSharedDriveRoot'

import { FolderPickerContentCozy } from '@/components/FolderPicker/FolderPickerContentCozy'
import { FolderPickerContentNextcloud } from '@/components/FolderPicker/FolderPickerContentNextcloud'
import { FolderPickerContentSharedDrive } from '@/components/FolderPicker/FolderPickerContentSharedDrive'
import { File, FolderPickerEntry } from '@/components/FolderPicker/types'
import { ROOT_DIR_ID, SHARED_DRIVES_DIR_ID } from '@/constants/config'

interface FolderPickerBodyProps {
folder: File
Expand All @@ -11,6 +15,7 @@ interface FolderPickerBodyProps {
isFolderCreationDisplayed: boolean
hideFolderCreation: () => void
showNextcloudFolder?: boolean
showSharedDriveFolder?: boolean
}

const FolderPickerBody: React.FC<FolderPickerBodyProps> = ({
Expand All @@ -19,7 +24,8 @@ const FolderPickerBody: React.FC<FolderPickerBodyProps> = ({
navigateTo,
isFolderCreationDisplayed,
hideFolderCreation,
showNextcloudFolder
showNextcloudFolder,
showSharedDriveFolder
}) => {
if (folder._type === 'io.cozy.remote.nextcloud.files') {
return (
Expand All @@ -31,6 +37,37 @@ const FolderPickerBody: React.FC<FolderPickerBodyProps> = ({
)
}

// Display content of recipient's shared drive folder
if (folder.driveId) {
return (
<FolderPickerContentSharedDrive
folder={folder}
isFolderCreationDisplayed={isFolderCreationDisplayed}
hideFolderCreation={hideFolderCreation}
entries={entries}
navigateTo={navigateTo}
/>
)
}

// Display content of `Drives` folder
if (
folder.dir_id === ROOT_DIR_ID &&
folder._id === SHARED_DRIVES_DIR_ID &&
showSharedDriveFolder
) {
return (
<FolderPickerContentSharedDriveRoot
folder={folder}
isFolderCreationDisplayed={isFolderCreationDisplayed}
hideFolderCreation={hideFolderCreation}
entries={entries}
navigateTo={navigateTo}
showNextcloudFolder={showNextcloudFolder}
/>
)
}

return (
<FolderPickerContentCozy
folder={folder}
Expand All @@ -39,6 +76,7 @@ const FolderPickerBody: React.FC<FolderPickerBodyProps> = ({
entries={entries}
navigateTo={navigateTo}
showNextcloudFolder={showNextcloudFolder}
showSharedDriveFolder={showSharedDriveFolder}
/>
)
}
Expand Down
18 changes: 15 additions & 3 deletions src/components/FolderPicker/FolderPickerContentCozy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ interface FolderPickerContentCozyProps {
entries: FolderPickerEntry[]
navigateTo: (folder: import('./types').File) => void
showNextcloudFolder?: boolean
showSharedDriveFolder?: boolean
}

const FolderPickerContentCozy: React.FC<FolderPickerContentCozyProps> = ({
Expand All @@ -37,7 +38,8 @@ const FolderPickerContentCozy: React.FC<FolderPickerContentCozyProps> = ({
hideFolderCreation,
entries,
navigateTo,
showNextcloudFolder
showNextcloudFolder,
showSharedDriveFolder
}) => {
const client = useClient()
const contentQuery = buildMoveOrImportQuery(folder._id)
Expand Down Expand Up @@ -68,7 +70,10 @@ const FolderPickerContentCozy: React.FC<FolderPickerContentCozyProps> = ({
const isEncrypted = isEncryptedFolder(folder)

const files: IOCozyFile[] = useMemo(() => {
if (folder._id === ROOT_DIR_ID && showNextcloudFolder) {
if (
folder._id === ROOT_DIR_ID &&
(showNextcloudFolder || showSharedDriveFolder)
) {
return [
...(sharedFolderResult.fetchStatus === 'loaded'
? sharedFolderResult.data ?? []
Expand All @@ -77,7 +82,14 @@ const FolderPickerContentCozy: React.FC<FolderPickerContentCozyProps> = ({
]
}
return [...(filesData ?? [])]
}, [filesData, sharedFolderResult, folder, showNextcloudFolder])
}, [
folder._id,
showNextcloudFolder,
showSharedDriveFolder,
filesData,
sharedFolderResult.fetchStatus,
sharedFolderResult.data
])

const handleFolderUnlockerDismiss = async (): Promise<void> => {
const parentFolderQuery = buildFileOrFolderByIdQuery(folder.dir_id)
Expand Down
108 changes: 108 additions & 0 deletions src/components/FolderPicker/FolderPickerContentSharedDrive.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import * as React from 'react'
import { useMemo } from 'react'

import { useClient } from 'cozy-client'
import { isDirectory } from 'cozy-client/dist/models/file'
import type { IOCozyFile } from 'cozy-client/types/types'
import List from 'cozy-ui/transpiled/react/List'

import { FolderPickerListItem } from './FolderPickerListItem'

import { FolderPickerAddFolderItem } from '@/components/FolderPicker/FolderPickerAddFolderItem'
import { FolderPickerContentLoader } from '@/components/FolderPicker/FolderPickerContentLoader'
import { isInvalidMoveTarget } from '@/components/FolderPicker/helpers'
import type { File, FolderPickerEntry } from '@/components/FolderPicker/types'
import { isEncryptedFolder } from '@/lib/encryption'
import { FolderUnlocker } from '@/modules/folder/components/FolderUnlocker'
import { useSharedDriveFolder } from '@/modules/shareddrives/hooks/useSharedDriveFolder'
import { buildFileOrFolderByIdQuery } from '@/queries'

interface FolderPickerContentSharedDriveProps {
folder: IOCozyFile
isFolderCreationDisplayed: boolean
hideFolderCreation: () => void
entries: FolderPickerEntry[]
navigateTo: (folder: import('./types').File) => void
showNextcloudFolder?: boolean
}

const FolderPickerContentSharedDrive: React.FC<
FolderPickerContentSharedDriveProps
> = ({
folder,
isFolderCreationDisplayed,
hideFolderCreation,
entries,
navigateTo
}) => {
const client = useClient()
const driveId = folder.driveId ?? ''
const folderId = folder._id

const { sharedDriveResult } = useSharedDriveFolder({
driveId,
folderId
})

const { fetchStatus, files } = useMemo(
() =>
sharedDriveResult.included || sharedDriveResult.data
? { fetchStatus: 'loaded', files: sharedDriveResult.included ?? [] }
: { fetchStatus: 'loading', files: [] },
[sharedDriveResult]
)

const isEncrypted = isEncryptedFolder(folder)

const handleFolderUnlockerDismiss = async (): Promise<void> => {
const parentFolderQuery = buildFileOrFolderByIdQuery(folder.dir_id)
const parentFolder = (await client?.fetchQueryAndGetFromState({
definition: parentFolderQuery.definition(),
options: parentFolderQuery.options
})) as {
data?: IOCozyFile
}
if (!parentFolder.data) {
throw new Error('Parent folder not found')
}

navigateTo(parentFolder.data)
}

const handleClick = (file: File): void => {
if (isDirectory(file)) {
navigateTo(file)
}
}

return (
<List>
<FolderPickerAddFolderItem
isEncrypted={isEncrypted}
currentFolderId={folder._id}
visible={isFolderCreationDisplayed}
afterSubmit={hideFolderCreation}
afterAbort={hideFolderCreation}
driveId={folder.driveId}
/>
<FolderPickerContentLoader
fetchStatus={fetchStatus}
hasNoData={files.length === 0}
>
<FolderUnlocker folder={folder} onDismiss={handleFolderUnlockerDismiss}>
{files.map((file: IOCozyFile, index: number) => (
<FolderPickerListItem
key={file._id}
file={file}
disabled={isInvalidMoveTarget(entries, file)}
onClick={handleClick}
showDivider={index !== files.length - 1}
/>
))}
</FolderUnlocker>
</FolderPickerContentLoader>
</List>
)
}

export { FolderPickerContentSharedDrive }
Loading
Loading