mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 15:30:39 +00:00
Merged via /review-pr -> /prepare-pr -> /merge-pr.
Prepared head SHA: 55b8a93a99
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Reviewed-by: @mbelinky
152 lines
4.8 KiB
TypeScript
152 lines
4.8 KiB
TypeScript
import { describe, expect, it, test } from "vitest";
|
|
import { extractTextFromChatContent } from "./chat-content.js";
|
|
import {
|
|
getFrontmatterString,
|
|
normalizeStringList,
|
|
parseFrontmatterBool,
|
|
resolveOpenClawManifestBlock,
|
|
} from "./frontmatter.js";
|
|
import { resolveNodeIdFromCandidates } from "./node-match.js";
|
|
|
|
describe("extractTextFromChatContent", () => {
|
|
it("normalizes string content", () => {
|
|
expect(extractTextFromChatContent(" hello\nworld ")).toBe("hello world");
|
|
});
|
|
|
|
it("extracts text blocks from array content", () => {
|
|
expect(
|
|
extractTextFromChatContent([
|
|
{ type: "text", text: " hello " },
|
|
{ type: "image_url", image_url: "https://example.com" },
|
|
{ type: "text", text: "world" },
|
|
]),
|
|
).toBe("hello world");
|
|
});
|
|
|
|
it("applies sanitizer when provided", () => {
|
|
expect(
|
|
extractTextFromChatContent("Here [Tool Call: foo (ID: 1)] ok", {
|
|
sanitizeText: (text) => text.replace(/\[Tool Call:[^\]]+\]\s*/g, ""),
|
|
}),
|
|
).toBe("Here ok");
|
|
});
|
|
|
|
it("supports custom join and normalization", () => {
|
|
expect(
|
|
extractTextFromChatContent(
|
|
[
|
|
{ type: "text", text: " hello " },
|
|
{ type: "text", text: "world " },
|
|
],
|
|
{
|
|
sanitizeText: (text) => text.trim(),
|
|
joinWith: "\n",
|
|
normalizeText: (text) => text.trim(),
|
|
},
|
|
),
|
|
).toBe("hello\nworld");
|
|
});
|
|
});
|
|
|
|
describe("shared/frontmatter", () => {
|
|
test("normalizeStringList handles strings and arrays", () => {
|
|
expect(normalizeStringList("a, b,,c")).toEqual(["a", "b", "c"]);
|
|
expect(normalizeStringList([" a ", "", "b"])).toEqual(["a", "b"]);
|
|
expect(normalizeStringList(null)).toEqual([]);
|
|
});
|
|
|
|
test("getFrontmatterString extracts strings only", () => {
|
|
expect(getFrontmatterString({ a: "b" }, "a")).toBe("b");
|
|
expect(getFrontmatterString({ a: 1 }, "a")).toBeUndefined();
|
|
});
|
|
|
|
test("parseFrontmatterBool respects fallback", () => {
|
|
expect(parseFrontmatterBool("true", false)).toBe(true);
|
|
expect(parseFrontmatterBool("false", true)).toBe(false);
|
|
expect(parseFrontmatterBool(undefined, true)).toBe(true);
|
|
});
|
|
|
|
test("resolveOpenClawManifestBlock parses JSON5 metadata and picks openclaw block", () => {
|
|
const frontmatter = {
|
|
metadata: "{ openclaw: { foo: 1, bar: 'baz' } }",
|
|
};
|
|
expect(resolveOpenClawManifestBlock({ frontmatter })).toEqual({ foo: 1, bar: "baz" });
|
|
});
|
|
|
|
test("resolveOpenClawManifestBlock returns undefined for invalid input", () => {
|
|
expect(resolveOpenClawManifestBlock({ frontmatter: {} })).toBeUndefined();
|
|
expect(
|
|
resolveOpenClawManifestBlock({ frontmatter: { metadata: "not-json5" } }),
|
|
).toBeUndefined();
|
|
expect(
|
|
resolveOpenClawManifestBlock({ frontmatter: { metadata: "{ nope: { a: 1 } }" } }),
|
|
).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe("resolveNodeIdFromCandidates", () => {
|
|
it("matches nodeId", () => {
|
|
expect(
|
|
resolveNodeIdFromCandidates(
|
|
[
|
|
{ nodeId: "mac-123", displayName: "Mac Studio", remoteIp: "100.0.0.1" },
|
|
{ nodeId: "pi-456", displayName: "Raspberry Pi", remoteIp: "100.0.0.2" },
|
|
],
|
|
"pi-456",
|
|
),
|
|
).toBe("pi-456");
|
|
});
|
|
|
|
it("matches displayName using normalization", () => {
|
|
expect(
|
|
resolveNodeIdFromCandidates([{ nodeId: "mac-123", displayName: "Mac Studio" }], "mac studio"),
|
|
).toBe("mac-123");
|
|
});
|
|
|
|
it("matches nodeId prefix (>=6 chars)", () => {
|
|
expect(resolveNodeIdFromCandidates([{ nodeId: "mac-abcdef" }], "mac-ab")).toBe("mac-abcdef");
|
|
});
|
|
|
|
it("throws unknown node with known list", () => {
|
|
expect(() =>
|
|
resolveNodeIdFromCandidates(
|
|
[
|
|
{ nodeId: "mac-123", displayName: "Mac Studio", remoteIp: "100.0.0.1" },
|
|
{ nodeId: "pi-456" },
|
|
],
|
|
"nope",
|
|
),
|
|
).toThrow(/unknown node: nope.*known: /);
|
|
});
|
|
|
|
it("throws ambiguous node with matches list", () => {
|
|
expect(() =>
|
|
resolveNodeIdFromCandidates([{ nodeId: "mac-abcdef" }, { nodeId: "mac-abc999" }], "mac-abc"),
|
|
).toThrow(/ambiguous node: mac-abc.*matches:/);
|
|
});
|
|
|
|
it("prefers a unique connected node when names are duplicated", () => {
|
|
expect(
|
|
resolveNodeIdFromCandidates(
|
|
[
|
|
{ nodeId: "ios-old", displayName: "iPhone", connected: false },
|
|
{ nodeId: "ios-live", displayName: "iPhone", connected: true },
|
|
],
|
|
"iphone",
|
|
),
|
|
).toBe("ios-live");
|
|
});
|
|
|
|
it("stays ambiguous when multiple connected nodes match", () => {
|
|
expect(() =>
|
|
resolveNodeIdFromCandidates(
|
|
[
|
|
{ nodeId: "ios-a", displayName: "iPhone", connected: true },
|
|
{ nodeId: "ios-b", displayName: "iPhone", connected: true },
|
|
],
|
|
"iphone",
|
|
),
|
|
).toThrow(/ambiguous node: iphone.*matches:/);
|
|
});
|
|
});
|