Files
openchamber/packages/vscode/src/bridge-git-runtime.test.js
T
Erman HAVUÇandBohdan Triapitsyn 52ffe9daef feat(git-graph): VS Code-style git graph with commit actions in History modal (#1431)
* feat(types): add parents to GitLogEntry and new commit action types

* feat(git): add parent hashes and --all flag to getLog

* fix(git): move record separator to start of log format string

* feat(git): add checkoutCommit server function and route

* feat(git): add cherryPick server function and route

* feat(git): add revertCommit server function and route

* feat(git): add resetToCommit server function and route

* fix(tests): make git service tests branch-name portable, add error path tests

* feat(client): add checkoutCommit, cherryPick, revertCommit, resetToCommit API wrappers

* feat(git-graph): add lane assignment algorithm with tests

* feat(git-graph): add GitGraphSegment per-row SVG renderer

* feat(i18n): add locale strings for git graph action buttons

* fix(git-graph): handle lane convergence, fix SVG path coords, add connector tests

* feat(git-graph): add ref badges and action buttons to HistoryCommitRow

* fix(git-graph): add loading guards to reset actions, use theme tokens for ref badges

* fix(git-graph): conditional hooks, stale graph log, conflict handling, i18n

* fix(types): replace toBeDefined with toBeTruthy, fix toast API usage

* fix(lint): remove unused variables

* fix(git-graph): fix SVG height causing 150px row spacing

* fix(git-graph): smooth bezier curves, fill row height, round line caps

* fix(git-graph): non-scaling-stroke fixes bezier white spaces, sort curves on top

* fix(git-graph): remove viewBox scaling, match SVG height to actual row height

* fix(git-graph): ResizeObserver tracks actual row height, eliminates SVG height mismatch

* feat(git-graph): replace SVG with Canvas for graph rendering

* fix(git-graph): isolate canvas from flex layout to prevent replaced-element height leak

* feat(git-graph): align action buttons, add confirmation popups for all actions

* fix(git-graph): address code review findings CR-001 through CR-005

- CR-001: VS Code getGitLog now forwards 'all' option and parses %P parents
- CR-002: VS Code bridge/gitService implement checkoutCommit, cherryPick,
  revertCommit, resetToCommit with conflict detection and hard-reset guard
- CR-003: server-side commit hash validated with /^[0-9a-fA-F]{7,40}$/
  in both routes.js and service.js; 12 new rejection tests added
- CR-004: cherry-pick/revert conflict path now refreshes fetchStatus/
  fetchBranches/fetchLog; conflict toast uses i18n keys in all 7 locales
- CR-005: corrected O(n) comment to O(n x lanes)

* fix(i18n): add zh-TW locale and common.language.traditionalChinese key to all locales

upstream/main added zh-TW.ts after branch diverged; CI type-check fails
when PR is merged because zh-TW.ts was missing all gitView.history.actions.*
keys and loadMore/loadingMore. Also adds common.language.traditionalChinese
to en.ts and all 6 non-English files to match upstream en.ts.

* fix: harden git history actions

* feat: split git history graph view

* chore: remove git graph planning docs

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-05-27 00:13:25 +03:00

113 lines
4.1 KiB
JavaScript

import { beforeEach, describe, expect, it, mock } from 'bun:test';
const gitService = {
stageGitFiles: mock(),
unstageGitFiles: mock(),
checkoutCommit: mock(),
cherryPick: mock(),
revertCommit: mock(),
resetToCommit: mock(),
};
mock.module('./gitService', () => gitService);
const { handleStandardGitBridgeMessage } = await import('./bridge-git-runtime');
describe('bridge git runtime index mutations', () => {
beforeEach(() => {
gitService.stageGitFiles.mockReset();
gitService.unstageGitFiles.mockReset();
gitService.checkoutCommit.mockReset();
gitService.cherryPick.mockReset();
gitService.revertCommit.mockReset();
gitService.resetToCommit.mockReset();
});
it('accepts legacy stage path payloads', async () => {
const response = await handleStandardGitBridgeMessage({
id: '1',
type: 'api:git/stage',
payload: { directory: '/repo', path: 'a.ts' },
});
expect(response).toEqual({ id: '1', type: 'api:git/stage', success: true, data: { success: true } });
expect(gitService.stageGitFiles).toHaveBeenCalledWith('/repo', ['a.ts']);
});
it('accepts bulk stage paths payloads', async () => {
const response = await handleStandardGitBridgeMessage({
id: '1',
type: 'api:git/stage',
payload: { directory: '/repo', paths: ['a.ts', 'b.ts'] },
});
expect(response?.success).toBe(true);
expect(gitService.stageGitFiles).toHaveBeenCalledWith('/repo', ['a.ts', 'b.ts']);
});
it('accepts legacy unstage path payloads', async () => {
const response = await handleStandardGitBridgeMessage({
id: '1',
type: 'api:git/unstage',
payload: { directory: '/repo', path: 'a.ts' },
});
expect(response).toEqual({ id: '1', type: 'api:git/unstage', success: true, data: { success: true } });
expect(gitService.unstageGitFiles).toHaveBeenCalledWith('/repo', ['a.ts']);
});
it('accepts bulk unstage paths payloads', async () => {
const response = await handleStandardGitBridgeMessage({
id: '1',
type: 'api:git/unstage',
payload: { directory: '/repo', paths: ['a.ts', 'b.ts'] },
});
expect(response?.success).toBe(true);
expect(gitService.unstageGitFiles).toHaveBeenCalledWith('/repo', ['a.ts', 'b.ts']);
});
it('rejects invalid path payloads', async () => {
const response = await handleStandardGitBridgeMessage({
id: '1',
type: 'api:git/stage',
payload: { directory: '/repo', paths: [' ', null] },
});
expect(response?.success).toBe(false);
expect(gitService.stageGitFiles).not.toHaveBeenCalled();
});
it('rejects invalid commit hashes before commit actions reach git service', async () => {
const checkoutResponse = await handleStandardGitBridgeMessage({
id: '1',
type: 'api:git/checkout-commit',
payload: { directory: '/repo', hash: 'HEAD' },
});
const cherryPickResponse = await handleStandardGitBridgeMessage({
id: '2',
type: 'api:git/cherry-pick',
payload: { directory: '/repo', hash: '--abort' },
});
const revertResponse = await handleStandardGitBridgeMessage({
id: '3',
type: 'api:git/revert-commit',
payload: { directory: '/repo', hash: '--continue' },
});
const resetResponse = await handleStandardGitBridgeMessage({
id: '4',
type: 'api:git/reset-to-commit',
payload: { directory: '/repo', hash: '--hard', mode: 'mixed' },
});
expect(checkoutResponse).toEqual({ id: '1', type: 'api:git/checkout-commit', success: false, error: 'Invalid commit hash' });
expect(cherryPickResponse).toEqual({ id: '2', type: 'api:git/cherry-pick', success: false, error: 'Invalid commit hash' });
expect(revertResponse).toEqual({ id: '3', type: 'api:git/revert-commit', success: false, error: 'Invalid commit hash' });
expect(resetResponse).toEqual({ id: '4', type: 'api:git/reset-to-commit', success: false, error: 'Invalid commit hash' });
expect(gitService.checkoutCommit).not.toHaveBeenCalled();
expect(gitService.cherryPick).not.toHaveBeenCalled();
expect(gitService.revertCommit).not.toHaveBeenCalled();
expect(gitService.resetToCommit).not.toHaveBeenCalled();
});
});