test(config): consolidate env/include scenario coverage

This commit is contained in:
Peter Steinberger
2026-02-23 22:16:30 +00:00
parent c248c515a3
commit cd5f3fe0c1
3 changed files with 587 additions and 466 deletions

View File

@@ -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);
}
});
});
});