← back to Kickbacks Ai Upstream
test/mocks/vscode.ts
102 lines
// Minimal hermetic mock of the `vscode` module surface S3 uses.
export const secrets = new Map<string, string>();
export const _opened: string[] = [];
// _shown: every showInformationMessage / showErrorMessage call, in order.
// Lets commands.test.ts assert that `kickbacks.status` produced the expected
// toast without having to spy() each handler individually.
export const _shown: { kind: "info" | "error"; text: string }[] = [];
// _warned: every showWarningMessage call, in order. Kept separate from _shown
// so adding warning capture never perturbs a test asserting on _shown counts.
export const _warned: string[] = [];
// _opened docs: every workspace.openTextDocument(path) call. Used to verify
// that `kickbacks.editConfig` actually opened the config file.
export const _openedDocs: string[] = [];
export const window = {
createStatusBarItem: () => ({
text: "", tooltip: "", command: "" as string | undefined,
color: undefined as string | undefined,
backgroundColor: undefined as ThemeColor | undefined,
show() {}, hide() {}, dispose() {},
}),
showInformationMessage: async (msg: unknown, ..._rest: unknown[]) => {
_shown.push({ kind: "info", text: String(msg) }); return undefined;
},
showErrorMessage: async (msg: unknown, ..._rest: unknown[]) => {
_shown.push({ kind: "error", text: String(msg) }); return undefined;
},
showWarningMessage: async (msg: unknown, ..._rest: unknown[]) => {
_warned.push(String(msg)); return undefined;
},
showQuickPick: async (_items: unknown[], _o?: unknown) => undefined as unknown,
showInputBox: async (_o?: unknown) => undefined as string | undefined,
showTextDocument: async (_doc: unknown, _o?: unknown) => undefined as unknown,
};
export const workspace = {
openTextDocument: async (p: unknown) => {
const path = typeof p === "string" ? p
: (p as { fsPath?: string; toString(): string })?.fsPath
?? (p as { toString(): string }).toString();
_openedDocs.push(path);
return { uri: { fsPath: path }, getText: () => "" };
},
};
export const env = {
openExternal: async (u: { toString(): string }) => { _opened.push(u.toString()); return true; },
// Remote port-forwarding tunnel; identity in tests/local desktop.
asExternalUri: async (u: { toString(): string }) => u,
clipboard: {
_last: "" as string,
writeText: async function (t: string) { this._last = t; },
},
};
export const commands = {
_handlers: new Map<string, (...a: unknown[]) => unknown>(),
// Every executeCommand call, in order. Useful when a handler delegates
// (e.g. the debug menu's "Sign in" item executes "kickbacks.signIn").
_executed: [] as { id: string; args: unknown[] }[],
registerCommand(id: string, h: (...a: unknown[]) => unknown) {
this._handlers.set(id, h); return { dispose() {} };
},
// Dispatch to a registered handler when present so tests can drive the
// exact command pipeline VS Code itself would. Unknown ids (e.g.
// "workbench.action.restartExtensionHost") fall through to undefined,
// matching the pre-dispatch behavior — extension code that fires them
// for side effect keeps working in tests.
async executeCommand(id: string, ...args: unknown[]) {
commands._executed.push({ id, args });
const h = commands._handlers.get(id);
if (!h) return undefined;
return await h(...args);
},
};
export const Uri = {
parse: (s: string) => ({ toString: () => s }),
file: (p: string) => ({ fsPath: p, toString: () => `file://${p}` }),
};
export const QuickPickItemKind = { Default: 0, Separator: -1 };
// Theme-token reference (e.g. "statusBarItem.errorBackground"); the mock just
// records the id so tests can assert which token a surface painted with.
export class ThemeColor { constructor(public readonly id: string) {} }
export class EventEmitter<T> { event = (_l: (e: T) => void) => ({ dispose() {} }); fire(_e: T) {} }
export const StatusBarAlignment = { Left: 1, Right: 2 };
export interface ExtensionContext {
secrets: { get(k: string): Thenable<string | undefined>;
store(k: string, v: string): Thenable<void>;
delete(k: string): Thenable<void>; };
globalState: { get<T>(k: string): T | undefined; update(k: string, v: unknown): Thenable<void> };
subscriptions: { dispose(): void }[];
}
export function makeContext(): ExtensionContext {
const gs = new Map<string, unknown>();
return {
secrets: {
get: async (k) => secrets.get(k),
store: async (k, v) => { secrets.set(k, v); },
delete: async (k) => { secrets.delete(k); },
},
globalState: { get: <T>(k: string) => gs.get(k) as T | undefined,
update: async (k, v) => { gs.set(k, v); } },
subscriptions: [],
};
}