Basic implementation of command manager.

This commit is contained in:
Anders Starcke Henriksen
2023-03-09 14:15:27 +01:00
parent 77dd9bff94
commit d87911a803
2 changed files with 155 additions and 6 deletions

View File

@@ -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 = [];
}
}

View File

@@ -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);
});
});