diff --git a/src/scorers/util.ts b/src/scorers/util.ts index 8f8aa26..4a82776 100644 --- a/src/scorers/util.ts +++ b/src/scorers/util.ts @@ -28,10 +28,15 @@ export function result( }; } -/** Normalize text for lenient comparisons (trim + collapse whitespace). */ +/** Normalize text for lenient comparisons. + * When `trim` is true (default): strip ends and collapse internal whitespace. + * When `trim` is false: leave whitespace untouched (only case folding may apply). + */ export function normalize(text: string, opts: { caseSensitive?: boolean; trim?: boolean } = {}): string { let out = text; - if (opts.trim !== false) out = out.trim(); + if (opts.trim !== false) { + out = out.trim().replace(/\s+/g, " "); + } if (!opts.caseSensitive) out = out.toLowerCase(); - return out.replace(/\s+/g, " "); + return out; } diff --git a/tests/scorers.test.ts b/tests/scorers.test.ts index b53c6a2..4ed04c3 100644 --- a/tests/scorers.test.ts +++ b/tests/scorers.test.ts @@ -170,3 +170,14 @@ describe("rubric", () => { expect(r.passed).toBe(true); }); }); + +describe("exact-match trim:false preserves internal whitespace", () => { + it("matches when internal spaces are identical", async () => { + const r = await run(exactMatchScorer, { type: "exact-match", expected: "a b", trim: false }, ctx("a b")); + expect(r.passed).toBe(true); + }); + it("fails when internal whitespace differs", async () => { + const r = await run(exactMatchScorer, { type: "exact-match", expected: "a b", trim: false }, ctx("a b")); + expect(r.passed).toBe(false); + }); +});