|
3 | 3 | * SPDX-License-Identifier: AGPL-3.0-or-later |
4 | 4 | */ |
5 | 5 |
|
| 6 | +import type { VueWrapper } from '@vue/test-utils' |
| 7 | +import type { ComponentProps } from 'vue-component-type-helpers' |
| 8 | + |
6 | 9 | import { afterEach, describe, expect, it, vi } from 'vitest' |
7 | | -import { File } from '@nextcloud/files' |
| 10 | +import { File, Folder, Permission } from '@nextcloud/files' |
8 | 11 | import { shallowMount } from '@vue/test-utils' |
9 | 12 |
|
10 | 13 | import FileListRow from './FileListRow.vue' |
11 | 14 | import { nextTick } from 'vue' |
12 | 15 |
|
13 | | -describe('FilePicker: FileListRow', () => { |
14 | | - const node = new File({ |
15 | | - owner: null, |
16 | | - mtime: new Date(), |
17 | | - mime: 'text/plain', |
18 | | - source: 'https://example.com/dav/a.txt', |
19 | | - root: '/', |
20 | | - attributes: { displayName: 'test' }, |
| 16 | +type SubmitAction = (wrapper: VueWrapper<any>) => Promise<void> |
| 17 | +type ElementEvent = { 'update:selected': boolean | undefined, enterDirectory: Folder | undefined } |
| 18 | + |
| 19 | +async function clickCheckboxAction(wrapper: VueWrapper<any>) { |
| 20 | + wrapper.find('input[type="checkbox"]').trigger('click') |
| 21 | +} |
| 22 | + |
| 23 | +async function clickElementAction(wrapper: VueWrapper<any>) { |
| 24 | + wrapper.find('[data-testid="row-name"]').trigger('click') |
| 25 | +} |
| 26 | + |
| 27 | +async function pressEnterAction(wrapper: VueWrapper<any>) { |
| 28 | + wrapper.element.dispatchEvent(new KeyboardEvent('keydown', { bubbles: true, key: 'Enter' })) |
| 29 | + await nextTick() |
| 30 | +} |
| 31 | + |
| 32 | +function testSubmitNode(name: string, propsData: ComponentProps<typeof FileListRow>, eventPayload: ElementEvent, actionCallback: SubmitAction) { |
| 33 | + it(name, async () => { |
| 34 | + const wrapper = shallowMount(FileListRow, { |
| 35 | + propsData, |
| 36 | + stubs: { |
| 37 | + NcCheckboxRadioSwitch: { |
| 38 | + template: '<label><input type="checkbox" @click="$emit(\'update:model-value\', true)" ></label>', |
| 39 | + }, |
| 40 | + }, |
| 41 | + }) |
| 42 | + |
| 43 | + await actionCallback(wrapper) |
| 44 | + |
| 45 | + for (const [event, payload] of Object.entries(eventPayload)) { |
| 46 | + if (payload === undefined) { |
| 47 | + expect(wrapper.emitted(event)).toBeUndefined() |
| 48 | + } else { |
| 49 | + expect(wrapper.emitted(event)).toEqual([[payload]]) |
| 50 | + } |
| 51 | + } |
21 | 52 | }) |
| 53 | +} |
| 54 | + |
| 55 | +const node = new File({ |
| 56 | + owner: 'alice', |
| 57 | + mtime: new Date(), |
| 58 | + mime: 'text/plain', |
| 59 | + source: 'https://example.com/remote.php/dav/alice/a.txt', |
| 60 | + root: '/', |
| 61 | + attributes: { displayName: 'test' }, |
| 62 | +}) |
| 63 | + |
| 64 | +const folder = new Folder({ |
| 65 | + owner: 'alice', |
| 66 | + mtime: new Date(), |
| 67 | + mime: 'httpd/unix-directory', |
| 68 | + source: 'https://example.com/remote.php/dav/alice/b', |
| 69 | + root: '/', |
| 70 | + permissions: Permission.ALL, |
| 71 | + attributes: { displayName: 'test folder' }, |
| 72 | +}) |
22 | 73 |
|
| 74 | +const folderNonReadable = new Folder({ |
| 75 | + owner: 'alice', |
| 76 | + mtime: new Date(), |
| 77 | + mime: 'httpd/unix-directory', |
| 78 | + source: 'https://example.com/remote.php/dav/alice/b', |
| 79 | + root: '/', |
| 80 | + permissions: Permission.ALL & ~Permission.READ, |
| 81 | + attributes: { displayName: 'test folder' }, |
| 82 | +}) |
| 83 | + |
| 84 | +const defaultOptions = { |
| 85 | + selected: false, |
| 86 | + cropImagePreviews: true, |
| 87 | + canPick: true, |
| 88 | + showCheckbox: true, |
| 89 | + allowPickDirectory: true, |
| 90 | +} |
| 91 | + |
| 92 | +const noEmits = { |
| 93 | + 'update:selected': undefined, |
| 94 | + enterDirectory: undefined, |
| 95 | +} |
| 96 | + |
| 97 | +const selectNode = { |
| 98 | + 'update:selected': true, |
| 99 | + enterDirectory: undefined, |
| 100 | +} |
| 101 | + |
| 102 | +const navigateToFolder = { |
| 103 | + 'update:selected': undefined, |
| 104 | + enterDirectory: folder, |
| 105 | +} |
| 106 | + |
| 107 | +describe('FilePicker: FileListRow', () => { |
23 | 108 | afterEach(() => { |
24 | 109 | vi.restoreAllMocks() |
25 | 110 | }) |
@@ -65,80 +150,73 @@ describe('FilePicker: FileListRow', () => { |
65 | 150 | expect(wrapper.find('[data-testid="row-checkbox"]').exists()).toBe(false) |
66 | 151 | }) |
67 | 152 |
|
68 | | - it('Click checkbox triggers select', async () => { |
69 | | - const wrapper = shallowMount(FileListRow, { |
70 | | - propsData: { |
71 | | - allowPickDirectory: false, |
72 | | - selected: false, |
73 | | - showCheckbox: true, |
74 | | - canPick: true, |
75 | | - node, |
76 | | - cropImagePreviews: true, |
77 | | - }, |
78 | | - stubs: { |
79 | | - NcCheckboxRadioSwitch: { |
80 | | - template: '<label><input type="checkbox" @click="$emit(\'update:model-value\', true)" ></label>', |
81 | | - }, |
82 | | - }, |
| 153 | + describe('when node is a file', () => { |
| 154 | + const fileOptions = { |
| 155 | + ...defaultOptions, |
| 156 | + node, |
| 157 | + } |
| 158 | + |
| 159 | + testSubmitNode('Click checkbox triggers select', { ...fileOptions }, selectNode, clickCheckboxAction) |
| 160 | + testSubmitNode('Click element triggers select', { ...fileOptions }, selectNode, clickElementAction) |
| 161 | + testSubmitNode('Click element without checkbox triggers select', { ...fileOptions, showCheckbox: false }, selectNode, clickElementAction) |
| 162 | + testSubmitNode('Enter triggers select', { ...fileOptions, showCheckbox: false }, selectNode, pressEnterAction) |
| 163 | + |
| 164 | + describe('canPick: false', () => { |
| 165 | + const options = { |
| 166 | + ...fileOptions, |
| 167 | + canPick: false, |
| 168 | + } |
| 169 | + |
| 170 | + testSubmitNode('Click checkbox does not triggers select', options, noEmits, clickCheckboxAction) |
| 171 | + testSubmitNode('Click element does not triggers select', options, noEmits, clickElementAction) |
| 172 | + testSubmitNode('Click element without checkbox does not triggers select', { ...options, showCheckbox: false }, noEmits, clickElementAction) |
| 173 | + testSubmitNode('Enter does not triggers select', { ...options, showCheckbox: false }, noEmits, pressEnterAction) |
83 | 174 | }) |
84 | | - |
85 | | - await wrapper.find('input[type="checkbox"]').trigger('click') |
86 | | - |
87 | | - // one event with payload `true` is expected |
88 | | - expect(wrapper.emitted('update:selected')).toEqual([[true]]) |
89 | 175 | }) |
90 | 176 |
|
91 | | - it('Click element triggers select', async () => { |
92 | | - const wrapper = shallowMount(FileListRow, { |
93 | | - propsData: { |
94 | | - allowPickDirectory: false, |
95 | | - selected: false, |
96 | | - showCheckbox: true, |
97 | | - canPick: true, |
98 | | - node, |
99 | | - cropImagePreviews: true, |
100 | | - }, |
| 177 | + describe('when node is a folder', () => { |
| 178 | + const folderOptions = { |
| 179 | + ...defaultOptions, |
| 180 | + node: folder, |
| 181 | + } |
| 182 | + |
| 183 | + testSubmitNode('Click checkbox triggers select', folderOptions, selectNode, clickCheckboxAction) |
| 184 | + testSubmitNode('Click element navigates to it', folderOptions, navigateToFolder, clickElementAction) |
| 185 | + testSubmitNode('Click element without checkbox navigates to it', { ...folderOptions, showCheckbox: false }, navigateToFolder, clickElementAction) |
| 186 | + testSubmitNode('Enter navigates to it', { ...folderOptions, showCheckbox: false }, navigateToFolder, pressEnterAction) |
| 187 | + |
| 188 | + describe('canPick: false', () => { |
| 189 | + const options = { |
| 190 | + ...folderOptions, |
| 191 | + canPick: false, |
| 192 | + } |
| 193 | + |
| 194 | + testSubmitNode('Click checkbox does not triggers select', options, noEmits, clickCheckboxAction) |
| 195 | + testSubmitNode('Click element navigates to it', options, navigateToFolder, clickElementAction) |
| 196 | + testSubmitNode('Click element without checkbox navigates to it', { ...options, showCheckbox: false }, navigateToFolder, clickElementAction) |
| 197 | + testSubmitNode('Enter navigates to it', { ...options, showCheckbox: false }, navigateToFolder, pressEnterAction) |
101 | 198 | }) |
102 | 199 |
|
103 | | - await wrapper.find('[data-testid="row-name"]').trigger('click') |
104 | | - |
105 | | - // one event with payload `true` is expected |
106 | | - expect(wrapper.emitted('update:selected')).toEqual([[true]]) |
107 | | - }) |
| 200 | + describe('without READ permissions', () => { |
| 201 | + const options = { |
| 202 | + ...folderOptions, |
| 203 | + node: folderNonReadable, |
| 204 | + } |
108 | 205 |
|
109 | | - it('Click element without checkbox triggers select', async () => { |
110 | | - const wrapper = shallowMount(FileListRow, { |
111 | | - propsData: { |
112 | | - allowPickDirectory: false, |
113 | | - selected: false, |
114 | | - showCheckbox: false, |
115 | | - canPick: true, |
116 | | - node, |
117 | | - cropImagePreviews: true, |
118 | | - }, |
| 206 | + testSubmitNode('Click checkbox triggers select', options, selectNode, clickCheckboxAction) |
| 207 | + testSubmitNode('Click element does not navigates to it', options, noEmits, clickElementAction) |
| 208 | + testSubmitNode('Click element without checkbox does not navigates to it', { ...options, showCheckbox: false }, noEmits, clickElementAction) |
| 209 | + testSubmitNode('Enter does not navigates to it', { ...options, showCheckbox: false }, noEmits, pressEnterAction) |
119 | 210 | }) |
120 | 211 |
|
121 | | - await wrapper.find('[data-testid="row-name"]').trigger('click') |
122 | | - |
123 | | - // one event with payload `true` is expected |
124 | | - expect(wrapper.emitted('update:selected')).toEqual([[true]]) |
125 | | - }) |
126 | | - |
127 | | - it('Enter triggers select', async () => { |
128 | | - const wrapper = shallowMount(FileListRow, { |
129 | | - propsData: { |
| 212 | + describe('allowPickDirectory: false', () => { |
| 213 | + const options = { |
| 214 | + ...folderOptions, |
| 215 | + node: folderNonReadable, |
130 | 216 | allowPickDirectory: false, |
131 | | - selected: false, |
132 | | - showCheckbox: false, |
133 | | - canPick: true, |
134 | | - node, |
135 | | - cropImagePreviews: true, |
136 | | - }, |
137 | | - }) |
| 217 | + } |
138 | 218 |
|
139 | | - wrapper.element.dispatchEvent(new KeyboardEvent('keydown', { bubbles: true, key: 'Enter' })) |
140 | | - await nextTick() |
141 | | - |
142 | | - expect(wrapper.emitted('update:selected')).toEqual([[true]]) |
| 219 | + testSubmitNode('Click checkbox does not triggers select', options, noEmits, clickCheckboxAction) |
| 220 | + }) |
143 | 221 | }) |
144 | 222 | }) |
0 commit comments