diff --git a/l10n/messages.pot b/l10n/messages.pot index 4d729085..cd818955 100644 --- a/l10n/messages.pot +++ b/l10n/messages.pot @@ -59,7 +59,7 @@ msgstr "" msgid "Files and folders you recently modified will show up here." msgstr "" -#: lib/components/FilePicker/FileList.vue:39 +#: lib/components/FilePicker/FileList.vue:47 msgid "Modified" msgstr "" @@ -71,7 +71,7 @@ msgstr "" msgid "Move to {target}" msgstr "" -#: lib/components/FilePicker/FileList.vue:19 +#: lib/components/FilePicker/FileList.vue:27 msgid "Name" msgstr "" @@ -94,7 +94,7 @@ msgstr "" msgid "Select entry" msgstr "" -#: lib/components/FilePicker/FileList.vue:29 +#: lib/components/FilePicker/FileList.vue:37 msgid "Size" msgstr "" diff --git a/lib/components/FilePicker/FileList.spec.ts b/lib/components/FilePicker/FileList.spec.ts new file mode 100644 index 00000000..e825044f --- /dev/null +++ b/lib/components/FilePicker/FileList.spec.ts @@ -0,0 +1,198 @@ +/** + * @copyright Copyright (c) 2023 Ferdinand Thiessen + * + * @author Ferdinand Thiessen + * + * @license AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +import { mount } from '@vue/test-utils' +import { beforeAll, describe, expect, it, vi } from 'vitest' + +import FileList from './FileList.vue' +import { File, Folder } from '@nextcloud/files' + +// mock OC.MimeType +window.OC = { + MimeType: { + getIconUrl: (mime: string) => `icon/${mime}`, + }, +} as never + +const exampleNodes = [ + new File({ + owner: null, + source: 'http://example.com/dav/a-file.txt', + mime: 'text/plain', + mtime: new Date(), + root: '/', + size: 200, + }), + new File({ + owner: null, + source: 'http://example.com/dav/favorite.txt', + mime: 'text/plain', + mtime: new Date(), + root: '/', + size: 321, + attributes: { + favorite: true, + }, + }), + new Folder({ + owner: null, + source: 'http://example.com/dav/directory', + mtime: new Date(), + root: '/', + size: 0, + }), + new File({ + owner: null, + source: 'http://example.com/dav/b-file.txt', + mime: 'text/plain', + mtime: new Date(), + root: '/', + size: 100, + }), +] + +describe('FilePicker FileList', () => { + beforeAll(() => { + vi.useFakeTimers() + }) + + it('is mountable', () => { + const consoleError = vi.spyOn(console, 'error') + const consoleWarning = vi.spyOn(console, 'warn') + + const wrapper = mount(FileList, { + propsData: { + multiselect: false, + allowPickDirectory: false, + loading: false, + files: [], + selectedFiles: [], + path: '/', + }, + }) + expect(wrapper.html()).toBeTruthy() + // No errors or warnings + expect(consoleError).not.toBeCalled() + expect(consoleWarning).not.toBeCalled() + }) + + it('header checkbox is not shown if multiselect is `false`', () => { + const wrapper = mount(FileList, { + propsData: { + multiselect: false, + allowPickDirectory: false, + loading: false, + files: [], + selectedFiles: [], + path: '/', + }, + }) + expect(wrapper.find('th.row-checkbox').exists()).toBe(false) + }) + + it('header checkbox is shown if multiselect is `true`', () => { + const wrapper = mount(FileList, { + propsData: { + multiselect: true, + allowPickDirectory: false, + loading: false, + files: [], + selectedFiles: [], + path: '/', + }, + }) + expect(wrapper.find('th.row-checkbox').exists()).toBe(true) + // there is an aria label + expect(wrapper.find('[data-test="file-picker_select-all"]').attributes('aria-label')).toBeTruthy() + // no checked + expect(wrapper.find('[data-test="file-picker_select-all"]').props('checked')).toBe(false) + }) + + it('header checkbox is checked when all nodes are selected', async () => { + const nodes = [...exampleNodes] + const wrapper = mount(FileList, { + propsData: { + multiselect: true, + allowPickDirectory: false, + loading: false, + files: nodes, + selectedFiles: nodes, + path: '/', + }, + }) + + const selectAll = wrapper.find('[data-test="file-picker_select-all"]') + expect(selectAll.props('checked')).toBe(true) + }) + + describe('file list sorting', () => { + it('is sorted initially by name', async () => { + const nodes = [...exampleNodes] + const wrapper = mount(FileList, { + propsData: { + multiselect: true, + allowPickDirectory: false, + loading: false, + files: nodes, + selectedFiles: [], + path: '/', + }, + }) + + const rows = wrapper.findAll('.file-picker__row') + // all nodes are shown + expect(rows.length).toBe(nodes.length) + // folder are sorted first + expect(rows.at(0).attributes('data-file')).toBe('directory') + // other files are ascending + expect(rows.at(1).attributes('data-file')).toBe('a-file.txt') + expect(rows.at(2).attributes('data-file')).toBe('b-file.txt') + expect(rows.at(3).attributes('data-file')).toBe('favorite.txt') + }) + + it('can sort descending by name', async () => { + const nodes = [...exampleNodes] + const wrapper = mount(FileList, { + propsData: { + multiselect: true, + allowPickDirectory: false, + loading: false, + files: nodes, + selectedFiles: [], + path: '/', + }, + }) + + await wrapper.find('[data-test="file-picker_sort-name"]').trigger('click') + + const rows = wrapper.findAll('.file-picker__row') + // all nodes are shown + expect(rows.length).toBe(nodes.length) + // folder are sorted first + expect(rows.at(0).attributes('data-file')).toBe('directory') + // other files are descending + expect(rows.at(1).attributes('data-file')).toBe('favorite.txt') + expect(rows.at(2).attributes('data-file')).toBe('b-file.txt') + expect(rows.at(3).attributes('data-file')).toBe('a-file.txt') + }) + }) +}) diff --git a/lib/components/FilePicker/FileList.vue b/lib/components/FilePicker/FileList.vue index 600dcf38..75a5b625 100644 --- a/lib/components/FilePicker/FileList.vue +++ b/lib/components/FilePicker/FileList.vue @@ -7,10 +7,18 @@ {{ t('Select entry') }} - + - +