Test the plugin's copy of the template tag scanner (#711)

This commit is contained in:
Gregory Schier authored and GitHub committed 2026-09-25 10:06:11 -07:00
1 parent 34815f327a
commit 411aa262c7
3 files changed
+127 -54

No files matched your search

@@ -0,0 +1,79 @@
/* oxlint-disable no-template-curly-in-string */
/*
* Shared expectations for `resolvedModelName`.
*
* The request template function plugin carries a hand-copied `resolvedModelName` and template-tag
* scanner, because importing this module into the plugin bundle fails to build on Windows in CI.
* Both `resolvedModelName.test.ts` here and `plugins/template-function-request/tests/
* resolvedModelName.test.ts` iterate these cases, so changing an expectation on this side
* automatically exercises the plugin's copy and a drifting copy fails its own suite.
*
* Keep this file dependency-free — the plugin test imports it by relative path, so anything it
* pulls in would have to resolve from both packages.
*/
export interface ResolvedModelNameCase {
/** Test name, used as the `test.each` title on both sides. */
name: string;
/** `url` of the `http_request` under test. */
url: string;
/** `name` of the `http_request`, when the case is about an explicit name. */
requestName?: string;
/** What `resolvedModelName` must return. */
expected: string;
}
export const resolvedModelNameCases: ResolvedModelNameCase[] = [
{
name: "returns the name when there is one",
url: "https://example.com",
requestName: "My Request",
expected: "My Request",
},
{
name: "replaces a simple tag with its contents",
url: "${[ base_url ]}/users",
expected: "base_url/users",
},
{
name: "replaces a tag containing a quoted argument with a space",
url: "${[ fn(arg='my key') ]}/users",
expected: "fn(arg='my key')/users",
},
{
name: "replaces each tag in a multi-tag string",
url: "${[ fn(arg='my key') ]}/a/${[ other(b='x y') ]}/b",
expected: "fn(arg='my key')/a/other(b='x y')/b",
},
{
name: "keeps a quoted `]}` inside the tag",
url: "${[ fn(arg='x]}y') ]}/users",
expected: "fn(arg='x]}y')/users",
},
{
name: "keeps an escaped quote inside the tag",
url: "${[ fn(arg='it\\'s ]}') ]}/users",
expected: "fn(arg='it\\'s ]}')/users",
},
{
name: "closes at the first `]}` when a quote is left unterminated",
url: "${[ fn(arg='oops ]}/users",
expected: "fn(arg='oops/users",
},
{
name: "stops each tag at its first closing bracket",
url: "${[ a ]}${[ b ]}",
expected: "ab",
},
{
name: "strips the protocol",
url: "https://example.com/${[ path ]}",
expected: "example.com/path",
},
{
name: "falls back to a generic name when the url is empty",
url: "",
expected: "HTTP Request",
},
];
+6 -54
View File
@@ -1,5 +1,6 @@
import type { HttpRequest } from "@yaakapp-internal/models";
import { describe, expect, test, vi } from "vite-plus/test";
import { resolvedModelNameCases } from "./resolvedModelName.cases";
// resolvedModelName only reads plain model fields, but its module imports pull in the
// platform singleton, which needs a browser. Stub them out to keep this a pure unit test.
@@ -8,62 +9,13 @@ vi.mock("./jotai", () => ({ jotaiStore: { get: () => [] } }));
const { resolvedModelName } = await import("./resolvedModelName");
function httpRequest(url: string): HttpRequest {
return { id: "rq_test", model: "http_request", name: "", url } as HttpRequest;
function httpRequest(url: string, name = ""): HttpRequest {
return { id: "rq_test", model: "http_request", name, url } as HttpRequest;
}
describe("resolvedModelName", () => {
test("returns the name when there is one", () => {
expect(
resolvedModelName({ ...httpRequest("https://example.com"), name: "My Request" }),
).toEqual("My Request");
});
test("replaces a simple tag with its contents", () => {
expect(resolvedModelName(httpRequest("${[ base_url ]}/users"))).toEqual("base_url/users");
});
test("replaces a tag containing a quoted argument with a space", () => {
expect(resolvedModelName(httpRequest("${[ fn(arg='my key') ]}/users"))).toEqual(
"fn(arg='my key')/users",
);
});
test("replaces each tag in a multi-tag string", () => {
expect(
resolvedModelName(httpRequest("${[ fn(arg='my key') ]}/a/${[ other(b='x y') ]}/b")),
).toEqual("fn(arg='my key')/a/other(b='x y')/b");
});
test("keeps a quoted `]}` inside the tag", () => {
expect(resolvedModelName(httpRequest("${[ fn(arg='x]}y') ]}/users"))).toEqual(
"fn(arg='x]}y')/users",
);
});
test("keeps an escaped quote inside the tag", () => {
expect(resolvedModelName(httpRequest("${[ fn(arg='it\\'s ]}') ]}/users"))).toEqual(
"fn(arg='it\\'s ]}')/users",
);
});
test("closes at the first `]}` when a quote is left unterminated", () => {
expect(resolvedModelName(httpRequest("${[ fn(arg='oops ]}/users"))).toEqual(
"fn(arg='oops/users",
);
});
test("stops each tag at its first closing bracket", () => {
expect(resolvedModelName(httpRequest("${[ a ]}${[ b ]}"))).toEqual("ab");
});
test("strips the protocol", () => {
expect(resolvedModelName(httpRequest("https://example.com/${[ path ]}"))).toEqual(
"example.com/path",
);
});
test("falls back to a generic name when the url is empty", () => {
expect(resolvedModelName(httpRequest(""))).toEqual("HTTP Request");
// Shared with the plugin's hand-copied scanner. See `resolvedModelName.cases.ts`.
test.each(resolvedModelNameCases)("$name", ({ url, requestName, expected }) => {
expect(resolvedModelName(httpRequest(url, requestName))).toEqual(expected);
});
});
@@ -0,0 +1,42 @@
/*
* This plugin carries its own copy of the template-tag scanner, because importing the client's
* copy into the plugin bundle fails to build on Windows in CI.
*
* Rather than copying the expectations too — copied expectations can't detect drift — this
* imports the client's `resolvedModelName.cases.ts` and runs the plugin's copy against it, then
* asserts the plugin's output matches the client's own `resolvedModelName` for every case. A
* change to the client scanner or its cases therefore fails here until the copy is updated. The
* import is test-only and never reaches the plugin bundle, so the Windows problem doesn't apply.
*/
import type { HttpRequest } from "@yaakapp-internal/models";
import { describe, expect, test, vi } from "vite-plus/test";
import { resolvedModelNameCases } from "../../../apps/yaak-client/lib/resolvedModelName.cases";
// `resolvedModelName` reads plain model fields, but the two modules under test pull in sibling
// plugins that only resolve once they're built, plus the client's platform singleton, which
// needs a browser. Stub them all out to keep this a pure unit test.
vi.mock("../../template-function-json", () => ({ filterJSONPath: () => null }));
vi.mock("../../template-function-xml", () => ({ filterXPath: () => null }));
vi.mock("@yaakapp-internal/models", () => ({ foldersAtom: {} }));
vi.mock("../../../apps/yaak-client/lib/jotai", () => ({ jotaiStore: { get: () => [] } }));
const { resolvedModelName } = await import("../src");
const { resolvedModelName: clientResolvedModelName } = await import(
"../../../apps/yaak-client/lib/resolvedModelName"
);
function httpRequest(url: string, name = ""): HttpRequest {
return { id: "rq_test", model: "http_request", name, url } as HttpRequest;
}
describe("resolvedModelName", () => {
test.each(resolvedModelNameCases)("$name", ({ url, requestName, expected }) => {
expect(resolvedModelName(httpRequest(url, requestName))).toEqual(expected);
});
test.each(resolvedModelNameCases)("matches the client for: $name", ({ url, requestName }) => {
const request = httpRequest(url, requestName);
expect(resolvedModelName(request)).toEqual(clientResolvedModelName(request));
});
});