diff --git a/src/renderer/src/components/settings/SshTargetCard.test.tsx b/src/renderer/src/components/settings/SshTargetCard.test.tsx new file mode 100644 index 00000000000..6bcdfd45a1e --- /dev/null +++ b/src/renderer/src/components/settings/SshTargetCard.test.tsx @@ -0,0 +1,84 @@ +// @vitest-environment happy-dom + +import { act } from 'react' +import { createRoot } from 'react-dom/client' +import { afterEach, describe, expect, it, vi } from 'vitest' +import { SshTargetCard } from './SshTargetCard' +import { TooltipProvider } from '../ui/tooltip' +import type { SshConnectionState, SshTarget } from '../../../../shared/ssh-types' + +afterEach(() => { + document.body.innerHTML = '' +}) + +const target: SshTarget = { + id: 'target-1', + label: 'build-01', + host: 'build-01.internal', + port: 22, + username: 'deploy' +} + +/** The real thing a host key mismatch produces: the remedy is the last clause. */ +const HOST_KEY_ERROR = + 'Host key verification failed for build-01.internal. The key does not match the entry in your known_hosts file. ssh and git will refuse this host too. Run: ssh-keygen -R build-01.internal' + +async function renderCard(state: SshConnectionState | undefined): Promise { + const container = document.createElement('div') + document.body.appendChild(container) + await act(async () => { + createRoot(container).render( + + + + ) + }) + return container +} + +const errorState = (error: string): SshConnectionState => ({ + targetId: 'target-1', + status: 'error', + error, + reconnectAttempt: 0, + supportsFolderDownload: false +}) + +describe('the connection error on an SSH target card', () => { + it('shows the whole message, remedy included', async () => { + const container = await renderCard(errorState(HOST_KEY_ERROR)) + + expect(container.textContent).toContain('ssh-keygen -R build-01.internal') + }) + + // This is the regression that made the careful wording pointless: `truncate` clamps to one line + // with an ellipsis and there is no title attribute, so the remedy was unreachable even on hover. + it('does not clamp it to a single line', async () => { + const container = await renderCard(errorState(HOST_KEY_ERROR)) + const paragraph = [...container.querySelectorAll('p')].find((node) => + node.textContent?.includes('ssh-keygen') + ) + + expect(paragraph).toBeDefined() + expect(paragraph?.className).not.toContain('truncate') + // A long hostname has no break opportunity, so wrapping alone would still overflow the column. + expect(paragraph?.className).toContain('[overflow-wrap:anywhere]') + }) + + it('renders nothing when there is no error', async () => { + const container = await renderCard(undefined) + + expect(container.textContent).not.toContain('Host key verification failed') + }) +}) diff --git a/src/renderer/src/components/settings/SshTargetCard.tsx b/src/renderer/src/components/settings/SshTargetCard.tsx index 6a99f0fe648..bf9043b68ec 100644 --- a/src/renderer/src/components/settings/SshTargetCard.tsx +++ b/src/renderer/src/components/settings/SshTargetCard.tsx @@ -301,8 +301,10 @@ export function SshTargetCard({ {target.identityFile ? ` \u2022 ${target.identityFile}` : ''} {` \u2022 ${terminalPersistence}`}

+ {/* Why not truncate: host key failures put the remedy (`ssh-keygen -R `) at the end, + and a one-line clamp with no tooltip made it unreachable even on hover. */} {state?.error ? ( -

{state.error}

+

{state.error}

) : null}