Files
WRNexusJS/packages/language-server/test/html-regions.test.ts
T

147 lines
4.7 KiB
TypeScript

import { expect, test } from "bun:test";
import {
clearHtmlRegionCache,
isInsideHtml,
viewRegions,
virtualHtmlDocument,
} from "../src/html-regions.ts";
function doc(text: string): { uri: string; text: string; version?: number } {
return { uri: `file:///${Math.random()}.wrn`, text };
}
const PAGE = `page Home {
view {
<div class="card">hello</div>
}
}
`;
test("the virtual document preserves length and newline offsets", () => {
// This is what makes position mapping unnecessary. If it breaks, every
// feature reports positions off by some amount instead of failing loudly.
const source = doc(PAGE);
const virtual = virtualHtmlDocument(source);
expect(virtual.text.length).toBe(source.text.length);
expect(virtual.languageId).toBe("html");
for (let i = 0; i < source.text.length; i += 1) {
if (source.text[i] === "\n") expect(virtual.text[i]).toBe("\n");
}
});
test("markup survives into the virtual document and everything else is blanked", () => {
const virtual = virtualHtmlDocument(doc(PAGE));
expect(virtual.text).toContain('<div class="card">hello</div>');
expect(virtual.text).not.toContain("page Home");
expect(virtual.text).not.toContain("view");
});
test("an apostrophe in text content does not swallow later regions", () => {
// A scanner treating ' as a string delimiter anywhere considers the rest of
// the file one open string and loses every later region.
const source = `page A {
view {
<p>it's fine</p>
}
}
component B {
view {
<span>second</span>
}
}
`;
expect(viewRegions(source)).toHaveLength(2);
expect(virtualHtmlDocument(doc(source)).text).toContain("<span>second</span>");
});
test("interpolation braces nest without ending the region early", () => {
const source = `page A {
view {
<div class={cond ? "a" : "b"} data-x={{ a: 1 }}>after</div>
}
}
`;
const regions = viewRegions(source);
expect(regions).toHaveLength(1);
expect(virtualHtmlDocument(doc(source)).text).toContain("after</div>");
});
test("unparseable mid-edit markup still yields a region", () => {
// Completion fires exactly when the document does not parse.
const source = `page A {
view {
<div class="
}
}
`;
expect(viewRegions(source).length).toBe(1);
});
test("a file with no view block yields no regions and a fully blank document", () => {
const source = `page A {
functions {
function go() {}
}
}
`;
expect(viewRegions(source)).toEqual([]);
expect(virtualHtmlDocument(doc(source)).text.trim()).toBe("");
});
test("regions are cached per document version", () => {
// One keystroke fans out into completion, hover, and tag-close requests.
const first = doc(PAGE);
first.version = 1;
expect(isInsideHtml(first, PAGE.indexOf("<div"))).toBe(true);
// Same version, mutated text: the cached regions are reused, proving the
// scan did not run again.
first.text = "page A { }";
expect(isInsideHtml(first, PAGE.indexOf("<div"))).toBe(true);
first.version = 2;
expect(isInsideHtml(first, PAGE.indexOf("<div"))).toBe(false);
});
test("clearHtmlRegionCache drops a closed document's cached regions", () => {
// A reopened document commonly restarts at version 1. Without clearing the
// cache on close, that version would match the stale entry from the prior
// session and serve regions scanned from the old text.
const first = doc(PAGE);
first.version = 1;
expect(isInsideHtml(first, PAGE.indexOf("<div"))).toBe(true);
clearHtmlRegionCache(first.uri);
// Same uri, same version 1, but a document with no view block at all: if
// the cache had survived, this would still report true from the old scan.
const reopened = { uri: first.uri, text: "page A { }", version: 1 };
expect(isInsideHtml(reopened, PAGE.indexOf("<div"))).toBe(false);
});
test("isInsideHtml distinguishes markup from surrounding code", () => {
const source = doc(PAGE);
const markupOffset = PAGE.indexOf("<div");
const keywordOffset = PAGE.indexOf("page");
expect(isInsideHtml(source, markupOffset)).toBe(true);
expect(isInsideHtml(source, keywordOffset)).toBe(false);
});
test("documents without a version field scan every time and detect mutations", () => {
// Without a version, the cache has no key to validate freshness. Mutations
// must be detected on every call, even when uri and document are reused.
const source = doc(PAGE);
// Explicitly verify version is undefined (not set by doc() helper).
expect(source.version).toBeUndefined();
const markupOffset = PAGE.indexOf("<div");
expect(isInsideHtml(source, markupOffset)).toBe(true);
// Mutate the text: remove the view block.
source.text = "page A { }";
// Same uri, same missing version, but different text: mutation must be detected.
expect(isInsideHtml(source, markupOffset)).toBe(false);
});