diff --git a/src/__tests__/show.test.ts b/src/__tests__/show.test.ts index 5e8467bb..7b75dc6c 100644 --- a/src/__tests__/show.test.ts +++ b/src/__tests__/show.test.ts @@ -1,18 +1,27 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; +import React from 'react'; +import { Readable, Writable } from 'node:stream'; +import { render } from 'ink'; import { getRunningContainers } from '../docker/containers'; import { getRunningPods } from '../kubernetes/pods'; import { logger } from '../utils/logger'; import { showRunners, showPods } from '../commands/show'; import { createSpinner } from '../ui/spinner'; import * as tableUtils from '../ui/table'; +import { ShowDashboard } from '../ui/show/ShowDashboard'; // Mock dependencies vi.mock('../docker/containers', () => ({ getRunningContainers: vi.fn(), + getDockerSystemStats: vi.fn(async () => null), + formatDockerBytes: vi.fn(() => '0 B'), })); vi.mock('../kubernetes/pods', () => ({ getRunningPods: vi.fn(), + getK8sClusterStats: vi.fn(async () => null), })); +vi.mock('../kubernetes/resources', () => ({ listNodes: vi.fn(async () => []) })); +vi.mock('../minikube/client', () => ({ getMinikubeStatus: vi.fn(async () => []) })); vi.mock('../utils/logger', () => ({ logger: { warn: vi.fn(), @@ -85,3 +94,76 @@ describe('show command runners', () => { expect(spinnerInstance.fail).toHaveBeenCalledWith(expect.stringContaining('Failed to fetch Kubernetes pods')); }); }); + +class DashboardStdout extends Writable { + output = ''; + isTTY = true; + columns = 100; + rows = 30; + _write(chunk: any, _encoding: string, callback: (error?: Error | null) => void) { + this.output += chunk.toString(); + callback(); + } +} +class DashboardStdin extends Readable { + isTTY = true; + setRawMode = vi.fn(); + setEncoding = vi.fn(); + ref = vi.fn(); + unref = vi.fn(); + _read() {} + send(value: string) { this.push(Buffer.from(value)); } +} + +describe('ShowDashboard filtered search', () => { + it('blurs search without losing the query and navigates filtered rows', async () => { + vi.mocked(getRunningPods).mockResolvedValue([ + { name: 'api-server', namespace: 'default', status: 'Running', restarts: 0, node: 'node-1' } as any, + { name: 'worker', namespace: 'default', status: 'Running', restarts: 0, node: 'node-1' } as any, + ]); + vi.mocked(getRunningContainers).mockResolvedValue([]); + const stdout = new DashboardStdout(); + const stdin = new DashboardStdin(); + const app = render(React.createElement(ShowDashboard), { stdin, stdout, debug: true }); + const wait = () => new Promise((resolve) => setTimeout(resolve, 60)); + await wait(); + stdin.send('/'); + await wait(); + stdin.send('api'); + await wait(); + stdin.send('\r'); + await wait(); + expect(stdout.output).toContain('Search: api'); + stdin.send('\u001b[B'); + await wait(); + stdin.send('\r'); + await wait(); + expect(stdout.output).toContain('Pod: api-server'); + app.unmount(); + stdin.push(null); + }); + + it('clears a blurred search filter on Escape', async () => { + vi.mocked(getRunningPods).mockResolvedValue([ + { name: 'api-server', namespace: 'default', status: 'Running', restarts: 0, node: 'node-1' } as any, + { name: 'worker', namespace: 'default', status: 'Running', restarts: 0, node: 'node-1' } as any, + ]); + vi.mocked(getRunningContainers).mockResolvedValue([]); + const stdout = new DashboardStdout(); + const stdin = new DashboardStdin(); + const app = render(React.createElement(ShowDashboard), { stdin, stdout, debug: true }); + const wait = () => new Promise((resolve) => setTimeout(resolve, 60)); + await wait(); + stdin.send('/'); + await wait(); + stdin.send('api'); + await wait(); + stdin.send('\r'); + await wait(); + stdin.send('\u001b'); + await wait(); + expect(stdout.output.slice(-500)).toContain('worker'); + app.unmount(); + stdin.push(null); + }); +}); diff --git a/src/ui/show/ShowDashboard.tsx b/src/ui/show/ShowDashboard.tsx index 5e7698f3..f3e5a1d0 100644 --- a/src/ui/show/ShowDashboard.tsx +++ b/src/ui/show/ShowDashboard.tsx @@ -265,13 +265,17 @@ export const ShowDashboard: React.FC = ({ onBack, onExit }) } if (showSearch) { - if (key.escape) { + if (key.escape || key.return) { setShowSearch(false); - setSearchQuery(''); } return; } + if (key.escape && searchQuery) { + setSearchQuery(''); + return; + } + if (key.escape || input.toLowerCase() === 'b') { triggerBack(onBack, exit); return; @@ -331,10 +335,6 @@ export const ShowDashboard: React.FC = ({ onBack, onExit }) return; } - if (key.escape && showSearch) { - setShowSearch(false); - setSearchQuery(''); - } }); const currentData = getTabData(activeTab);