// @vitest-environment jsdom import { afterEach, beforeAll, describe, expect, it, vi } from 'vitest' import { act, cleanup, fireEvent, render, waitFor } from '@testing-library/react' import React from 'react' 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(() => { globalThis.ResizeObserver = class { observe() {} unobserve() {} disconnect() {} } as never if (!Element.prototype.scrollIntoView) Element.prototype.scrollIntoView = () => {} }) afterEach(() => { cleanup(); localStorage.clear() }) function find(c: HTMLElement, sel: string, text: string): HTMLElement | undefined { return Array.from(c.querySelectorAll(sel)).find((el) => el.textContent?.trim() === text) } async function openChanged(): Promise { const c = render().container const row = await waitFor(() => { const r = Array.from(c.querySelectorAll('.git-row')).find((el) => el.textContent?.includes('UserController.php')) if (!r) throw new Error('git not ready') return r }) fireEvent.click(row) await waitFor(() => expect(c.querySelector('.diff-bar')).toBeTruthy()) return c } describe('Editor view modes', () => { it('Actual mode is an editable buffer; Original is read-only', async () => { const c = await openChanged() fireEvent.click(find(c, '.seg button', 'Actual')!) await waitFor(() => expect(c.querySelector('.ce-ta')).toBeTruthy()) fireEvent.click(find(c, '.seg button', 'Original')!) await waitFor(() => expect(c.querySelector('.ce-ta')).toBeNull()) expect(c.querySelector('.editor .ln-row')).toBeTruthy() }) it('Split opens a full-screen two-pane overlay and Esc collapses it', async () => { const c = await openChanged() fireEvent.click(c.querySelector('.split-btn')!) await waitFor(() => expect(c.querySelector('.split-overlay')).toBeTruthy()) expect(c.querySelector('.split-pane.left')).toBeTruthy() expect(c.querySelector('.split-pane.right')).toBeTruthy() act(() => { window.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })) }) await waitFor(() => expect(c.querySelector('.split-overlay')).toBeNull()) }) }) // Default config wraps Markdown only (editor.wordWrap: 'markdown'). A wrapped // buffer drops the fixed gutter column for one block per line, so the folded // rows carry no number. describe('Word wrap', () => { async function openTree(c: HTMLElement, name: string): Promise { const row = await waitFor(() => { const r = Array.from(c.querySelectorAll('.tree-row')).find((el) => el.textContent?.includes(name)) if (!r) throw new Error(`${name} not ready`) return r }) fireEvent.click(row) } it('folds long lines in Markdown, one numbered block per source line', async () => { const c = render().container await openTree(c, 'README.md') const edit = await waitFor(() => { const el = c.querySelector('.code-edit') if (!el) throw new Error('editor not ready') return el }) expect(edit.classList.contains('wrap')).toBe(true) expect(edit.querySelector('.ce-gutter')).toBeNull() const ta = edit.querySelector('.ce-ta')! expect(ta.getAttribute('wrap')).toBe('soft') expect(edit.querySelectorAll('.ce-line').length).toBe(ta.value.split('\n').length) }) it('leaves other file types on the scrolling gutter layout', async () => { const c = render().container await openTree(c, 'composer.json') const edit = await waitFor(() => { const el = c.querySelector('.code-edit') if (!el) throw new Error('editor not ready') return el }) expect(edit.classList.contains('wrap')).toBe(false) expect(edit.querySelector('.ce-gutter')).toBeTruthy() expect(edit.querySelector('.ce-line')).toBeNull() }) })