From 240a410e38deeecbec7d091b1ad8f00baf3d86ac Mon Sep 17 00:00:00 2001 From: Alexander Ackermann Date: Mon, 2 Nov 2026 09:43:27 +0100 Subject: [PATCH 1/3] fix: show the full value of truncated user content on hover --- .../OcBreadcrumb/OcBreadcrumb.spec.ts | 18 ++++++++++++ .../components/OcBreadcrumb/OcBreadcrumb.vue | 6 +++- .../OcBreadcrumb/OcBreadcrumbItem.spec.ts | 8 +++++ .../OcBreadcrumb/OcBreadcrumbItem.vue | 2 +- .../OcDefinitionList/OcDefinitionList.vue | 2 +- .../components/OcDrop/OcMobileDrop.spec.ts | 9 ++++++ .../src/components/OcDrop/OcMobileDrop.vue | 2 +- .../OcFilterChip/OcFilterChip.spec.ts | 11 +++++++ .../components/OcFilterChip/OcFilterChip.vue | 1 + .../src/components/OcModal/OcModal.spec.ts | 10 +++++++ .../src/components/OcModal/OcModal.vue | 2 +- .../__snapshots__/OcModal.spec.ts.snap | 8 ++--- .../src/components/Groups/GroupsList.vue | 7 ++++- .../src/views/Groups.vue | 1 + .../src/views/Users.vue | 1 + .../src/components/AppDetailsGallery.vue | 6 +++- .../src/components/AppResources.vue | 7 ++++- .../src/components/AppTitle.vue | 6 +++- .../unit/components/AppDetailsGallery.spec.ts | 1 + .../unit/components/AppResources.spec.ts | 1 + .../tests/unit/components/AppTitle.spec.ts | 29 +++++++++++++++++++ .../src/components/AddressBooksList.vue | 6 +++- .../src/components/ContactsListItem.vue | 7 +++-- .../src/views/ContactsList.vue | 1 + .../components/SideBar/Details/TagsSelect.vue | 2 +- .../InviteCollaborator/AutocompleteItem.vue | 14 +++++++-- .../src/components/Spaces/SpaceHeader.vue | 6 +++- .../AutocompleteItem.spec.ts | 6 ++++ .../src/components/MailAppointmentItem.vue | 2 +- .../src/components/MailDetails.vue | 14 +++++---- .../src/components/MailListItem.vue | 8 +++-- .../src/components/MailboxTree.vue | 2 +- .../src/views/OutgoingInvitations.vue | 2 +- .../components/FilesList/ResourceListItem.vue | 6 +++- .../src/components/FilesList/ResourceTile.vue | 2 +- .../Groupware/Accounts/AccountsSwitch.vue | 20 +++++++++++-- .../web-pkg/src/components/ItemFilter.vue | 2 +- .../components/Search/SearchHighlights.vue | 2 +- .../src/components/SideBar/WebDavDetails.vue | 4 +-- .../src/components/Yjs/YjsCollaborators.vue | 2 +- .../src/editor/components/MentionMenu.vue | 6 +++- .../FilesList/ResourceListItem.spec.ts | 7 +++++ .../components/FilesList/ResourceTile.spec.ts | 5 ++++ .../ResourceListItem.spec.ts.snap | 2 +- .../tests/unit/components/ItemFilter.spec.ts | 7 +++++ .../Search/SearchHighlights.spec.ts | 1 + .../components/Yjs/YjsCollaborators.spec.ts | 1 + .../__snapshots__/ItemFilter.spec.ts.snap | 8 ++--- .../components/sidebar/WebDavDetails.spec.ts | 8 +++++ .../editor/components/MentionMenu.spec.ts | 5 ++++ .../src/components/Account/AppTokens.vue | 2 +- .../src/components/Announcement.vue | 4 +-- .../src/components/Topbar/Notifications.vue | 8 ++++- .../src/components/Topbar/UserMenu.vue | 10 +++++-- .../src/pages/account/accountCalendar.vue | 5 ++-- .../unit/components/Announcement.spec.ts | 10 +++++++ .../components/Topbar/Notifications.spec.ts | 3 ++ .../__snapshots__/UserMenu.spec.ts.snap | 16 +++++----- .../pages/account/accountCalendar.spec.ts | 1 + 59 files changed, 291 insertions(+), 64 deletions(-) create mode 100644 packages/web-app-app-store/tests/unit/components/AppTitle.spec.ts diff --git a/packages/design-system/src/components/OcBreadcrumb/OcBreadcrumb.spec.ts b/packages/design-system/src/components/OcBreadcrumb/OcBreadcrumb.spec.ts index 4f679e027c3..db741760b64 100644 --- a/packages/design-system/src/components/OcBreadcrumb/OcBreadcrumb.spec.ts +++ b/packages/design-system/src/components/OcBreadcrumb/OcBreadcrumb.spec.ts @@ -1,4 +1,5 @@ import { defaultPlugins, PartialComponentProps, shallowMount } from '@opencloud-eu/web-test-helpers' +import { nextTick } from 'vue' import Breadcrumb from './OcBreadcrumb.vue' import OcBreadcrumbItem from './OcBreadcrumbItem.vue' import OcBreadcrumbContextMenu from './OcBreadcrumbContextMenu.vue' @@ -56,6 +57,23 @@ describe('OcBreadcrumb', () => { expect(wrapper.findComponent(OcBreadcrumbContextMenu).exists()).toBe(false) }) }) + describe('collapsed items', () => { + it('shows the full name as title on the truncated item text', async () => { + const longName = 'A very long folder name that does not fit into the drop' + const { wrapper } = getWrapper({ + maxWidth: 50, + items: [ + { id: '1', text: 'Root', to: { path: '/root' } }, + { id: '2', text: longName, to: { path: '/root/long' } }, + { id: '3', text: 'Parent', to: { path: '/root/long/parent' } }, + { id: '4', text: 'Current', to: { path: '/root/long/parent/current' } } + ] + }) + await nextTick() + const texts = wrapper.findAll('.oc-breadcrumb-truncation-item-text') + expect(texts.map((text) => text.attributes('title'))).toEqual([longName, 'Parent']) + }) + }) describe('mobile navigation', () => { it.each([ { items: [], shows: false }, diff --git a/packages/design-system/src/components/OcBreadcrumb/OcBreadcrumb.vue b/packages/design-system/src/components/OcBreadcrumb/OcBreadcrumb.vue index 540c52f8eeb..52a28e8037b 100644 --- a/packages/design-system/src/components/OcBreadcrumb/OcBreadcrumb.vue +++ b/packages/design-system/src/components/OcBreadcrumb/OcBreadcrumb.vue @@ -66,7 +66,11 @@ size-class="size-4" /> - {{ truncationItem.text }} + diff --git a/packages/design-system/src/components/OcBreadcrumb/OcBreadcrumbItem.spec.ts b/packages/design-system/src/components/OcBreadcrumb/OcBreadcrumbItem.spec.ts index 85fd1cced4e..a98cc68dee4 100644 --- a/packages/design-system/src/components/OcBreadcrumb/OcBreadcrumbItem.spec.ts +++ b/packages/design-system/src/components/OcBreadcrumb/OcBreadcrumbItem.spec.ts @@ -41,6 +41,14 @@ describe('OcBreadcrumbItem', () => { expect(wrapper.find('[aria-current="page"]').exists()).toBe(false) expect(wrapper.find('.font-bold').exists()).toBe(false) }) + it('shows the full item text as title, so truncated names stay readable', () => { + const text = 'A very long folder name that gets truncated in the breadcrumb' + const { wrapper } = getWrapper({ + item: { text, to: { path: 'folder' } }, + textClass: 'truncate' + }) + expect(wrapper.find('.truncate').attributes('title')).toBe(text) + }) it('renders item icons passed via props', () => { const { wrapper } = getWrapper({ item: { text: 'Vault', icon: 'resource-type-vault', iconAccessibleLabel: 'Encrypted vault' } diff --git a/packages/design-system/src/components/OcBreadcrumb/OcBreadcrumbItem.vue b/packages/design-system/src/components/OcBreadcrumb/OcBreadcrumbItem.vue index a8609ae1209..133d78bbdff 100644 --- a/packages/design-system/src/components/OcBreadcrumb/OcBreadcrumbItem.vue +++ b/packages/design-system/src/components/OcBreadcrumb/OcBreadcrumbItem.vue @@ -5,7 +5,7 @@ :aria-current="current ? 'page' : null" :class="{ 'text-role-on-surface': !item.onClick, 'font-bold': current && isInteractive }" > - + () @layer components { .details-list dd { - @apply font-semibold mb-2 last-of-type:mb-0; + @apply font-semibold mb-2 last-of-type:mb-0 wrap-anywhere; } .details-list dt { diff --git a/packages/design-system/src/components/OcDrop/OcMobileDrop.spec.ts b/packages/design-system/src/components/OcDrop/OcMobileDrop.spec.ts index 93de3bd330c..5cf7d7c389c 100644 --- a/packages/design-system/src/components/OcDrop/OcMobileDrop.spec.ts +++ b/packages/design-system/src/components/OcDrop/OcMobileDrop.spec.ts @@ -47,6 +47,15 @@ describe('OcMobileDrop', () => { expect(label).toBe('Close Bottom Drawer') }) + it('shows the full drawer title as native title on the truncated header', async () => { + const { wrapper } = getWrapper() + await wrapper.find(selectors.toggle).trigger('click') + const header = wrapper.find('.oc-mobile-drop span.truncate') + const title = header.attributes('title') + wrapper.unmount() + expect(title).toBe('Bottom Drawer') + }) + it('does not render when toggle is not clicked', async () => { const { wrapper } = getWrapper() await nextTick() diff --git a/packages/design-system/src/components/OcDrop/OcMobileDrop.vue b/packages/design-system/src/components/OcDrop/OcMobileDrop.vue index 2baf29be499..7014c28be40 100644 --- a/packages/design-system/src/components/OcDrop/OcMobileDrop.vue +++ b/packages/design-system/src/components/OcDrop/OcMobileDrop.vue @@ -23,7 +23,7 @@ > - + { const { wrapper } = getWrapper({ props: { selectedItemNames } }) expect(wrapper.find(selectors.filterChipLabel).text()).toEqual(selectedItemNames[0]) }) + it('shows the full first selected item name as title on the truncated label', () => { + const selectedItemNames = ['A very long tag name that gets truncated', 'Marie'] + const { wrapper } = getWrapper({ props: { selectedItemNames } }) + expect(wrapper.find(selectors.filterChipLabel).attributes('title')).toEqual( + selectedItemNames[0] + ) + }) + it('does not set a title on the static filter label', () => { + const { wrapper } = getWrapper() + expect(wrapper.find(selectors.filterChipLabel).attributes('title')).toBeUndefined() + }) it('emits the "clearFilter"-event when clicking the clear-button', async () => { const { wrapper } = getWrapper({ props: { selectedItemNames: ['Einstein', 'Marie'] } }) await wrapper.find(selectors.clearBtn).trigger('click') diff --git a/packages/design-system/src/components/OcFilterChip/OcFilterChip.vue b/packages/design-system/src/components/OcFilterChip/OcFilterChip.vue index 340d81046dd..ed2d3f88c49 100644 --- a/packages/design-system/src/components/OcFilterChip/OcFilterChip.vue +++ b/packages/design-system/src/components/OcFilterChip/OcFilterChip.vue @@ -31,6 +31,7 @@ /> diff --git a/packages/design-system/src/components/OcModal/OcModal.spec.ts b/packages/design-system/src/components/OcModal/OcModal.spec.ts index 33fe7636214..e60f48405b2 100644 --- a/packages/design-system/src/components/OcModal/OcModal.spec.ts +++ b/packages/design-system/src/components/OcModal/OcModal.spec.ts @@ -42,6 +42,16 @@ describe('OcModal', () => { expect(wrapper.html()).toMatchSnapshot() }) + it('shows the full title as native title on the truncated heading', () => { + const title = 'Rename folder »A very long folder name that gets truncated«' + const wrapper = shallowMount(Modal, { + global: { renderStubDefaultSlot: true, plugins: [...defaultPlugins()] }, + props: { ...defaultProps, title } + }) + + expect(wrapper.find('.oc-modal-title h2').attributes('title')).toBe(title) + }) + it('only dims the background while it is the topmost modal', () => { const backdrop = (active: boolean) => shallowMount(Modal, { diff --git a/packages/design-system/src/components/OcModal/OcModal.vue b/packages/design-system/src/components/OcModal/OcModal.vue index c46f33d5189..f7f2071c43b 100644 --- a/packages/design-system/src/components/OcModal/OcModal.vue +++ b/packages/design-system/src/components/OcModal/OcModal.vue @@ -20,7 +20,7 @@
-

+

displays input 1`] = `