Skip to content

Commit

Permalink
Merge pull request #21 from vim-denops/test
Browse files Browse the repository at this point in the history
Improve test and remove workarounds
  • Loading branch information
lambdalisue committed May 12, 2024
2 parents 9385b45 + eccaac6 commit e4036c9
Show file tree
Hide file tree
Showing 7 changed files with 315 additions and 39 deletions.
10 changes: 10 additions & 0 deletions conf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,13 @@ export function getConfig(): Config {
};
return conf;
}

/** @internal for test */
function resetConfig(newConf?: Config): void {
conf = newConf;
}

/** @internal */
export const _internal = {
resetConfig,
};
129 changes: 129 additions & 0 deletions conf_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import {
assert,
assertEquals,
assertObjectMatch,
assertThrows,
} from "jsr:@std/[email protected]";
import { stub } from "jsr:@std/[email protected]/mock";
import { basename, isAbsolute } from "jsr:@std/[email protected]";
import { _internal, getConfig } from "./conf.ts";

const ENV_VARS: Readonly<Record<string, string | undefined>> = {
DENOPS_TEST_DENOPS_PATH: "denops.vim",
DENOPS_TEST_VIM_EXECUTABLE: undefined,
DENOPS_TEST_NVIM_EXECUTABLE: undefined,
DENOPS_TEST_VERBOSE: undefined,
DENOPS_TEST_CONNECT_TIMEOUT: undefined,
};

function stubEnvVars(envVars: Readonly<Record<string, string | undefined>>) {
return stub(Deno.env, "get", (name) => envVars[name]);
}

function stubConfModule(): Disposable {
const savedConf = getConfig();
_internal.resetConfig(undefined);
return {
[Symbol.dispose]() {
_internal.resetConfig(savedConf);
},
};
}

Deno.test("getConfig() throws if DENOPS_TEST_DENOPS_PATH env var is not set", () => {
using _module = stubConfModule();
using _env = stubEnvVars({ ...ENV_VARS, DENOPS_TEST_DENOPS_PATH: undefined });
assertThrows(
() => {
getConfig();
},
Error,
"'DENOPS_TEST_DENOPS_PATH' is required",
);
});

Deno.test("getConfig() returns `{ denopsPath: ... }` with resolved DENOPS_TEST_DENOPS_PATH env var", () => {
using _module = stubConfModule();
using _env = stubEnvVars({ ...ENV_VARS, DENOPS_TEST_DENOPS_PATH: "foo" });
const actual = getConfig();
assert(isAbsolute(actual.denopsPath), "`denopsPath` should be absolute path");
assertEquals(basename(actual.denopsPath), "foo");
});

Deno.test("getConfig() returns `{ vimExecutable: 'vim' }` if DENOPS_TEST_VIM_EXECUTABLE env var is not set", () => {
using _module = stubConfModule();
using _env = stubEnvVars({
...ENV_VARS,
DENOPS_TEST_VIM_EXECUTABLE: undefined,
});
const actual = getConfig();
assertObjectMatch(actual, { vimExecutable: "vim" });
});

Deno.test("getConfig() returns `{ vimExecutable: ... }` with DENOPS_TEST_VIM_EXECUTABLE env var", () => {
using _module = stubConfModule();
using _env = stubEnvVars({ ...ENV_VARS, DENOPS_TEST_VIM_EXECUTABLE: "foo" });
const actual = getConfig();
assertObjectMatch(actual, { vimExecutable: "foo" });
});

Deno.test("getConfig() returns `{ nvimExecutable: 'nvim' }` if DENOPS_TEST_NVIM_EXECUTABLE env var is not set", () => {
using _module = stubConfModule();
using _env = stubEnvVars({
...ENV_VARS,
DENOPS_TEST_NVIM_EXECUTABLE: undefined,
});
const actual = getConfig();
assertObjectMatch(actual, { nvimExecutable: "nvim" });
});

Deno.test("getConfig() returns `{ nvimExecutable: ... }` with DENOPS_TEST_NVIM_EXECUTABLE env var", () => {
using _module = stubConfModule();
using _env = stubEnvVars({ ...ENV_VARS, DENOPS_TEST_NVIM_EXECUTABLE: "foo" });
const actual = getConfig();
assertObjectMatch(actual, { nvimExecutable: "foo" });
});

Deno.test("getConfig() returns `{ verbose: false }` if DENOPS_TEST_VERBOSE env var is not set", () => {
using _module = stubConfModule();
using _env = stubEnvVars({ ...ENV_VARS, DENOPS_TEST_VERBOSE: undefined });
const actual = getConfig();
assertObjectMatch(actual, { verbose: false });
});

for (
const [input, expected] of [
["false", false],
["0", false],
["invalid", false],
["true", true],
["1", true],
] as const
) {
Deno.test(`getConfig() returns \`{ verbose: ${expected} }\` if DENOPS_TEST_VERBOSE env var is '${input}'`, () => {
using _module = stubConfModule();
using _env = stubEnvVars({ ...ENV_VARS, DENOPS_TEST_VERBOSE: input });
const actual = getConfig();
assertObjectMatch(actual, { verbose: expected });
});
}

for (
const [input, expected] of [
["123", 123],
["123.456", 123],
["0", undefined],
["-123", undefined],
["string", undefined],
] as const
) {
Deno.test(`getConfig() returns \`{ connectTimeout: ${expected} }\` if DENOPS_TEST_CONNECT_TIMEOUT env var is '${input}'`, () => {
using _module = stubConfModule();
using _env = stubEnvVars({
...ENV_VARS,
DENOPS_TEST_CONNECT_TIMEOUT: input,
});
const actual = getConfig();
assertObjectMatch(actual, { connectTimeout: expected });
});
}
13 changes: 2 additions & 11 deletions denops.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,8 @@ export class DenopsImpl implements Denops {
return this.#client.call("invoke", "redraw", [force]) as Promise<void>;
}

async call(fn: string, ...args: unknown[]): Promise<unknown> {
try {
return await this.#client.call("invoke", "call", [fn, ...args]);
} catch (err) {
// Denops v5 or earlier may throws an error as string in Neovim
// so convert the error into an Error instance
if (typeof err === "string") {
throw new Error(err);
}
throw err;
}
call(fn: string, ...args: unknown[]): Promise<unknown> {
return this.#client.call("invoke", "call", [fn, ...args]);
}

batch(
Expand Down
50 changes: 33 additions & 17 deletions tester.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,6 @@ export function test(
fn?: TestDefinition["fn"],
): void {
if (typeof modeOrDefinition === "string") {
if (!name) {
throw new Error(`'name' attribute is required`);
}
if (!fn) {
throw new Error(`'fn' attribute is required`);
}
testInternal({
mode: modeOrDefinition,
name,
Expand All @@ -109,35 +103,57 @@ export function test(
}
}

function testInternal(def: TestDefinition): void {
const { mode } = def;
function testInternal(def: Partial<TestDefinition>): void {
const {
mode,
name,
fn,
pluginName,
verbose,
prelude,
postlude,
...denoTestDef
} = def;
if (!mode) {
throw new Error("'mode' attribute is required");
}
if (!name) {
throw new Error("'name' attribute is required");
}
if (!fn) {
throw new Error("'fn' attribute is required");
}
if (mode === "all") {
testInternal({
...def,
name: `${def.name} (vim)`,
name: `${name} (vim)`,
mode: "vim",
});
testInternal({
...def,
name: `${def.name} (nvim)`,
name: `${name} (nvim)`,
mode: "nvim",
});
} else if (mode === "any") {
const m = sample(["vim", "nvim"] as const)!;
testInternal({
...def,
name: `${def.name} (${m})`,
name: `${name} (${m})`,
mode: m,
});
} else {
if (!["vim", "nvim"].includes(mode)) {
throw new Error(`'mode' attribute is invalid: ${mode}`);
}
Deno.test({
...def,
...denoTestDef,
name,
fn: (t) => {
return withDenops(mode, (denops) => def.fn(denops, t), {
pluginName: def.pluginName,
verbose: def.verbose,
prelude: def.prelude,
postlude: def.postlude,
return withDenops(mode, (denops) => fn.call(def, denops, t), {
pluginName,
verbose,
prelude,
postlude,
});
},
});
Expand Down
63 changes: 62 additions & 1 deletion tester_test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { assert, assertEquals, assertFalse } from "jsr:@std/[email protected]";
import {
assert,
assertEquals,
assertFalse,
assertThrows,
} from "jsr:@std/[email protected]";
import { test } from "./tester.ts";

test({
Expand Down Expand Up @@ -106,3 +111,59 @@ test({
});
},
});

Deno.test("test() throws if 'mode' option is empty", () => {
assertThrows(
() => {
test({
mode: "" as "vim",
name: "name",
fn: () => {},
});
},
Error,
"'mode' attribute is required",
);
});

Deno.test("test() throws if 'mode' option is invalid", () => {
assertThrows(
() => {
test({
mode: "foo" as "vim",
name: "name",
fn: () => {},
});
},
Error,
"'mode' attribute is invalid",
);
});

Deno.test("test() throws if 'name' option is empty", () => {
assertThrows(
() => {
test({
mode: "vim",
name: "",
fn: () => {},
});
},
Error,
"'name' attribute is required",
);
});

Deno.test("test() throws if 'fn' option is empty", () => {
assertThrows(
() => {
test({
mode: "vim",
name: "name",
fn: undefined as unknown as () => void,
});
},
Error,
"'fn' attribute is required",
);
});
8 changes: 0 additions & 8 deletions with.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,14 +138,6 @@ export async function withDenops(
session.start();
try {
const denops = await createDenops(session);

// Workaround for an unexpected "leaking async ops"
// https://github.com/denoland/deno/issues/15425#issuecomment-1368245954
// Maybe fixed in v1.41.0
// https://github.com/denoland/deno/pull/22413
// TODO: Remove this workaround when Deno minimum version changes to v1.41.0 or higher
await new Promise((resolve) => setTimeout(resolve, 0));

await main(denops);
} finally {
try {
Expand Down
Loading

0 comments on commit e4036c9

Please sign in to comment.