diff --git a/src/search/bitap/index.ts b/src/search/bitap/index.ts index f2b4fa2db..0fff03d0b 100644 --- a/src/search/bitap/index.ts +++ b/src/search/bitap/index.ts @@ -102,6 +102,13 @@ export default class BitapSearch { // Exact match if (this.pattern === text) { + // Respect minMatchCharLength: the whole string is the matched region, so + // its length must meet the minimum. The non-exact bitap path enforces + // this via convertMaskToIndices; the shortcut must do the same. + if (text.length < this.options.minMatchCharLength) { + return { isMatch: false, score: 1 } + } + const result: SearchResult = { isMatch: true, score: 0 diff --git a/test/match.test.js b/test/match.test.js index f354c70d9..18e17eb3d 100644 --- a/test/match.test.js +++ b/test/match.test.js @@ -82,4 +82,26 @@ describe('Fuse.match', () => { const result = Fuse.match('apple', 'apple pie', { useTokenSearch: false }) expect(result.isMatch).toBe(true) }) + + test('exact match returns isMatch: false when pattern is shorter than minMatchCharLength', () => { + // The exact-match shortcut (pattern === text) used to bypass the + // minMatchCharLength constraint, returning isMatch: true even though the + // matched region was shorter than the required minimum. + const result = Fuse.match('abc', 'abc', { minMatchCharLength: 5 }) + expect(result.isMatch).toBe(false) + }) + + test('exact match still succeeds when pattern length meets minMatchCharLength', () => { + const result = Fuse.match('hello', 'hello', { minMatchCharLength: 5 }) + expect(result.isMatch).toBe(true) + expect(result.score).toBe(0) + }) + + test('corpus search: exact-match item excluded when shorter than minMatchCharLength', () => { + const fuse = new Fuse(['abc', 'abcde', 'abcdefgh'], { minMatchCharLength: 5 }) + const results = fuse.search('abc') + // 'abc' (3 chars) must not appear; bitap non-exact matches inside longer + // strings are correctly filtered by convertMaskToIndices already. + expect(results.map((r) => r.item)).not.toContain('abc') + }) })