mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-26 16:41:49 +00:00
test(config): consolidate env/include scenario coverage
This commit is contained in:
@@ -54,145 +54,171 @@ describe("stripReasoningTagsFromText", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("handles mixed real tags and code tags", () => {
|
||||
const input = "<think>hidden</think>Visible text with `<think>` example.";
|
||||
expect(stripReasoningTagsFromText(input)).toBe("Visible text with `<think>` example.");
|
||||
});
|
||||
|
||||
it("handles code block followed by real tags", () => {
|
||||
const input = "```\n<think>code</think>\n```\n<think>real hidden</think>visible";
|
||||
expect(stripReasoningTagsFromText(input)).toBe("```\n<think>code</think>\n```\nvisible");
|
||||
it("handles mixed code-tag and real-tag content", () => {
|
||||
const cases = [
|
||||
{
|
||||
input: "<think>hidden</think>Visible text with `<think>` example.",
|
||||
expected: "Visible text with `<think>` example.",
|
||||
},
|
||||
{
|
||||
input: "```\n<think>code</think>\n```\n<think>real hidden</think>visible",
|
||||
expected: "```\n<think>code</think>\n```\nvisible",
|
||||
},
|
||||
] as const;
|
||||
for (const { input, expected } of cases) {
|
||||
expect(stripReasoningTagsFromText(input)).toBe(expected);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("edge cases", () => {
|
||||
it("preserves unclosed <think without angle bracket", () => {
|
||||
const input = "Here is how to use <think tags in your code";
|
||||
expect(stripReasoningTagsFromText(input)).toBe(input);
|
||||
it("handles malformed tags and null-ish inputs", () => {
|
||||
const cases = [
|
||||
{
|
||||
input: "Here is how to use <think tags in your code",
|
||||
expected: "Here is how to use <think tags in your code",
|
||||
},
|
||||
{
|
||||
input: "You can start with <think and then close with </think>",
|
||||
expected: "You can start with <think and then close with",
|
||||
},
|
||||
{
|
||||
input: "A < think >content< /think > B",
|
||||
expected: "A B",
|
||||
},
|
||||
{
|
||||
input: "",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
input: null as unknown as string,
|
||||
expected: null,
|
||||
},
|
||||
] as const;
|
||||
for (const { input, expected } of cases) {
|
||||
expect(stripReasoningTagsFromText(input)).toBe(expected);
|
||||
}
|
||||
});
|
||||
|
||||
it("strips lone closing tag outside code", () => {
|
||||
const input = "You can start with <think and then close with </think>";
|
||||
expect(stripReasoningTagsFromText(input)).toBe(
|
||||
"You can start with <think and then close with",
|
||||
);
|
||||
it("handles fenced and inline code edge behavior", () => {
|
||||
const cases = [
|
||||
{
|
||||
input: "Example:\n~~~\n<think>reasoning</think>\n~~~\nDone!",
|
||||
expected: "Example:\n~~~\n<think>reasoning</think>\n~~~\nDone!",
|
||||
},
|
||||
{
|
||||
input: "Example:\n~~~js\n<think>code</think>\n~~~",
|
||||
expected: "Example:\n~~~js\n<think>code</think>\n~~~",
|
||||
},
|
||||
{
|
||||
input: "Use ``code`` with <think>hidden</think> text",
|
||||
expected: "Use ``code`` with text",
|
||||
},
|
||||
{
|
||||
input: "Before\n```\ncode\n```\nAfter with <think>hidden</think>",
|
||||
expected: "Before\n```\ncode\n```\nAfter with",
|
||||
},
|
||||
{
|
||||
input: "```\n<think>not protected\n~~~\n</think>text",
|
||||
expected: "```\n<think>not protected\n~~~\n</think>text",
|
||||
},
|
||||
{
|
||||
input: "Start `unclosed <think>hidden</think> end",
|
||||
expected: "Start `unclosed end",
|
||||
},
|
||||
] as const;
|
||||
for (const { input, expected } of cases) {
|
||||
expect(stripReasoningTagsFromText(input)).toBe(expected);
|
||||
}
|
||||
});
|
||||
|
||||
it("handles tags with whitespace", () => {
|
||||
const input = "A < think >content< /think > B";
|
||||
expect(stripReasoningTagsFromText(input)).toBe("A B");
|
||||
it("handles nested and final tag behavior", () => {
|
||||
const cases = [
|
||||
{
|
||||
input: "<think>outer <think>inner</think> still outer</think>visible",
|
||||
expected: "still outervisible",
|
||||
},
|
||||
{
|
||||
input: "A<final>1</final>B<final>2</final>C",
|
||||
expected: "A1B2C",
|
||||
},
|
||||
{
|
||||
input: "`<final>` in code, <final>visible</final> outside",
|
||||
expected: "`<final>` in code, visible outside",
|
||||
},
|
||||
] as const;
|
||||
for (const { input, expected } of cases) {
|
||||
expect(stripReasoningTagsFromText(input)).toBe(expected);
|
||||
}
|
||||
});
|
||||
|
||||
it("handles empty and null-ish inputs", () => {
|
||||
expect(stripReasoningTagsFromText("")).toBe("");
|
||||
expect(stripReasoningTagsFromText(null as unknown as string)).toBe(null);
|
||||
it("handles unicode, attributes, and case-insensitive tag names", () => {
|
||||
const cases = [
|
||||
{
|
||||
input: "你好 <think>思考 🤔</think> 世界",
|
||||
expected: "你好 世界",
|
||||
},
|
||||
{
|
||||
input: "A <think id='test' class=\"foo\">hidden</think> B",
|
||||
expected: "A B",
|
||||
},
|
||||
{
|
||||
input: "A <THINK>hidden</THINK> <Thinking>also hidden</Thinking> B",
|
||||
expected: "A B",
|
||||
},
|
||||
] as const;
|
||||
for (const { input, expected } of cases) {
|
||||
expect(stripReasoningTagsFromText(input)).toBe(expected);
|
||||
}
|
||||
});
|
||||
|
||||
it("preserves think tags inside tilde fenced code blocks", () => {
|
||||
const input = "Example:\n~~~\n<think>reasoning</think>\n~~~\nDone!";
|
||||
expect(stripReasoningTagsFromText(input)).toBe(input);
|
||||
});
|
||||
|
||||
it("preserves tags in tilde block at EOF without trailing newline", () => {
|
||||
const input = "Example:\n~~~js\n<think>code</think>\n~~~";
|
||||
expect(stripReasoningTagsFromText(input)).toBe(input);
|
||||
});
|
||||
|
||||
it("handles nested think patterns (first close ends block)", () => {
|
||||
const input = "<think>outer <think>inner</think> still outer</think>visible";
|
||||
expect(stripReasoningTagsFromText(input)).toBe("still outervisible");
|
||||
});
|
||||
|
||||
it("strips final tag markup but preserves content (by design)", () => {
|
||||
const input = "A<final>1</final>B<final>2</final>C";
|
||||
expect(stripReasoningTagsFromText(input)).toBe("A1B2C");
|
||||
});
|
||||
|
||||
it("preserves final tags in inline code (markup only stripped outside)", () => {
|
||||
const input = "`<final>` in code, <final>visible</final> outside";
|
||||
expect(stripReasoningTagsFromText(input)).toBe("`<final>` in code, visible outside");
|
||||
});
|
||||
|
||||
it("handles double backtick inline code with tags", () => {
|
||||
const input = "Use ``code`` with <think>hidden</think> text";
|
||||
expect(stripReasoningTagsFromText(input)).toBe("Use ``code`` with text");
|
||||
});
|
||||
|
||||
it("handles fenced code blocks with content", () => {
|
||||
const input = "Before\n```\ncode\n```\nAfter with <think>hidden</think>";
|
||||
expect(stripReasoningTagsFromText(input)).toBe("Before\n```\ncode\n```\nAfter with");
|
||||
});
|
||||
|
||||
it("does not match mismatched fence types (``` vs ~~~)", () => {
|
||||
const input = "```\n<think>not protected\n~~~\n</think>text";
|
||||
const result = stripReasoningTagsFromText(input);
|
||||
expect(result).toBe(input);
|
||||
});
|
||||
|
||||
it("handles unicode content inside and around tags", () => {
|
||||
const input = "你好 <think>思考 🤔</think> 世界";
|
||||
expect(stripReasoningTagsFromText(input)).toBe("你好 世界");
|
||||
});
|
||||
|
||||
it("handles very long content between tags efficiently", () => {
|
||||
it("handles long content and pathological backtick patterns efficiently", () => {
|
||||
const longContent = "x".repeat(10000);
|
||||
const input = `<think>${longContent}</think>visible`;
|
||||
expect(stripReasoningTagsFromText(input)).toBe("visible");
|
||||
});
|
||||
expect(stripReasoningTagsFromText(`<think>${longContent}</think>visible`)).toBe("visible");
|
||||
|
||||
it("handles tags with attributes", () => {
|
||||
const input = "A <think id='test' class=\"foo\">hidden</think> B";
|
||||
expect(stripReasoningTagsFromText(input)).toBe("A B");
|
||||
});
|
||||
|
||||
it("is case-insensitive for tag names", () => {
|
||||
const input = "A <THINK>hidden</THINK> <Thinking>also hidden</Thinking> B";
|
||||
expect(stripReasoningTagsFromText(input)).toBe("A B");
|
||||
});
|
||||
|
||||
it("handles pathological nested backtick patterns without hanging", () => {
|
||||
const input = "`".repeat(100) + "<think>test</think>" + "`".repeat(100);
|
||||
const pathological = "`".repeat(100) + "<think>test</think>" + "`".repeat(100);
|
||||
const start = Date.now();
|
||||
stripReasoningTagsFromText(input);
|
||||
stripReasoningTagsFromText(pathological);
|
||||
const elapsed = Date.now() - start;
|
||||
expect(elapsed).toBeLessThan(1000);
|
||||
});
|
||||
|
||||
it("handles unclosed inline code gracefully", () => {
|
||||
const input = "Start `unclosed <think>hidden</think> end";
|
||||
const result = stripReasoningTagsFromText(input);
|
||||
expect(result).toBe("Start `unclosed end");
|
||||
});
|
||||
});
|
||||
|
||||
describe("strict vs preserve mode", () => {
|
||||
it("strict mode truncates on unclosed tag", () => {
|
||||
it("applies strict and preserve modes to unclosed tags", () => {
|
||||
const input = "Before <think>unclosed content after";
|
||||
expect(stripReasoningTagsFromText(input, { mode: "strict" })).toBe("Before");
|
||||
});
|
||||
|
||||
it("preserve mode keeps content after unclosed tag", () => {
|
||||
const input = "Before <think>unclosed content after";
|
||||
expect(stripReasoningTagsFromText(input, { mode: "preserve" })).toBe(
|
||||
"Before unclosed content after",
|
||||
);
|
||||
const cases = [
|
||||
{ mode: "strict" as const, expected: "Before" },
|
||||
{ mode: "preserve" as const, expected: "Before unclosed content after" },
|
||||
];
|
||||
for (const { mode, expected } of cases) {
|
||||
expect(stripReasoningTagsFromText(input, { mode })).toBe(expected);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("trim options", () => {
|
||||
it("trims both sides by default", () => {
|
||||
const input = " <think>x</think> result <think>y</think> ";
|
||||
expect(stripReasoningTagsFromText(input)).toBe("result");
|
||||
});
|
||||
|
||||
it("trim=none preserves whitespace", () => {
|
||||
const input = " <think>x</think> result ";
|
||||
expect(stripReasoningTagsFromText(input, { trim: "none" })).toBe(" result ");
|
||||
});
|
||||
|
||||
it("trim=start only trims start", () => {
|
||||
const input = " <think>x</think> result ";
|
||||
expect(stripReasoningTagsFromText(input, { trim: "start" })).toBe("result ");
|
||||
it("applies configured trim strategies", () => {
|
||||
const cases = [
|
||||
{
|
||||
input: " <think>x</think> result <think>y</think> ",
|
||||
expected: "result",
|
||||
opts: undefined,
|
||||
},
|
||||
{
|
||||
input: " <think>x</think> result ",
|
||||
expected: " result ",
|
||||
opts: { trim: "none" as const },
|
||||
},
|
||||
{
|
||||
input: " <think>x</think> result ",
|
||||
expected: "result ",
|
||||
opts: { trim: "start" as const },
|
||||
},
|
||||
] as const;
|
||||
for (const testCase of cases) {
|
||||
expect(stripReasoningTagsFromText(testCase.input, testCase.opts)).toBe(testCase.expected);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user