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 { } }`, ); writeFileSync( join(root, "app", "pages", "users", "[id].wrn"), `page User { 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: "
Hello
", }; const range = { start: { line: 0, character: 0 }, end: { line: 0, character: doc.text.length } }; const edit = extractComponentRefactor(doc, range, "Greeting"); expect(edit.documentChanges).toHaveLength(3); expect(JSON.stringify(edit)).toContain(""); expect(htmlToWrn(``, "Imported")).toContain( `@click="save()"`, ); }); test("provides a mapped TypeScript virtual document and expression type diagnostics", () => { const typed = { uri: "file:///Typed.wrn", text: `component Typed {\nprops { count: number }\nstate label: string = count\nview {

{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"); expect(virtual.mappings.length).toBeGreaterThan(0); expect(documentDiagnostics(typed).some((item) => String(item.code).startsWith("WRN-TYPE-"))).toBe( true, ); }); test("reports compiler and accessibility diagnostics", () => { const diagnostics = documentDiagnostics({ uri: "file:///bad.wrn", text: `page Bad {\n view {\n \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; });