test(coding-agent/test): added markdown-aware keyword tests in prose mode

- Added markdown prose tests to verify maskNonProse preserves plain prose and masks fenced/tilde blocks.
- Added keyword-in-prose tests that enforced lowercase token matching and ignored code spans, fences, XML, and comments.
- Added highlight-mode tests ensuring only prose keywords are ANSI-highlighted and inline/fenced/XML regions stay unmodified.
- Added workflow/orchestrate assertions that uppercase or path-like forms remain unchanged while lowercase token boundaries are handled correctly.
This commit is contained in:
can1357
2026-05-31 09:47:24 +02:00
parent bfdbb3ff20
commit a5c71bb775
5 changed files with 197 additions and 12 deletions
@@ -0,0 +1,36 @@
import { beforeAll, describe, expect, it } from "bun:test";
import { UserMessageComponent } from "../../../src/modes/components/user-message";
import { initTheme } from "../../../src/modes/theme/theme";
beforeAll(async () => {
await initTheme(false);
});
function render(text: string): string {
return new UserMessageComponent(text).render(80).join("\n");
}
describe("UserMessageComponent magic-keyword highlighting", () => {
it("gradient-paints a magic keyword in the rendered (sent) message bubble", () => {
const raw = render("please orchestrate the rollout");
// Visible text is preserved.
expect(Bun.stripANSI(raw)).toContain("please orchestrate the rollout");
// The keyword is gradient-painted: a per-character foreground sequence is emitted,
// and the word no longer survives as a contiguous run in the rendered bytes.
expect(raw).toContain("\x1b[38");
expect(raw).not.toContain("orchestrate");
});
it("does not paint a keyword inside an inline code span", () => {
const raw = render("ship the `orchestrate` helper");
expect(Bun.stripANSI(raw)).toContain("orchestrate");
// Code spans render through the code style as a single run — the word stays intact.
expect(raw).toContain("orchestrate");
});
it("does not paint a keyword inside a fenced code block", () => {
const raw = render("intro\n```\norchestrate\n```");
expect(Bun.stripANSI(raw)).toContain("orchestrate");
expect(raw).toContain("orchestrate");
});
});
@@ -0,0 +1,46 @@
import { beforeAll, describe, expect, it } from "bun:test";
import { highlightMagicKeywords } from "../../src/modes/magic-keywords";
import { initTheme } from "../../src/modes/theme/theme";
beforeAll(async () => {
// Gradient palettes read the active theme's color mode.
await initTheme(false);
});
describe("highlightMagicKeywords", () => {
it("paints every magic keyword in a single prose pass, preserving visible text", () => {
const input = "first ultrathink then orchestrate the workflow";
const decorated = highlightMagicKeywords(input);
expect(decorated).not.toBe(input);
expect(decorated).toContain("\x1b[38");
expect(Bun.stripANSI(decorated)).toBe(input);
// Each keyword is gradient-painted character-by-character, so none survives as a
// contiguous run in the decorated output.
for (const keyword of ["ultrathink", "orchestrate", "workflow"]) {
expect(decorated).not.toContain(keyword);
expect(Bun.stripANSI(decorated)).toContain(keyword);
}
});
it("never paints keywords inside code spans, fenced blocks, or XML sections", () => {
const input = "`ultrathink`\n```\norchestrate\n```\n<x>workflow</x>";
expect(highlightMagicKeywords(input)).toBe(input);
});
it("paints only the prose occurrence when the keyword also appears in code", () => {
const decorated = highlightMagicKeywords("`orchestrate` but please orchestrate now");
// The code-span occurrence stays literal; the prose one is split by gradient escapes.
expect(decorated).toContain("`orchestrate`");
expect(Bun.stripANSI(decorated)).toBe("`orchestrate` but please orchestrate now");
// Exactly one prose occurrence painted ⇒ one contiguous "orchestrate" remains (the code one).
expect(decorated.split("orchestrate").length - 1).toBe(1);
});
it("restores the supplied foreground after each painted keyword", () => {
const reset = "\x1b[38;2;1;2;3m";
const decorated = highlightMagicKeywords("go orchestrate go", reset);
expect(decorated).toContain(reset);
// The reset must land before the trailing prose so it keeps the bubble color.
expect(decorated.endsWith(`${reset} go`)).toBe(true);
});
});
@@ -0,0 +1,80 @@
import { describe, expect, it } from "bun:test";
import { keywordInProse, maskNonProse } from "../../src/modes/markdown-prose";
const ORCHESTRATE = /\borchestrate\b/i;
describe("maskNonProse", () => {
it("preserves length and leaves plain prose untouched", () => {
const text = "please orchestrate the rollout across teams";
expect(maskNonProse(text)).toBe(text);
});
it("returns the input unchanged when no code/markup constructs are present", () => {
// Fast path: no backtick, angle bracket, or tilde fence.
const text = "a multi line\nmessage with no markup";
expect(maskNonProse(text)).toBe(text);
});
it("blanks fenced code blocks while keeping indices aligned", () => {
const text = "before\n```\norchestrate\n```\nafter orchestrate";
const masked = maskNonProse(text);
expect(masked.length).toBe(text.length);
// The fenced occurrence is blanked; the prose occurrence survives at its index.
expect(masked.indexOf("orchestrate")).toBe(text.lastIndexOf("orchestrate"));
expect(masked.startsWith("before\n")).toBe(true);
});
it("blanks tilde fences and language-tagged fences", () => {
expect(maskNonProse("~~~\norchestrate\n~~~").includes("orchestrate")).toBe(false);
expect(maskNonProse("```ts\nconst orchestrate = 1\n```").includes("orchestrate")).toBe(false);
});
it("treats an unterminated fence as code through end of text", () => {
expect(maskNonProse("```\norchestrate").includes("orchestrate")).toBe(false);
});
});
describe("keywordInProse", () => {
it("matches a standalone keyword in prose", () => {
expect(keywordInProse("please orchestrate this", ORCHESTRATE)).toBe(true);
expect(keywordInProse("orchestrate", ORCHESTRATE)).toBe(true);
});
it("ignores keywords inside inline code spans", () => {
expect(keywordInProse("use `orchestrate` now", ORCHESTRATE)).toBe(false);
expect(keywordInProse("``a `orchestrate` b``", ORCHESTRATE)).toBe(false);
});
it("treats an unmatched backtick run as literal text", () => {
// A lone backtick opens no span, so the keyword is still prose.
expect(keywordInProse("the ` then orchestrate", ORCHESTRATE)).toBe(true);
});
it("ignores keywords inside fenced code blocks", () => {
expect(keywordInProse("```\norchestrate\n```", ORCHESTRATE)).toBe(false);
expect(keywordInProse("text\n```\norchestrate here\n```\ndone", ORCHESTRATE)).toBe(false);
});
it("ignores keywords inside XML/HTML sections, including nested and comments", () => {
expect(keywordInProse("<note>orchestrate</note>", ORCHESTRATE)).toBe(false);
expect(keywordInProse("<a><a>orchestrate</a></a>", ORCHESTRATE)).toBe(false);
expect(keywordInProse("<!-- orchestrate -->", ORCHESTRATE)).toBe(false);
expect(keywordInProse('<tag attr="x>y">orchestrate</tag>', ORCHESTRATE)).toBe(false);
});
it("still matches prose outside of code/markup regions", () => {
expect(keywordInProse("<b>orchestrate</b> then orchestrate", ORCHESTRATE)).toBe(true);
expect(keywordInProse("run `x` then orchestrate", ORCHESTRATE)).toBe(true);
expect(keywordInProse("```\ncode\n```\norchestrate", ORCHESTRATE)).toBe(true);
});
it("does not over-mask on a stray less-than or an unbalanced tag", () => {
expect(keywordInProse("a < b and orchestrate", ORCHESTRATE)).toBe(true);
// <br> has no matching close, so only the tag is masked, not the rest.
expect(keywordInProse("<br> orchestrate after", ORCHESTRATE)).toBe(true);
});
it("respects word boundaries regardless of region", () => {
expect(keywordInProse("reorchestrate the orchestration", ORCHESTRATE)).toBe(false);
});
});
@@ -10,22 +10,35 @@ beforeAll(() => {
});
describe("orchestrate keyword detection", () => {
it("matches the standalone word in any case", () => {
it("matches the lowercase word delimited by whitespace or a string edge", () => {
expect(containsOrchestrate("orchestrate")).toBe(true);
expect(containsOrchestrate("Orchestrate")).toBe(true);
expect(containsOrchestrate("ORCHESTRATE")).toBe(true);
expect(containsOrchestrate("please orchestrate this rollout")).toBe(true);
expect(containsOrchestrate("do it. orchestrate.")).toBe(true);
expect(containsOrchestrate("orchestrate the rollout")).toBe(true);
// A newline is whitespace, and end-of-string is a valid right boundary.
expect(containsOrchestrate("do it now\norchestrate")).toBe(true);
});
it("ignores inflected forms and embedded substrings", () => {
it("ignores casing, inflections, punctuation-adjacent, and path-embedded forms", () => {
expect(containsOrchestrate("Orchestrate")).toBe(false);
expect(containsOrchestrate("ORCHESTRATE")).toBe(false);
expect(containsOrchestrate("orchestrated the build")).toBe(false);
expect(containsOrchestrate("orchestrating now")).toBe(false);
expect(containsOrchestrate("a clean orchestration")).toBe(false);
expect(containsOrchestrate("it orchestrates well")).toBe(false);
expect(containsOrchestrate("reorchestrate everything")).toBe(false);
// The reported bug: a path/extension is not whitespace, so the word never triggers.
expect(containsOrchestrate("packages/coding-agent/src/modes/orchestrate.ts")).toBe(false);
expect(containsOrchestrate("do it. orchestrate.")).toBe(false);
expect(containsOrchestrate("nothing to see here")).toBe(false);
});
it("ignores keywords inside code spans, fenced blocks, and XML sections", () => {
expect(containsOrchestrate("use `orchestrate` here")).toBe(false);
expect(containsOrchestrate("```\norchestrate\n```")).toBe(false);
expect(containsOrchestrate("<note>orchestrate</note>")).toBe(false);
// A real prose request alongside code still triggers.
expect(containsOrchestrate("run `setup` then orchestrate the rollout")).toBe(true);
});
});
describe("orchestrate keyword highlighting", () => {
@@ -38,8 +51,12 @@ describe("orchestrate keyword highlighting", () => {
it("leaves text without the standalone keyword untouched", () => {
expect(highlightOrchestrate("nothing here")).toBe("nothing here");
// Probe hits the substring but the word boundary fails — no decoration.
// Probe hits the substring but the whitespace boundary fails — no decoration.
expect(highlightOrchestrate("orchestrated builds")).toBe("orchestrated builds");
expect(highlightOrchestrate("Orchestrate this")).toBe("Orchestrate this");
// The reported bug: a filename must not be painted.
const filePath = "packages/coding-agent/src/modes/orchestrate.ts";
expect(highlightOrchestrate(filePath)).toBe(filePath);
});
it("does not cross-trigger with the ultrathink highlighter", () => {
@@ -8,18 +8,21 @@ beforeAll(() => {
});
describe("workflow keyword detection", () => {
it("matches the standalone word (singular or plural) in any case", () => {
it("matches the lowercase word (singular or plural) delimited by whitespace", () => {
expect(containsWorkflow("workflow")).toBe(true);
expect(containsWorkflow("Workflow")).toBe(true);
expect(containsWorkflow("WORKFLOW")).toBe(true);
expect(containsWorkflow("please workflow this rollout")).toBe(true);
expect(containsWorkflow("do it. workflow.")).toBe(true);
expect(containsWorkflow("run these workflows")).toBe(true);
expect(containsWorkflow("design the workflow")).toBe(true);
});
it("ignores inflected forms and embedded substrings", () => {
it("ignores casing, inflections, punctuation-adjacent, and path-embedded forms", () => {
expect(containsWorkflow("Workflow")).toBe(false);
expect(containsWorkflow("WORKFLOW")).toBe(false);
expect(containsWorkflow("workflowed the build")).toBe(false);
expect(containsWorkflow("reworkflow everything")).toBe(false);
// A path/extension is not whitespace, so the word never triggers.
expect(containsWorkflow("packages/coding-agent/test/modes/workflow.test.ts")).toBe(false);
expect(containsWorkflow("do it. workflow.")).toBe(false);
expect(containsWorkflow("nothing to see here")).toBe(false);
});
});
@@ -34,8 +37,11 @@ describe("workflow keyword highlighting", () => {
});
it("leaves text without the standalone keyword untouched", () => {
// Probe hits the substring but the word boundary fails — no decoration.
// Probe hits the substring but the whitespace boundary fails — no decoration.
expect(highlightWorkflow("workflowed builds")).toBe("workflowed builds");
expect(highlightWorkflow("Workflow this")).toBe("Workflow this");
const filePath = "packages/coding-agent/test/modes/workflow.test.ts";
expect(highlightWorkflow(filePath)).toBe(filePath);
});
});