import { expect, test } from "bun:test";
import { fileURLToPath } from "node:url";
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import {
completionItems,
definitionLocation,
documentDiagnostics,
documentSymbols,
formatDocument,
positionAt,
symbolLocations,
virtualTypeScriptDocument,
workspaceCompletionItems,
clearWorkspaceIndexCache,
extractComponentRefactor,
htmlToWrn,
} from "../src/index.ts";
const document = {
uri: "file:///Counter.wrn",
text: `component Counter {\nstate count = 0\nview { }\n}`,
};
test("provides editor-neutral language features", () => {
expect(documentDiagnostics(document)).toEqual([]);
expect(completionItems().some((item) => item.label === "state")).toBe(true);
expect(documentSymbols(document).map((item) => item.name)).toContain("count");
const reference = positionAt(document.text, document.text.lastIndexOf("count"));
expect(definitionLocation(document, reference)).not.toBeNull();
expect(symbolLocations(document, reference)).toHaveLength(3);
expect(formatDocument(document)).toBeArray();
});
test("indexes workspace components, contracts, routes, translations, schemas and classes", () => {
const root = mkdtempSync(join(tmpdir(), "wrnexus-lsp-workspace-"));
try {
for (const directory of ["components", "pages", "locales", "db"])
mkdirSync(join(root, "app", directory), { recursive: true });
mkdirSync(join(root, "app", "pages", "users"), { recursive: true });
writeFileSync(
join(root, "app", "components", "Card.wrn"),
`component Card { props { title: string } outputs { close() } view {
User
} }`, { flag: "w" }, ); writeFileSync( join(root, "app", "locales", "en.json"), JSON.stringify({ user: { title: "User" } }), ); writeFileSync(join(root, "app", "db", "schema.ts"), `export const UserSchema = {};`); const labels = workspaceCompletionItems(root).map((item) => item.label); for (const label of [ "Card", "title", "@close", "slot:body", "/users/:id", "user.title", "UserSchema", "text-red-500", ]) expect(labels).toContain(label); } finally { rmSync(root, { recursive: true, force: true }); } }); test("workspace indexing is cached and skips dependency and generated trees", () => { const root = mkdtempSync(join(tmpdir(), "wrnexus-lsp-bounded-")); try { mkdirSync(join(root, "app", "pages"), { recursive: true }); mkdirSync(join(root, "app", "node_modules", "large-package"), { recursive: true }); mkdirSync(join(root, "app", ".wrnexus"), { recursive: true }); writeFileSync(join(root, "app", "pages", "index.wrn"), `page Home { view {Home
} }`); writeFileSync( join(root, "app", "node_modules", "large-package", "secret.ts"), `export const DependencySchema = {};`, ); writeFileSync(join(root, "app", ".wrnexus", "generated.ts"), `export const CacheSchema = {};`); const first = workspaceCompletionItems(root); const second = workspaceCompletionItems(root); expect(second).toBe(first); expect(first.map((item) => item.label)).not.toContain("DependencySchema"); expect(first.map((item) => item.label)).not.toContain("CacheSchema"); writeFileSync(join(root, "app", "pages", "later.wrn"), `page Later { view {Later
} }`); expect(workspaceCompletionItems(root).map((item) => item.label)).not.toContain("/later"); clearWorkspaceIndexCache(root); expect(workspaceCompletionItems(root).map((item) => item.label)).toContain("/later"); } finally { clearWorkspaceIndexCache(root); rmSync(root, { recursive: true, force: true }); } }); test("provides safe component extraction and HTML conversion refactors", () => { const doc = { uri: "file:///project/app/pages/index.wrn", version: 2, text: "{label}
}\n}`, }; const virtual = virtualTypeScriptDocument(typed); expect(virtual.languageId).toBe("typescript"); expect(virtual.uri).toEndWith(".wrn.ts"); expect(virtual.text).toContain("declare const count: Readonly
\n }\n}`,
});
expect(diagnostics.some((item) => String(item.code).startsWith("WRN-A11Y"))).toBe(true);
});
test("skips compiler graph creation for oversized WRN documents", () => {
const diagnostics = documentDiagnostics({
uri: "file:///large.wrn",
text: `page Large { view { ${"x".repeat(1_048_576)}
} }`, }); expect(diagnostics).toHaveLength(1); expect(diagnostics[0]?.code).toBe("WRN-LSP-FILE-SIZE"); }); function packet(value: unknown): Uint8Array { const body = JSON.stringify(value); return new TextEncoder().encode(`Content-Length: ${Buffer.byteLength(body)}\r\n\r\n${body}`); } test("serves initialize over standard LSP stdio framing", async () => { const process = Bun.spawn( ["bun", "run", fileURLToPath(new URL("../src/server.ts", import.meta.url))], { stdin: "pipe", stdout: "pipe", stderr: "pipe", }, ); process.stdin.write(packet({ jsonrpc: "2.0", id: 1, method: "initialize", params: {} })); await process.stdin.flush(); const reader = process.stdout.getReader(); let output = ""; while (!output.includes('"id":1')) { const chunk = await reader.read(); if (chunk.done) break; output += new TextDecoder().decode(chunk.value); } expect(output).toContain("WRNexus Language Server"); expect(output).toContain("documentFormattingProvider"); expect(output).toContain("wrnexusVirtualTypeScript"); process.kill(); await process.exited; }); test("coalesces rapid document changes into one pending diagnostic analysis", async () => { const process = Bun.spawn( ["bun", "run", fileURLToPath(new URL("../src/server.ts", import.meta.url))], { stdin: "pipe", stdout: "pipe", stderr: "pipe" }, ); const uri = "file:///rapid.wrn"; process.stdin.write( packet({ jsonrpc: "2.0", method: "textDocument/didOpen", params: { textDocument: { uri, version: 1, text: `page Rapid { view {1
} }` } }, }), ); for (let version = 2; version <= 50; version++) { process.stdin.write( packet({ jsonrpc: "2.0", method: "textDocument/didChange", params: { textDocument: { uri, version }, contentChanges: [{ text: `page Rapid { view {${version}
} }` }], }, }), ); } process.stdin.write( packet({ jsonrpc: "2.0", id: 99, method: "wrnexus/serverStatus", params: {} }), ); await process.stdin.flush(); const reader = process.stdout.getReader(); let output = ""; while (!output.includes('"id":99')) { const chunk = await reader.read(); if (chunk.done) break; output += new TextDecoder().decode(chunk.value); } expect(output).toContain('"pendingDiagnostics":1'); expect(output).toContain('"openDocuments":1'); process.kill(); await process.exited; });