Files
helder/test/editor-modes.test.tsx
2026-08-17 10:43:38 +02:00

100 lines
4.0 KiB
TypeScript

// @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<HTMLElement>(sel)).find((el) => el.textContent?.trim() === text)
}
async function openChanged(): Promise<HTMLElement> {
const c = render(<ProjectProvider><App /></ProjectProvider>).container
const row = await waitFor(() => {
const r = Array.from(c.querySelectorAll<HTMLElement>('.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<void> {
const row = await waitFor(() => {
const r = Array.from(c.querySelectorAll<HTMLElement>('.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(<ProjectProvider><App /></ProjectProvider>).container
await openTree(c, 'README.md')
const edit = await waitFor(() => {
const el = c.querySelector<HTMLElement>('.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<HTMLTextAreaElement>('.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(<ProjectProvider><App /></ProjectProvider>).container
await openTree(c, 'composer.json')
const edit = await waitFor(() => {
const el = c.querySelector<HTMLElement>('.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()
})
})