const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); const test = require('node:test'); const GenerationPages = require('../public/js/generation-pages.js'); test('字辈页只从 URL 读取真实家谱编号', () => { assert.equal(GenerationPages.getCurrentGenealogyId('?genealogyId=900001001'), '900001001'); assert.equal(GenerationPages.getCurrentGenealogyId(''), ''); }); test('字辈新增和修改只构造 Apifox 定义的字段', () => { assert.deepEqual( GenerationPages.buildGenerationPoemBody({ generationNo: '3', generationText: ' 万 ', description: '第三世', sortOrder: '30', status: '1', content: '旧字段', stopMissingOldGeneration: true }), { generationNo: 3, generationText: '万', description: '第三世', sortOrder: 30, status: '1' } ); }); test('批量字辈请求只提交 poemText 与 disableMissing', () => { assert.deepEqual( GenerationPages.buildBatchBody({ poemText: '德 承 家 亦', disableMissing: true, content: '旧字段', stopMissingOldGeneration: false }), { poemText: '德 承 家 亦', disableMissing: true } ); }); test('字辈列表要求管理接口返回稳定的直接字段', () => { assert.deepEqual( GenerationPages.normalizeGenerationPoem({ poemId: '2060000000000000000', generationNo: 3, generationText: '万', description: '第三世', sortOrder: 30, status: '0' }), { poemId: '2060000000000000000', generationNo: 3, generationText: '万', status: '0', description: '第三世', sortOrder: 30 } ); assert.equal(GenerationPages.normalizeGenerationPoem({ poemId: 1, generationNo: 3, content: '旧字段', status: '0' }), null); assert.equal(GenerationPages.normalizeGenerationPoem({ poemId: Number('2060000000000000000'), generationNo: 3, generationText: '万', status: '0' }), null); }); test('字辈响应保留页面闭环所需字段并保持所有业务 ID 为字符串', () => { assert.deepEqual(GenerationPages.normalizeGenerationPoem({ poemId: '2060000000000000000', genealogyId: '2061000000000000000', genealogyNo: 'G20260728001', genealogyName: '叶氏家谱', generationNo: 3, generationText: '万', description: '第三世', sortOrder: 30, status: '0', remark: '祖训' }), { poemId: '2060000000000000000', genealogyId: '2061000000000000000', genealogyNo: 'G20260728001', genealogyName: '叶氏家谱', generationNo: 3, generationText: '万', status: '0', description: '第三世', sortOrder: 30, remark: '祖训' }); }); test('字辈批量输入按 Apifox 限制校验', () => { assert.equal(GenerationPages.validateGenerationPoemBody({ generationNo: 0, generationText: '万' }), '世代序号必须是 1 到 2147483647 之间的整数'); assert.equal(GenerationPages.validateGenerationPoemBody({ generationNo: 3, generationText: '' }), '请填写字辈文字'); assert.equal(GenerationPages.validateGenerationPoemBody(GenerationPages.buildGenerationPoemBody({ generationNo: 3, generationText: '万', sortOrder: '1.5' })), '排序值必须是整数'); assert.equal(GenerationPages.validateGenerationPoemBody({ generationNo: 3, generationText: '万', sortOrder: 2147483648 }), '排序值必须在 -2147483648 到 2147483647 之间'); assert.equal(GenerationPages.validateBatchBody({ poemText: '', disableMissing: false }), '请填写批量字辈内容'); assert.deepEqual(GenerationPages.splitPoemText('德,承;家、亦/传|芳\n远'), ['德', '承', '家', '亦', '传', '芳', '远']); assert.deepEqual(GenerationPages.splitPoemText('德承家亦'), ['德', '承', '家', '亦']); assert.equal(GenerationPages.validateBatchBody({ poemText: '甲'.repeat(51) + ' 德', disableMissing: false }), '单个字辈不能超过 50 个字符'); assert.equal(GenerationPages.validateBatchBody({ poemText: Array(502).fill('甲').join(' '), disableMissing: false }), '一次最多导入 500 个世代'); assert.equal(GenerationPages.validateBatchBody({ poemText: '德承家亦', disableMissing: false }), ''); }); test('家谱内容权限决定读取正常列表还是包含停用项的管理列表', () => { assert.deepEqual(GenerationPages.normalizeGenerationAccess({ canEditContent: false }), { canEditContent: false, listMethod: 'generationPoems' }); assert.deepEqual(GenerationPages.normalizeGenerationAccess({ canEditContent: true }), { canEditContent: true, listMethod: 'generationPoemsManagement' }); assert.deepEqual(GenerationPages.normalizeGenerationAccess({ canManage: true }), { canEditContent: true, listMethod: 'generationPoemsManagement' }); assert.equal(GenerationPages.canEditGenerations({ canEditContent: 'true', canManage: false }), false); assert.equal(GenerationPages.canEditGenerations({ canEditContent: false, canManage: 'true' }), false); }); test('批量预览逐项校验动作、状态、ID 与汇总计数', () => { assert.deepEqual(GenerationPages.normalizePreview({ createCount: 1, updateCount: 0, keepCount: 0, disableCount: 1, items: [ { generationNo: 1, newGenerationText: '德', newStatus: '0', action: 'create' }, { poemId: '2060000000000000000', generationNo: 2, oldGenerationText: '承', oldStatus: '0', newStatus: '1', action: 'disable', warning: '保存后该世代会停用,不会删除历史记录' } ] }), { createCount: 1, updateCount: 0, keepCount: 0, disableCount: 1, items: [ { generationNo: 1, newGenerationText: '德', newStatus: '0', action: 'create' }, { poemId: '2060000000000000000', generationNo: 2, oldGenerationText: '承', oldStatus: '0', newStatus: '1', action: 'disable', warning: '保存后该世代会停用,不会删除历史记录' } ] }); assert.equal(GenerationPages.normalizePreview({ createCount: 0, updateCount: 0, keepCount: 0, disableCount: 0, items: [] }), null); assert.equal(GenerationPages.normalizePreview({ createCount: 1, updateCount: 0, keepCount: 0, disableCount: 0, items: [{ generationNo: 1, action: 'create' }] }), null); assert.equal(GenerationPages.normalizePreview({ createCount: 2, updateCount: 0, keepCount: 0, disableCount: 0, items: [{ generationNo: 1, newGenerationText: '德', newStatus: '0', action: 'create' }] }), null); }); test('停用缺失世代的批量保存确认文案明确不会删除历史', () => { assert.equal( GenerationPages.buildBatchConfirmMessage(true), '确认按当前预览保存字辈,并停用未出现在文本中的后续世代吗?历史记录不会删除。' ); assert.equal(GenerationPages.buildBatchConfirmMessage(false), '确认按当前预览保存字辈吗?'); }); test('字辈管理提供页内完整表单、统一状态和严格权限,而非浏览器提示框', () => { const page = fs.readFileSync(path.join(__dirname, '..', 'profile-generation.html'), 'utf8'); const styles = fs.readFileSync(path.join(__dirname, '..', 'public', 'css', 'profile-module.css'), 'utf8'); const script = fs.readFileSync(path.join(__dirname, '..', 'public', 'js', 'generation-pages.js'), 'utf8'); assert.match(page, /placeholder="例如:德 承 家 亦(用空格或标点分隔)"/); assert.doesNotMatch(page, /placeholder="例如:德承家亦"/); assert.match(page, /data-generation-management[^>]+hidden/); assert.match(page, /data-generation-add[^>]+disabled|disabled[^>]+data-generation-add/); assert.match(styles, /\[data-generation-management\]\[hidden\]\s*\{[^}]*display:\s*none\s*!important/s); assert.match(page, /data-generation-form novalidate/); assert.match(page, /name="generationNo"[^>]+required/); assert.match(page, /name="generationText"[^>]+required/); assert.match(page, /data-generation-form-status/); assert.match(page, /data-generation-editor-submit/); assert.match(script, /function openPoemEditor\(poemId\)/); assert.match(script, /function submitPoem\(form\)/); assert.match(script, /root\.ProfileUI && root\.ProfileUI\.setApiState/); assert.match(script, /if \(writePending \|\| !managementAccess \|\| redirectUnauthorized\(api\) \|\| !form\) return;/); assert.doesNotMatch(script, /root\.prompt/); }); test('字辈管理接口的 403 不是登录失效', () => { assert.equal(GenerationPages.isForbidden({ status: 403 }), true); assert.equal(GenerationPages.shouldRedirectToLogin({ getToken() { return 'token'; } }, { status: 403 }), false); });