Files
PiAgentUI/extensions/piagentui-git-manager.test.ts
Evandro b3489e1ede
Build & Push Frontend Docker Image / build-and-push (push) Canceled after 0s
Build Validation / validate (push) Canceled after 6s
Build & Push Backend Docker Image / build-and-push (push) Failing after 10m10s
Build & Push Gateway Docker Image / build-and-push (push) Failing after 10m9s
🎉 Initial commit: PiAgentUI — AI Agent Desktop & Web UI
A professional workspace for AI coding agents with:
- Real-time streaming chat with SSE
- Session management (fork, clone, tree navigation)
- MCP tools browser & metadata panel
- Skills browser (global, project, package sources)
- Voice input & STT provider configuration
- Integrated terminal & file explorer
- Model selector with capability metadata
- Keyboard-first UI with command palette
- 8 languages (en, pt-BR, es, ja, zh-CN, ar, bn, hi)
- Tauri 2 desktop app
- React 19 + TypeScript + Vite 8 + Tailwind CSS 4
- Pi Agent extension runtime

Originally forked from OpenCodeUI, now an independent project.
License: GPL-3.0-only
2026-06-01 00:50:06 -03:00

104 lines
3.9 KiB
TypeScript

import * as os from 'node:os'
import * as path from 'node:path'
import { describe, expect, it, vi } from 'vitest'
import {
checkoutGitBranch,
commitGitChanges,
createGitBranch,
getGitStatus,
listGitBranches,
stageGitPaths,
unstageGitPaths,
type GitCommandRunner,
} from './piagentui-git-manager.js'
const repoDir = path.join(os.tmpdir(), 'piagentui-git-manager-test')
function createRunner(outputs: Record<string, { stdout?: string; stderr?: string }>): GitCommandRunner {
return vi.fn(async args => {
const key = args.join(' ')
const output = outputs[key]
if (!output) throw new Error(`Unexpected git args: ${key}`)
return { stdout: output.stdout ?? '', stderr: output.stderr ?? '' }
})
}
describe('piagentui git manager', () => {
it('parses branch output into current local and remote branches', async () => {
const runner = createRunner({
'branch --format=%(refname:short)|%(upstream:short)|%(objectname:short)|%(HEAD) --all': {
stdout: [
'main|origin/main|1111111| ',
'feature/git-ui|origin/feature/git-ui|2222222|*',
'origin/main||3333333| ',
].join('\n'),
},
})
const result = await listGitBranches(repoDir, runner)
expect(result.current).toBe('feature/git-ui')
expect(result.branches).toEqual([
{ name: 'main', current: false, remote: false, upstream: 'origin/main', commit: '1111111' },
{ name: 'feature/git-ui', current: true, remote: false, upstream: 'origin/feature/git-ui', commit: '2222222' },
{ name: 'origin/main', current: false, remote: true, commit: '3333333' },
])
})
it('parses porcelain v2 status and ahead behind data', async () => {
const runner = createRunner({
'status --porcelain=v2 --branch': {
stdout: [
'# branch.oid abcdef1',
'# branch.head feature/git-ui',
'# branch.upstream origin/feature/git-ui',
'# branch.ab +2 -1',
'1 M. N... 100644 100644 100644 a b src/app.tsx',
'1 .M N... 100644 100644 100644 a b src/input.tsx',
'? src/new-file.ts',
].join('\n'),
},
})
const status = await getGitStatus(repoDir, runner)
expect(status.branch).toBe('feature/git-ui')
expect(status.upstream).toBe('origin/feature/git-ui')
expect(status.ahead).toBe(2)
expect(status.behind).toBe(1)
expect(status.dirty).toBe(true)
expect(status.files).toEqual([
{ path: 'src/app.tsx', index: 'M', workingTree: '.', staged: true, unstaged: false, untracked: false },
{ path: 'src/input.tsx', index: '.', workingTree: 'M', staged: false, unstaged: true, untracked: false },
{ path: 'src/new-file.ts', index: '?', workingTree: '?', staged: false, unstaged: false, untracked: true },
])
})
it('runs safe fixed argv for branch and stage operations', async () => {
const runner = createRunner({
'checkout main': {},
'checkout -b feature/new-branch': {},
'add -- src/app.tsx src/input.tsx': {},
'restore --staged -- src/app.tsx': {},
})
await checkoutGitBranch(repoDir, { branch: 'main' }, runner)
await createGitBranch(repoDir, { name: 'feature/new-branch' }, runner)
await stageGitPaths(repoDir, { paths: ['src/app.tsx', 'src/input.tsx'] }, runner)
await unstageGitPaths(repoDir, { paths: ['src/app.tsx'] }, runner)
expect(runner).toHaveBeenCalledWith(['checkout', 'main'], { cwd: repoDir })
expect(runner).toHaveBeenCalledWith(['checkout', '-b', 'feature/new-branch'], { cwd: repoDir })
expect(runner).toHaveBeenCalledWith(['add', '--', 'src/app.tsx', 'src/input.tsx'], { cwd: repoDir })
expect(runner).toHaveBeenCalledWith(['restore', '--staged', '--', 'src/app.tsx'], { cwd: repoDir })
})
it('rejects empty commit messages before running git', async () => {
const runner = vi.fn()
await expect(commitGitChanges(repoDir, { message: ' ' }, runner)).rejects.toThrow('Commit message is required')
expect(runner).not.toHaveBeenCalled()
})
})