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
99 changes: 99 additions & 0 deletions lib/components/FilePicker/FilePicker.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import type { INode } from '@nextcloud/files'

import { Folder } from '@nextcloud/files'
import { shallowMount } from '@vue/test-utils'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { ref, shallowRef } from 'vue'
import FilePicker from './FilePicker.vue'

const axios = vi.hoisted(() => ({
get: vi.fn(() => new Promise(() => {})),
}))
vi.mock('@nextcloud/axios', () => ({ default: axios }))

// Shared state injected into the mocked `useDAVFiles` composable so we can
// control the current folder without hitting WebDAV.
const dav = vi.hoisted(() => ({
files: undefined as never,
folder: undefined as never,
isLoading: undefined as never,
}))
vi.mock('../../composables/dav.ts', () => ({
useDAVFiles: () => ({
files: dav.files,
folder: dav.folder,
isLoading: dav.isLoading,
loadFiles: vi.fn(),
createDirectory: vi.fn(),
}),
}))

const currentFolder = new Folder({
owner: null,
source: 'http://example.com/dav/folder',
root: '/',
})

describe('FilePicker', () => {
beforeEach(() => {
dav.files = shallowRef([]) as never
dav.folder = shallowRef(currentFolder) as never
dav.isLoading = ref(false) as never
})

afterEach(() => {
vi.clearAllMocks()
})

/**
* Mount the FilePicker with a `buttons` factory that captures the nodes
* that would be passed to the button callback.
*
* @param props Additional props for the FilePicker
*/
function mountWithButtonSpy(props: Record<string, unknown> = {}) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const buttons = vi.fn((_nodes: INode[]) => [{
label: 'Pick',
callback: () => {},
}])

shallowMount(FilePicker, {
propsData: {
name: 'Test picker',
buttons,
...props,
},
})

// The last call reflects the current selection state
return buttons.mock.calls.at(-1)![0]
}

it('picks the current folder if directory picking is allowed and nothing is selected', () => {
const nodes = mountWithButtonSpy({ allowPickDirectory: true })
expect(nodes).toEqual([currentFolder])
})

it('picks the current folder if `canPickFn` allows it', () => {
const canPickFn = vi.fn(() => true)
const nodes = mountWithButtonSpy({ allowPickDirectory: true, canPickFn })

expect(canPickFn).toHaveBeenCalledWith(currentFolder)
expect(nodes).toEqual([currentFolder])
})

it('does not pick the current folder if `canPickFn` rejects it', () => {
const canPickFn = vi.fn(() => false)
const nodes = mountWithButtonSpy({ allowPickDirectory: true, canPickFn })

expect(canPickFn).toHaveBeenCalledWith(currentFolder)
// The unpickable folder must not be passed as a selected node
expect(nodes).toEqual([])
})
})
2 changes: 2 additions & 0 deletions lib/components/FilePicker/FilePicker.vue
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ const dialogButtons = computed(() => {
const nodes = selectedFiles.value.length === 0
&& props.allowPickDirectory
&& currentFolder.value
// either no canPickFn is provided or the current folder is pickable
&& (!props.canPickFn || props.canPickFn(currentFolder.value))
? [currentFolder.value]
: selectedFiles.value

Expand Down
Loading