Something went wrong. Try again.
Monorepo for Tangled forked from tangled.org/core
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238import { describe, expect, it } from "vitest";import { code, codeBlock, findTrigger, inlineCode, insertInline, link, markdownLink, list, pasteLink, separator, table, uploadedImage, wrap, type Edit, type Selection} from "#lib/components/ui/markdownFormat.ts";
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("pasteLink", () => { const paste = (marked: string, pasted: string) => { const selection = select(marked); const edit = pasteLink(selection, pasted); return edit && result(selection, edit); };
it("links the selected text to a pasted url", () => { expect(paste("see «docs» here", " https://tangled.org\n")).toBe( "see [docs](https://tangled.org)«» here" ); });
it("leaves pastes without a selection alone", () => { expect(paste("see «»", "https://tangled.org")).toBeNull(); });
it("leaves pastes that are not a url alone", () => { expect(paste("«docs»", "tangled docs")).toBeNull(); });
it("replaces a selected url with the pasted one", () => { expect(paste("«https://a.org»", "https://tangled.org")).toBeNull(); });
it("leaves multi-line selections alone", () => { expect(paste("«one\ntwo»", "https://tangled.org")).toBeNull(); });
it("leaves a selected link destination alone", () => { expect(paste("", "https://tangled.org")).toBeNull(); expect(paste("[docs](«url»)", "https://tangled.org")).toBeNull(); });});
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("uploadedImage", () => { const size = { width: 640, height: 480 }; const upload = (selection: Selection) => uploadedImage(selection, "/blob/x/bafy", size);
it("inserts an img block with the selection as its alt", () => { expect(run("see «the cat» here", upload)).toBe( 'see \n\n<img alt="«the cat»" src="/blob/x/bafy" width="640" height="480">\n\n here' ); });
it("escapes the alt and keeps it on one line", () => { expect(run('«a "b" <c>\n& d»', upload)).toBe( '<img alt="«a "b" <c> & d»" src="/blob/x/bafy" width="640" height="480">' ); });
it("falls back to a generic alt", () => { expect(run("«»", upload)).toBe( '<img alt="«Image»" src="/blob/x/bafy" width="640" height="480">' ); });});
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(); });});
describe("insertInline", () => { const quote = (s: Selection) => insertInline(s, "Q");
it("puts text on an empty line as is", () => { expect(run("«»", quote)).toBe("Q«»"); expect(run("one\n«»", quote)).toBe("one\nQ«»"); });
it("starts a new line after text that ends its line", () => { expect(run("one«»", quote)).toBe("one\nQ«»"); expect(run("one «»\ntwo", quote)).toBe("one \nQ«»\ntwo"); });
it("keeps a space either side within a line", () => { expect(run("one«» two", quote)).toBe("one Q«» two"); expect(run("one «»two", quote)).toBe("one Q «»two"); });
it("replaces a selection as is", () => { expect(run("one «two» three", quote)).toBe("one Q«» three"); });});
describe("inlineCode", () => { it("fences past the backticks inside", () => { expect(inlineCode("a.ts:3")).toBe("`a.ts:3`"); expect(inlineCode("a`b")).toBe("``a`b``"); expect(inlineCode("`a")).toBe("`` `a ``"); });});
describe("markdownLink", () => { it("keeps parens in the url", () => { expect(markdownLink("x", "https://a.b/c(1)#d")).toBe("[x](<https://a.b/c(1)#d>)"); });
it("encodes what <> cannot hold", () => { expect(markdownLink("x", "https://a.b/<c>")).toBe("[x](<https://a.b/%3Cc%3E>)"); });});