import { describe, it } from "@std/testing/bdd"; import { expect } from "@std/expect"; import { decodeLanguageInput, languageForFile, languageForSource, languageMatchingSources, } from "../../../../../lib/view/languages/language.ts"; import { xmlLanguage } from "../../../../../lib/view/languages/xml/language.ts"; import type { Line, TokenClass } from "../../../../../lib/view/model.ts"; /** The source each colored line reconstructs. */ function verbatim(lines: readonly Line[]): string { return lines.map((line) => line.spans.map((span) => span.text).join("")) .join("\n"); } /** Every distinct class the colored lines give to an exact fragment. */ function classesOf(lines: readonly Line[], text: string): TokenClass[] { return [ ...new Set( lines.flatMap((line) => line.spans.filter((span) => span.text === text).map((span) => span.cls) ), ), ]; } /** The labels and depths of a document's elements, in order. */ function outline(source: string): [string, number, number, number][] { return xmlLanguage.parseDocument(source).flatStructure.map((node) => [ node.label, node.depth, node.startLine, node.endLine, ]); } describe("xmlLanguage", () => { it("selects XML and the formats built on it", () => { for ( const path of [ "app/src/main/AndroidManifest.xml", "logo.svg", "Info.plist", "App.entitlements", "PrivacyInfo.xcprivacy", "contents.xcworkspacedata", ] ) { expect(languageForFile(path).id).toBe("xml"); } }); it("selects XML for a source that opens with an XML declaration", () => { const declared = '\n\n'; for (const path of ["App.csproj", "feed", undefined]) { expect(languageForSource(path, declared).id).toBe("xml"); } const bytes = new TextEncoder().encode(`\uFEFF${declared}`); expect(decodeLanguageInput("Web.config", bytes).language.id).toBe("xml"); expect(languageMatchingSources("Main.storyboard", ["", declared])?.id) .toBe("xml"); }); it("leaves other sources and recognized names to their own language", () => { for ( const text of [ ' \n\n', "\n", "Type \n\n'; expect(languageForSource("notes.txt", declared).id).toBe("plain-text"); expect(languageForSource("README.md", declared).id).toBe("markdown"); }); it("colors tags, attributes, and the markup around them", () => { const source = [ '', ' %shared; ]>', "", '', ' Tom & Jerry ©', " ", " & ]]>", "", ].join("\n"); const lines = xmlLanguage.highlightLines(source); expect(classesOf(lines, "")).toEqual(["punctuation"]); expect(classesOf(lines, "DOCTYPE")).toEqual(["keyword"]); expect(classesOf(lines, "note")).toEqual(["identifier"]); expect(classesOf(lines, "ENTITY")).toEqual(["keyword"]); expect(classesOf(lines, '"Fabric"')).toEqual(["string"]); expect(classesOf(lines, "%shared;")).toEqual(["keyword"]); expect(classesOf(lines, "[")).toEqual(["bracket"]); expect(classesOf(lines, "")).toEqual(["comment"]); expect(classesOf(lines, "manifest")).toEqual(["typeName"]); expect(classesOf(lines, "xmlns:android")).toEqual(["propertyName"]); expect(classesOf(lines, "=")).toEqual(["operator"]); expect(classesOf(lines, "Tom")).toEqual(["plain"]); expect(classesOf(lines, "&")).toEqual(["keyword"]); expect(classesOf(lines, "©")).toEqual(["keyword"]); expect(classesOf(lines, "<")).toEqual(["keyword"]); expect(classesOf(lines, "")).toEqual(["punctuation"]); expect(classesOf(lines, " & ")).toEqual(["string"]); expect(classesOf(lines, "]]>")).toEqual(["punctuation"]); expect(verbatim(lines)).toBe(source); }); it("runs a processing instruction to its end, whatever its data holds", () => { const source = ' 1) echo ""; ?>\n\n'; const lines = xmlLanguage.highlightLines(source); expect(classesOf(lines, "php")).toEqual(["keyword"]); expect(classesOf(lines, "b")).toEqual([]); expect(classesOf(lines, "xml-stylesheet")).toEqual(["keyword"]); expect(classesOf(lines, "href")).toEqual(["propertyName"]); expect(classesOf(lines, '"s.css"')).toEqual(["string"]); expect(outline(source)).toEqual([["a", 0, 2, 2]]); expect(verbatim(lines)).toBe(source); }); it("ends an unclosed tag or value where the next tag begins", () => { const source = "text"; const lines = xmlLanguage.highlightLines(source); expect(classesOf(lines, '"open')).toEqual(["string"]); expect(classesOf(lines, "b")).toEqual(["typeName"]); expect(classesOf(lines, "text")).toEqual(["plain"]); expect(outline(source)).toEqual([["a", 0, 0, 1], ["b x", 1, 1, 1]]); }); it("outlines elements under their name or id", () => { const source = [ "", ' ", '