Files
openchamber/packages/ui/src/lib/gitApiHttp.test.ts
T
Paolo InsognaandBohdan Triapitsyn e16097b05d feat: Redesign git changes to split stage/unstaged files. (#1359)
* feat: Redesign git changes to split stage/unstaged files.

Signed-off-by: Paolo Insogna <paolo@cowtech.it>

* fixup

Signed-off-by: Paolo Insogna <paolo@cowtech.it>

* fixup

Signed-off-by: Paolo Insogna <paolo@cowtech.it>

* refactor: streamline git changes panel

* fix: label staged and working diff tabs

* fix: isolate staged and working diff files

* fix: scope staged and working diff updates

* fix: scope git row revert to working changes

---------

Signed-off-by: Paolo Insogna <paolo@cowtech.it>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-05-24 00:49:38 +03:00

113 lines
3.4 KiB
TypeScript

import { describe, expect, test } from 'bun:test';
import { stageGitFile, stageGitFiles, unstageGitFile, unstageGitFiles } from './gitApiHttp';
type FetchCall = {
input: RequestInfo | URL;
init?: RequestInit;
};
const previousFetch = globalThis.fetch;
const previousWindowDescriptor = Object.getOwnPropertyDescriptor(globalThis, 'window');
const installFetchMock = () => {
const calls: FetchCall[] = [];
globalThis.fetch = (async (input, init) => {
calls.push({ input, init });
return new Response(JSON.stringify({ success: true }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
}) as typeof fetch;
return calls;
};
const installWindowMock = () => {
Object.defineProperty(globalThis, 'window', {
configurable: true,
value: {
location: { origin: 'http://localhost:3000' },
},
});
};
const restoreMocks = () => {
globalThis.fetch = previousFetch;
if (previousWindowDescriptor) {
Object.defineProperty(globalThis, 'window', previousWindowDescriptor);
} else {
delete (globalThis as { window?: Window }).window;
}
};
const captureError = async (callback: () => Promise<void>): Promise<unknown> => {
try {
await callback();
return null;
} catch (error) {
return error;
}
};
describe('gitApiHttp index mutations', () => {
test('sends bulk stage payloads as paths', async () => {
installWindowMock();
const calls = installFetchMock();
try {
await stageGitFiles('/repo', ['a.ts', 'b.ts']);
expect(calls).toHaveLength(1);
expect(String(calls[0].input)).toBe('http://localhost:3000/api/git/stage?directory=%2Frepo');
expect(calls[0].init?.method).toBe('POST');
expect(JSON.parse(String(calls[0].init?.body))).toEqual({ paths: ['a.ts', 'b.ts'] });
} finally {
restoreMocks();
}
});
test('sends bulk unstage payloads as paths', async () => {
installWindowMock();
const calls = installFetchMock();
try {
await unstageGitFiles('/repo', ['a.ts', 'b.ts']);
expect(calls).toHaveLength(1);
expect(String(calls[0].input)).toBe('http://localhost:3000/api/git/unstage?directory=%2Frepo');
expect(calls[0].init?.method).toBe('POST');
expect(JSON.parse(String(calls[0].init?.body))).toEqual({ paths: ['a.ts', 'b.ts'] });
} finally {
restoreMocks();
}
});
test('single-file helpers use the bulk paths payload shape', async () => {
installWindowMock();
const calls = installFetchMock();
try {
await stageGitFile('/repo', 'a.ts');
await unstageGitFile('/repo', 'b.ts');
expect(JSON.parse(String(calls[0].init?.body))).toEqual({ paths: ['a.ts'] });
expect(JSON.parse(String(calls[1].init?.body))).toEqual({ paths: ['b.ts'] });
} finally {
restoreMocks();
}
});
test('rejects empty bulk path lists before fetching', async () => {
installWindowMock();
const calls = installFetchMock();
try {
const stageError = await captureError(() => stageGitFiles('/repo', [' ', '']));
const unstageError = await captureError(() => unstageGitFiles('/repo', []));
expect(stageError).toBeInstanceOf(Error);
expect((stageError as Error).message).toBe('path is required to stage git changes');
expect(unstageError).toBeInstanceOf(Error);
expect((unstageError as Error).message).toBe('path is required to unstage git changes');
expect(calls).toHaveLength(0);
} finally {
restoreMocks();
}
});
});