mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-09-01 05:07:59 +00:00
fix: truncate long error text in fleet prune estimate per-node rows (#1802)
Unreachable nodes rendered their error string in a shrink-0 span with no width bound, so a verbose daemon or proxy error pushed the row past the card edge. Bound the error span to 55% and ellipsize, matching the existing pattern in the label-and-stop card; the full message stays available via the row title tooltip.
This commit is contained in:
@@ -126,6 +126,31 @@ it('shows 0 reclaimable · partial when successful targets report zero bytes', a
|
|||||||
expect(screen.getByText('PART')).toBeInTheDocument();
|
expect(screen.getByText('PART')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('truncates a long unreachable error without clipping reachable byte counts', async () => {
|
||||||
|
const longError = `Pilot tunnel is disconnected: ${'x'.repeat(400)}`;
|
||||||
|
const twoNodes = [...nodes, { id: 2, name: 'edge', status: 'offline' }] as unknown as FleetNode[];
|
||||||
|
mockedFetch.mockImplementation((url: string) => {
|
||||||
|
if (url === '/fleet/prune/estimate') {
|
||||||
|
return Promise.resolve(jsonResponse(200, {
|
||||||
|
totalBytes: 4096,
|
||||||
|
perNode: [
|
||||||
|
{ nodeId: 1, nodeName: 'central', reclaimableBytes: 4096, reachable: true },
|
||||||
|
{ nodeId: 2, nodeName: 'edge', reclaimableBytes: 0, reachable: false, error: longError },
|
||||||
|
],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return Promise.resolve(jsonResponse(404, {}));
|
||||||
|
});
|
||||||
|
|
||||||
|
render(<FleetPruneCard nodes={twoNodes} />);
|
||||||
|
const errorSpan = await screen.findByText(longError);
|
||||||
|
expect(errorSpan.className).toContain('truncate');
|
||||||
|
expect(errorSpan.className).toContain('max-w-[55%]');
|
||||||
|
expect(screen.getByTitle(longError)).toBeInTheDocument();
|
||||||
|
const byteSpan = screen.getByText('4 KB');
|
||||||
|
expect(byteSpan.className).not.toContain('truncate');
|
||||||
|
});
|
||||||
|
|
||||||
it('drops the unlock footer once dry run review is valid', async () => {
|
it('drops the unlock footer once dry run review is valid', async () => {
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
mockedFetch.mockImplementation((url: string) => {
|
mockedFetch.mockImplementation((url: string) => {
|
||||||
|
|||||||
@@ -416,7 +416,7 @@ function EstimateSection({ estimate }: { estimate: EstimateState }) {
|
|||||||
{node.reachable ? (node.partial ? 'PART' : 'OK') : '--'}
|
{node.reachable ? (node.partial ? 'PART' : 'OK') : '--'}
|
||||||
</span>
|
</span>
|
||||||
<span className="min-w-0 flex-1 truncate font-mono text-[11px] text-stat-value">{node.nodeName}</span>
|
<span className="min-w-0 flex-1 truncate font-mono text-[11px] text-stat-value">{node.nodeName}</span>
|
||||||
<span className={cn(KICKER, 'shrink-0 tabular-nums', node.reachable ? 'text-stat-subtitle' : 'text-stat-icon')}>
|
<span className={cn(KICKER, 'shrink-0 tabular-nums', node.reachable ? 'text-stat-subtitle' : 'max-w-[55%] truncate text-stat-icon')}>
|
||||||
{node.reachable ? formatBytes(node.reclaimableBytes) : (node.error ?? 'unreachable')}
|
{node.reachable ? formatBytes(node.reclaimableBytes) : (node.error ?? 'unreachable')}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
Reference in New Issue
Block a user