341 lines
15 KiB
JavaScript
341 lines
15 KiB
JavaScript
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const test = require('node:test');
|
|
|
|
const GenealogyEntryPages = require('../public/js/genealogy-entry-pages.js');
|
|
|
|
function read(relativePath) {
|
|
return fs.readFileSync(path.resolve(__dirname, '..', relativePath), 'utf8');
|
|
}
|
|
|
|
test('家谱入口只接受本地家谱业务页作为返回目标', () => {
|
|
assert.equal(GenealogyEntryPages.getTargetPage('?next=profile-feed.html'), 'profile-feed.html');
|
|
assert.equal(
|
|
GenealogyEntryPages.getTargetPage('?next=' + encodeURIComponent('profile-feed-detail.html?feedId=12#comments')),
|
|
'profile-feed-detail.html?feedId=12#comments'
|
|
);
|
|
assert.equal(GenealogyEntryPages.getTargetPage('?next=https%3A%2F%2Fevil.example'), 'profile-family-home.html');
|
|
});
|
|
|
|
test('家谱入口只使用 AppGenealogyVo 的真实字段并保持大整数 ID 字符串', () => {
|
|
assert.deepEqual(GenealogyEntryPages.normalizeGenealogy({
|
|
genealogyId: '2062179707935264769',
|
|
genealogyName: '汤氏家谱',
|
|
surname: '汤',
|
|
regionFullName: '四川省广安市武胜县',
|
|
memberCount: 12,
|
|
personCount: 34,
|
|
roleType: 'owner',
|
|
canManage: true,
|
|
legacyId: 'forbidden'
|
|
}), {
|
|
genealogyId: '2062179707935264769',
|
|
genealogyName: '汤氏家谱',
|
|
surname: '汤',
|
|
regionFullName: '四川省广安市武胜县',
|
|
memberCount: 12,
|
|
personCount: 34,
|
|
roleType: 'owner',
|
|
canManage: true
|
|
});
|
|
assert.equal(
|
|
GenealogyEntryPages.buildEntryUrl('2062179707935264769', 'profile-feed.html'),
|
|
'profile-feed.html?genealogyId=2062179707935264769'
|
|
);
|
|
assert.equal(
|
|
GenealogyEntryPages.buildEntryUrl('2062179707935264769', 'profile-feed-detail.html?feedId=12&genealogyId=old#comments'),
|
|
'profile-feed-detail.html?feedId=12&genealogyId=2062179707935264769#comments'
|
|
);
|
|
});
|
|
|
|
test('家谱创建只构造 AppGenealogyCreateBody 并省略空可选字段', () => {
|
|
assert.deepEqual(GenealogyEntryPages.buildGenealogyCreateBody({
|
|
genealogyName: ' 汤氏家谱 ',
|
|
surname: ' 汤 ',
|
|
regionCode: '511622',
|
|
ancestralHall: '',
|
|
originPlace: ' 四川 ',
|
|
addressDetail: '',
|
|
coverOssId: '2062179707935264769',
|
|
intro: ' 家谱简介 ',
|
|
visibility: '1',
|
|
joinMode: '1',
|
|
genealogyId: 'must-drop',
|
|
ownerUserId: 'must-drop'
|
|
}), {
|
|
genealogyName: '汤氏家谱',
|
|
surname: '汤',
|
|
regionCode: '511622',
|
|
originPlace: '四川',
|
|
coverOssId: '2062179707935264769',
|
|
intro: '家谱简介',
|
|
visibility: '1',
|
|
joinMode: '1'
|
|
});
|
|
});
|
|
|
|
test('家谱创建校验必填、枚举、上传派生 ID 和真实额度', () => {
|
|
assert.equal(
|
|
GenealogyEntryPages.validateGenealogyCreateBody(
|
|
GenealogyEntryPages.buildGenealogyCreateBody({ surname: '汤', regionCode: '511622' })
|
|
),
|
|
'请填写谱名'
|
|
);
|
|
assert.equal(
|
|
GenealogyEntryPages.validateGenealogyCreateBody(
|
|
GenealogyEntryPages.buildGenealogyCreateBody({ genealogyName: '汤氏家谱', regionCode: '511622' })
|
|
),
|
|
'请填写姓氏'
|
|
);
|
|
assert.equal(
|
|
GenealogyEntryPages.validateGenealogyCreateBody(
|
|
GenealogyEntryPages.buildGenealogyCreateBody({ genealogyName: '汤氏家谱', surname: '汤' })
|
|
),
|
|
'请选择家谱所在地区'
|
|
);
|
|
assert.equal(
|
|
GenealogyEntryPages.validateGenealogyCreateBody(
|
|
GenealogyEntryPages.buildGenealogyCreateBody({
|
|
genealogyName: '汤氏家谱',
|
|
surname: '汤',
|
|
regionCode: '511622',
|
|
visibility: '9'
|
|
})
|
|
),
|
|
'可见范围选项无效'
|
|
);
|
|
assert.equal(
|
|
GenealogyEntryPages.validateGenealogyCreateBody(
|
|
GenealogyEntryPages.buildGenealogyCreateBody({
|
|
genealogyName: '汤氏家谱',
|
|
surname: '汤',
|
|
regionCode: '511622',
|
|
joinMode: '9'
|
|
})
|
|
),
|
|
'加入方式选项无效'
|
|
);
|
|
assert.equal(
|
|
GenealogyEntryPages.validateGenealogyCreateBody(
|
|
GenealogyEntryPages.buildGenealogyCreateBody({
|
|
genealogyName: '汤氏家谱',
|
|
surname: '汤',
|
|
regionCode: '511622',
|
|
coverOssId: Number.MAX_SAFE_INTEGER + 1
|
|
})
|
|
),
|
|
'封面文件无效,请重新选择'
|
|
);
|
|
assert.equal(GenealogyEntryPages.canCreateGenealogy({ createRemaining: 1 }), true);
|
|
assert.equal(GenealogyEntryPages.canCreateGenealogy({ createRemaining: 0 }), false);
|
|
assert.equal(GenealogyEntryPages.canCreateGenealogy({ createRemaining: -1, canCreate: true }), true);
|
|
assert.equal(GenealogyEntryPages.canCreateGenealogy({ createRemaining: 3, canCreate: false }), false);
|
|
});
|
|
|
|
test('创建响应必须提供稳定家谱 ID、谱名和姓氏', () => {
|
|
const created = GenealogyEntryPages.normalizeCreatedGenealogy({
|
|
genealogyId: '2062179707935264769',
|
|
genealogyName: '汤氏家谱',
|
|
surname: '汤',
|
|
regionFullName: '四川省广安市武胜县',
|
|
status: '0'
|
|
});
|
|
|
|
assert.deepEqual(created, {
|
|
genealogyId: '2062179707935264769',
|
|
genealogyName: '汤氏家谱',
|
|
surname: '汤',
|
|
regionFullName: '四川省广安市武胜县'
|
|
});
|
|
assert.equal(
|
|
GenealogyEntryPages.buildCreatedGenealogyUrl(created),
|
|
'profile-family-home.html?genealogyId=2062179707935264769'
|
|
);
|
|
assert.equal(GenealogyEntryPages.normalizeCreatedGenealogy({
|
|
genealogyId: Number.MAX_SAFE_INTEGER + 1,
|
|
genealogyName: '汤氏家谱',
|
|
surname: '汤'
|
|
}), null);
|
|
assert.equal(GenealogyEntryPages.normalizeCreatedGenealogy({
|
|
genealogyId: '2062179707935264769',
|
|
genealogyName: '汤氏家谱'
|
|
}), null);
|
|
});
|
|
|
|
test('负数额度不向用户展示为可用数量', () => {
|
|
const status = GenealogyEntryPages.buildStatus({ createRemaining: -1, joinRemaining: -1 });
|
|
|
|
assert.doesNotMatch(status, /-1/);
|
|
assert.match(status, /创建不限/);
|
|
assert.match(status, /加入不限/);
|
|
});
|
|
|
|
test('我的家谱页是可用的真实列表入口', () => {
|
|
const source = read('profile-families.html');
|
|
const script = read('public/js/genealogy-entry-pages.js');
|
|
|
|
assert.doesNotMatch(source, /data-feature-status="pending"/);
|
|
assert.doesNotMatch(source, /pending-pages\.js/);
|
|
assert.match(source, /data-genealogy-list="mine"/);
|
|
assert.match(source, /data-genealogy-list="mine"[^>]*>[\s\S]*data-api-state="loading"/);
|
|
assert.match(source, /genealogy-entry-pages\.js/);
|
|
assert.match(script, /function setGenealogyListState\(container, type, message\)/);
|
|
assert.match(script, /setGenealogyListState\(container, 'loading', '正在读取我的家谱…'\)/);
|
|
assert.match(script, /setGenealogyListState\(container, 'empty', '当前账号还没有可进入的家谱'\)/);
|
|
assert.match(script, /当前账号无权查看我的家谱。/);
|
|
assert.match(script, /result = await api\.genealogiesMine\(\);/);
|
|
assert.match(script, /result = await api\.genealogyQuota\(\);/);
|
|
});
|
|
|
|
test('家谱排序在未声明 PC 读取和保存契约时保持明确不可操作状态', () => {
|
|
const source = read('profile-families.html');
|
|
const apiClient = read('utils/ApiClient.js');
|
|
const verification = read('docs/验证20%记录.md');
|
|
|
|
assert.match(source, /class="module-card is-unavailable" aria-disabled="true">\s*<span class="icon">序<\/span>\s*<h3>家谱排序<\/h3>/);
|
|
assert.match(source, /当前 PC 家谱契约未提供自定义排序的读取或保存接口/);
|
|
assert.doesNotMatch(apiClient, /sortGenealogy|genealogySort/);
|
|
assert.match(verification, /家谱邀请、家谱删除、家谱排序、分享奖励/);
|
|
});
|
|
|
|
test('创建家谱页开放真实 PC 表单且不允许手填业务 ID', () => {
|
|
const source = read('profile-create-family.html');
|
|
|
|
assert.doesNotMatch(source, /data-feature-status="pending"/);
|
|
assert.doesNotMatch(source, /pending-pages\.js/);
|
|
assert.match(source, /data-genealogy-create-page/);
|
|
assert.match(source, /data-genealogy-create-quota role="status" aria-live="polite"/);
|
|
assert.match(source, /data-genealogy-create-status/);
|
|
assert.match(source, /data-genealogy-create-form novalidate/);
|
|
assert.match(source, /class="field-required"/);
|
|
assert.match(source, /role="group" aria-labelledby="family-region-label" aria-describedby="family-create-status"/);
|
|
assert.match(source, /<label for="familyProvinceCode">省/);
|
|
assert.match(source, /<label for="familyCityCode">市/);
|
|
assert.match(source, /<label for="familyDistrictCode">区县/);
|
|
assert.match(source, /data-region-picker-status role="status" aria-live="polite"/);
|
|
assert.match(source, /<div id="family-create-status" class="upload-status" data-genealogy-create-status role="status" aria-live="polite"/);
|
|
assert.match(source, /<p id="familyCoverStatus" class="upload-status" role="status" aria-live="polite">/);
|
|
assert.match(source, /name="coverOssId"\s+type="hidden"/);
|
|
assert.match(source, /data-upload-target="#familyCoverOssId"/);
|
|
assert.match(source, /public\/js\/region-pages\.js/);
|
|
assert.match(source, /public\/js\/md5\.js/);
|
|
assert.match(source, /public\/js\/upload-pages\.js/);
|
|
assert.match(source, /public\/js\/genealogy-entry-pages\.js/);
|
|
assert.doesNotMatch(source, /name="(?:genealogyId|applyId|inviterUserId)"/);
|
|
assert.doesNotMatch(source, /name="coverOssId"[^>]*type="text"/);
|
|
});
|
|
|
|
test('公共创建入口复用真实 PC 创建表单,不保留无效的静态提交', () => {
|
|
const source = read('create-genealogy.html');
|
|
|
|
assert.match(source, /data-genealogy-create-page/);
|
|
assert.match(source, /data-genealogy-create-form novalidate aria-describedby="public-create-status"/);
|
|
assert.match(source, /data-genealogy-create-quota role="status" aria-live="polite"/);
|
|
assert.match(source, /data-genealogy-create-status role="status" aria-live="polite"/);
|
|
assert.match(source, /<label class="field" for="publicFamilyName">谱名/);
|
|
assert.match(source, /<label class="field" for="publicFamilySurname">主要姓氏/);
|
|
assert.match(source, /name="(?:ancestralHall|originPlace|addressDetail|intro|visibility|joinMode)"/);
|
|
assert.match(source, /data-region-picker-status role="status" aria-live="polite"/);
|
|
assert.match(source, /id="publicJoinMode"[^>]*aria-describedby="public-invite-mode-note"/);
|
|
assert.match(source, /当前 PC 未提供邀请码生成、发送或管理接口/);
|
|
assert.match(source, /data-genealogy-create-submit/);
|
|
assert.match(source, /public\/js\/profile-common\.js/);
|
|
assert.match(source, /public\/js\/region-pages\.js/);
|
|
assert.match(source, /public\/js\/genealogy-entry-pages\.js/);
|
|
assert.doesNotMatch(source, /data-genealogy-form="create"|data-success-url/);
|
|
assert.doesNotMatch(source, /name="(?:genealogyId|ownerUserId|coverOssId)"/);
|
|
});
|
|
|
|
test('创建页保留合法邀请模式,但不把缺失的邀请码管理伪装成已开放能力', () => {
|
|
const profileCreate = read('profile-create-family.html');
|
|
const publicCreate = read('create-genealogy.html');
|
|
|
|
[profileCreate, publicCreate].forEach((source) => {
|
|
assert.match(source, /<option value="2">邀请码加入<\/option>/);
|
|
assert.match(source, /当前 PC 未提供邀请码生成、发送或管理接口/);
|
|
});
|
|
assert.match(profileCreate, /id="joinMode"[^>]*aria-describedby="family-invite-mode-note"/);
|
|
assert.match(publicCreate, /id="publicJoinMode"[^>]*aria-describedby="public-invite-mode-note"/);
|
|
});
|
|
|
|
test('创建家谱把加载、额度不足、校验和请求失败写入统一状态容器', () => {
|
|
const source = read('public/js/genealogy-entry-pages.js');
|
|
|
|
assert.match(source, /root\.ProfileUI && root\.ProfileUI\.setApiState/);
|
|
assert.match(source, /正在读取创建额度…', 'loading'/);
|
|
assert.match(source, /当前没有可用的家谱创建额度', 'empty'/);
|
|
assert.match(source, /setCreateStatus\(validation, 'error'\)/);
|
|
assert.match(source, /getApiStateType\(error\)/);
|
|
assert.match(source, /正在创建…/);
|
|
});
|
|
|
|
test('加入家谱页面不允许用户手工填写 genealogyId', () => {
|
|
const source = read('join-genealogy.html');
|
|
|
|
assert.doesNotMatch(source, /name="genealogyId"/);
|
|
assert.doesNotMatch(source, /请输入家谱ID/);
|
|
});
|
|
|
|
test('家谱业务模块运行时统一委托 ProfileUI 读取和传播上下文', () => {
|
|
[
|
|
'feed-pages.js',
|
|
'growth-pages.js',
|
|
'lineage-pages.js',
|
|
'memo-pages.js',
|
|
'relative-pages.js',
|
|
'generation-pages.js'
|
|
].forEach((file) => {
|
|
const source = read('public/js/' + file);
|
|
|
|
assert.match(source, /root\.ProfileUI && root\.ProfileUI\.getGenealogyId/);
|
|
assert.match(source, /root\.ProfileUI && root\.ProfileUI\.syncGenealogyContextLinks/);
|
|
});
|
|
});
|
|
|
|
test('个人中心只保留真实家谱入口,内容发布仍会透传家谱上下文', () => {
|
|
const profile = read('profile.html');
|
|
const content = read('profile-content.html');
|
|
const familyHome = read('profile-family-home.html');
|
|
const familyEntry = read('profile-families.html');
|
|
const profileCommon = read('public/js/profile-common.js');
|
|
const profileMain = profile.slice(0, profile.indexOf('</main>'));
|
|
|
|
assert.match(profileMain, /进入我的家谱/);
|
|
assert.match(profileMain, /href="profile-families\.html"/);
|
|
assert.doesNotMatch(profileMain, /鹿野 Loyel|待处理|已发布内容|资料完善度/);
|
|
assert.doesNotMatch(profileMain, /href="profile-(?:article|album|video|merit|gift|join-review)\.html"/);
|
|
assert.match(content, /href="profile-feed\.html" data-feature-link="available" data-genealogy-context-link/);
|
|
assert.match(familyHome, /href="profile-family-admin\.html" data-genealogy-context-link/);
|
|
assert.match(familyEntry, /href="profile-family-admin\.html" data-genealogy-context-link/);
|
|
assert.match(profileCommon, /function getGenealogyEntryUrl\(href\)/);
|
|
assert.match(profileCommon, /genealogyId \? withGenealogyId\([^\n]+\) : getGenealogyEntryUrl\(/);
|
|
assert.match(read('public/js/genealogy-entry-pages.js'), /if \(shouldRedirectToLogin\(api\)\) \{\s+redirectToLogin\(api\);/);
|
|
});
|
|
|
|
test('当前家谱名称从真实家谱响应写入会话上下文,并在页头提供切换入口', () => {
|
|
const common = read('public/js/profile-common.js');
|
|
const entry = read('public/js/genealogy-entry-pages.js');
|
|
const familyHome = read('public/js/family-home-pages.js');
|
|
|
|
assert.match(common, /genealogyContextStorageKey/);
|
|
assert.match(common, /function setCurrentGenealogyContext\(genealogyId, genealogyName\)/);
|
|
assert.match(common, /class="profile-current-genealogy"/);
|
|
assert.match(common, /href="profile-families\.html"/);
|
|
assert.match(entry, /data-genealogy-name=/);
|
|
assert.match(entry, /root\.ProfileUI\.setCurrentGenealogyContext\(verified\.genealogyId, verified\.genealogyName\)/);
|
|
assert.match(familyHome, /root\.ProfileUI\.setCurrentGenealogyContext\(result\.genealogyId, result\.genealogyName\)/);
|
|
});
|
|
|
|
test('小屏导航提供统一入口,并且所有家谱内链接继续透传当前上下文', () => {
|
|
const common = read('public/js/profile-common.js');
|
|
const css = read('public/css/public.css');
|
|
|
|
assert.match(common, /data-profile-mobile-menu-toggle/);
|
|
assert.match(common, /data-profile-mobile-menu/);
|
|
assert.match(common, /profile-family-home\.html" data-genealogy-context-link/);
|
|
assert.match(common, /profile-family-admin\.html" data-genealogy-context-link/);
|
|
assert.match(css, /\.profile-mobile-menu\.is-open/);
|
|
assert.match(css, /\.profile-current-genealogy/);
|
|
});
|