'use strict'; const test = require('node:test'); const assert = require('node:assert'); const { SubtitleChatMonitor, containsFuzzy, boundedLevenshtein } = require('../src/core/subtitleChatMonitor'); /** 收集回调结果的假监听器 */ function makeListener() { const events = []; return { events, onProgress: (percent) => events.push({ type: 'progress', percent }), onSuccess: (chunk, blockX, blockZ) => events.push({ type: 'success', chunk, blockX, blockZ }), onGenericSuccess: () => events.push({ type: 'generic' }) }; } test('字幕解析开垦进度百分比', () => { const listener = makeListener(); const monitor = new SubtitleChatMonitor(listener); monitor.onSubtitle('开垦中 50%'); assert.deepStrictEqual(listener.events, [{ type: 'progress', percent: 50 }]); }); test('字幕进度支持小数并钳制到 0-100', () => { const listener = makeListener(); const monitor = new SubtitleChatMonitor(listener); monitor.onSubtitle('开垦中 99.6%'); monitor.onSubtitle('开垦中 150%'); // 负号不属于百分比模式(与原 Java 版一致:模式为数字 + %,负号被当作普通文本忽略) monitor.onSubtitle('开垦中 -20%'); assert.deepStrictEqual(listener.events.map((e) => e.percent), [100, 100, 20]); }); test('不含开垦关键词的字幕被忽略', () => { const listener = makeListener(); const monitor = new SubtitleChatMonitor(listener); monitor.onSubtitle('服务器公告:今晚 20:00 攻城 50%'); assert.strictEqual(listener.events.length, 0); }); test('聊天解析带坐标的开垦成功消息', () => { const listener = makeListener(); const monitor = new SubtitleChatMonitor(listener); monitor.onChat('疆土(123,-45) [1968,-720] 已为你的邦国开拓'); assert.deepStrictEqual(listener.events, [{ type: 'success', chunk: { x: 123, z: -45 }, blockX: 1968, blockZ: -720 }]); }); test('聊天成功消息兼容全角括号与全角逗号', () => { const listener = makeListener(); const monitor = new SubtitleChatMonitor(listener); monitor.onChat('疆土(7,8) [ 112 , 128 ] 已为你的邦国开拓'); assert.strictEqual(listener.events.length, 1); assert.deepStrictEqual(listener.events[0].chunk, { x: 7, z: 8 }); assert.strictEqual(listener.events[0].blockX, 112); assert.strictEqual(listener.events[0].blockZ, 128); }); test('无坐标的通用成功消息走 onGenericSuccess', () => { const listener = makeListener(); const monitor = new SubtitleChatMonitor(listener); monitor.onChat('你的疆土已开拓,继续努力!'); assert.deepStrictEqual(listener.events, [{ type: 'generic' }]); }); test('完全无关的聊天不触发任何回调', () => { const listener = makeListener(); const monitor = new SubtitleChatMonitor(listener); monitor.onChat(' 大家好'); monitor.onChat(''); monitor.onChat(null); assert.strictEqual(listener.events.length, 0); }); test('containsFuzzy 容忍细微文案差异', () => { assert.strictEqual(containsFuzzy('开垦中 50%', '开垦中', 0), true); // 差一个字("开垦" vs "开恳")在 maxEdits=1 下应命中 assert.strictEqual(containsFuzzy('开恳中 50%', '开垦中', 1), true); assert.strictEqual(containsFuzzy('开恳中 50%', '开垦中', 0), false); assert.strictEqual(containsFuzzy('完全无关的文本', '开垦中', 1), false); }); test('containsFuzzy 空关键词视为命中,空文本视为不命中', () => { assert.strictEqual(containsFuzzy('任意文本', '', 0), true); assert.strictEqual(containsFuzzy('', '开垦中', 1), false); assert.strictEqual(containsFuzzy(null, '开垦中', 1), false); }); test('boundedLevenshtein 计算有界编辑距离', () => { assert.strictEqual(boundedLevenshtein('abc', 0, 'abc', 3), 0); assert.strictEqual(boundedLevenshtein('abd', 0, 'abc', 3), 1); assert.strictEqual(boundedLevenshtein('xyz', 0, 'abc', 3), 3); // 超过 limit 时提前返回 limit+1 assert.strictEqual(boundedLevenshtein('xyzxyz', 0, 'abc', 2), 3); });