|
1 | | -import { create, insert } from '@orama/orama' |
| 1 | +import { create, insert, Tokenizer } from '@orama/orama' |
2 | 2 | import t from 'tap' |
3 | 3 | import { |
4 | 4 | afterInsert, |
@@ -143,3 +143,46 @@ t.test('should correctly save and load data with positions', async (t) => { |
143 | 143 | text: { hello: [{ start: 0, length: 5 }], world: [{ start: 6, length: 5 }] } |
144 | 144 | }) |
145 | 145 | }) |
| 146 | + |
| 147 | +// A minimal word-granularity CJK tokenizer, equivalent to @orama/tokenizers/mandarin. |
| 148 | +// CJK text has no word spaces, so a whole run is matched as a single word by the |
| 149 | +// position indexer and the tokenizer splits it into several tokens. |
| 150 | +function createCjkTokenizer(): Tokenizer { |
| 151 | + const segmenter = new Intl.Segmenter('zh-CN', { granularity: 'word' }) |
| 152 | + return { |
| 153 | + language: 'mandarin', |
| 154 | + normalizationCache: new Map(), |
| 155 | + tokenize(input: string): string[] { |
| 156 | + if (typeof input !== 'string') return [input] |
| 157 | + const tokens: string[] = [] |
| 158 | + for (const segment of segmenter.segment(input)) { |
| 159 | + if (segment.isWordLike) tokens.push(segment.segment) |
| 160 | + } |
| 161 | + return tokens |
| 162 | + } |
| 163 | + } |
| 164 | +} |
| 165 | + |
| 166 | +t.test('it should record a position for every token of a multi-token word (CJK)', async (t) => { |
| 167 | + const tokenizer = createCjkTokenizer() |
| 168 | + const db = create({ |
| 169 | + schema: { text: 'string' } as const, |
| 170 | + components: { tokenizer }, |
| 171 | + plugins: [{ name: 'highlight', afterInsert }] |
| 172 | + }) |
| 173 | + |
| 174 | + const text = '我喜欢编程' |
| 175 | + const expected = new Set(tokenizer.tokenize(text)) |
| 176 | + t.ok(expected.size > 1, 'the tokenizer splits the run into multiple tokens') |
| 177 | + |
| 178 | + const id = await insert(db, { text }) |
| 179 | + const recorded = (db as OramaWithHighlight<typeof db>).data.positions[id].text |
| 180 | + |
| 181 | + t.same(new Set(Object.keys(recorded)), expected, 'every token has a recorded position') |
| 182 | + |
| 183 | + // a token other than the first one is found by search and can be highlighted |
| 184 | + const lastToken = [...expected][expected.size - 1] |
| 185 | + const results = await searchWithHighlight(db, { term: lastToken }) |
| 186 | + t.ok(results.hits.length > 0, 'search finds the document') |
| 187 | + t.ok(Array.isArray(results.hits[0].positions.text[lastToken]), 'a non-leading token is highlightable') |
| 188 | +}) |
0 commit comments