mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 00:12:39 +00:00
fix: show the full folder name as a tooltip when the sidebar cuts it
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import type { Mailbox } from '@/lib/jmap/types';
|
||||
|
||||
// R11: text cut with an ellipsis gets a title with the full text. A folder
|
||||
// name the sidebar had no shorter form for was cut by CSS with no title
|
||||
// ("Gemeinsamer Postein…").
|
||||
|
||||
vi.mock('@/components/plugins/plugin-slot', () => ({ PluginSlot: () => null }));
|
||||
vi.mock('@/components/tour/tour-provider', () => ({
|
||||
useTour: () => ({ startTour: vi.fn(), resetTourCompletion: vi.fn(), activeTour: null, endTour: vi.fn(), registerStep: vi.fn() }),
|
||||
}));
|
||||
vi.mock('@/contexts/drag-drop-context', () => ({
|
||||
useDragDropContext: () => ({ isDragging: false }),
|
||||
}));
|
||||
vi.mock('@/hooks/use-mailbox-drop', () => ({
|
||||
useMailboxDrop: () => ({ dropHandlers: {}, isValidDropTarget: false, isInvalidDropTarget: false }),
|
||||
}));
|
||||
vi.mock('@/hooks/use-tag-drop', () => ({
|
||||
useTagDrop: () => ({ dropHandlers: {}, isValidDropTarget: false }),
|
||||
}));
|
||||
vi.mock('../account-switcher', () => ({ AccountSwitcher: () => null }));
|
||||
|
||||
const { Sidebar } = await import('../sidebar');
|
||||
const { useAuthStore } = await import('@/stores/auth-store');
|
||||
const { useSettingsStore } = await import('@/stores/settings-store');
|
||||
|
||||
const box = (id: string, name: string) => ({
|
||||
id, name, role: null, parentId: null, totalEmails: 0, unreadEmails: 0, myRights: {},
|
||||
}) as unknown as Mailbox;
|
||||
const mailboxes: Mailbox[] = [box('a', 'A very long folder name that does not fit'), box('b', 'Short')];
|
||||
|
||||
function label(name: string): HTMLElement {
|
||||
return screen.getByText(name);
|
||||
}
|
||||
function setWidths(el: HTMLElement, scroll: number, client: number) {
|
||||
Object.defineProperty(el, 'scrollWidth', { configurable: true, value: scroll });
|
||||
Object.defineProperty(el, 'clientWidth', { configurable: true, value: client });
|
||||
}
|
||||
|
||||
describe('cut folder names', () => {
|
||||
beforeEach(() => {
|
||||
useAuthStore.setState({ activeAccountId: 'login-a', client: { getAccountId: () => 'me', getCapabilities: () => ({}) } } as never);
|
||||
useSettingsStore.setState({ hideAccountSwitcher: true } as never);
|
||||
});
|
||||
afterEach(() => vi.restoreAllMocks());
|
||||
|
||||
it('show the full name as a title when the ellipsis cuts them', () => {
|
||||
render(<Sidebar mailboxes={mailboxes} selectedMailbox="b" />);
|
||||
const long = label('A very long folder name that does not fit');
|
||||
setWidths(long, 300, 120);
|
||||
fireEvent.pointerEnter(long);
|
||||
expect(long.title).toBe('A very long folder name that does not fit');
|
||||
});
|
||||
|
||||
it('get no title when they fit', () => {
|
||||
render(<Sidebar mailboxes={mailboxes} selectedMailbox="b" />);
|
||||
const short = label('Short');
|
||||
setWidths(short, 40, 120);
|
||||
fireEvent.pointerEnter(short);
|
||||
expect(short.title).toBe('');
|
||||
});
|
||||
});
|
||||
@@ -304,6 +304,15 @@ function SidebarRowCounts({
|
||||
* What a row's menu handler needs from an event, for a menu opened by a
|
||||
* long-press (at the finger) or from the row's ⋮ (under the button).
|
||||
*/
|
||||
/**
|
||||
* R11: a name cut with an ellipsis gets a title with the full text. Measured
|
||||
* when the pointer arrives, since only then is the title shown: a shortened
|
||||
* form, or one the row's width still cuts.
|
||||
*/
|
||||
function titleIfCut(element: HTMLElement, full: string, shortened: boolean) {
|
||||
element.title = shortened || element.scrollWidth > element.clientWidth ? full : "";
|
||||
}
|
||||
|
||||
function menuEventAt(element: HTMLElement, point?: { clientX: number; clientY: number }): React.MouseEvent {
|
||||
return {
|
||||
type: point ? "contextmenu" : "click",
|
||||
@@ -460,7 +469,14 @@ function SidebarRow({
|
||||
</span>
|
||||
{!isCollapsed && (
|
||||
<>
|
||||
<span ref={labelRef} className="flex-1 truncate" title={shortenedLabel !== label ? label : undefined}>{shortenedLabel}</span>
|
||||
<span
|
||||
ref={labelRef}
|
||||
className="flex-1 truncate"
|
||||
title={shortenedLabel !== label ? label : undefined}
|
||||
onPointerEnter={(e) => titleIfCut(e.currentTarget, label, shortenedLabel !== label)}
|
||||
>
|
||||
{shortenedLabel}
|
||||
</span>
|
||||
{muted && (
|
||||
<BellOff
|
||||
className="w-3.5 h-3.5 flex-shrink-0 text-muted-foreground"
|
||||
|
||||
Reference in New Issue
Block a user