Something went wrong. Try again.
Monorepo for Tangled forked from tangled.org/core
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218import { describe, expect, it } from "vitest";import { accept, activeState, labelQuery, queryFromUrl, quoteValue, resolveAuthors, statesOf, suggest, tokenize, valueProblem, withoutStates, withState} from "#lib/ticketQuery.ts";
const kinds = (q: string) => tokenize(q).map((t) => t.kind === "qualifier" ? `${t.negated ? "-" : ""}${t.key}:${t.values.map((v) => v.text).join(",")}` : `${t.negated ? "-" : ""}${t.kind}(${t.text})` );
describe("tokenize", () => { it("splits words, phrases and qualifiers like bobbin's lexer", () => { expect(kinds(`kelp "sea urchin" -squid is:open,merged label:"good first issue"`)).toEqual([ "word(kelp)", "phrase(sea urchin)", "-word(squid)", "is:open,merged", "label:good first issue" ]); });
it("keeps spans that cover the source", () => { const q = ` kelp -is:open "a b"`; expect(tokenize(q).map((t) => q.slice(t.start, t.end))).toEqual([ "kelp", "-is:open", `"a b"` ]); });
it("leaves out keys the list pages do not need", () => { for (const q of ["type:pull", "created:>=2026-01-01", "in:title"]) { const [token] = tokenize(q); expect(token, q).toMatchObject({ kind: "qualifier", known: false }); } expect(suggest("ty", 2)).toBeNull(); expect(suggest("i", 1)!.items.map((i) => i.label)).toEqual(["is:"]); });
it("treats a lone dash, colons in values and unknown keys like bobbin", () => { expect(kinds("- author:did:plc:x https://x.y")).toEqual([ "word(-)", "author:did:plc:x", "https://x.y" ]); const [unknown] = tokenize("foo:bar"); expect(unknown).toMatchObject({ kind: "qualifier", known: false }); });
it("tolerates unterminated phrases and escapes", () => { expect(kinds(`"say \\"hi\\"" "open ended`)).toEqual([ `phrase(say "hi")`, "phrase(open ended)" ]); });
it("keeps empty value slots so they can be flagged", () => { const [token] = tokenize("label:a,,b"); expect(token.kind === "qualifier" && token.values.map((v) => v.text)).toEqual([ "a", "", "b" ]); });});
describe("valueProblem", () => { it("accepts what bobbin accepts", () => { for (const [key, value] of [ ["is", "merged"], ["sort", "oldest"], ["label", "anything goes"] ] as const) expect(valueProblem(key, value), `${key}:${value}`).toBeNull(); });
it("flags what bobbin rejects", () => { for (const [key, value] of [ ["is", "nope"], ["is", "issue"], ["sort", "sideways"], ["label", ""] ] as const) expect(valueProblem(key, value), `${key}:${value}`).not.toBeNull(); });});
describe("state tabs", () => { it("reads the state the query narrows to", () => { expect(statesOf("kelp")).toBeNull(); expect(activeState("is:open kelp")).toBe("open"); expect(activeState("is:merged")).toBe("completed"); expect(activeState("is:open,closed")).toBeNull(); expect(activeState("-is:open")).toBeNull(); expect(activeState("type:issue")).toBeNull(); });
it("rewrites only the state clauses", () => { expect(withState("is:open kelp label:bug", "closed")).toBe("is:closed kelp label:bug"); expect(withState("kelp", "completed", true)).toBe("is:merged kelp"); expect(withState("is:open,closed -is:open kelp", "open")).toBe("is:open kelp"); expect(withoutStates("is:open")).toBe(""); expect(withState("", "open")).toBe("is:open"); });
it("leaves other qualifiers alone", () => { expect(withState("type:issue kelp", "closed")).toBe("is:closed type:issue kelp"); });});
describe("urls", () => { const at = (search: string) => new URL(`http://web.test/a/b/issues${search}`);
it("defaults to open and honours old links", () => { expect(queryFromUrl(at(""))).toBe("is:open"); expect(queryFromUrl(at("?state=closed"))).toBe("is:closed"); expect(queryFromUrl(at("?label=good%20first%20issue"))).toBe( `is:open label:"good first issue"` ); expect(queryFromUrl(at("?q=%20kelp%20"))).toBe("kelp"); expect(queryFromUrl(at("?q="))).toBe(""); });
it("quotes values the lexer would split", () => { expect(quoteValue("bug")).toBe("bug"); expect(quoteValue("good first issue")).toBe(`"good first issue"`); expect(quoteValue(`a"b`)).toBe(`"a\\"b"`); expect(quoteValue("a,b")).toBe(`"a,b"`); expect(labelQuery("needs review")).toBe(`is:open label:"needs review"`); });});
describe("suggest", () => { it("offers every key in empty space", () => { const s = suggest("kelp ", 5)!; expect(s.items.map((i) => i.label)).toContain("label:"); expect([s.from, s.to]).toEqual([5, 5]); });
it("completes a key prefix", () => { const s = suggest("kelp la", 7)!; expect(s.items.map((i) => i.insert)).toEqual(["label:"]); expect(accept("kelp la", s, s.items[0])).toEqual({ query: "kelp label:", caret: 11 }); });
it("completes a negated key prefix after the dash", () => { const s = suggest("-la", 3)!; expect(s.from).toBe(1); expect(accept("-la", s, s.items[0]).query).toBe("-label:"); });
it("offers state values, merged only on pulls", () => { expect(suggest("is:", 3)!.items.map((i) => i.label)).toEqual(["open", "closed"]); expect(suggest("is:", 3, { isPull: true })!.items.map((i) => i.label)).toEqual([ "open", "merged", "closed" ]); expect(suggest("is:cl", 5)!.items.map((i) => i.label)).toEqual(["closed"]); });
it("quotes label names and skips ones already listed", () => { const labels = ["bug", "good first issue"]; const s = suggest("label:bug,", 10, { labels })!; expect(s.items.map((i) => i.insert)).toEqual([`"good first issue"`]); expect(accept("label:bug,", s, s.items[0])).toEqual({ query: `label:bug,"good first issue" `, caret: 29 }); });
it("spaces off the next token when completing mid-query", () => { const s = suggest("is:o kelp", 4)!; expect(accept("is:o kelp", s, s.items[0]).query).toBe("is:open kelp"); });
it("stays quiet inside phrases, unknown keys and exact matches", () => { expect(suggest(`"kelp`, 3)).toBeNull(); expect(suggest("foo:b", 5)).toBeNull(); expect(suggest("is:open", 7)).toBeNull(); expect(suggest("is", 2)!.items.map((i) => i.label)).toEqual(["is:"]); });});
describe("resolveAuthors", () => { const dids: Record<string, string> = { "alice.tngl.sh": "did:plc:alice" }; const resolve = async (handle: string) => dids[handle];
it("swaps handles for dids and keeps the rest", async () => { expect( await resolveAuthors("kelp author:@alice.tngl.sh,did:plc:bob is:open", resolve) ).toEqual({ query: "kelp author:did:plc:alice,did:plc:bob is:open", unresolved: [] }); });
it("reports handles that do not resolve", async () => { expect(await resolveAuthors("-author:ghost.tngl.sh", resolve)).toEqual({ query: "-author:ghost.tngl.sh", unresolved: ["ghost.tngl.sh"] }); });});