Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ score is the weighted mean of the scorer scores.

| Scorer | Passes when | Key options |
| --- | --- | --- |
| `exact-match` | output equals `expected` | `expected`, `caseSensitive`, `trim` |
| `exact-match` | output equals `expected` | `expected`, `caseSensitive`, `trim`, `collapseWhitespace` |
| `regex` | output matches a pattern | `pattern`, `flags`, `expectMatch` |
| `contains` | all substrings present (partial credit) | `value` / `values`, `caseSensitive` |
| `not-contains` | no banned substring present | `value` / `values`, `caseSensitive` |
Expand Down
6 changes: 4 additions & 2 deletions src/scorers/exact-match.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { normalize, result } from "./util.js";
* - `expected`: the value to match (falls back to `case.expected`)
* - `caseSensitive`: default false
* - `trim`: default true
* - `collapseWhitespace`: default true
*/
export const exactMatchScorer: Scorer = {
type: "exact-match",
Expand All @@ -23,8 +24,9 @@ export const exactMatchScorer: Scorer = {
}
const caseSensitive = spec.caseSensitive === true;
const trim = spec.trim !== false;
const a = normalize(ctx.output, { caseSensitive, trim });
const b = normalize(expected, { caseSensitive, trim });
const collapseWhitespace = spec.collapseWhitespace !== false;
const a = normalize(ctx.output, { caseSensitive, trim, collapseWhitespace });
const b = normalize(expected, { caseSensitive, trim, collapseWhitespace });
const passed = a === b;
return result(spec, {
score: passed ? 1 : 0,
Expand Down
7 changes: 5 additions & 2 deletions src/scorers/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,12 @@ export function result(
}

/** Normalize text for lenient comparisons (trim + collapse whitespace). */
export function normalize(text: string, opts: { caseSensitive?: boolean; trim?: boolean } = {}): string {
export function normalize(
text: string,
opts: { caseSensitive?: boolean; trim?: boolean; collapseWhitespace?: boolean } = {},
): string {
let out = text;
if (opts.trim !== false) out = out.trim();
if (!opts.caseSensitive) out = out.toLowerCase();
return out.replace(/\s+/g, " ");
return opts.collapseWhitespace === false ? out : out.replace(/\s+/g, " ");
}
22 changes: 22 additions & 0 deletions tests/scorers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,28 @@ describe("exact-match", () => {
const r = await run(exactMatchScorer, { type: "exact-match" }, ctx("yes", {}, { expected: "yes" }));
expect(r.passed).toBe(true);
});
it("collapses internal whitespace by default", async () => {
const r = await run(
exactMatchScorer,
{ type: "exact-match", expected: "hello world" },
ctx("hello\n\nworld"),
);
expect(r.passed).toBe(true);
});
it("keeps internal whitespace when collapseWhitespace is false", async () => {
const r = await run(
exactMatchScorer,
{
type: "exact-match",
expected: "hello\nworld",
caseSensitive: true,
trim: false,
collapseWhitespace: false,
},
ctx("hello world"),
);
expect(r.passed).toBe(false);
});
});

describe("regex", () => {
Expand Down
Loading