diff --git a/src/tests/views/FilesList/FileEntryStatus.spec.ts b/src/tests/views/FilesList/FileEntryStatus.spec.ts new file mode 100644 index 0000000000..d1e3e90464 --- /dev/null +++ b/src/tests/views/FilesList/FileEntryStatus.spec.ts @@ -0,0 +1,61 @@ +/** + * SPDX-FileCopyrightText: 2026 LibreCode coop and contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { describe, expect, it } from 'vitest' +import { mount } from '@vue/test-utils' + +import FileEntryStatus from '../../../views/FilesList/FileEntry/FileEntryStatus.vue' + +function mountStatus(props: { status: number; statusText: string; signers?: unknown[] }) { + return mount(FileEntryStatus, { + props: { + signers: [], + ...props, + }, + }) +} + +describe('FileEntryStatus.vue', () => { + it('renders nothing when statusText is "none"', () => { + const wrapper = mountStatus({ status: 0, statusText: 'none' }) + expect(wrapper.find('.status-chip').exists()).toBe(false) + }) + + it('renders chip when statusText is not "none"', () => { + const wrapper = mountStatus({ status: 3, statusText: 'Signed' }) + expect(wrapper.find('.status-chip').exists()).toBe(true) + }) + + it('displays the statusText inside the chip', () => { + const wrapper = mountStatus({ status: 3, statusText: 'Signed' }) + expect(wrapper.find('.status-chip__text').text()).toBe('Signed') + }) + + it.each([ + { status: -1, expected: 'status-chip--not-libresign' }, + { status: 0, expected: 'status-chip--draft' }, + { status: 1, expected: 'status-chip--available' }, + { status: 2, expected: 'status-chip--partial' }, + { status: 3, expected: 'status-chip--signed' }, + { status: 4, expected: 'status-chip--deleted' }, + { status: 5, expected: 'status-chip--signing' }, + ])('applies variant class "$expected" for status $status', ({ status, expected }) => { + const wrapper = mountStatus({ status, statusText: 'any' }) + expect(wrapper.find('.status-chip').classes()).toContain(expected) + }) + + it('falls back to draft variant for unknown status', () => { + const wrapper = mountStatus({ status: 99, statusText: 'Unknown' }) + expect(wrapper.find('.status-chip').classes()).toContain('status-chip--draft') + }) + + it('chip does not apply any inline style that would override the nowrap fix', () => { + const wrapper = mountStatus({ status: 3, statusText: 'Signed' }) + const chip = wrapper.find('.status-chip') + // white-space is controlled by scoped CSS (nowrap); no inline style should override it + expect(chip.exists()).toBe(true) + expect(chip.attributes('style')).toBeUndefined() + }) +}) diff --git a/src/tests/views/FilesList/FilesList.spec.ts b/src/tests/views/FilesList/FilesList.spec.ts index 802059efbf..0703bf671a 100644 --- a/src/tests/views/FilesList/FilesList.spec.ts +++ b/src/tests/views/FilesList/FilesList.spec.ts @@ -127,11 +127,6 @@ describe('FilesList.vue rendering rules', () => { mocks: { $route: routeMock, }, - stubs: { - ListViewIcon: { - template: '', - }, - }, }, }) } @@ -145,6 +140,7 @@ describe('FilesList.vue rendering rules', () => { expect(wrapper.vm.mdiFolder).toBeTruthy() expect(wrapper.vm.mdiViewGrid).toBeTruthy() + expect(wrapper.vm.mdiViewList).toBeTruthy() }) it('shows empty-state request action when user can request sign', async () => { @@ -193,4 +189,17 @@ describe('FilesList.vue rendering rules', () => { const iconWithPath = wrapper.findAll('.nc-icon').find((node) => !!node.attributes('data-path')) expect(iconWithPath?.attributes('data-path')).toBe(wrapper.vm.mdiViewGrid) }) + + it('renders list toggle icon path when in grid mode', async () => { + const filesStore = useFilesStore() + const userConfigStore = useUserConfigStore() + vi.spyOn(filesStore, 'getAllFiles').mockResolvedValue({}) + userConfigStore.files_list_grid_view = true + + const wrapper = mountComponent() + await flushPromises() + + const iconWithPath = wrapper.findAll('.nc-icon').find((node) => !!node.attributes('data-path')) + expect(iconWithPath?.attributes('data-path')).toBe(wrapper.vm.mdiViewList) + }) }) diff --git a/src/views/FilesList/FileEntry/FileEntryStatus.vue b/src/views/FilesList/FileEntry/FileEntryStatus.vue index d12d574a58..047ed553ed 100644 --- a/src/views/FilesList/FileEntry/FileEntryStatus.vue +++ b/src/views/FilesList/FileEntry/FileEntryStatus.vue @@ -52,25 +52,23 @@ export default { --chip-size: 24px; --chip-radius: 12px; - display: inline-block; + display: inline-flex; + align-items: center; min-height: var(--chip-size); max-width: 100%; - padding: 4px 12px; + padding: 4px 10px; border-radius: var(--chip-radius); line-height: 1.3; text-align: center; - white-space: pre-wrap; - word-wrap: break-word; - overflow-wrap: break-word; - hyphens: auto; + white-space: nowrap; vertical-align: middle; &__text { display: inline-block; max-width: 100%; - white-space: pre-wrap; - word-wrap: break-word; - overflow-wrap: break-word; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } &--error { diff --git a/src/views/FilesList/FilesList.vue b/src/views/FilesList/FilesList.vue index 68e40dfc63..2c8a1a8d4f 100644 --- a/src/views/FilesList/FilesList.vue +++ b/src/views/FilesList/FilesList.vue @@ -31,8 +31,8 @@ variant="tertiary" @click="toggleGridView"> @@ -77,6 +77,7 @@ import HomeSvg from '@mdi/svg/svg/home.svg?raw' import { mdiFolder, mdiViewGrid, + mdiViewList, } from '@mdi/js' import NcAppContent from '@nextcloud/vue/components/NcAppContent' @@ -120,6 +121,7 @@ export default { sidebarStore, mdiFolder, mdiViewGrid, + mdiViewList, } }, data() { diff --git a/src/views/FilesList/FilesListVirtual.vue b/src/views/FilesList/FilesListVirtual.vue index 8598510227..b88335da39 100644 --- a/src/views/FilesList/FilesListVirtual.vue +++ b/src/views/FilesList/FilesListVirtual.vue @@ -581,8 +581,11 @@ tbody.files-list__tbody.files-list__tbody--grid { position: absolute; top: var(--item-padding); inset-inline-end: var(--item-padding); - width: var(--clickable-area); - height: var(--clickable-area); + width: auto; + max-width: calc(var(--row-width) - 2 * var(--item-padding)); + height: auto; + min-height: var(--clickable-area); + justify-content: flex-end; } .files-list__row-signers {