diff --git a/packages/sanity/src/core/search/text-search/createTextSearch.test.ts b/packages/sanity/src/core/search/text-search/createTextSearch.test.ts index b8bca154ad5..c169a4fc9fb 100644 --- a/packages/sanity/src/core/search/text-search/createTextSearch.test.ts +++ b/packages/sanity/src/core/search/text-search/createTextSearch.test.ts @@ -6,6 +6,7 @@ import { getDocumentTypeConfiguration, getOrder, getQueryString, + isExactMatchToken, isNegationToken, isPrefixToken, prefixLast, @@ -268,7 +269,7 @@ describe('prefixLast', () => { expect(prefixLast('a b')).toBe('a b*') expect(prefixLast('a -b')).toBe('a* -b') expect(prefixLast('a "bc" d')).toBe('a "bc" d*') - expect(prefixLast('ab "cd"')).toBe('ab "cd"*') + expect(prefixLast('ab "cd"')).toBe('ab* "cd"') expect(prefixLast('a --')).toBe('a* --') }) @@ -288,7 +289,19 @@ describe('prefixLast', () => { it('preserves quoted tokens', () => { expect(prefixLast('"a b" c d')).toBe('"a b" c d*') - expect(prefixLast('"a b" c d "ef" "g "')).toBe('"a b" c d "ef" "g "*') + expect(prefixLast('"a b" c d "ef" "g "')).toBe('"a b" c d* "ef" "g "') expect(prefixLast('"a " b" c d')).toBe('"a " b c d*') }) }) + +describe('isExactMatchToken', () => { + it('recognises that a token is encased in quote marks', () => { + expect(isExactMatchToken(undefined)).toBe(false) + expect(isExactMatchToken('"a"')).toBe(true) + expect(isExactMatchToken('"a b"')).toBe(true) + expect(isExactMatchToken('"a')).toBe(false) + expect(isExactMatchToken('a"')).toBe(false) + expect(isExactMatchToken('"a b')).toBe(false) + expect(isExactMatchToken('a b"')).toBe(false) + }) +}) diff --git a/packages/sanity/src/core/search/text-search/createTextSearch.ts b/packages/sanity/src/core/search/text-search/createTextSearch.ts index a005d7eefb3..a6f7d6ab166 100644 --- a/packages/sanity/src/core/search/text-search/createTextSearch.ts +++ b/packages/sanity/src/core/search/text-search/createTextSearch.ts @@ -94,21 +94,29 @@ export function isPrefixToken(token: string | undefined): boolean { return typeof token !== 'undefined' && token.trim().at(-1) === WILDCARD_TOKEN } +export function isExactMatchToken(token: string | undefined): boolean { + return [token?.at(0), token?.at(-1)].every((character) => character === '"') +} + export function prefixLast(query: string): string { const tokens = (query.match(TOKEN_REGEX) ?? []).map((token) => token.trim()) - const finalNonNegationTokenIndex = tokens.findLastIndex((token) => !isNegationToken(token)) - const finalNonNegationToken = tokens[finalNonNegationTokenIndex] + + const finalIncrementalTokenIndex = tokens.findLastIndex( + (token) => !isNegationToken(token) && !isExactMatchToken(token), + ) + + const finalIncrementalToken = tokens[finalIncrementalTokenIndex] if (tokens.length === 0) { return WILDCARD_TOKEN } - if (isPrefixToken(finalNonNegationToken) || typeof finalNonNegationToken === 'undefined') { + if (isPrefixToken(finalIncrementalToken) || typeof finalIncrementalToken === 'undefined') { return tokens.join(' ') } const prefixedTokens = [...tokens] - prefixedTokens.splice(finalNonNegationTokenIndex, 1, `${finalNonNegationToken}${WILDCARD_TOKEN}`) + prefixedTokens.splice(finalIncrementalTokenIndex, 1, `${finalIncrementalToken}${WILDCARD_TOKEN}`) return prefixedTokens.join(' ') }