Skip to content

Commit

Permalink
Merge pull request #407 from vim-denops/plugin-type-check-no-loaded
Browse files Browse the repository at this point in the history
👍 `denops#plugin#check_type()` shows 'no plugins are loaded' message
  • Loading branch information
lambdalisue authored Aug 7, 2024
2 parents 95f076b + 33ff712 commit eb9ef6a
Show file tree
Hide file tree
Showing 11 changed files with 2,507 additions and 2,227 deletions.
11 changes: 8 additions & 3 deletions autoload/denops/plugin.vim
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,15 @@ endfunction

function! denops#plugin#check_type(...) abort
if a:0
let l:scripts = [denops#_internal#plugin#get(a:1).script]
const l:plugins = [denops#_internal#plugin#get(a:1)]
else
let l:scripts = denops#_internal#plugin#list()
\->copy()->map({ _, v -> v.script })->filter({ _, v -> v !=# '' })
const l:plugins = denops#_internal#plugin#list()
endif
const l:scripts = l:plugins
\->copy()->map({ _, v -> v.script })->filter({ _, v -> v !=# '' })
if empty(l:scripts)
call denops#_internal#echo#info("No plugins are loaded")
return
endif
let l:args = [g:denops#deno, 'check'] + l:scripts
let l:job = denops#_internal#job#start(l:args, {
Expand Down
2 changes: 1 addition & 1 deletion doc/denops.txt
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@ denops#plugin#reload({plugin}[, {options}])

*denops#plugin#check_type()*
denops#plugin#check_type([{name}])
Runs Deno's type check feature for {name} or all registered plugins.
Runs Deno's type check feature for {name} or all loaded plugins.
The result of the check will be displayed in |message-history|. It
throws an error when {name} does not match the |denops-plugin-name|.

Expand Down
218 changes: 218 additions & 0 deletions tests/denops/runtime/functions/plugin/check_type_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
import {
assertMatch,
assertNotMatch,
assertRejects,
} from "jsr:@std/assert@^1.0.1";
import { join } from "jsr:@std/path@^1.0.2/join";
import { testHost } from "/denops-testutil/host.ts";
import { wait } from "/denops-testutil/wait.ts";

const scriptValid = resolve("dummy_valid_plugin.ts");

testHost({
name: "denops#plugin#check_type()",
mode: "all",
postlude: [
"runtime plugin/denops.vim",
],
fn: async ({ host, t, stderr }) => {
let outputs: string[] = [];
stderr.pipeTo(
new WritableStream({ write: (s) => void outputs.push(s) }),
).catch(() => {});
await wait(() => host.call("eval", "denops#server#status() ==# 'running'"));
await host.call("execute", [
"let g:__test_denops_events = []",
"autocmd User DenopsPlugin* call add(g:__test_denops_events, expand('<amatch>'))",
], "");

await t.step("if no arguments is specified", async (t) => {
await t.step("if no plugins are loaded", async (t) => {
outputs = [];
await host.call("execute", [
"let g:__test_denops_events = []",
`call denops#plugin#check_type()`,
], "");

await t.step("outputs an info message after delayed", async () => {
await wait(() => outputs.join("").includes("No plugins are loaded"));
});
});

await t.step("if some plugins are loaded", async (t) => {
await host.call("execute", [
`call denops#plugin#load('dummyCheckNoArguments', '${scriptValid}')`,
], "");
outputs = [];
await host.call("execute", [
"let g:__test_denops_events = []",
`call denops#plugin#check_type()`,
], "");

await t.step("outputs an info message after delayed", async () => {
await wait(() => outputs.join("").includes("Type check"));
assertMatch(outputs.join(""), /Type check succeeded/);
});
});

await t.step("if not exists plugins are tried to load", async (t) => {
await host.call("execute", [
"call denops#plugin#load('notexistsplugin', 'path-to-not-exists-plugin.ts')",
], "");
outputs = [];
await host.call("execute", [
"let g:__test_denops_events = []",
`call denops#plugin#check_type()`,
], "");

await t.step("outputs an error message after delayed", async () => {
await wait(() => outputs.join("").includes("Type check"));
assertMatch(outputs.join(""), /Type check failed:/);
});

await t.step("does not outputs an usage", () => {
assertNotMatch(outputs.join(""), /Usage:/);
});
});
});

await t.step("if the plugin name is invalid", async (t) => {
await t.step("throws an error", async () => {
// NOTE: '.' is not allowed in plugin name.
await assertRejects(
() => host.call("denops#plugin#check_type", "dummy.invalid"),
Error,
"Invalid plugin name: dummy.invalid",
);
});
});

await t.step("if the plugin is not yet loaded", async (t) => {
outputs = [];
await host.call("execute", [
"let g:__test_denops_events = []",
`call denops#plugin#check_type('notloadedplugin')`,
], "");

await t.step("outputs an info message after delayed", async () => {
await wait(() => outputs.join("").includes("No plugins are loaded"));
});

await t.step("does not outputs an usage", () => {
assertNotMatch(outputs.join(""), /Usage:/);
});
});

await t.step("if the plugin is not exists", async (t) => {
await host.call("execute", [
"call denops#plugin#load('notexistsplugin', 'path-to-not-exists-plugin.ts')",
], "");
outputs = [];
await host.call("execute", [
"let g:__test_denops_events = []",
`call denops#plugin#check_type('notexistsplugin')`,
], "");

await t.step("outputs an error message after delayed", async () => {
await wait(() => outputs.join("").includes("Type check"));
assertMatch(outputs.join(""), /Type check failed:/);
});

await t.step("does not outputs an usage", () => {
assertNotMatch(outputs.join(""), /Usage:/);
});
});

await t.step("if the plugin is loaded", async (t) => {
// Load plugin and wait.
await host.call("execute", [
"let g:__test_denops_events = []",
`call denops#plugin#load('dummyCheckTypeLoaded', '${scriptValid}')`,
], "");
await wait(async () =>
(await host.call("eval", "g:__test_denops_events") as string[])
.includes("DenopsPluginPost:dummyCheckTypeLoaded")
);

outputs = [];
await host.call("execute", [
"let g:__test_denops_events = []",
`call denops#plugin#check_type('dummyCheckTypeLoaded')`,
], "");

await t.step("outputs an info message after delayed", async () => {
await wait(() => outputs.join("").includes("Type check"));
assertMatch(outputs.join(""), /Type check succeeded/);
});
});

await t.step("if the plugin is unloaded", async (t) => {
// Load plugin and wait.
await host.call("execute", [
"let g:__test_denops_events = []",
`call denops#plugin#load('dummyCheckTypeUnloaded', '${scriptValid}')`,
], "");
await wait(async () =>
(await host.call("eval", "g:__test_denops_events") as string[])
.includes("DenopsPluginPost:dummyCheckTypeUnloaded")
);
// Unload plugin and wait.
await host.call("execute", [
"let g:__test_denops_events = []",
`call denops#plugin#unload('dummyCheckTypeUnloaded')`,
], "");
await wait(async () =>
(await host.call("eval", "g:__test_denops_events") as string[])
.includes("DenopsPluginUnloadPost:dummyCheckTypeUnloaded")
);

outputs = [];
await host.call("execute", [
"let g:__test_denops_events = []",
`call denops#plugin#check_type('dummyCheckTypeUnloaded')`,
], "");

await t.step("outputs an info message after delayed", async () => {
await wait(() => outputs.join("").includes("Type check"));
assertMatch(outputs.join(""), /Type check succeeded/);
});
});

await t.step("if the plugin is reloaded", async (t) => {
// Load plugin and wait.
await host.call("execute", [
"let g:__test_denops_events = []",
`call denops#plugin#load('dummyCheckTypeReloaded', '${scriptValid}')`,
], "");
await wait(async () =>
(await host.call("eval", "g:__test_denops_events") as string[])
.includes("DenopsPluginPost:dummyCheckTypeReloaded")
);
// Reload plugin and wait.
await host.call("execute", [
"let g:__test_denops_events = []",
`call denops#plugin#reload('dummyCheckTypeReloaded')`,
], "");
await wait(async () =>
(await host.call("eval", "g:__test_denops_events") as string[])
.includes("DenopsPluginPost:dummyCheckTypeReloaded")
);

outputs = [];
await host.call("execute", [
"let g:__test_denops_events = []",
`call denops#plugin#check_type('dummyCheckTypeReloaded')`,
], "");

await t.step("outputs an info message after delayed", async () => {
await wait(() => outputs.join("").includes("Type check"));
assertMatch(outputs.join(""), /Type check succeeded/);
});
});
},
});

/** Resolve testdata script path. */
function resolve(path: string): string {
return join(import.meta.dirname!, `../../../testdata/${path}`);
}
90 changes: 90 additions & 0 deletions tests/denops/runtime/functions/plugin/discover_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import {
assertArrayIncludes,
assertEquals,
assertMatch,
} from "jsr:@std/assert@^1.0.1";
import { delay } from "jsr:@std/async@^0.224.0";
import { join } from "jsr:@std/path@^1.0.2/join";
import { testHost } from "/denops-testutil/host.ts";
import { wait } from "/denops-testutil/wait.ts";

const MESSAGE_DELAY = 200; // msc

const runtimepathPlugin = resolve("dummy_plugins");

testHost({
name: "denops#plugin#discover()",
mode: "all",
postlude: [
"runtime plugin/denops.vim",
],
fn: async ({ host, t, stderr }) => {
let outputs: string[] = [];
stderr.pipeTo(
new WritableStream({ write: (s) => void outputs.push(s) }),
).catch(() => {});
await wait(() => host.call("eval", "denops#server#status() ==# 'running'"));
await host.call("execute", [
"let g:__test_denops_events = []",
"autocmd User DenopsPlugin* call add(g:__test_denops_events, expand('<amatch>'))",
], "");

outputs = [];
await host.call("execute", [
`set runtimepath+=${await host.call("fnameescape", runtimepathPlugin)}`,
`call denops#plugin#discover()`,
], "");

await t.step("loads denops plugins", async () => {
const loaded_events = [
"DenopsPluginPost:",
"DenopsPluginFail:",
];
await wait(async () =>
(await host.call("eval", "g:__test_denops_events") as string[])
.filter((ev) => loaded_events.some((name) => ev.startsWith(name)))
.length >= 2
);
});

await t.step("fires DenopsPlugin* events", async () => {
assertArrayIncludes(
await host.call("eval", "g:__test_denops_events") as string[],
[
"DenopsPluginPre:dummy_valid",
"DenopsPluginPost:dummy_valid",
"DenopsPluginPre:dummy_invalid",
"DenopsPluginFail:dummy_invalid",
],
);
});

await t.step("does not load invaid name plugins", async () => {
const valid_names = [
":dummy_valid",
":dummy_invalid",
] as const;
const actual =
(await host.call("eval", "g:__test_denops_events") as string[])
.filter((ev) => !valid_names.some((name) => ev.endsWith(name)));
assertEquals(actual, []);
});

await t.step("calls the plugin entrypoint", () => {
assertMatch(outputs.join(""), /Hello, Denops!/);
});

await t.step("outputs an error message after delayed", async () => {
await delay(MESSAGE_DELAY);
assertMatch(
outputs.join(""),
/Failed to load plugin 'dummy_invalid': Error: This is dummy error/,
);
});
},
});

/** Resolve testdata script path. */
function resolve(path: string): string {
return join(import.meta.dirname!, `../../../testdata/${path}`);
}
Loading

0 comments on commit eb9ef6a

Please sign in to comment.