154 lines
3.4 KiB
TypeScript
154 lines
3.4 KiB
TypeScript
import { expect, test } from "bun:test";
|
|
import {
|
|
htmlCompletions,
|
|
htmlFoldingRanges,
|
|
htmlHover,
|
|
htmlTagComplete,
|
|
} from "../src/html-service.ts";
|
|
|
|
function doc(text: string) {
|
|
return { uri: "file:///Page.wrn", text };
|
|
}
|
|
|
|
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("suggests HTML tags inside a view block", () => {
|
|
const text = `page A {
|
|
view {
|
|
<
|
|
}
|
|
}
|
|
`;
|
|
const items = htmlCompletions(doc(text), positionOf(text, " <"));
|
|
expect(items.some((item) => item.label === "div")).toBe(true);
|
|
expect(items.every((item) => item.sortText?.startsWith("1"))).toBe(true);
|
|
});
|
|
|
|
test("suggests attributes inside a tag", () => {
|
|
const text = `page A {
|
|
view {
|
|
<input
|
|
}
|
|
}
|
|
`;
|
|
const items = htmlCompletions(doc(text), positionOf(text, "<input "));
|
|
expect(items.some((item) => item.label === "type")).toBe(true);
|
|
});
|
|
|
|
test("returns nothing outside a view block", () => {
|
|
const text = `page A {
|
|
functions {
|
|
function go() { }
|
|
}
|
|
}
|
|
`;
|
|
expect(htmlCompletions(doc(text), positionOf(text, "function go() "))).toEqual([]);
|
|
});
|
|
|
|
test("hovers a tag inside a view block and nothing outside one", () => {
|
|
const text = `page A {
|
|
view {
|
|
<div>x</div>
|
|
}
|
|
}
|
|
`;
|
|
expect(htmlHover(doc(text), positionOf(text, "<di"))).not.toBeNull();
|
|
|
|
const code = `page A {
|
|
functions {
|
|
function go() { }
|
|
}
|
|
}
|
|
`;
|
|
expect(htmlHover(doc(code), positionOf(code, "func"))).toBeNull();
|
|
});
|
|
|
|
test("closes an open tag and leaves void elements alone", () => {
|
|
const open = `page A {
|
|
view {
|
|
<div>
|
|
}
|
|
}
|
|
`;
|
|
expect(htmlTagComplete(doc(open), positionOf(open, "<div>"))).toContain("</div>");
|
|
|
|
const void_ = `page A {
|
|
view {
|
|
<br>
|
|
}
|
|
}
|
|
`;
|
|
expect(htmlTagComplete(doc(void_), positionOf(void_, "<br>"))).toBeNull();
|
|
});
|
|
|
|
test("completes a self-closing component tag", () => {
|
|
const text = `page A {
|
|
view {
|
|
<Card /
|
|
}
|
|
}
|
|
`;
|
|
expect(htmlTagComplete(doc(text), positionOf(text, "<Card /"))).toBe(">");
|
|
});
|
|
|
|
test("does not misfire inside a quoted attribute value containing a slash", () => {
|
|
const doubleQuoted = `page A {
|
|
view {
|
|
<a href="https:/
|
|
}
|
|
}
|
|
`;
|
|
expect(htmlTagComplete(doc(doubleQuoted), positionOf(doubleQuoted, `href="https:/`))).toBeNull();
|
|
|
|
const singleQuoted = `page A {
|
|
view {
|
|
<img src='/assets/
|
|
}
|
|
}
|
|
`;
|
|
expect(
|
|
htmlTagComplete(doc(singleQuoted), positionOf(singleQuoted, `src='/assets/`)),
|
|
).toBeNull();
|
|
});
|
|
|
|
test("still completes a self-close after a preceding attribute", () => {
|
|
const text = `page A {
|
|
view {
|
|
<Card title="x" /
|
|
}
|
|
}
|
|
`;
|
|
expect(htmlTagComplete(doc(text), positionOf(text, `title="x" /`))).toBe(">");
|
|
});
|
|
|
|
test("returns no tag completion outside a view block", () => {
|
|
const text = `page A {
|
|
functions {
|
|
function go() { }
|
|
}
|
|
}
|
|
`;
|
|
expect(htmlTagComplete(doc(text), positionOf(text, "function go() "))).toBeNull();
|
|
});
|
|
|
|
test("folding ranges stay inside view regions", () => {
|
|
const text = `page A {
|
|
view {
|
|
<ul>
|
|
<li>one</li>
|
|
</ul>
|
|
}
|
|
}
|
|
`;
|
|
const ranges = htmlFoldingRanges(doc(text));
|
|
expect(ranges.length).toBeGreaterThan(0);
|
|
|
|
const viewStartLine = text.slice(0, text.indexOf("view {")).split("\n").length - 1;
|
|
for (const range of ranges) expect(range.startLine).toBeGreaterThan(viewStartLine - 1);
|
|
});
|