From 08626a00ac2f2593c3fecbcf713dfd712b301562 Mon Sep 17 00:00:00 2001 From: CyC2018 Date: Sun, 1 Nov 2026 20:08:24 +0800 Subject: [PATCH] Update test_search.cjs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 补充搜索排序、问法、同义词和中英文混写的测试。 --- website/builder/test_search.cjs | 41 ++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/website/builder/test_search.cjs b/website/builder/test_search.cjs index c8e6ee85..c42ffe1f 100644 --- a/website/builder/test_search.cjs +++ b/website/builder/test_search.cjs @@ -1,7 +1,7 @@ const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); -const { search } = require('../templates/search.js'); +const { search, highlight } = require('../templates/search.js'); const dist = process.argv[2] || path.join(__dirname, '../dist'); const info = JSON.parse(fs.readFileSync(path.join(dist, 'build-info.json'))); const index = JSON.parse(fs.readFileSync(path.join(dist, info.searchIndex))); @@ -9,6 +9,45 @@ assert(search(index, '拥塞控制').some(r => r.path.includes('计算机网络 assert(search(index, '检索增强生成').some(r => r.path === '/notes/大模型应用' && r.id === '三、检索增强生成')); assert(search(index, 'Token 分词').some(r => r.path === '/notes/大模型应用' && r.id === 'token-与分词')); assert.equal(search(index, '').length, 0); +const ranking = { documents: [ + { title: '学习路线 - 示例', path: '/notes/学习路线 - 示例', sections: [{ title: '第 1 天:拥塞控制', id: 'day', text: '阅读拥塞控制' }] }, + { title: '网络', path: '/notes/网络', sections: [ + { title: 'TCP 拥塞控制', id: 'tcp', text: '拥塞控制用于……' }, + { title: 'A1 线程池的拒绝策略', id: 'a1', text: '线程池实验' }, + { title: 'ThreadPoolExecutor', id: 'pool', text: '线程池指的是……' }, + { title: 'Thread Local Storage', id: 'tls', text: '线程本地存储' }, + { title: 'Agentic RAG', id: 'rag', text: '检索' }, + ] }, +] }; +assert.equal(search(ranking, '拥塞控制')[0].id, 'tcp', '讲解正文排在学习路线之前'); +assert.equal(search(ranking, '线程池')[0].id, 'pool', '讲解正文排在附录实验之前'); +assert.equal(search(ranking, 'rag')[0].id, 'rag', '英文词按完整单词优先'); +assert.equal(search(ranking, '第 1 天')[0].id, 'day', '学习路线仍能搜到'); +const phrasing = { documents: [ + { title: '死锁', path: '/notes/死锁', sections: [ + { title: '死锁预防', id: 'pre', text: '破坏某个必要条件' }, + { title: '必要条件', id: 'cond', text: '只有以下四个条件同时成立时才会发生死锁' }, + ] }, + { title: '容器', path: '/notes/容器', sections: [ + { title: 'HashMap', id: 'map', text: '哈希冲突用拉链法解决,扩容时容量翻倍' }, + { title: '与 Hashtable 的比较', id: 'cmp', text: 'HashMap 不是线程安全的' }, + ] }, + { title: 'Java 虚拟机', path: '/notes/jvm', sections: [{ title: '垃圾收集器', id: 'gc', text: 'G1' }] }, + { title: '57.1 和为 S 的两个数字', path: '/notes/57.1', sections: [ + { title: '57.1 和为 S 的两个数字', id: '', text: '' }, { title: '57.1 和为 S 的两个数字', id: 'h1', text: '' }, + ] }, +] }; +assert.equal(search(phrasing, '死锁的必要条件')[0].id, 'cond', '去掉问法后,小节标题正好匹配的排在前面'); +assert.equal(search(phrasing, '死锁的四个条件')[0].id, 'cond', '去掉问法后再搜'); +assert(search(phrasing, '同时成立的四条件').fuzzy && search(phrasing, '同时成立的四条件')[0].id === 'cond', '没有完全匹配时按词组找相近的小节,并标出来'); +assert.equal(search(phrasing, 'hashmap扩容')[0].id, 'map', '中英文连写时拆开'); +assert.equal(search(phrasing, '散列冲突')[0].id, 'map', '术语的另一种说法'); +assert.equal(search(phrasing, '垃圾回收器')[0].id, 'gc', '垃圾回收按垃圾收集搜索'); +assert.equal(search(phrasing, 'HashMap和Hashtable的区别')[0].id, 'cmp', '“和……的区别”问法'); +assert.equal(search(phrasing, '和为 S').length, 1, '没有正文的开头部分不和一级标题重复出现'); +assert.equal(search(phrasing, '什么是').length, 0, '只有问法时没有结果'); +assert.equal(highlight('aTCP 与 tcp', '搜索词高亮不区分大小写,其余文字转义'); +assert.equal(highlight('& (x)', 'amp (x)'), '& (x)', '高亮先切分再转义,正则字符按字面匹配'); const sample = { documents: [{ title: '数据库', path: '/notes/示例', sections: [ { title: '事务', id: '事务', text: '一致性 '.repeat(20) }, { title: '一致性', id: '一致性', text: 'SQL 与数据库' },