Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

👍 Add DenopsStub class to stub denops for tests. #6

Merged
merged 3 commits into from
Jun 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 24 additions & 16 deletions denops_test/conf.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
import * as path from "https://deno.land/std@0.187.0/path/mod.ts";
import * as path from "https://deno.land/std@0.190.0/path/mod.ts";
import { orElse } from "./or_else.ts";

function get(name: string): string | undefined {
return Deno.env.get(`DENOPS_TEST_${name}`);
}
let conf: Config | undefined;

/** Absolute local path of denops added to Vim runtimepath */
export const DENOPS_PATH = path.resolve(
orElse(get("DENOPS_PATH"), () => {
throw new Error(
"Environment variable 'DENOPS_TEST_DENOPS_PATH' is required",
);
}),
);
export type Config = {
denopsPath: string;
vimExecutable: string;
nvimExecutable: string;
};

/** Executable name of Vim */
export const VIM_EXECUTABLE = get("VIM_EXECUTABLE") ?? "vim";
export function getConfig(): Config {
if (!conf) {
conf = {
denopsPath: path.resolve(orElse(get("DENOPS_PATH"), () => {
throw new Error(
"Environment variable 'DENOPS_TEST_DENOPS_PATH' is required",
);
})),
vimExecutable: get("VIM_EXECUTABLE") ?? "vim",
nvimExecutable: get("NVIM_EXECUTABLE") ?? "nvim",
};
}
return conf;
}

/** Executable name of Neovim */
export const NVIM_EXECUTABLE = get("NVIM_EXECUTABLE") ?? "nvim";
function get(name: string): string | undefined {
return Deno.env.get(`DENOPS_TEST_${name}`);
}
20 changes: 20 additions & 0 deletions denops_test/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,29 @@
* },
* });
* ```
*
* Use `DenopsStub` class to create a stub instance of `Denops` interface
* if no real Vim/Neovim behavior is required for the tests.
*
* ```typescript
* import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
* import { DenopsStub } from "./mod.ts";
*
* Deno.test("denops.call", async () => {
* const denops = new DenopsStub({
* call: (fn, ...args) => {
* return Promise.resolve([fn, ...args]);
* },
* });
* assertEquals(await denops.call("foo", "bar"), ["foo", "bar"]);
* });
* ```
*
* @module
*/
export type { DenopsStubber } from "./stub.ts";
export type { TestDefinition } from "./tester.ts";
export type { WithDenopsOptions } from "./with.ts";
export { DenopsStub } from "./stub.ts";
export { test } from "./tester.ts";
export { withDenops } from "./with.ts";
7 changes: 4 additions & 3 deletions denops_test/runner.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { NVIM_EXECUTABLE, VIM_EXECUTABLE } from "./conf.ts";
import { getConfig } from "./conf.ts";

/** Runner mode */
export type RunMode = "vim" | "nvim";
Expand Down Expand Up @@ -46,15 +46,16 @@ export function isRunMode(mode: string): mode is RunMode {
}

function buildArgs(mode: RunMode): [string, string[]] {
const conf = getConfig();
switch (mode) {
case "vim":
return [
VIM_EXECUTABLE,
conf.vimExecutable,
["-u", "NONE", "-i", "NONE", "-n", "-N", "-X", "-e", "-s"],
];
case "nvim":
return [
NVIM_EXECUTABLE,
conf.nvimExecutable,
["--clean", "--embed", "--headless", "-n"],
];
}
Expand Down
181 changes: 181 additions & 0 deletions denops_test/stub.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import {
Context,
Denops,
Dispatcher,
Meta,
} from "https://deno.land/x/[email protected]/mod.ts";

/**
* Represents a `Denops` stubber object.
*/
export type DenopsStubber = {
/**
* Denops instance name whihc uses to communicate with vim.
*
* If not specified, the default value is `denops-test-stub`.
*/
name?: string;
/**
* Environment meta information.
*
* If not specified, the default value is:
* ```json
* {
* "mode": "release",
* "host": "vim",
* "version": "0.0.0",
* "platform": "linux"
* }
* ```
*/
meta?: Meta;
/**
* A stub function of `redraw` method of the `Denops`.
*
* If not specified, it returns a promise resolves to undefined.
*/
redraw?: (force?: boolean) => Promise<void>;
/**
* A stub function of `call` method of the `Denops`.
*
* If not specified, it returns a promise resolves to undefined.
*/
call?(fn: string, ...args: unknown[]): Promise<unknown>;
/**
* A stub function of `batch` method of the `Denops`.
*
* If not specified, it returns a promise resolves to an empty list.
*/
batch?(...calls: [string, ...unknown[]][]): Promise<unknown[]>;
/**
* A stub function of `cmd` method of the `Denops`.
*
* If not specified, it returns a promise resolves to undefined.
*/
cmd?(cmd: string, ctx: Context): Promise<void>;
/**
* A stub function of `eval` method of the `Denops`.
*
* If not specified, it returns a promise resolves to undefined.
*/
eval?(expr: string, ctx: Context): Promise<unknown>;
/**
* A stub function of `dispatch` method of the `Denops`.
*
* If not specified, it returns a promise resolves to undefined.
*/
dispatch?(name: string, fn: string, ...args: unknown[]): Promise<unknown>;
/**
* Indicates whether to use `call` function in `batch` method.
*/
useCallInBatch?: boolean;
/**
* Indicates whether to use `call` function in `cmd` method.
*/
useCallInCmd?: boolean;
/**
* Indicates whether to use `call` function in `eval` method.
*/
useCallInEval?: boolean;
};

/**
* Represents a `Denops` stub object.
*/
export class DenopsStub implements Denops {
readonly context: Record<string | number | symbol, unknown> = {};
dispatcher: Dispatcher = {};

#stubber: DenopsStubber;

/**
* Creates a new instance of DenopsStub.
* @param stubber - The Denops stubber object.
*/
constructor(stubber: DenopsStubber = {}) {
this.#stubber = stubber;
}

get name(): string {
return this.#stubber.name ?? "denops-test-stub";
}

get meta(): Meta {
return this.#stubber.meta ?? {
mode: "release",
host: "vim",
version: "0.0.0",
platform: "linux",
};
}

redraw(force?: boolean): Promise<void> {
if (this.#stubber.redraw) {
return this.#stubber.redraw(force);
}
return Promise.resolve();
}

call(fn: string, ...args: unknown[]): Promise<unknown> {
if (this.#stubber.call) {
args = normArgs(args);
return this.#stubber.call(fn, ...args);
}
return Promise.resolve();
}

batch(...calls: [string, ...unknown[]][]): Promise<unknown[]> {
if (this.#stubber.batch) {
calls = calls.map(([fn, ...args]) => [fn, ...normArgs(args)]);
return this.#stubber.batch(...calls);
}
if (this.#stubber.call && this.#stubber.useCallInBatch) {
return Promise.all(
calls.map(([fn, ...args]) => this.call(fn, ...args)),
);
}
return Promise.resolve(calls.map(() => undefined));
}

cmd(cmd: string, ctx: Context = {}): Promise<void> {
if (this.#stubber.cmd) {
return this.#stubber.cmd(cmd, ctx);
}
if (this.#stubber.call && this.#stubber.useCallInCmd) {
return this.call("denops#api#cmd", cmd, ctx).then(() => {});
}
return Promise.resolve();
}

eval(expr: string, ctx: Context = {}): Promise<unknown> {
if (this.#stubber.eval) {
return this.#stubber.eval(expr, ctx);
}
if (this.#stubber.call && this.#stubber.useCallInEval) {
return this.call("denops#api#eval", expr, ctx);
}
return Promise.resolve();
}

dispatch(
name: string,
fn: string,
...args: unknown[]
): Promise<unknown> {
if (this.#stubber.dispatch) {
return this.#stubber.dispatch(name, fn, ...args);
}
return Promise.resolve();
}
}

function normArgs(args: unknown[]): unknown[] {
const normArgs = [];
for (const arg of args) {
if (arg === undefined) {
break;
}
normArgs.push(arg);
}
return normArgs;
}
Loading