This commit is contained in:
2026-06-16 06:18:42 +02:00
parent 3f5078841d
commit 66248c4736
39 changed files with 6699 additions and 94 deletions

93
test/app.test.tsx Normal file
View File

@@ -0,0 +1,93 @@
// @vitest-environment jsdom
import { afterEach, beforeAll, describe, expect, it, vi } from 'vitest'
import { cleanup, fireEvent, render, waitFor, within } from '@testing-library/react'
import React from 'react'
// The terminals use xterm + ResizeObserver, which don't belong in a jsdom unit
// test. Stub them — the rest of the workbench renders for real against the mock.
vi.mock('../src/renderer/src/terminals', () => {
let n = 0
return { Terminal: () => null, lid: () => ++n }
})
import { App } from '../src/renderer/src/App'
import { ProjectProvider } from '../src/renderer/src/project'
beforeAll(() => {
// jsdom gaps used by the tree/tab code.
globalThis.ResizeObserver = class { observe() {} unobserve() {} disconnect() {} } as never
if (!Element.prototype.scrollIntoView) Element.prototype.scrollIntoView = () => {}
})
afterEach(() => { cleanup(); localStorage.clear() })
function renderApp(): HTMLElement {
const { container } = render(
<ProjectProvider>
<App />
</ProjectProvider>,
)
return container
}
function rowWithText(container: HTMLElement, selector: string, text: string): HTMLElement | undefined {
return Array.from(container.querySelectorAll<HTMLElement>(selector)).find((el) => el.textContent?.includes(text))
}
describe('App (mock data, jsdom)', () => {
it('renders the four-column workbench with the git change list', async () => {
const c = renderApp()
await waitFor(() => expect(rowWithText(c, '.git-row', 'UserController.php')).toBeTruthy())
expect(c.querySelector('.workbench')).toBeTruthy()
expect(c.textContent).toContain('Source Control')
expect(c.textContent).toContain('Explorer')
// no file open yet
expect(c.textContent).toContain('No file open')
})
it('opens a changed file from the git panel into a diff tab', async () => {
const c = renderApp()
const row = await waitFor(() => {
const r = rowWithText(c, '.git-row', 'UserController.php')
if (!r) throw new Error('row not ready')
return r
})
fireEvent.click(row)
await waitFor(() => expect(rowWithText(c, '.tab', 'UserController.php')).toBeTruthy())
// changed file → diff toolbar with a status word
expect(c.querySelector('.diff-bar')?.textContent).toContain('Modified')
})
it('makes an edited buffer dirty (tab dot)', async () => {
const c = renderApp()
const treeRow = await waitFor(() => {
const r = rowWithText(c, '.tree-row', 'store.js')
if (!r) throw new Error('tree not ready')
return r
})
fireEvent.click(treeRow) // unchanged file → editable "code" mode
const ta = await waitFor(() => {
const t = c.querySelector<HTMLTextAreaElement>('.ce-ta')
if (!t) throw new Error('editor not ready')
return t
})
expect(c.querySelector('.tab.dirtyclose')).toBeNull()
fireEvent.change(ta, { target: { value: '// edited\n' } })
await waitFor(() => expect(c.querySelector('.tab.dirtyclose')).toBeTruthy())
})
it('searches file contents from the search modal', async () => {
const c = renderApp()
await waitFor(() => expect(rowWithText(c, '.git-row', 'UserController.php')).toBeTruthy())
const searchBtn = rowWithText(c, '.tb-btn', 'Search')!
fireEvent.click(searchBtn)
const modal = await waitFor(() => {
const m = c.querySelector('.search-modal')
if (!m) throw new Error('modal not open')
return m as HTMLElement
})
const input = within(modal).getByPlaceholderText(/Search content/i)
fireEvent.change(input, { target: { value: 'balance' } })
await waitFor(() => expect(modal.querySelectorAll('.sr-file').length).toBeGreaterThan(0))
})
})