diff --git a/client/tests/_assert.js b/client/tests/_assert.js new file mode 100644 index 0000000..2b6866a --- /dev/null +++ b/client/tests/_assert.js @@ -0,0 +1,16 @@ +// 极简断言助手(测试代码不受严格 ES5 限制,dev-guide 05 §10) +module.exports = function () { + let pass = true; + let count = 0; + function eq(name, got, exp) { + const p = JSON.stringify(got) === JSON.stringify(exp); + count++; + if (!p) pass = false; + console.log((p ? 'PASS' : 'FAIL') + ' ' + name + ' got=' + JSON.stringify(got) + ' exp=' + JSON.stringify(exp)); + } + function done(label) { + console.log((pass ? 'ALL PASS' : 'HAS FAILURE') + ' [' + label + '] ' + count + ' checks'); + return pass; + } + return { eq, done }; +}; diff --git a/client/tests/_load.js b/client/tests/_load.js new file mode 100644 index 0000000..3f0bb68 --- /dev/null +++ b/client/tests/_load.js @@ -0,0 +1,27 @@ +// 把前端 ES5 全局脚本加载进 Node global。 +// 正式代码是浏览器脚本(var X = {...},无 module.exports), +// 这里用 vm.runInThisContext 还原浏览器语义——顶层 var 挂到 globalThis。 +// 【关键】不得为了测试而给正式代码加 module.exports(client 05 §10)。 +const fs = require('fs'); +const path = require('path'); +const vm = require('vm'); + +const ROOT = path.resolve(__dirname, '..', '..'); // 仓库根 + +function load(rel) { + const abs = path.join(ROOT, rel); + const code = fs.readFileSync(abs, 'utf8'); + vm.runInThisContext(code, { filename: rel }); +} + +function loadAll(rels) { + rels.forEach(load); +} + +// 断言抛错的小工具:期望 fn 抛异常 +function throws(fn) { + try { fn(); } catch (e) { return true; } + return false; +} + +module.exports = { ROOT, load, loadAll, throws }; diff --git a/client/tests/run.js b/client/tests/run.js new file mode 100644 index 0000000..c35a9c1 --- /dev/null +++ b/client/tests/run.js @@ -0,0 +1,16 @@ +// 二七王前端单测总运行器:逐个 spawn test_*.js(各自独立进程/全局),汇总结果 +// 用法:node client/tests/run.js +const fs = require('fs'); +const path = require('path'); +const cp = require('child_process'); + +const dir = __dirname; +const files = fs.readdirSync(dir).filter(f => /^test_.*\.js$/.test(f)).sort(); +let allOk = true; +for (const f of files) { + console.log('\n===== ' + f + ' ====='); + const r = cp.spawnSync(process.execPath, [path.join(dir, f)], { stdio: 'inherit' }); + if (r.status !== 0) allOk = false; +} +console.log('\n' + (allOk ? '===== 全部单测通过 =====' : '===== 存在失败单测 =====')); +process.exit(allOk ? 0 : 1); diff --git a/client/tests/test_shared_sync.js b/client/tests/test_shared_sync.js new file mode 100644 index 0000000..9383b5f --- /dev/null +++ b/client/tests/test_shared_sync.js @@ -0,0 +1,23 @@ +// shared 同步守卫:服务端权威源与前端只读副本必须逐字节一致。 +// 忘了跑 sync_shared.cmd、或有人手改了前端副本,这条立刻红。 +const fs = require('fs'); +const path = require('path'); +const { ROOT } = require('./_load'); +const t = require('./_assert')(); + +const srcDir = path.join(ROOT, 'server', 'games', 'erqiwang', 'shared'); +const dstDir = path.join(ROOT, 'client', 'js', '01_SubGame', 'codes', 'shared'); + +const srcFiles = fs.readdirSync(srcDir).filter(f => f.endsWith('.js')).sort(); +const dstFiles = fs.readdirSync(dstDir).filter(f => f.endsWith('.js')).sort(); + +t.eq('文件清单一致', dstFiles, srcFiles); +t.eq('源目录非空', srcFiles.length > 0, true); + +srcFiles.forEach(f => { + const a = fs.readFileSync(path.join(srcDir, f)); + const b = fs.existsSync(path.join(dstDir, f)) ? fs.readFileSync(path.join(dstDir, f)) : Buffer.alloc(0); + t.eq('字节一致: ' + f, a.equals(b), true); +}); + +process.exit(t.done('shared_sync') ? 0 : 1);