167 lines
6.2 KiB
TypeScript
167 lines
6.2 KiB
TypeScript
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
|
|
import { tmpdir } from 'node:os'
|
|
import { join } from 'node:path'
|
|
import { afterEach, describe, expect, it } from 'vitest'
|
|
import { simpleGit } from 'simple-git'
|
|
import { readFile } from 'node:fs/promises'
|
|
import { existsSync } from 'node:fs'
|
|
import { classify, discard, load, stage } from '../src/main/git-service'
|
|
|
|
describe('classify', () => {
|
|
it('reads the index code as staged, working code as unstaged', () => {
|
|
expect(classify('M', ' ')).toEqual([{ letter: 'M', staged: true }])
|
|
expect(classify(' ', 'M')).toEqual([{ letter: 'M', staged: false }])
|
|
expect(classify('A', ' ')).toEqual([{ letter: 'A', staged: true }])
|
|
expect(classify('D', ' ')).toEqual([{ letter: 'D', staged: true }])
|
|
expect(classify('R', ' ')).toEqual([{ letter: 'R', staged: true }])
|
|
})
|
|
it('treats untracked as a new (A) unstaged file', () => {
|
|
expect(classify('?', '?')).toEqual([{ letter: 'A', staged: false }])
|
|
})
|
|
it('splits a staged-then-edited file into two rows', () => {
|
|
expect(classify('M', 'M')).toEqual([
|
|
{ letter: 'M', staged: true },
|
|
{ letter: 'M', staged: false },
|
|
])
|
|
expect(classify('A', 'M')).toEqual([
|
|
{ letter: 'A', staged: true },
|
|
{ letter: 'M', staged: false },
|
|
])
|
|
expect(classify('M', 'D')).toEqual([
|
|
{ letter: 'M', staged: true },
|
|
{ letter: 'D', staged: false },
|
|
])
|
|
})
|
|
it('keeps a merge conflict as one row', () => {
|
|
expect(classify('U', 'U')).toEqual([{ letter: 'M', staged: true }])
|
|
expect(classify('A', 'A')).toEqual([{ letter: 'M', staged: true }])
|
|
expect(classify('D', 'D')).toEqual([{ letter: 'M', staged: true }])
|
|
})
|
|
})
|
|
|
|
describe('load (integration against a temp repo)', () => {
|
|
let dir = ''
|
|
afterEach(async () => { if (dir) await rm(dir, { recursive: true, force: true }) })
|
|
|
|
async function repo(): Promise<string> {
|
|
const d = await mkdtemp(join(tmpdir(), 'helder-git-'))
|
|
const g = simpleGit(d)
|
|
await g.init()
|
|
await g.addConfig('user.email', 't@example.com')
|
|
await g.addConfig('user.name', 'Test')
|
|
await g.addConfig('commit.gpgsign', 'false')
|
|
await writeFile(join(d, 'a.txt'), '1\n2\n3\n')
|
|
await g.add('.')
|
|
await g.commit('init')
|
|
return d
|
|
}
|
|
|
|
it('returns null for a non-repo directory', async () => {
|
|
dir = await mkdtemp(join(tmpdir(), 'helder-nogit-'))
|
|
expect(await load(dir)).toBeNull()
|
|
})
|
|
|
|
it('reports a modified file with HEAD-vs-worktree text', async () => {
|
|
dir = await repo()
|
|
await writeFile(join(dir, 'a.txt'), '1\nX\n3\n')
|
|
const res = await load(dir)
|
|
expect(res).not.toBeNull()
|
|
expect(res!.branch).toBeTruthy()
|
|
const a = res!.changes.find((c) => c.path === 'a.txt')
|
|
expect(a?.status).toBe('M')
|
|
expect(a?.staged).toBe(false)
|
|
expect(a?.original).toBe('1\n2\n3\n')
|
|
expect(a?.updated).toBe('1\nX\n3\n')
|
|
})
|
|
|
|
it('reports an untracked file as new (A), original empty', async () => {
|
|
dir = await repo()
|
|
await writeFile(join(dir, 'new.txt'), 'fresh\n')
|
|
const res = await load(dir)
|
|
const n = res!.changes.find((c) => c.path === 'new.txt')
|
|
expect(n?.status).toBe('A')
|
|
expect(n?.staged).toBe(false)
|
|
expect(n?.original).toBe('')
|
|
expect(n?.updated).toBe('fresh\n')
|
|
})
|
|
|
|
it('reflects staging', async () => {
|
|
dir = await repo()
|
|
await writeFile(join(dir, 'a.txt'), '1\n2\n3\n4\n')
|
|
await stage(dir, ['a.txt'])
|
|
const res = await load(dir)
|
|
expect(res!.changes.find((c) => c.path === 'a.txt')?.staged).toBe(true)
|
|
})
|
|
|
|
it('shows a staged-then-edited file in both groups, each with its own pair', async () => {
|
|
dir = await repo()
|
|
await writeFile(join(dir, 'a.txt'), '1\nSTAGED\n3\n')
|
|
await stage(dir, ['a.txt'])
|
|
await writeFile(join(dir, 'a.txt'), '1\nSTAGED\n3\n4\n')
|
|
const res = await load(dir)
|
|
const rows = res!.changes.filter((c) => c.path === 'a.txt')
|
|
expect(rows).toHaveLength(2)
|
|
|
|
// Staged row: HEAD -> index. It must NOT include the newer edit.
|
|
const s = rows.find((c) => c.staged)!
|
|
expect(s.original).toBe('1\n2\n3\n')
|
|
expect(s.updated).toBe('1\nSTAGED\n3\n')
|
|
|
|
// Unstaged row: index -> disk. Only the newer edit.
|
|
const w = rows.find((c) => !c.staged)!
|
|
expect(w.original).toBe('1\nSTAGED\n3\n')
|
|
expect(w.updated).toBe('1\nSTAGED\n3\n4\n')
|
|
})
|
|
|
|
it('gives a staged-new file that was edited again both rows', async () => {
|
|
dir = await repo()
|
|
await writeFile(join(dir, 'fresh.txt'), 'one\n')
|
|
await stage(dir, ['fresh.txt'])
|
|
await writeFile(join(dir, 'fresh.txt'), 'one\ntwo\n')
|
|
const res = await load(dir)
|
|
const rows = res!.changes.filter((c) => c.path === 'fresh.txt')
|
|
expect(rows.map((r) => [r.status, r.staged])).toEqual([['A', true], ['M', false]])
|
|
expect(rows[0].original).toBe('')
|
|
expect(rows[0].updated).toBe('one\n')
|
|
expect(rows[1].original).toBe('one\n')
|
|
expect(rows[1].updated).toBe('one\ntwo\n')
|
|
})
|
|
|
|
it('keeps one row when a file is only staged', async () => {
|
|
dir = await repo()
|
|
await writeFile(join(dir, 'a.txt'), '1\n2\n3\n4\n')
|
|
await stage(dir, ['a.txt'])
|
|
const rows = (await load(dir))!.changes.filter((c) => c.path === 'a.txt')
|
|
expect(rows).toHaveLength(1)
|
|
expect(rows[0].staged).toBe(true)
|
|
expect(rows[0].original).toBe('1\n2\n3\n')
|
|
expect(rows[0].updated).toBe('1\n2\n3\n4\n')
|
|
})
|
|
|
|
it('discard reverts a modified tracked file to HEAD', async () => {
|
|
dir = await repo()
|
|
await writeFile(join(dir, 'a.txt'), '1\nCHANGED\n3\n')
|
|
await discard(dir, ['a.txt'])
|
|
expect(await readFile(join(dir, 'a.txt'), 'utf8')).toBe('1\n2\n3\n')
|
|
const res = await load(dir)
|
|
expect(res!.changes.find((c) => c.path === 'a.txt')).toBeUndefined()
|
|
})
|
|
|
|
it('discard removes a new (untracked) file from disk', async () => {
|
|
dir = await repo()
|
|
await writeFile(join(dir, 'new.txt'), 'fresh\n')
|
|
await discard(dir, ['new.txt'])
|
|
expect(existsSync(join(dir, 'new.txt'))).toBe(false)
|
|
})
|
|
|
|
it('discard removes a staged-new file', async () => {
|
|
dir = await repo()
|
|
await writeFile(join(dir, 'staged-new.txt'), 'x\n')
|
|
await stage(dir, ['staged-new.txt'])
|
|
await discard(dir, ['staged-new.txt'])
|
|
expect(existsSync(join(dir, 'staged-new.txt'))).toBe(false)
|
|
const res = await load(dir)
|
|
expect(res!.changes.find((c) => c.path === 'staged-new.txt')).toBeUndefined()
|
|
})
|
|
})
|