// @vitest-environment jsdom import { cleanup, fireEvent, render, screen } from '@testing-library/react'; import { afterEach, describe, expect, it, vi } from 'vitest'; import { FileOpsSummary } from '../../src/components/FileOpsSummary'; import type { FileOpEntry } from '../../src/runtime/file-ops'; function entry(partial: Partial & { path: string }): FileOpEntry { return { fullPath: `/repo/${partial.path}`, ops: ['write'], opCounts: { read: 0, write: 1, edit: 0, delete: 0 }, total: 1, status: 'done', ...partial, }; } describe('FileOpsSummary', () => { afterEach(() => cleanup()); it('renders nothing when there are no entries', () => { const { container } = render( , ); expect(container.firstChild).toBeNull(); }); it('renders one produced file as a framed direct row without a redundant title card', () => { const { container } = render( , ); expect(screen.getByTestId('file-ops-row-result.html')).toBeTruthy(); expect(container.querySelector('.file-ops')).not.toHaveClass('file-ops--single'); expect(screen.queryByTestId('file-ops-toggle')).toBeNull(); expect(screen.queryByText('Files from this turn')).toBeNull(); }); it('shows up to four files directly without inheriting the run state', () => { const { container } = render( , ); expect(screen.getByText(/Write 2/)).toBeTruthy(); // #5909: the "Files from this turn" header counts unique produced files, // not write operations. c.ts was edited three times but is one file, so // the edit total is 2 (a.ts + c.ts), not the op-level count of 4. expect(screen.getByText(/Edit 2/)).toBeTruthy(); expect(screen.queryByText(/Delete/)).toBeNull(); expect(screen.queryByText(/Read/)).toBeNull(); expect(screen.getByTestId('file-ops-row-a.ts')).toBeTruthy(); expect(screen.getByTestId('file-ops-row-b.ts')).toBeTruthy(); expect(container.querySelector('.file-ops')).not.toHaveClass('is-streaming'); const toggle = screen.getByTestId('file-ops-toggle'); expect(toggle.getAttribute('aria-expanded')).toBeNull(); }); it('shows small completed result sets without a disclosure click', () => { render( , ); expect(screen.getByTestId('file-ops-row-a.ts')).toBeTruthy(); expect(screen.getByTestId('file-ops-row-b.ts')).toBeTruthy(); }); it('keeps a small result set visible across rerenders', () => { const { rerender } = render( , ); expect(screen.getByTestId('file-ops-row-a.ts')).toBeTruthy(); rerender( , ); expect(screen.getByTestId('file-ops-row-a.ts')).toBeTruthy(); }); it('shows the first four files and collapses only the remaining rows', () => { render( , ); const toggle = screen.getByTestId('file-ops-toggle'); expect(toggle.getAttribute('aria-expanded')).toBe('false'); expect(screen.getByTestId('file-ops-row-a.ts')).toBeTruthy(); expect(screen.getByTestId('file-ops-row-d.ts')).toBeTruthy(); expect(screen.queryByTestId('file-ops-row-e.ts')).toBeNull(); expect(screen.getByText('+1 more')).toBeTruthy(); fireEvent.click(toggle); expect(toggle.getAttribute('aria-expanded')).toBe('true'); expect(screen.getByTestId('file-ops-row-e.ts')).toBeTruthy(); }); it('shows the open button only for files that are present in the project file set', () => { const onRequestOpenFile = vi.fn(); render( , ); expect(screen.getByTestId('file-ops-row-open-a.ts')).toBeTruthy(); expect(screen.queryByTestId('file-ops-row-open-missing.ts')).toBeNull(); fireEvent.click(screen.getByTestId('file-ops-row-open-a.ts')); expect(onRequestOpenFile).toHaveBeenCalledWith('a.ts'); }); it('keeps the header free of a redundant open action', () => { const onRequestOpenFile = vi.fn(); render( , ); expect(screen.queryByTestId('file-ops-primary-open-result.ts')).toBeNull(); fireEvent.click(screen.getByTestId('file-ops-row-open-result.ts')); expect(onRequestOpenFile).toHaveBeenCalledWith('result.ts'); expect(screen.getByTestId('file-ops-toggle').getAttribute('aria-expanded')).toBe('false'); }); it('does not show the open button for deleted files', () => { const onRequestOpenFile = vi.fn(); render( , ); expect(screen.getByTestId('file-ops-row-gone.ts')).toBeTruthy(); expect(screen.queryByTestId('file-ops-row-open-gone.ts')).toBeNull(); }); it('keeps execution history and run state out of artifact rows', () => { render( , ); const row = screen.getByTestId('file-ops-row-index.html'); expect(row.className).not.toContain('file-ops-row--running'); expect(row.querySelector('.file-ops-badge--edit')).toBeTruthy(); expect(row.querySelector('.file-ops-badge--read')).toBeNull(); expect(row.querySelectorAll('.file-ops-badge')).toHaveLength(1); expect(row.querySelector('.file-ops-badge--edit svg')).toBeTruthy(); expect(row.querySelector('.file-ops-badge--edit')?.textContent).toBe(''); expect(row.querySelector('.file-ops-row-status')).toBeNull(); expect(screen.queryByText('running…')).toBeNull(); }); });