const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); const test = require('node:test'); const FamilyHomePages = require('../public/js/family-home-pages.js'); const genealogyId = '2062179707935264769'; const root = path.resolve(__dirname, '..'); function overviewFixture(overrides) { return Object.assign({ genealogyId, genealogyNo: 'G20260729001', genealogyName: '叶氏家谱', surname: '叶', ancestralHall: '南阳堂', originPlace: '四川成都', regionCode: '510100', regionName: '成都市', regionFullName: '四川省 成都市', addressDetail: '高新区', coverOssId: '2062179707935264700', intro: '叶氏源流', ownerUserId: '2062179707935264701', visibility: '1', joinMode: '1', memberCount: 12, personCount: 36, status: '0', roleType: 'owner', canManage: true, canEditContent: true, memberStatus: '0', joinTime: '2026-07-29 12:00:00' }, overrides); } test('家谱主页只保留 AppGenealogyVo 的安全展示字段并匹配当前上下文', () => { assert.deepEqual(FamilyHomePages.normalizeFamilyOverview( overviewFixture(), genealogyId ), { genealogyId, genealogyNo: 'G20260729001', genealogyName: '叶氏家谱', surname: '叶', ancestralHall: '南阳堂', originPlace: '四川成都', regionFullName: '四川省 成都市', memberCount: 12, personCount: 36, canManage: true, canEditContent: true }); assert.equal(FamilyHomePages.normalizeFamilyOverview( overviewFixture({ genealogyId: '2062179707935264770' }), genealogyId ), null); assert.equal(FamilyHomePages.normalizeFamilyOverview( overviewFixture({ genealogyId: Number.MAX_SAFE_INTEGER + 1 }), genealogyId ), null); }); test('家谱主页拒绝停用、空名称和非法计数响应', () => { assert.equal(FamilyHomePages.normalizeFamilyOverview( overviewFixture({ status: '1' }), genealogyId ), null); assert.equal(FamilyHomePages.normalizeFamilyOverview( overviewFixture({ genealogyName: '' }), genealogyId ), null); assert.equal(FamilyHomePages.normalizeFamilyOverview( overviewFixture({ memberCount: -1 }), genealogyId ), null); assert.equal(FamilyHomePages.normalizeFamilyOverview( overviewFixture({ personCount: Number.MAX_SAFE_INTEGER + 1 }), genealogyId ), null); }); test('家谱概览渲染转义文本并隐藏内部用户与文件编号', () => { const overview = FamilyHomePages.normalizeFamilyOverview( overviewFixture({ genealogyName: '<叶氏家谱>', ancestralHall: '<南阳堂>' }), genealogyId ); const html = FamilyHomePages.renderFamilyOverview(overview); assert.match(html, /<叶氏家谱>/); assert.match(html, /<南阳堂>/); assert.match(html, /成员 12 人/); assert.match(html, /世系人物 36 人/); assert.doesNotMatch(html, /ownerUserId|coverOssId|206217970793526470[01]/); }); test('家谱管理入口只接受后端布尔能力字段', () => { assert.equal(FamilyHomePages.shouldShowFamilyManagement( FamilyHomePages.normalizeFamilyOverview(overviewFixture({ canManage: true }), genealogyId) ), true); assert.equal(FamilyHomePages.shouldShowFamilyManagement( FamilyHomePages.normalizeFamilyOverview(overviewFixture({ canManage: 'true' }), genealogyId) ), false); }); test('家谱主页只把缺失登录态和 401 视为登录失效', () => { assert.equal(FamilyHomePages.shouldRedirectToLogin({ getToken() { return 'token'; } }, { status: 403 }), false); assert.equal(FamilyHomePages.shouldRedirectToLogin({ getToken() { return 'token'; } }, { status: 401 }), true); assert.equal(FamilyHomePages.shouldRedirectToLogin({ getToken() { return ''; } }), true); assert.equal(FamilyHomePages.isForbidden({ status: 403 }), true); assert.equal(FamilyHomePages.isForbidden({ status: 401 }), false); }); test('家谱主页只读取当前家谱概览和世系树', async () => { const calls = []; const tree = [{ personId: '2062179707935264770', genealogyId, name: '始祖', spouses: [], children: [] }]; const result = await FamilyHomePages.loadFamilyHome({ genealogyOverview(receivedId) { calls.push(['overview', receivedId]); return Promise.resolve(overviewFixture()); }, lineageTree(receivedId) { calls.push(['tree', receivedId]); return Promise.resolve(tree); } }, genealogyId); assert.deepEqual(calls, [ ['overview', genealogyId], ['tree', genealogyId] ]); assert.deepEqual(result.overview, FamilyHomePages.normalizeFamilyOverview( overviewFixture(), genealogyId )); assert.deepEqual(result.tree, tree); }); test('家谱主页以 PC 工作台分组开放已接入功能且不制造旧 APP 伪功能', () => { const source = fs.readFileSync(path.join(root, 'profile-family-home.html'), 'utf8'); const script = fs.readFileSync(path.join(root, 'public', 'js', 'family-home-pages.js'), 'utf8'); assert.doesNotMatch(source, /data-feature-status="pending"|pending-pages\.js/); assert.doesNotMatch(source, /四川武胜汤氏族/); assert.doesNotMatch(source, /href="profile-invite\.html"/); assert.doesNotMatch(source, /href="profile-document/); assert.match(source, /PC 暂未开放邀请/); assert.match(source, /class="module-card is-unavailable" aria-disabled="true">\s*邀<\/span>/); assert.match(source, /重要证件/); assert.match(source, /当前 PC 接口未提供家谱证件的列表、上传或维护能力/); assert.match(source, /

删除家谱<\/h3>/); assert.match(source, /当前 PC 家谱契约未提供删除接口,暂不能查看或执行删除操作。/); assert.doesNotMatch(source, /data-genealogy-delete|deleteGenealogy/); assert.match(source, /亲友往来(贺礼簿)/); assert.match(source, /备忘录(家族恩人)/); assert.match(source, /当前 PC 不区分独立分类/); assert.match(source, /data-family-home-title/); assert.match(source, /data-family-home-overview/); assert.match(source, /data-family-home-member-count/); assert.match(source, /data-family-home-person-count/); assert.match(source, /data-family-home-management/); assert.match(source, /data-lineage-home-tree/); assert.match(source, /data-family-home-overview[^>]*>[\s\S]*data-api-state="loading"/); assert.match(source, /data-lineage-home-tree[^>]*>[\s\S]*data-api-state="loading"/); assert.match(source, /src="public\/js\/lineage-pages\.js"/); assert.match(source, /src="public\/js\/family-home-pages\.js"/); assert.match(script, /root\.ProfileUI && root\.ProfileUI\.setApiState/); assert.match(script, /setHomeState\('\[data-family-home-overview\]', 'loading', '正在加载家谱概览…'\)/); assert.match(script, /setHomeState\('\[data-lineage-home-tree\]', 'loading', '正在加载世系树…'\)/); assert.match(script, /setHomeState\('\[data-lineage-home-tree\]', 'empty', '暂无世系树'\)/); assert.match(script, /treeHtml = Array\.isArray\(result\) \? root\.LineagePages\.renderLineageTreeHtml\(result\) : '';/); assert.match(script, /if \(!treeHtml\) \{/); assert.match(script, /当前账号无权查看该家谱世系树。/); [ 'profile-feed.html', 'profile-tree.html', 'profile-generation.html', 'profile-article.html', 'profile-album.html', 'profile-video.html', 'profile-merit.html', 'profile-ceremony.html', 'profile-gift.html', 'profile-growth.html', 'profile-relative.html', 'profile-memo.html', 'profile-family-admin.html', 'profile-services.html' ].forEach((href) => { assert.match( source, new RegExp('href="' + href.replace('.', '\\.') + '"[^>]*data-genealogy-context-link'), `${href} 缺少家谱上下文` ); }); assert.match(source, /

会员与订单<\/h3>/); assert.match(source, /关联当前家谱创建会员订单,并查看已有订单。/); });