import { expect, test } from "bun:test"; import { documentDiagnostics } from "../src/index.ts"; import { htmlCompletions, isApiAttributeValuePosition } from "../src/html-service.ts"; import { apiCallCompletions } from "../src/server.ts"; const SOURCE = `page Search { apis { searchUsers POST /api/users { request { body { name?: string } } response { return data.users } } } view { } } `; // `html-regions.ts` caches view regions by `uri` + `version`, so each fixture // needs its own uri — otherwise a later document reuses an earlier one's // cached regions and the position checks below silently look at stale spans. let nextDocId = 0; function doc(text: string) { nextDocId += 1; return { uri: `file:///Page-${nextDocId}.wrn`, text, version: 1 }; } function positionOf(text: string, needle: string) { const offset = text.indexOf(needle) + needle.length; const before = text.slice(0, offset); const lines = before.split("\n"); return { line: lines.length - 1, character: lines[lines.length - 1]!.length }; } test('api="searchUsers" produces no unknown-attribute diagnostic', () => { const diagnostics = documentDiagnostics(doc(SOURCE)); expect(diagnostics.filter((item) => item.severity === 1)).toEqual([]); expect( diagnostics.some((item) => /unknown/i.test(item.message) && /api/i.test(item.message)), ).toBe(false); }); test("`api` is offered as an attribute name on any element, not flagged unknown", () => { const opening = " } } `; const barePosition = isApiAttributeValuePosition(doc(bare), positionOf(bare, 'api="sear')); expect(barePosition).toBe(true); expect(apiCallCompletions(bare).map((item) => item.label)).toContain("searchUsers"); const call = `page Search { apis { searchUsers POST /api/users { response { return data.users } } } view { } } `; expect(isApiAttributeValuePosition(doc(call), positionOf(call, 'api="searchUsers('))).toBe(true); const callWithArgs = `page Search { apis { searchUsers POST /api/users { request { body { name?: string } } response { return data.users } } } view { } } `; expect( isApiAttributeValuePosition(doc(callWithArgs), positionOf(callWithArgs, "{ name: '")), ).toBe(true); }); test('html completion defers to apiCallCompletions inside api="…" — it does not offer its own value completion', () => { const items = htmlCompletions(doc(SOURCE), positionOf(SOURCE, 'api="search')); expect(items).toEqual([]); }); test("a badly-broken document (unclosed braces, truncated apis block) still answers rather than throwing", () => { const broken = `page Search { apis { searchUsers POST /api/users { request { body { name?: string view { `; expect(() => apiCallCompletions(broken)).not.toThrow(); expect(apiCallCompletions(broken)).toEqual([]); expect(() => documentDiagnostics(doc(broken))).not.toThrow(); expect(() => isApiAttributeValuePosition(doc(broken), positionOf(broken, 'api="sear')), ).not.toThrow(); }); test("an ssr {} data block, which the parser now rejects with a ParseError, still answers rather than throwing", () => { const legacy = `page Search { ssr { api x GET /api/x { response { return data } } } view { } } `; expect(() => apiCallCompletions(legacy)).not.toThrow(); expect(apiCallCompletions(legacy)).toEqual([]); expect(() => documentDiagnostics(doc(legacy))).not.toThrow(); });