Basic implementation of command manager.
This commit is contained in:
@@ -1 +1,47 @@
|
||||
export class CommandManager {}
|
||||
export interface Disposable {
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export type CommandFunction = (...args: any[]) => Promise<unknown>;
|
||||
|
||||
export class CommandManager<
|
||||
Commands extends Record<string, CommandFunction>,
|
||||
CommandName extends keyof Commands & string = keyof Commands & string,
|
||||
> implements Disposable
|
||||
{
|
||||
// TODO: should this be a map?
|
||||
// TODO: handle multiple command names
|
||||
private commands: Disposable[] = [];
|
||||
|
||||
constructor(
|
||||
private readonly commandRegister: <T extends CommandName>(
|
||||
commandName: T,
|
||||
definition: Commands[T],
|
||||
) => Disposable,
|
||||
private readonly commandExecute: <T extends CommandName>(
|
||||
commandName: T,
|
||||
...args: Parameters<Commands[T]>
|
||||
) => Promise<ReturnType<Commands[T]>>,
|
||||
) {}
|
||||
|
||||
registerCommand<T extends CommandName>(
|
||||
commandName: T,
|
||||
definition: Commands[T],
|
||||
): void {
|
||||
this.commands.push(this.commandRegister(commandName, definition));
|
||||
}
|
||||
|
||||
executeCommand<T extends CommandName>(
|
||||
commandName: T,
|
||||
...args: Parameters<Commands[T]>
|
||||
): Promise<ReturnType<Commands[T]>> {
|
||||
return this.commandExecute(commandName, ...args);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.commands.forEach((cmd) => {
|
||||
cmd.dispose();
|
||||
});
|
||||
this.commands = [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,111 @@
|
||||
import { CommandManager } from "../../../../src/packages/commands";
|
||||
import {
|
||||
CommandFunction,
|
||||
CommandManager,
|
||||
} from "../../../../src/packages/commands";
|
||||
|
||||
describe(CommandManager.name, () => {
|
||||
it("can create a command manager", () => {
|
||||
const commandManager = new CommandManager();
|
||||
expect(commandManager).not.toBeUndefined();
|
||||
describe("CommandManager", () => {
|
||||
it("can register a command", () => {
|
||||
const commandRegister = jest.fn();
|
||||
const commandManager = new CommandManager<Record<string, CommandFunction>>(
|
||||
commandRegister,
|
||||
jest.fn(),
|
||||
);
|
||||
const myCommand = jest.fn();
|
||||
commandManager.registerCommand("abc", myCommand);
|
||||
expect(commandRegister).toHaveBeenCalledTimes(1);
|
||||
expect(commandRegister).toHaveBeenCalledWith("abc", myCommand);
|
||||
});
|
||||
|
||||
it("can register typed commands", async () => {
|
||||
const commands = {
|
||||
"codeQL.openVariantAnalysisLogs": async (variantAnalysisId: number) => {
|
||||
return variantAnalysisId * 10;
|
||||
},
|
||||
};
|
||||
const commandManager = new CommandManager<typeof commands>(
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
);
|
||||
|
||||
// @ts-expect-error wrong command name should give a type error
|
||||
commandManager.registerCommand("abc", jest.fn());
|
||||
|
||||
commandManager.registerCommand(
|
||||
"codeQL.openVariantAnalysisLogs",
|
||||
// @ts-expect-error wrong function parameter type should give a type error
|
||||
async (variantAnalysisId: string): Promise<number> => 10,
|
||||
);
|
||||
|
||||
commandManager.registerCommand(
|
||||
"codeQL.openVariantAnalysisLogs",
|
||||
// @ts-expect-error wrong function return type should give a type error
|
||||
async (variantAnalysisId: number): Promise<string> => "hello",
|
||||
);
|
||||
|
||||
// Working types
|
||||
commandManager.registerCommand(
|
||||
"codeQL.openVariantAnalysisLogs",
|
||||
async (variantAnalysisId: number): Promise<number> =>
|
||||
variantAnalysisId * 10,
|
||||
);
|
||||
});
|
||||
|
||||
it("can dispose of its commands", () => {
|
||||
const dispose1 = jest.fn();
|
||||
const dispose2 = jest.fn();
|
||||
const commandRegister = jest
|
||||
.fn()
|
||||
.mockReturnValueOnce({ dispose: dispose1 })
|
||||
.mockReturnValueOnce({ dispose: dispose2 });
|
||||
const commandManager = new CommandManager<Record<string, CommandFunction>>(
|
||||
commandRegister,
|
||||
jest.fn(),
|
||||
);
|
||||
commandManager.registerCommand("abc", jest.fn());
|
||||
commandManager.registerCommand("def", jest.fn());
|
||||
expect(dispose1).not.toHaveBeenCalled();
|
||||
expect(dispose2).not.toHaveBeenCalled();
|
||||
commandManager.dispose();
|
||||
expect(dispose1).toHaveBeenCalledTimes(1);
|
||||
expect(dispose2).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("can execute a command", async () => {
|
||||
const commandExecute = jest.fn().mockReturnValue(7);
|
||||
const commandManager = new CommandManager<Record<string, CommandFunction>>(
|
||||
jest.fn(),
|
||||
commandExecute,
|
||||
);
|
||||
const result = await commandManager.executeCommand("abc", "hello", true);
|
||||
expect(result).toEqual(7);
|
||||
expect(commandExecute).toHaveBeenCalledTimes(1);
|
||||
expect(commandExecute).toHaveBeenCalledWith("abc", "hello", true);
|
||||
});
|
||||
|
||||
it("can execute typed commands", async () => {
|
||||
const commands = {
|
||||
"codeQL.openVariantAnalysisLogs": async (variantAnalysisId: number) => {
|
||||
return variantAnalysisId * 10;
|
||||
},
|
||||
};
|
||||
const commandManager = new CommandManager<typeof commands>(
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
);
|
||||
|
||||
// @ts-expect-error wrong command name should give a type error
|
||||
await commandManager.executeCommand("abc", 4);
|
||||
|
||||
await commandManager.executeCommand(
|
||||
"codeQL.openVariantAnalysisLogs",
|
||||
// @ts-expect-error wrong argument type should give a type error
|
||||
"xyz",
|
||||
);
|
||||
|
||||
// @ts-expect-error wrong number of arguments should give a type error
|
||||
await commandManager.executeCommand("codeQL.openVariantAnalysisLogs", 2, 3);
|
||||
|
||||
// Working types
|
||||
await commandManager.executeCommand("codeQL.openVariantAnalysisLogs", 7);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user