Skip to content

Commit 912045e

Browse files
authored
Merge pull request #2513 from nextcloud-libraries/backport/2512/stable6
[stable6] fix(filepicker): only add current folder if pickable
2 parents 1645d94 + c7643aa commit 912045e

2 files changed

Lines changed: 101 additions & 0 deletions

File tree

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/**
2+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import type { INode } from '@nextcloud/files'
7+
8+
import { Folder } from '@nextcloud/files'
9+
import { shallowMount } from '@vue/test-utils'
10+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
11+
import { ref, shallowRef } from 'vue'
12+
import FilePicker from './FilePicker.vue'
13+
14+
const axios = vi.hoisted(() => ({
15+
get: vi.fn(() => new Promise(() => {})),
16+
}))
17+
vi.mock('@nextcloud/axios', () => ({ default: axios }))
18+
19+
// Shared state injected into the mocked `useDAVFiles` composable so we can
20+
// control the current folder without hitting WebDAV.
21+
const dav = vi.hoisted(() => ({
22+
files: undefined as never,
23+
folder: undefined as never,
24+
isLoading: undefined as never,
25+
}))
26+
vi.mock('../../composables/dav.ts', () => ({
27+
useDAVFiles: () => ({
28+
files: dav.files,
29+
folder: dav.folder,
30+
isLoading: dav.isLoading,
31+
loadFiles: vi.fn(),
32+
createDirectory: vi.fn(),
33+
}),
34+
}))
35+
36+
const currentFolder = new Folder({
37+
owner: null,
38+
source: 'http://example.com/dav/folder',
39+
root: '/',
40+
})
41+
42+
describe('FilePicker', () => {
43+
beforeEach(() => {
44+
dav.files = shallowRef([]) as never
45+
dav.folder = shallowRef(currentFolder) as never
46+
dav.isLoading = ref(false) as never
47+
})
48+
49+
afterEach(() => {
50+
vi.clearAllMocks()
51+
})
52+
53+
/**
54+
* Mount the FilePicker with a `buttons` factory that captures the nodes
55+
* that would be passed to the button callback.
56+
*
57+
* @param props Additional props for the FilePicker
58+
*/
59+
function mountWithButtonSpy(props: Record<string, unknown> = {}) {
60+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
61+
const buttons = vi.fn((_nodes: INode[]) => [{
62+
label: 'Pick',
63+
callback: () => {},
64+
}])
65+
66+
shallowMount(FilePicker, {
67+
propsData: {
68+
name: 'Test picker',
69+
buttons,
70+
...props,
71+
},
72+
})
73+
74+
// The last call reflects the current selection state
75+
return buttons.mock.calls.at(-1)![0]
76+
}
77+
78+
it('picks the current folder if directory picking is allowed and nothing is selected', () => {
79+
const nodes = mountWithButtonSpy({ allowPickDirectory: true })
80+
expect(nodes).toEqual([currentFolder])
81+
})
82+
83+
it('picks the current folder if `canPickFn` allows it', () => {
84+
const canPickFn = vi.fn(() => true)
85+
const nodes = mountWithButtonSpy({ allowPickDirectory: true, canPickFn })
86+
87+
expect(canPickFn).toHaveBeenCalledWith(currentFolder)
88+
expect(nodes).toEqual([currentFolder])
89+
})
90+
91+
it('does not pick the current folder if `canPickFn` rejects it', () => {
92+
const canPickFn = vi.fn(() => false)
93+
const nodes = mountWithButtonSpy({ allowPickDirectory: true, canPickFn })
94+
95+
expect(canPickFn).toHaveBeenCalledWith(currentFolder)
96+
// The unpickable folder must not be passed as a selected node
97+
expect(nodes).toEqual([])
98+
})
99+
})

lib/components/FilePicker/FilePicker.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@ const dialogButtons = computed(() => {
157157
const nodes = selectedFiles.value.length === 0
158158
&& props.allowPickDirectory
159159
&& currentFolder.value
160+
// either no canPickFn is provided or the current folder is pickable
161+
&& (!props.canPickFn || props.canPickFn(currentFolder.value))
160162
? [currentFolder.value]
161163
: selectedFiles.value
162164

0 commit comments

Comments
 (0)