Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Merge branch 'customize' into main: Customize replaces the Marketplace, the Artifacts trash and spaces changes, the Security-free cleanup, with the desktop app's own title bar kept in the shell. | 1 | import assert from "node:assert/strict"; |
| 2 | import { test } from "node:test"; | |
| 3 | ||
| 4 | import type { InstallRequest, PluginComponents, PluginListing, SkillListing, WorkspaceConnector } from "@g1t/contracts"; | |
| 5 | import { connectorsFor } from "@g1t/contracts/connectors"; | |
| 6 | ||
| 7 | import { | |
| 8 | addPath, | |
| 9 | availabilityWord, | |
| 10 | categoriesOf, | |
| 11 | claudeCodeOnly, | |
| 12 | componentChips, | |
| 13 | connectorCallbackPath, | |
| 14 | connectorNewPath, | |
| 15 | connectorPagePath, | |
| 16 | connectorsDirectory, | |
| 17 | customizeForm, | |
| 18 | fetchWords, | |
| 19 | filterPlugins, | |
| 20 | filterSkills, | |
| 21 | flashOf, | |
| 22 | fromWords, | |
| 23 | groupByMarketplace, | |
| 24 | kindWord, | |
| 25 | matches, | |
| 26 | needsReload, | |
| 27 | openRequests, | |
| 28 | personalDirectory, | |
| 29 | pluginAgentDraft, | |
| 30 | pluginListingRef, | |
| 31 | pluginPath, | |
| 32 | reachOptions, | |
| 33 | reachWords, | |
| 34 | readPluginFilters, | |
| 35 | readQuery, | |
| 36 | readView, | |
| 37 | requestedBy, | |
| 38 | skillListingRef, | |
| 39 | splitTarget, | |
| 40 | tabOf, | |
| 41 | titleOf, | |
| 42 | waitingFor, | |
| 43 | withParams, | |
| 44 | } from "./customize.ts"; | |
| 45 | import { modeOf, workspaceRedirect } from "./workspace-nav.ts"; | |
| 46 | ||
| 47 | const request = (listing: string, by: string, status: InstallRequest["status"] = "open"): InstallRequest => ({ | |
| 48 | id: `ins_${listing}_${by}`, | |
| 49 | listing, | |
| 50 | kind: listing.startsWith("plugin:") ? "plugin" : listing.startsWith("skill:") ? "skill" : listing.startsWith("connector:") ? "connector" : "integration", | |
| 51 | name: listing, | |
| 52 | note: null, | |
| 53 | requested_by: by, | |
| 54 | requested_at: "2026-10-10T12:00:00Z", | |
| 55 | status, | |
| 56 | resolved_by: null, | |
| 57 | resolved_at: null, | |
| 58 | }); | |
| 59 | ||
| 60 | const plugin = (name: string, marketplace: string, extra: Partial<PluginListing> = {}): PluginListing => ({ | |
| 61 | ref: `${name}@${marketplace}`, | |
| 62 | name, | |
| 63 | marketplace, | |
| 64 | display_name: null, | |
| 65 | description: `${name} does things`, | |
| 66 | version: "1.0.0", | |
| 67 | author: "Anthropic", | |
| 68 | homepage: null, | |
| 69 | category: null, | |
| 70 | tags: [], | |
| 71 | keywords: [], | |
| 72 | fetch: { kind: "github", repo: "anthropics/skills", ref: null, sha: null, path: name }, | |
| 73 | availability: { kind: "available" }, | |
| 74 | skills: [], | |
| 75 | ...extra, | |
| 76 | }); | |
| 77 | ||
| 78 | const skill = (name: string, plugin: string, marketplace: string, extra: Partial<SkillListing> = {}): SkillListing => ({ | |
| 79 | ref: `${plugin}:${name}`, | |
| 80 | name, | |
| 81 | plugin, | |
| 82 | marketplace, | |
| 83 | description: `${name} skill`, | |
| 84 | category: null, | |
| 85 | author: null, | |
| 86 | added: false, | |
| 87 | availability: { kind: "available" }, | |
| 88 | ...extra, | |
| 89 | }); | |
| 90 | ||
| 91 | const connector = (id: string, from: WorkspaceConnector["from"]): WorkspaceConnector => ({ | |
| 92 | id, | |
| 93 | name: id.replace("con_", ""), | |
| 94 | title: id, | |
| 95 | url: "https://mcp.example.com/mcp", | |
| 96 | category: null, | |
| 97 | about: null, | |
| 98 | auth: "oauth", | |
| 99 | state: "connected", | |
| 100 | problem: null, | |
| 101 | from, | |
| 102 | tools: [], | |
| 103 | checked_at: null, | |
| 104 | header_name: null, | |
| 105 | signed_in_as: null, | |
| 106 | signed_in_at: "2026-10-10T12:00:00Z", | |
| 107 | grants: [], | |
| 108 | added_by: "ana", | |
| 109 | added_at: "2026-10-10T12:00:00Z", | |
| 110 | }); | |
| 111 | ||
| 112 | test("the view and search live in the address, and Yours has a clean one", () => { | |
| 113 | assert.equal(readView(new URLSearchParams("")), "yours"); | |
| 114 | assert.equal(readView(new URLSearchParams("view=discover")), "discover"); | |
| 115 | assert.equal(readView(new URLSearchParams("view=nonsense")), "yours"); | |
| 116 | assert.equal(readQuery(new URLSearchParams("q=+pdf+")), "pdf"); | |
| 117 | assert.equal(withParams("/acme/-/customize/skills", new URLSearchParams("q=pdf"), { view: "discover" }), "/acme/-/customize/skills?q=pdf&view=discover"); | |
| 118 | assert.equal(withParams("/acme/-/customize/skills", new URLSearchParams("view=discover&q=pdf"), { view: "yours", q: null }), "/acme/-/customize/skills"); | |
| 119 | assert.equal(tabOf("/acme/-/customize/plugins"), "plugins"); | |
| 120 | assert.equal(tabOf("/acme/-/customize/plugins.data"), "plugins"); | |
| 121 | assert.equal(tabOf("/acme/-/customize/plugins/pdf%40anthropic-agent-skills"), null); | |
| 122 | assert.equal(tabOf("/acme/-/customize/requests"), null); | |
| 123 | }); | |
| 124 | ||
| 125 | test("pages are under -/customize, with a plugin's ref encoded", () => { | |
| 126 | assert.equal(pluginPath("acme", "pdf@anthropic-agent-skills"), "/acme/-/customize/plugins/pdf%40anthropic-agent-skills"); | |
| 127 | assert.equal(connectorPagePath("acme", "con_abc"), "/acme/-/customize/connectors/con_abc"); | |
| 128 | assert.equal(connectorNewPath("acme"), "/acme/-/customize/connectors/new"); | |
| 129 | assert.equal(connectorNewPath("acme", "notion"), "/acme/-/customize/connectors/new?directory=notion"); | |
| 130 | assert.equal(connectorCallbackPath("acme"), "/acme/-/customize/connectors/callback"); | |
| 131 | }); | |
| 132 | ||
| 133 | test("Customize is part of Apps, and the Marketplace's addresses lead to it", () => { | |
| 134 | assert.equal(modeOf("/acme/-/customize", "acme"), "apps"); | |
| 135 | assert.equal(modeOf("/acme/-/customize/plugins/pdf%40g1t", "acme"), "apps"); | |
| 136 | assert.equal(workspaceRedirect("/acme/-/marketplace"), "/acme/-/customize"); | |
| 137 | assert.equal(workspaceRedirect("/acme/-/marketplace/extensions"), "/acme/-/customize/plugins"); | |
| 138 | assert.equal(workspaceRedirect("/acme/-/marketplace/extensions/mail"), "/acme/-/customize/plugins/mail%40g1t"); | |
| 139 | assert.equal(workspaceRedirect("/acme/-/marketplace/integrations"), "/acme/-/customize/connectors"); | |
| 140 | assert.equal(workspaceRedirect("/acme/-/marketplace/integrations/linear"), "/acme/-/customize/connectors?directory=linear"); | |
| 141 | assert.equal(workspaceRedirect("/acme/-/marketplace/requests", "?x=1"), "/acme/-/customize/requests?x=1"); | |
| 142 | assert.equal(workspaceRedirect("/acme/-/agents/skills"), "/acme/-/customize/skills?view=yours"); | |
| 143 | assert.equal(workspaceRedirect("/acme/-/agents/skills.data", "?_routes=x&q=pdf"), "/acme/-/customize/skills?q=pdf&view=yours"); | |
| 144 | assert.equal(workspaceRedirect("/acme/-/agents/skills/pdf"), null, "a skill's own page stays where it is"); | |
| 145 | assert.equal(workspaceRedirect("/acme/-/customize/skills"), null); | |
| 146 | }); | |
| 147 | ||
| 148 | test("requests name plugins, skills and connectors, and an owner adds each where it is added", () => { | |
| 149 | const views = connectorsFor("workspace"); | |
| 150 | assert.equal(pluginListingRef(plugin("pdf", "anthropic-agent-skills")), "plugin:pdf@anthropic-agent-skills"); | |
| 151 | assert.equal(skillListingRef(skill("pdf", "document-skills", "anthropic-agent-skills")), "skill:document-skills@anthropic-agent-skills/pdf"); | |
| 152 | assert.equal(addPath(request("plugin:pdf@anthropic-agent-skills", "ana"), "acme", views), "/acme/-/customize/plugins/pdf%40anthropic-agent-skills"); | |
| 153 | assert.equal(addPath(request("skill:document-skills@anthropic-agent-skills/pdf", "ana"), "acme", views), "/acme/-/customize/plugins/document-skills%40anthropic-agent-skills"); | |
| 154 | assert.equal(addPath(request("connector:notion", "ana"), "acme", views), "/acme/-/customize/connectors/new?directory=notion"); | |
| 155 | assert.equal(addPath(request("integration:jira", "ana"), "acme", views), "/acme/-/integrations/trackers?add=jira#add"); | |
| 156 | assert.equal(addPath({ listing: "extension:mail" }, "acme", views), null, "extensions are g1t's own, announced"); | |
| 157 | assert.equal(addPath({ listing: "nonsense" }, "acme", views), null); | |
| 158 | assert.equal(kindWord("skill"), "Skill"); | |
| 159 | assert.equal(kindWord("integration"), "Integration"); | |
| 160 | }); | |
| 161 | ||
| 162 | test("who asked for what, and what is still waiting", () => { | |
| 163 | const requests = [request("plugin:pdf@m", "ana"), request("plugin:pdf@m", "bo"), request("plugin:xlsx@m", "ana", "done")]; | |
| 164 | assert.equal(openRequests(requests).length, 2); | |
| 165 | assert.ok(requestedBy(requests, "plugin:pdf@m", "Ana")); | |
| 166 | assert.ok(!requestedBy(requests, "plugin:xlsx@m", "ana"), "answered is not waiting"); | |
| 167 | assert.equal(waitingFor(requests, "plugin:pdf@m"), 2); | |
| 168 | assert.equal(waitingFor(requests, "plugin:none@m"), 0); | |
| 169 | }); | |
| 170 | ||
| 171 | test("the requests forms are read strictly", () => { | |
| 172 | const form = (fields: Record<string, string>) => { | |
| 173 | const data = new FormData(); | |
| 174 | for (const [k, v] of Object.entries(fields)) data.set(k, v); | |
| 175 | return data; | |
| 176 | }; | |
| 177 | assert.deepEqual(customizeForm(form({ intent: "request", listing: "plugin:pdf@anthropic-agent-skills", note: " for invoices " })), { intent: "request", listing: "plugin:pdf@anthropic-agent-skills", note: "for invoices" }); | |
| 178 | assert.deepEqual(customizeForm(form({ intent: "request", listing: "skill:a@m/b" })), { intent: "request", listing: "skill:a@m/b", note: null }); | |
| 179 | assert.equal(customizeForm(form({ intent: "request", listing: "agent:qa" })), null); | |
| 180 | assert.deepEqual(customizeForm(form({ intent: "resolve", id: "ins_1", status: "declined" })), { intent: "resolve", id: "ins_1", status: "declined" }); | |
| 181 | assert.equal(customizeForm(form({ intent: "resolve", id: "ins_1", status: "open" })), null); | |
| 182 | assert.equal(customizeForm(form({ intent: "install" })), null); | |
| 183 | }); | |
| 184 | ||
| 185 | test("plugins are narrowed by what was typed, a category and a marketplace, and ordered by marketplace or name", () => { | |
| 186 | const listings = [ | |
| 187 | plugin("xlsx", "anthropic-agent-skills", { category: "Documents" }), | |
| 188 | plugin("pdf", "anthropic-agent-skills", { category: "Documents", keywords: ["invoices"] }), | |
| 189 | plugin("linear", "claude-plugins-official", { category: "Productivity", author: "Linear" }), | |
| 190 | plugin("support", "g1t", { availability: { kind: "soon" } }), | |
| 191 | ]; | |
| 192 | assert.deepEqual(categoriesOf(listings), ["Documents", "Productivity"]); | |
| 193 | assert.deepEqual(filterPlugins(listings, readPluginFilters(new URLSearchParams(""))).map((l) => l.ref), ["pdf@anthropic-agent-skills", "xlsx@anthropic-agent-skills", "linear@claude-plugins-official", "support@g1t"]); | |
| 194 | assert.deepEqual(filterPlugins(listings, readPluginFilters(new URLSearchParams("sort=name"))).map((l) => l.name), ["linear", "pdf", "support", "xlsx"]); | |
| 195 | assert.deepEqual(filterPlugins(listings, readPluginFilters(new URLSearchParams("category=Documents&q=invoice"))).map((l) => l.name), ["pdf"]); | |
| 196 | assert.deepEqual(filterPlugins(listings, readPluginFilters(new URLSearchParams("marketplace=g1t"))).map((l) => l.name), ["support"]); | |
| 197 | assert.deepEqual(filterPlugins(listings, readPluginFilters(new URLSearchParams("q=Linear"))).map((l) => l.name), ["linear"], "the author counts"); | |
| 198 | assert.ok(matches(["Agents can read", "PostHog"], "post hog")); | |
| 199 | assert.ok(!matches(["PostHog"], "sentry")); | |
| 200 | assert.deepEqual(groupByMarketplace(filterPlugins(listings, readPluginFilters(new URLSearchParams("")))).map((g) => [g.marketplace, g.items.length]), [["anthropic-agent-skills", 2], ["claude-plugins-official", 1], ["g1t", 1]]); | |
| 201 | }); | |
| 202 | ||
| 203 | test("skills in Discover are searched and filtered the same way", () => { | |
| 204 | const listings = [skill("pdf", "document-skills", "anthropic-agent-skills", { category: "Documents" }), skill("brand", "design", "anthropic-agent-skills", { category: "Design" }), skill("abc", "zzz", "other")]; | |
| 205 | assert.deepEqual(filterSkills(listings, { q: "", category: null }).map((l) => l.name), ["abc", "brand", "pdf"]); | |
| 206 | assert.deepEqual(filterSkills(listings, { q: "document", category: null }).map((l) => l.name), ["pdf"]); | |
| 207 | assert.deepEqual(filterSkills(listings, { q: "", category: "Design" }).map((l) => l.name), ["brand"]); | |
| 208 | }); | |
| 209 | ||
| 210 | test("a listing's availability and what a plugin brings read in a few words", () => { | |
| 211 | assert.deepEqual(availabilityWord({ kind: "available" }), { label: "Available", tone: "neutral" }); | |
| 212 | assert.deepEqual(availabilityWord({ kind: "installed", enabled: true }), { label: "Installed", tone: "success" }); | |
| 213 | assert.deepEqual(availabilityWord({ kind: "installed", enabled: false }), { label: "Switched off", tone: "warn" }); | |
| 214 | assert.equal(availabilityWord({ kind: "unavailable", why: "npm" }).label, "Not available here"); | |
| 215 | assert.equal(availabilityWord({ kind: "soon" }).label, "Soon"); | |
| 216 | const components: PluginComponents = { | |
| 217 | skills: [{ name: "pdf", description: null, path: "skills/pdf" }], | |
| 218 | agents: [{ name: "reviewer", description: null, path: "agents/reviewer.md" }], | |
| 219 | commands: [{ name: "release", path: "commands/release.md" }], | |
| 220 | mcp_servers: [ | |
| 221 | { name: "linear", type: "http", url: "https://mcp.linear.app/mcp", remote: true }, | |
| 222 | { name: "fs", type: "stdio", url: null, remote: false }, | |
| 223 | ], | |
| 224 | hooks: true, | |
| 225 | lsp: false, | |
| 226 | bin: true, | |
| 227 | output_styles: false, | |
| 228 | }; | |
| 229 | assert.deepEqual(componentChips(components), ["2 skills", "1 agent", "1 connector"]); | |
| 230 | assert.deepEqual(claudeCodeOnly(components), ["hooks", "bin", "1 local MCP server"]); | |
| 231 | assert.deepEqual(componentChips({ ...components, skills: [], commands: [], agents: [], mcp_servers: [] }), []); | |
| 232 | assert.equal(fromWords({ kind: "plugin", ref: "linear@claude-plugins-official", server: "linear" }), "Brought by linear@claude-plugins-official"); | |
| 233 | assert.equal(fromWords({ kind: "agent", handle: "margo" }), "Moved from @margo"); | |
| 234 | assert.equal(fetchWords({ kind: "github", repo: "anthropics/skills", ref: null, sha: null, path: "skills/pdf" }), "github.com/anthropics/skills/skills/pdf"); | |
| 235 | assert.equal(fetchWords({ kind: "g1t", repo: "acme/plugins", ref: "main", path: "" }), "acme/plugins on this g1t at main"); | |
| 236 | assert.equal(fetchWords({ kind: "unsupported", why: "It is published on npm." }), "It is published on npm."); | |
| 237 | }); | |
| 238 | ||
| 239 | test("the connectors directory puts MCP servers first, says which are connected, and keeps g1t's integrations apart", () => { | |
| 240 | const views = connectorsFor("workspace"); | |
| 241 | const notion = connector("con_notion", { kind: "directory", id: "notion" }); | |
| 242 | const directory = connectorsDirectory(views, [notion, connector("con_x", { kind: "address" })], { sentry: { detail: "acme", problem: null, manage: "/acme/-/integrations/alerts" } }, { github: "No GitHub App." }, "acme"); | |
| 243 | assert.ok(directory.mcp.every((entry) => entry.view.mcp && entry.view.status === "available")); | |
| 244 | assert.equal(directory.mcp.at(-1)?.view.id, "notion", "the connected one comes last"); | |
| 245 | assert.equal(directory.mcp.find((entry) => entry.view.id === "notion")?.connector?.id, "con_notion"); | |
| 246 | assert.ok(directory.mcp.some((entry) => entry.view.mcp?.caveat), "a caveat the directory knows is kept"); | |
| 247 | assert.equal(directory.integrations[0]?.view.id, "sentry"); | |
| 248 | assert.equal(directory.integrations[0]?.href, "/acme/-/integrations/alerts"); | |
| 249 | const github = directory.integrations.find((entry) => entry.view.id === "github")!; | |
| 250 | assert.equal(github.why, "No GitHub App."); | |
| 251 | assert.equal(github.href, null); | |
| 252 | assert.equal(directory.integrations.find((entry) => entry.view.id === "jira")?.href, "/acme/-/integrations/trackers?add=jira#add"); | |
| 253 | assert.ok(directory.soon.some((view) => view.id === "slack")); | |
| 254 | assert.ok(!directory.integrations.some((entry) => entry.view.mcp)); | |
| 255 | const searched = connectorsDirectory(views, [], {}, {}, "acme", "notion"); | |
| 256 | assert.deepEqual(searched.mcp.map((entry) => entry.view.id), ["notion"]); | |
| 257 | assert.equal(searched.soon.length, 0); | |
| 258 | const personal = personalDirectory(connectorsFor("personal")); | |
| 259 | assert.ok(personal.some((view) => view.id === "mcp")); | |
| 260 | assert.ok(personal.every((view) => view.href)); | |
| 261 | }); | |
| 262 | ||
| 263 | test("where something reaches: the options, what is already given, and the line that says so", () => { | |
| 264 | const reach = [ | |
| 265 | { id: "g1", scope: "team" as const, target: "qa", label: "QA" }, | |
| 266 | { id: "g2", scope: "agent" as const, target: "Margo", label: "@margo" }, | |
| 267 | ]; | |
| 268 | const options = reachOptions(reach, { can_manage: true, teams: [{ slug: "qa", name: "QA" }], agents: [{ handle: "margo", display_name: "Margo" }, { handle: "otto", display_name: "Otto" }] }); | |
| 269 | assert.deepEqual(options.map((o) => [o.value, o.checked]), [["workspace:", false], ["team:qa", true], ["agent:margo", true], ["agent:otto", false]]); | |
| 270 | assert.equal(options[1]!.reach?.id, "g1"); | |
| 271 | const maintainer = reachOptions([], { can_manage: false, teams: [{ slug: "qa", name: "QA" }], agents: [{ handle: "margo", display_name: "Margo" }] }); | |
| 272 | assert.deepEqual(maintainer.map((o) => o.value), ["team:qa"]); | |
| 273 | assert.deepEqual(splitTarget("workspace:"), { scope: "workspace", target: null }); | |
| 274 | assert.deepEqual(splitTarget("team:qa"), { scope: "team", target: "qa" }); | |
| 275 | assert.deepEqual(splitTarget("nonsense:x"), { scope: "workspace", target: "x" }); | |
| 276 | assert.equal(reachWords([]), "no agent yet"); | |
| 277 | assert.equal(reachWords([{ id: "g0", scope: "workspace", target: null, label: "Every agent" }, ...reach]), "Every agent"); | |
| 278 | assert.equal(reachWords(reach), "QA and @margo"); | |
| 279 | assert.equal(reachWords([...reach, { id: "g3", scope: "agent", target: "otto", label: "@otto" }]), "QA, @margo and 1 more"); | |
| 280 | }); | |
| 281 | ||
| 282 | test("a plugin's subagent starts the new-agent form", () => { | |
| 283 | const draft = pluginAgentDraft({ ref: "pr-review-toolkit:code-reviewer", plugin: "pr-review-toolkit", marketplace: "claude-plugins-official", name: "code-reviewer", description: "Reviews pull requests for bugs.", prompt: "You review code.", model: "opus", skills: [], tools: [] }); | |
| 284 | assert.equal(draft.handle, "code-reviewer"); | |
| 285 | assert.equal(draft.display_name, "Code Reviewer"); | |
| 286 | assert.deepEqual(draft.responsibilities, ["Reviews pull requests for bugs."]); | |
| 287 | assert.equal(draft.instructions, "You review code."); | |
| 288 | assert.equal(draft.routing.floor, "frontier"); | |
| 289 | assert.equal(pluginAgentDraft({ ref: "a:b", plugin: "a", marketplace: "m", name: "b", description: "", prompt: "", model: "haiku", skills: [], tools: [] }).routing.ceiling, "small"); | |
| 290 | assert.equal(pluginAgentDraft({ ref: "a:b", plugin: "a", marketplace: "m", name: "b", description: "", prompt: "", model: null, skills: [], tools: [] }).routing.floor, null); | |
| 291 | assert.equal(titleOf("my_fine-agent"), "My Fine Agent"); | |
| 292 | }); | |
| 293 | ||
| 294 | test("typing a search or picking a filter reloads nothing; another page, view or a posted form does", () => { | |
| 295 | const at = (path: string) => new URL(`https://g1t.sh${path}`); | |
| 296 | assert.equal(needsReload(at("/acme/-/customize/plugins"), at("/acme/-/customize/plugins?q=pdf"), undefined), false); | |
| 297 | assert.equal(needsReload(at("/acme/-/customize/plugins?q=p"), at("/acme/-/customize/plugins?q=pd&category=Documents"), undefined), false); | |
| 298 | assert.equal(needsReload(at("/acme/-/customize/plugins"), at("/acme/-/customize/plugins?view=discover"), undefined), true); | |
| 299 | assert.equal(needsReload(at("/acme/-/customize/plugins"), at("/acme/-/customize/skills"), undefined), true); | |
| 300 | assert.equal(needsReload(at("/acme/-/customize/plugins"), at("/acme/-/customize/plugins"), "POST"), true); | |
| 301 | assert.equal(needsReload(at("/acme/-/customize/plugins"), at("/acme/-/customize/plugins"), "GET"), false); | |
| 302 | }); | |
| 303 | ||
| 304 | test("a page's flash comes from its address", () => { | |
| 305 | assert.deepEqual(flashOf(new URLSearchParams("problem=The+server+refused")), { tone: "danger", text: "The server refused" }); | |
| 306 | assert.equal(flashOf(new URLSearchParams("signed_in=1"))?.tone, "success"); | |
| 307 | assert.equal(flashOf(new URLSearchParams("added=1"))?.tone, "success"); | |
| 308 | assert.equal(flashOf(new URLSearchParams("")), null); | |
| 309 | }); |
This file's history is long; its oldest lines are credited to the oldest commit read.