improvements
Some checks failed
CI / check (push) Has been cancelled

This commit is contained in:
2026-06-19 09:59:03 +02:00
parent 0a90ab822f
commit 5e5fc53dde
15 changed files with 592 additions and 105 deletions

View File

@@ -34,9 +34,9 @@ async function openChanged(): Promise<HTMLElement> {
}
describe('Editor view modes', () => {
it('Updated mode is an editable buffer; Original is read-only', async () => {
it('Actual mode is an editable buffer; Original is read-only', async () => {
const c = await openChanged()
fireEvent.click(find(c, '.seg button', 'Updated')!)
fireEvent.click(find(c, '.seg button', 'Actual')!)
await waitFor(() => expect(c.querySelector('.ce-ta')).toBeTruthy())
fireEvent.click(find(c, '.seg button', 'Original')!)

View File

@@ -3,7 +3,7 @@ import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
import { simpleGit } from 'simple-git'
import { buildTreeFromPaths, createProjectFile, readAll, readProjectFile, readTree, writeProjectFile } from '../src/main/fs-service'
import { buildTreeFromPaths, createProjectFile, readAll, readDirChildren, readProjectFile, readTree, writeProjectFile } from '../src/main/fs-service'
let dir = ''
afterEach(async () => { if (dir) await rm(dir, { recursive: true, force: true }) })
@@ -33,6 +33,51 @@ describe('readTree', () => {
expect(top.indexOf('src')).toBeLessThan(top.indexOf('README.md'))
expect(top.indexOf('src')).toBeLessThan(top.indexOf('logo.bin'))
})
it('shows an empty folder (no files yet) that rg --files can not emit', async () => {
dir = await fixture()
await mkdir(join(dir, 'empty'), { recursive: true })
await mkdir(join(dir, 'src', 'nested', 'deep'), { recursive: true }) // file-empty nested chain
const tree = await readTree(dir)
const top = (tree.children || []).map((c) => c.name)
expect(top).toContain('empty')
const empty = (tree.children || []).find((c) => c.name === 'empty')!
expect(empty.type).toBe('dir')
// the nested empty chain shows under the (file-bearing) src folder
const src = (tree.children || []).find((c) => c.name === 'src')!
const nested = (src.children || []).find((c) => c.name === 'nested')!
expect(nested.type).toBe('dir')
expect((nested.children || []).map((c) => c.name)).toEqual(['deep'])
})
})
describe('readDirChildren', () => {
it('returns a single folder\'s children (root and subdir), ignoring node_modules', async () => {
dir = await fixture()
const top = (await readDirChildren(dir, '')).map((c) => c.name)
expect(top).toContain('src')
expect(top).toContain('README.md')
expect(top).not.toContain('node_modules')
const src = await readDirChildren(dir, 'src')
expect(src.map((c) => c.name)).toEqual(['a.ts'])
expect(src[0].path).toBe('src/a.ts')
})
it('shows an empty subfolder created since the initial tree read', async () => {
dir = await fixture()
await mkdir(join(dir, 'src', 'fresh'), { recursive: true })
const src = await readDirChildren(dir, 'src')
const fresh = src.find((c) => c.name === 'fresh')
expect(fresh?.type).toBe('dir')
expect(fresh?.path).toBe('src/fresh')
})
it('reflects files added/removed since the initial tree read', async () => {
dir = await fixture()
await writeFile(join(dir, 'src', 'b.ts'), 'export const b = 2\n')
await rm(join(dir, 'src', 'a.ts'))
expect((await readDirChildren(dir, 'src')).map((c) => c.name)).toEqual(['b.ts'])
})
})
describe('readAll', () => {
@@ -67,6 +112,15 @@ describe('buildTreeFromPaths', () => {
expect((src.children || []).map((c) => c.name)).toEqual(['util', 'a.ts', 'b.ts'])
expect((src.children || []).find((c) => c.name === 'util')!.path).toBe('src/util')
})
it('forces dirPaths in as empty folders, deduped against file-derived dirs', () => {
const t = buildTreeFromPaths('proj', ['src/a.ts'], ['empty', 'src/sub', 'src'])
const top = (t.children || []).map((c) => c.name)
expect(top).toEqual(['empty', 'src']) // dirs alphabetical, both present once
const src = (t.children || []).find((c) => c.name === 'src')!
expect((src.children || []).map((c) => c.name)).toEqual(['sub', 'a.ts'])
expect((src.children || []).find((c) => c.name === 'sub')!.type).toBe('dir')
})
})
describe('read/write round-trip', () => {

44
test/markdown.test.ts Normal file
View File

@@ -0,0 +1,44 @@
import { describe, expect, it } from 'vitest'
import { renderMarkdown } from '../src/renderer/src/markdown'
describe('renderMarkdown', () => {
it('renders headings, bold, italic and links', () => {
const html = renderMarkdown('# Title\n\nSome **bold** and *italic* and [a](https://x.com).')
expect(html).toContain('<h1>Title</h1>')
expect(html).toContain('<strong>bold</strong>')
expect(html).toContain('<em>italic</em>')
expect(html).toContain('<a href="https://x.com" target="_blank" rel="noreferrer">a</a>')
})
it('restores code spans without colliding with surrounding digits', () => {
// " 0 " around the text used to clash with the placeholder index — guard it.
const html = renderMarkdown('I have 0 cats and `code` and 1 dog.')
expect(html).toContain('<code>code</code>')
expect(html).toContain('I have 0 cats')
expect(html).toContain('1 dog.')
expect(html).not.toContain('undefined')
})
it('escapes HTML and never passes through raw tags', () => {
const html = renderMarkdown('A <script>alert(1)</script> tag.')
expect(html).toContain('&lt;script&gt;')
expect(html).not.toContain('<script>')
})
it('drops dangerous link schemes but keeps the text', () => {
const html = renderMarkdown('[click](javascript:alert(1))')
expect(html).not.toContain('javascript:')
expect(html).toContain('click')
})
it('highlights fenced code blocks', () => {
const html = renderMarkdown('```js\nconst a = 1\n```')
expect(html).toContain('<pre class="md-code">')
expect(html).toContain('const')
})
it('renders unordered and ordered lists', () => {
expect(renderMarkdown('- a\n- b')).toContain('<ul><li>a</li><li>b</li></ul>')
expect(renderMarkdown('1. a\n2. b')).toContain('<ol><li>a</li><li>b</li></ol>')
})
})