diff --git a/test/utils/search.spec.ts b/test/utils/search.spec.ts
index 9fdd146e..209c6a0c 100644
--- a/test/utils/search.spec.ts
+++ b/test/utils/search.spec.ts
@@ -1,4 +1,5 @@
import { describe, it, expect, vi } from 'vitest'
+import Fuse from 'fuse.js'
import { highlight, sanitizeSnippet } from '../../src/runtime/utils/search'
describe('sanitizeSnippet', () => {
@@ -283,6 +284,29 @@ describe('highlight', () => {
expect(highlightRegions(value, [[1, 4]])).toBe('\u{1F600}\u{1F600}abc')
})
+ it('snaps the boundaries real fuse.js reports, not only recorded ones', () => {
+ // Every other fixture here is a recorded copy of fuse output. A recording
+ // cannot notice when an upgrade stops producing those offsets, and a
+ // fixture that no longer straddles a character passes without exercising
+ // anything. Drive the real library instead, at the options ContentSearch
+ // ships (`ContentSearch.vue`), and assert the offsets it returns — so that
+ // upgrade fails here loudly rather than going quiet.
+ //
+ // Well-formed input on both sides: the user searched with the wrong emoji.
+ const label = 'deployment \u{1F600} pipeline'
+ const fuse = new Fuse([{ label }], { ignoreLocation: true, includeMatches: true, threshold: 0.1, keys: ['label'] })
+ const matches = fuse.search('deployment \u{1F680}')[0]?.matches
+
+ // 12 is the low half of the emoji, so the first region's exclusive end
+ // falls inside it.
+ expect(matches?.[0]?.indices).toEqual([[0, 11], [13, 13]])
+
+ const result = highlight({ label, matches: [...matches!] }, 'deployment \u{1F680}', 'label', undefined, true)
+
+ expect(result).not.toMatch(LONE_SURROGATE)
+ expect(result).toBe('deployment \u{1F600} pipeline')
+ })
+
it('does not duplicate text when adjacent regions meet inside one character', () => {
// Both boundaries land on index 2, the low half of the emoji: the first
// region's end snaps forward and the second region's start snaps back, so