mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
Long project names in the "Existing projects" section of the automation project picker rendered verbatim with no truncate styling, so a single name like "A very long project name that would otherwise wrap onto several lines" blew up the row height and made the dropdown messy to scan. The expected behavior is a single-line label with ellipsis, with the full name still discoverable on hover. Add the standard truncate triad (`white-space: nowrap`, `overflow: hidden`, `text-overflow: ellipsis`) to `.automation-popover__label`. The parent `.automation-popover__body` already sets `min-width: 0`, so the ellipsis renders cleanly. Thread an optional `title` prop through `PopoverItem` and pass each project's full name from the picker call site, so the native hover tooltip carries the unclipped name. Other PopoverItems with fixed in-product copy (e.g. "New project each run") deliberately omit the title — they never exceed the row width and the redundant tooltip would be noise. Regression test covers the DOM contract (every project row has `title=<full name>`, fixed rows do not); the CSS half is verified by code review since jsdom does not apply stylesheets.
This commit is contained in:
parent
e76eb6da63
commit
9305bd1cff
3 changed files with 102 additions and 0 deletions
|
|
@ -818,6 +818,7 @@ export function NewAutomationModal({
|
|||
setPopover(null);
|
||||
}}
|
||||
label={p.name}
|
||||
title={p.name}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
|
|
@ -1018,17 +1019,24 @@ function PopoverItem({
|
|||
label,
|
||||
hint,
|
||||
onClick,
|
||||
title,
|
||||
}: {
|
||||
selected?: boolean;
|
||||
label: string;
|
||||
hint?: string;
|
||||
onClick: () => void;
|
||||
// Native hover tooltip surfaced when the visible label is truncated to
|
||||
// ellipsis (e.g. long project names in the picker, #3274). Optional so
|
||||
// unchanged call sites with short fixed labels don't grow a noisy
|
||||
// duplicate tooltip.
|
||||
title?: string;
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={`automation-popover__item${selected ? ' is-selected' : ''}`}
|
||||
onClick={onClick}
|
||||
title={title}
|
||||
>
|
||||
<span className="automation-popover__check">
|
||||
{selected ? <Icon name="check" size={12} /> : null}
|
||||
|
|
|
|||
|
|
@ -1444,6 +1444,20 @@
|
|||
font-size: 12.5px;
|
||||
font-weight: 680;
|
||||
color: var(--text-strong);
|
||||
/*
|
||||
* Single-line truncate with ellipsis (#3274). Long project names in the
|
||||
* "Existing projects" section of the automation project picker used to
|
||||
* wrap across multiple lines, blowing up each row's height and making
|
||||
* the dropdown hard to scan. The parent `.automation-popover__body`
|
||||
* already sets `min-width: 0` so the flex child can shrink below its
|
||||
* intrinsic content width — these three properties complete the
|
||||
* standard truncate triad. The full name is preserved via the row's
|
||||
* `title` attribute (set by the picker call site) so it remains
|
||||
* discoverable via the native hover tooltip.
|
||||
*/
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.automation-popover__hint {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,80 @@
|
|||
// @vitest-environment jsdom
|
||||
//
|
||||
// Regression for #3274. The automation project picker rendered long project
|
||||
// names verbatim with no truncate styling, so a single long name blew up
|
||||
// each row's height and made the dropdown messy to scan. The fix adds the
|
||||
// single-line truncate-with-ellipsis CSS triad to `.automation-popover__label`
|
||||
// and threads each project's full name through to the row's `title`
|
||||
// attribute so the native hover tooltip still surfaces it. The CSS half
|
||||
// is verified by code review (jsdom does not apply stylesheets); this
|
||||
// test locks in the DOM contract — every existing-project row must carry
|
||||
// `title=<full name>` so the tooltip exists even when the visible label
|
||||
// is clipped.
|
||||
|
||||
import { cleanup, fireEvent, render, screen } from '@testing-library/react';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { NewAutomationModal } from '../../src/components/NewAutomationModal';
|
||||
import { listPlugins } from '../../src/state/projects';
|
||||
import { fetchMcpServers } from '../../src/state/mcp';
|
||||
|
||||
vi.mock('../../src/state/projects', () => ({
|
||||
listPlugins: vi.fn().mockResolvedValue([]),
|
||||
}));
|
||||
|
||||
vi.mock('../../src/state/mcp', () => ({
|
||||
fetchMcpServers: vi.fn().mockResolvedValue({ servers: [], templates: [] }),
|
||||
}));
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('NewAutomationModal project picker', () => {
|
||||
it('exposes each project\'s full name as the row title so truncated labels still surface via tooltip (#3274)', () => {
|
||||
const longName = 'A very long project name that would otherwise wrap onto several lines inside the automation picker';
|
||||
render(
|
||||
<NewAutomationModal
|
||||
open
|
||||
templates={[]}
|
||||
projects={[
|
||||
{ id: 'p-1', name: longName },
|
||||
{ id: 'p-2', name: 'Short' },
|
||||
]}
|
||||
skills={[]}
|
||||
connectors={[]}
|
||||
onClose={() => undefined}
|
||||
onSaved={() => undefined}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Open the project popover. It is the only PillButton on the row that
|
||||
// toggles `popover === 'project'`; the visible label is the current
|
||||
// selection ("New project each run" by default) but the button still
|
||||
// shows the project icon, which we use as a stable accessible cue.
|
||||
const projectButton =
|
||||
screen.getByRole('button', { name: /New project each run/i });
|
||||
fireEvent.click(projectButton);
|
||||
|
||||
// Both project rows render, each with `title=<full name>` on the
|
||||
// button so the native tooltip preserves the full project name even
|
||||
// when the visible label is clipped by the ellipsis CSS.
|
||||
const longRow = screen.getByRole('button', { name: longName });
|
||||
expect(longRow.getAttribute('title')).toBe(longName);
|
||||
const shortRow = screen.getByRole('button', { name: 'Short' });
|
||||
expect(shortRow.getAttribute('title')).toBe('Short');
|
||||
|
||||
// PopoverItems with fixed in-product copy ("New project each run")
|
||||
// intentionally do NOT carry a tooltip; the truncate optimisation
|
||||
// is project-name-specific.
|
||||
const fixedRows = screen.getAllByRole('button', {
|
||||
name: /New project each run/i,
|
||||
});
|
||||
// The first match is the PillButton trigger we just clicked; the
|
||||
// second is the PopoverItem inside the open popover.
|
||||
expect(fixedRows.length).toBeGreaterThanOrEqual(2);
|
||||
const popoverFixedRow = fixedRows.at(-1);
|
||||
expect(popoverFixedRow?.getAttribute('title')).toBeNull();
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue