Files
plainleaf/plugs/editor/lua.test.ts
T

163 lines
4.3 KiB
TypeScript

import { afterEach, expect, test } from "vitest";
import type {
CompleteEvent,
EnrichedClickEvent,
} from "@silverbulletmd/silverbullet/type/client";
import { luaComplete, luaNavigate } from "./lua.ts";
const originalSyscall = globalThis.syscall;
afterEach(() => {
globalThis.syscall = originalSyscall;
});
function completionEvent(
linePrefix: string,
luaCode = linePrefix,
): CompleteEvent {
return {
pageName: "Test",
linePrefix,
pos: luaCode.length,
parentNodes: [`FencedCode:space-lua\n${luaCode}`],
};
}
function clickEvent(pos: number, parentNodes: string[]): EnrichedClickEvent {
return {
page: "Test",
pos,
metaKey: false,
ctrlKey: true,
altKey: false,
parentNodes,
};
}
test("luaComplete returns documented function snippets", async () => {
globalThis.syscall = async (name: string, ...args: any[]) => {
expect(name).toBe("lua.inspect");
expect(args).toEqual([["string"]]);
return {
type: "table",
properties: [
{
key: "gmatch",
type: "function",
functionInfo: {
kind: "builtin",
name: "string.gmatch",
description: "Iterates over matches in a string.",
parameters: [
{ name: "s", type: "string" },
{ name: "pattern", type: "string" },
],
},
},
],
};
};
expect(await luaComplete(completionEvent("string.gm"))).toEqual({
from: "string.".length,
options: [
expect.objectContaining({
label: "gmatch(s, pattern)",
snippet: "gmatch(${})",
detail: "Iterates over matches in a string.",
documentation: expect.stringContaining("## string.gmatch"),
}),
],
});
});
test("luaComplete includes properties from schema-derived locals", async () => {
globalThis.syscall = async (name: string, ...args: any[]) => {
if (name === "config.get") {
expect(args[0]).toEqual(["tags", "person", "schema"]);
return {
type: "object",
properties: {
name: { type: "string" },
age: { type: "number" },
},
};
}
throw new Error(`Unexpected syscall: ${name}`);
};
const luaCode = "local person = tags.person\nperson.na";
expect(await luaComplete(completionEvent("person.na", luaCode))).toEqual({
from: luaCode.length - 2,
options: [
{
label: "name",
detail: "string",
},
],
});
});
test("luaNavigate opens Lua-defined function sources", async () => {
const calls: [string, ...any[]][] = [];
globalThis.syscall = async (name: string, ...args: any[]) => {
calls.push([name, ...args]);
if (name === "editor.getText") return "demo.greet()";
if (name === "lua.inspect") {
expect(args).toEqual([["demo", "greet"]]);
return {
type: "function",
functionInfo: { kind: "lua", name: "demo.greet" },
definition: "Library/Test@123",
properties: [],
};
}
if (name === "editor.navigate") return;
throw new Error(`Unexpected syscall: ${name}`);
};
await luaNavigate(clickEvent(7, ["FencedCode:space-lua\ndemo.greet()"]));
expect(calls).toContainEqual([
"editor.navigate",
"Library/Test@123",
false,
false,
]);
});
test("luaNavigate leaves Markdown links to regular navigation", async () => {
globalThis.syscall = async (name: string) => {
throw new Error(`Unexpected syscall: ${name}`);
};
await luaNavigate(
clickEvent(7, ["WikiLink", "FencedCode:space-lua\nprint([[Some Page]])"]),
);
});
test("luaNavigate reports values without Lua definitions", async () => {
const calls: [string, ...any[]][] = [];
globalThis.syscall = async (name: string, ...args: any[]) => {
calls.push([name, ...args]);
if (name === "editor.getText") return "string.gmatch()";
if (name === "lua.inspect") {
return {
type: "function",
functionInfo: { kind: "builtin", name: "string.gmatch" },
properties: [],
};
}
if (name === "editor.flashNotification") return;
throw new Error(`Unexpected syscall: ${name}`);
};
await luaNavigate(clickEvent(8, ["FencedCode:space-lua\nstring.gmatch()"]));
expect(calls).toContainEqual([
"editor.flashNotification",
"Cannot navigate to definition; not defined in Lua.",
"info",
]);
});