import { describe, expect, it } from "vitest"; import { code, codeBlock, findTrigger, link, list, separator, table, wrap, type Edit, type Selection } from "$lib/components/ui/markdownFormat"; const select = (marked: string): Selection => { const start = marked.indexOf("«"); const end = marked.indexOf("»") - 1; return { value: marked.replace("«", "").replace("»", ""), start, end }; }; const result = ({ value }: Selection, edit: Edit) => { const next = value.slice(0, edit.from) + edit.insert + value.slice(edit.to); return `${next.slice(0, edit.selectionStart)}«${next.slice(edit.selectionStart, edit.selectionEnd)}»${next.slice(edit.selectionEnd)}`; }; const run = (marked: string, format: (selection: Selection) => Edit) => { const selection = select(marked); return result(selection, format(selection)); }; describe("wrap", () => { it("wraps the selection and keeps it selected", () => { expect(run("say «hello» there", (s) => wrap(s, "**"))).toBe("say **«hello»** there"); }); it("places the caret between the markers when nothing is selected", () => { expect(run("say «»", (s) => wrap(s, "_"))).toBe("say _«»_"); }); it("unwraps when the markers surround the selection", () => { expect(run("say **«hello»** there", (s) => wrap(s, "**"))).toBe("say «hello» there"); }); it("unwraps when the selection includes the markers", () => { expect(run("say «~~hello~~» there", (s) => wrap(s, "~~"))).toBe("say «hello» there"); }); }); describe("code", () => { it("uses backticks for a single line", () => { expect(run("run «make» now", code)).toBe("run `«make»` now"); }); it("uses a fence for several lines", () => { expect(run("«a\nb»", code)).toBe("```\n«a\nb»\n```"); }); }); describe("codeBlock", () => { it("pads the fence with blank lines", () => { expect(run("intro\n«x»\noutro", codeBlock)).toBe("intro\n\n```\n«x»\n```\n\noutro"); }); }); describe("link", () => { it("selects the url placeholder around selected text", () => { expect(run("see «docs»", link)).toBe("see [docs](«url»)"); }); it("puts a selected url in the destination", () => { expect(run("«https://tangled.org»", link)).toBe("[«»](https://tangled.org)"); }); }); describe("separator", () => { it("inserts a rule after the selection on its own paragraph", () => { expect(run("para«»", separator)).toBe("para\n\n---«»"); }); it("keeps following text a paragraph away", () => { expect(run("one«»\ntwo", separator)).toBe("one\n\n---«»\n\ntwo"); }); }); describe("table", () => { it("selects the first header cell", () => { expect(run("«»", table)).toBe( "| «Column» | Column |\n| ------ | ------ |\n| Cell | Cell |" ); }); }); describe("list", () => { it("prefixes every selected line", () => { expect(run("«one\ntwo»", (s) => list(s, "ordered"))).toBe("«1. one\n2. two»"); }); it("removes the prefix when every line already has it", () => { expect(run("«- [ ] one\n- [ ] two»", (s) => list(s, "task"))).toBe("«one\ntwo»"); }); it("keeps the caret on the same text for a single line", () => { expect(run("item«»", (s) => list(s, "bullet"))).toBe("- item«»"); }); it("does not treat a task item as a bullet", () => { expect(run("«- [ ] one»", (s) => list(s, "bullet"))).toBe("- «- [ ] one»"); }); }); describe("findTrigger", () => { it("finds a mention being typed", () => { expect(findTrigger("hi @ada.te", 10)).toEqual({ char: "@", query: "ada.te", from: 3, to: 10 }); }); it("finds a reference at the start of a line", () => { expect(findTrigger("one\n#12", 7)).toEqual({ char: "#", query: "12", from: 4, to: 7 }); }); it("ignores markers glued to other text", () => { expect(findTrigger("mail@ada", 8)).toBeNull(); expect(findTrigger("##", 2)).toBeNull(); }); it("stops at whitespace", () => { expect(findTrigger("# heading", 9)).toBeNull(); }); });