家谱现有接口调试50%

This commit is contained in:
rain
2026-07-09 17:29:25 +08:00
commit 6050508144
262 changed files with 63354 additions and 0 deletions
+53
View File
@@ -0,0 +1,53 @@
const assert = require('assert');
const AlbumPages = require('../public/js/album-pages.js');
function run() {
assert.deepStrictEqual(AlbumPages.normalizeList({ rows: [{ albumId: 1 }] }), [{ albumId: 1 }]);
assert.deepStrictEqual(AlbumPages.normalizeList({ records: [{ albumId: 2 }] }), [{ albumId: 2 }]);
assert.deepStrictEqual(AlbumPages.buildAlbumBody({
albumName: ' 老照片 ',
albumDesc: ' 家族旧影 ',
coverOssId: '',
sortOrder: '',
status: ''
}), {
albumName: '老照片',
albumDesc: '家族旧影',
coverOssId: undefined,
sortOrder: 1,
status: '0'
});
assert.deepStrictEqual(AlbumPages.buildPhotoBody({
ossId: ' 2060001 ',
photoTitle: ' 老宅 ',
photoDesc: ' 合影 ',
photographer: ' 汤明 ',
shootTime: '2026-07-09',
sortOrder: '',
status: ''
}), {
ossId: 2060001,
photoTitle: '老宅',
photoDesc: '合影',
photographer: '汤明',
shootTime: '2026-07-09',
sortOrder: 1,
status: '0'
});
assert.strictEqual(
AlbumPages.buildAlbumRow({
albumId: 301,
albumName: '老照片',
photoCount: 6,
albumDesc: '家族旧影'
}, '900001001'),
'<button class="module-row album-row" type="button" data-album-id="301"><div><h3>老照片</h3><p>6 张照片 · 家族旧影</p></div><span class="pill">管理</span></button>'
);
console.log('album-pages tests passed');
}
run();
+132
View File
@@ -0,0 +1,132 @@
const assert = require('assert');
const GenealogyApi = require('../public/js/api-client.js');
function createStore(initial) {
const values = Object.assign({}, initial);
return {
getItem(key) {
return Object.prototype.hasOwnProperty.call(values, key) ? values[key] : null;
},
setItem(key, value) {
values[key] = String(value);
},
removeItem(key) {
delete values[key];
},
values
};
}
async function run() {
const calls = [];
const store = createStore({
'token-key': 'abc-token'
});
assert.strictEqual(GenealogyApi.DEFAULT_BASE_URL, 'http://test-genealogy-api.ddxcjp.cn');
const client = GenealogyApi.createClient({
baseUrl: 'https://test-genealogy-api.ddxcjp.cn',
clientId: 'client-x',
tenantId: 'tenant-x',
tokenKey: 'token-key',
tokenHeaderName: 'Authorization',
tokenStore: store,
fetchImpl: async (url, options) => {
calls.push({ url, options });
return {
ok: true,
status: 200,
json: async () => ({
code: 200,
msg: '操作成功',
data: { token: 'server-token', url }
})
};
}
});
await client.login({ phone: '13800000000', password: 'md5' });
assert.strictEqual(calls[0].url, 'https://test-genealogy-api.ddxcjp.cn/genealogy/pc/auth/login');
assert.strictEqual(calls[0].options.headers.clientid, 'client-x');
assert.strictEqual(calls[0].options.headers.Authorization, undefined);
assert.strictEqual(JSON.parse(calls[0].options.body).tenantId, 'tenant-x');
assert.strictEqual(store.values['token-key'], 'server-token');
await client.getProfile();
assert.strictEqual(calls[1].url, 'https://test-genealogy-api.ddxcjp.cn/genealogy/pc/auth/profile');
assert.strictEqual(calls[1].options.headers.Authorization, 'Bearer server-token');
await client.updateProfile({
nickName: '张三',
provinceCode: '510000',
cityCode: '510100',
districtCode: '510104'
});
assert.strictEqual(calls[2].url, 'https://test-genealogy-api.ddxcjp.cn/genealogy/pc/auth/profile');
assert.strictEqual(calls[2].options.method, 'PUT');
await client.uploadFile('file-content');
assert.strictEqual(calls[3].url, 'https://test-genealogy-api.ddxcjp.cn/genealogy/pc/files/upload');
assert.strictEqual(calls[3].options.headers['Content-Type'], undefined);
await client.initResumableUpload({
fileName: 'cover.jpg',
fileSize: 9532,
fileMd5: 'md5',
chunkSize: 4194304,
totalChunks: 1
});
assert.strictEqual(calls[4].url, 'https://test-genealogy-api.ddxcjp.cn/genealogy/pc/files/resumable/init');
await client.getRegionChildren('51');
assert.strictEqual(calls[5].url, 'https://test-genealogy-api.ddxcjp.cn/genealogy/region/children?parentCode=51');
await client.captchaRequire({
sceneCode: 'WEB_H5_LOGIN',
subject: '13800000000'
});
assert.strictEqual(
calls[6].url,
'https://test-genealogy-api.ddxcjp.cn/captcha/require?tenantId=tenant-x&clientId=client-x&sceneCode=WEB_H5_LOGIN&subject=13800000000'
);
assert.strictEqual(calls[6].options.headers.Authorization, undefined);
await client.loginBySms({
phone: '13800000000',
smsCode: '6666',
validToken: 'sms-ticket'
});
assert.strictEqual(calls[7].url, 'https://test-genealogy-api.ddxcjp.cn/genealogy/pc/auth/login/sms');
assert.strictEqual(calls[7].options.headers.Authorization, undefined);
assert.strictEqual(JSON.parse(calls[7].options.body).grantType, 'sms');
assert.strictEqual(JSON.parse(calls[7].options.body).tenantId, 'tenant-x');
assert.strictEqual(JSON.parse(calls[7].options.body).clientId, 'client-x');
await client.sendSmsCode({
sceneCode: 'WEB_H5_LOGIN',
phone: '13800000000',
validToken: 'captcha-ticket'
});
assert.strictEqual(calls[8].url, 'https://test-genealogy-api.ddxcjp.cn/genealogy/pc/auth/sms/code');
assert.strictEqual(calls[8].options.headers.Authorization, undefined);
assert.strictEqual(JSON.parse(calls[8].options.body).grantType, 'sms');
assert.strictEqual(JSON.parse(calls[8].options.body).sceneCode, 'WEB_H5_LOGIN');
assert.strictEqual(JSON.parse(calls[8].options.body).validToken, 'captcha-ticket');
assert.throws(
() => client.listGenealogies(),
(error) => error.code === 'PC_API_NOT_AVAILABLE'
);
assert.throws(
() => client.publicGenealogies(),
(error) => error.code === 'PC_API_NOT_AVAILABLE'
);
assert.strictEqual(calls.length, 9);
console.log('api-client tests passed');
}
run();
+45
View File
@@ -0,0 +1,45 @@
const assert = require('assert');
const ArticlePages = require('../public/js/article-pages.js');
function run() {
assert.deepStrictEqual(ArticlePages.normalizeList({ rows: [{ articleId: 1 }] }), [{ articleId: 1 }]);
assert.deepStrictEqual(ArticlePages.normalizeList({ records: [{ articleId: 2 }] }), [{ articleId: 2 }]);
assert.deepStrictEqual(ArticlePages.buildArticleBody({
categoryId: ' 900040001 ',
articleTitle: ' 族谱源流 ',
articleSummary: ' 本支源流 ',
coverOssId: '',
articleContent: '<p>正文</p>',
authorName: ' 管理员 ',
sortOrder: '',
status: ''
}), {
categoryId: 900040001,
articleTitle: '族谱源流',
articleSummary: '本支源流',
coverOssId: undefined,
articleContent: '<p>正文</p>',
authorName: '管理员',
sortOrder: 1,
status: '0'
});
assert.strictEqual(
ArticlePages.buildArticleRow({
articleId: 801,
articleTitle: '族谱源流',
categoryName: '谱序',
status: '0',
updateTime: '2026-07-09'
}, '900001001'),
'<a class="module-row article-row" href="article-detail.html?id=801&genealogyId=900001001"><div><h3>族谱源流</h3><p>谱序 · 已发布 · 2026-07-09</p></div><span class="pill">查看</span></a>'
);
assert.strictEqual(ArticlePages.formatStatus('0'), '已发布');
assert.strictEqual(ArticlePages.formatStatus('1'), '草稿');
console.log('article-pages tests passed');
}
run();
+34
View File
@@ -0,0 +1,34 @@
const assert = require('assert');
const fs = require('fs');
const path = require('path');
function readProjectFile(fileName) {
return fs.readFileSync(path.join(__dirname, '..', fileName), 'utf8');
}
function run() {
[
'public/js/auth-pages.js',
'public/js/captcha-pages.js'
].forEach((fileName) => {
const content = readProjectFile(fileName);
assert.strictEqual(
content.includes('root.alert'),
false,
`${fileName} 不应再使用浏览器原生 alert 作为提示`
);
});
const publicCss = readProjectFile('public/css/public.css');
assert.strictEqual(
publicCss.includes('.auth-layer-message'),
true,
'public.css 应提供认证页 layui 提示皮肤'
);
console.log('auth-message tests passed');
}
run();
+35
View File
@@ -0,0 +1,35 @@
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const pages = [
'login.html',
'register.html',
'forgot-password.html'
];
function readPage(fileName) {
return fs.readFileSync(path.join(__dirname, '..', fileName), 'utf8');
}
function run() {
pages.forEach((fileName) => {
const html = readPage(fileName);
const formCaptchaBox = /<form[\s\S]*?data-captcha-box[\s\S]*?<\/form>/i.test(html);
const pageModalBox = /class="[^"]*\bauth-captcha-modal\b[^"]*"[^>]*data-captcha-box/.test(html)
|| /data-captcha-box[^>]*class="[^"]*\bauth-captcha-modal\b[^"]*"/.test(html);
const hasLayuiCss = html.includes('href="public/layui/css/layui.css"');
const layuiScriptIndex = html.indexOf('src="public/layui/layui.js"');
const authScriptIndex = html.indexOf('src="public/js/auth-pages.js"');
assert.strictEqual(formCaptchaBox, false, `${fileName} 不应把 TAC 挂载点放在表单内部`);
assert.strictEqual(pageModalBox, true, `${fileName} 应提供页面级 TAC 弹窗挂载点`);
assert.strictEqual(hasLayuiCss, true, `${fileName} 应加载 layui.css 作为 layer 提示样式依赖`);
assert.notStrictEqual(layuiScriptIndex, -1, `${fileName} 应加载 layui.js 使用 layer.msg`);
assert.ok(layuiScriptIndex < authScriptIndex, `${fileName} 应在 auth-pages.js 之前加载 layui.js`);
});
console.log('auth-page-structure tests passed');
}
run();
+65
View File
@@ -0,0 +1,65 @@
const assert = require('assert');
const AuthPages = require('../public/js/auth-pages.js');
function fakeHash(value) {
return `hashed:${value}`;
}
function run() {
assert.deepStrictEqual(AuthPages.buildLoginBody({
phone: '13800000000',
password: '123456',
validToken: 'ticket'
}, fakeHash), {
phone: '13800000000',
password: 'hashed:123456',
validToken: 'ticket'
});
assert.deepStrictEqual(AuthPages.buildSmsLoginBody({
phone: '13800000000',
smsCode: '6666',
validToken: 'ticket'
}), {
phone: '13800000000',
smsCode: '6666',
validToken: 'ticket'
});
assert.deepStrictEqual(AuthPages.buildRegisterBody({
phone: '13800000000',
password: '123456',
nickName: '汤小明',
validToken: 'ticket'
}, fakeHash), {
phone: '13800000000',
password: 'hashed:123456',
nickName: '汤小明',
validToken: 'ticket'
});
assert.deepStrictEqual(AuthPages.buildPasswordResetBody({
phone: '13800000000',
smsCode: '8888',
newPassword: 'abcdef',
validToken: 'ticket'
}, fakeHash), {
phone: '13800000000',
smsCode: '8888',
newPassword: 'hashed:abcdef',
validToken: 'ticket'
});
assert.strictEqual(AuthPages.getCaptchaScene('login'), 'WEB_H5_LOGIN');
assert.strictEqual(AuthPages.getCaptchaScene('register'), 'WEB_H5_REGISTER');
assert.strictEqual(AuthPages.getCaptchaScene('password-reset'), 'WEB_H5_FORGOT_PASSWORD');
assert.strictEqual(AuthPages.getFormCaptchaScene({
getAttribute(name) {
return name === 'data-captcha-scene' ? 'WEB_H5_REGISTER' : '';
}
}, 'login'), 'WEB_H5_REGISTER');
console.log('auth-pages tests passed');
}
run();
+105
View File
@@ -0,0 +1,105 @@
const assert = require('assert');
const CaptchaPages = require('../public/js/captcha-pages.js');
const fakeApi = {
clientId: 'client-x',
tenantId: 'tenant-x'
};
function run() {
assert.deepStrictEqual(CaptchaPages.buildChallengeBody({
sceneCode: 'WEB_H5_LOGIN',
subject: '13800000000'
}, fakeApi), {
tenantId: 'tenant-x',
clientId: 'client-x',
sceneCode: 'WEB_H5_LOGIN',
subject: '13800000000'
});
const challenge = CaptchaPages.adaptChallengeResponse({
code: 200,
data: {
required: true,
providerCode: 'tianai',
captchaType: 'SLIDER',
challengeId: 'challenge-1',
payload: {
id: 'tianai-1',
backgroundImage: 'bg.png',
templateImage: 'tpl.png',
backgroundImageWidth: 340
}
}
});
assert.strictEqual(challenge.code, 200);
assert.strictEqual(challenge.data.type, 'SLIDER');
assert.strictEqual(challenge.data.id, 'tianai-1');
assert.strictEqual(challenge.data.challengeId, 'challenge-1');
assert.strictEqual(challenge.data.backgroundImage, 'bg.png');
assert.strictEqual(challenge.data.templateImage, 'tpl.png');
assert.deepStrictEqual(CaptchaPages.buildVerifyBody({
id: 'tianai-1',
data: {
trackList: [{ x: 10, y: 2 }]
}
}, {
sceneCode: 'WEB_H5_LOGIN',
subject: '13800000000',
providerCode: 'tianai',
captchaType: 'SLIDER',
challengeId: 'challenge-1'
}, fakeApi), {
tenantId: 'tenant-x',
clientId: 'client-x',
sceneCode: 'WEB_H5_LOGIN',
subject: '13800000000',
challengeId: 'challenge-1',
providerCode: 'tianai',
captchaType: 'SLIDER',
payload: {
id: 'tianai-1',
data: {
trackList: [{ x: 10, y: 2 }]
}
}
});
assert.strictEqual(CaptchaPages.extractValidToken({
code: 200,
data: {
passed: true,
validToken: 'captcha-ticket'
}
}), 'captcha-ticket');
assert.strictEqual(CaptchaPages.shouldRequireCaptcha({
required: false
}), false);
assert.strictEqual(CaptchaPages.shouldRequireCaptcha({
required: true
}), true);
const modalBox = { nodeName: 'modal' };
const formBox = { nodeName: 'form-box' };
const originalDocument = global.document;
global.document = {
querySelector(selector) {
return selector === '.auth-captcha-modal[data-captcha-box]' ? modalBox : null;
}
};
assert.strictEqual(CaptchaPages.getCaptchaBox({
querySelector(selector) {
return selector === '[data-captcha-box]' ? formBox : null;
}
}), modalBox);
global.document = originalDocument;
console.log('captcha-pages tests passed');
}
run();
+62
View File
@@ -0,0 +1,62 @@
const assert = require('assert');
const CeremonyPages = require('../public/js/ceremony-pages.js');
function run() {
assert.deepStrictEqual(CeremonyPages.normalizeList({ rows: [{ ceremonyId: 1 }] }), [{ ceremonyId: 1 }]);
assert.deepStrictEqual(CeremonyPages.normalizeList({ records: [{ ceremonyId: 2 }] }), [{ ceremonyId: 2 }]);
assert.deepStrictEqual(CeremonyPages.buildCeremonyBody({
ceremonyType: ' wedding ',
ceremonyTitle: ' 婚礼邀请 ',
ceremonyDesc: ' 邀请亲友 ',
ceremonyTime: '2026-07-09 10:00:00',
location: ' 祠堂 ',
coverOssId: '',
sortOrder: '',
status: ''
}), {
ceremonyType: 'wedding',
ceremonyTitle: '婚礼邀请',
ceremonyDesc: '邀请亲友',
ceremonyTime: '2026-07-09 10:00:00',
location: '祠堂',
coverOssId: undefined,
sortOrder: 1,
status: '0'
});
assert.deepStrictEqual(CeremonyPages.buildMeritBody({
donorName: ' 汤明 ',
meritType: '',
meritTitle: ' 修谱捐款 ',
meritContent: ' 支持修谱 ',
amount: ' 100 ',
meritTime: '',
sortOrder: '',
status: ''
}), {
donorName: '汤明',
meritType: 'donation',
meritTitle: '修谱捐款',
meritContent: '支持修谱',
amount: 100,
meritTime: undefined,
sortOrder: 1,
status: '0'
});
assert.strictEqual(
CeremonyPages.buildCeremonyRow({
ceremonyId: 601,
ceremonyTitle: '婚礼邀请',
ceremonyType: 'wedding',
ceremonyTime: '2026-07-09',
giftCount: 2
}, '900001001'),
'<button class="module-row ceremony-row" type="button" data-ceremony-id="601"><div><h3>婚礼邀请</h3><p>wedding · 2026-07-09 · 2 条献礼</p></div><span class="pill">管理</span></button>'
);
console.log('ceremony-pages tests passed');
}
run();
+26
View File
@@ -0,0 +1,26 @@
const assert = require('assert');
const configFactory = require('../config.js');
const store = {};
const config = configFactory({
localStorage: {
getItem: (key) => Object.prototype.hasOwnProperty.call(store, key) ? store[key] : null,
setItem: (key, value) => {
store[key] = String(value);
}
}
});
assert.strictEqual(config.getApiBaseUrl(), 'http://test-genealogy-api.ddxcjp.cn');
assert.strictEqual(config.getClientId(), 'ced7e5f0498645c6ec642dcf450b036f');
assert.strictEqual(config.getTenantId(), '000000');
assert.strictEqual(config.getTokenKey(), 'genealogy_auth_token');
assert.strictEqual(config.getConfig().tokenHeaderName, 'Authorization');
store.genealogy_api_base_url = 'https://custom.example.test';
assert.strictEqual(config.getApiBaseUrl(), 'https://custom.example.test');
store.genealogy_api_base_url = 'https://test-genealogy-api.ddxcjp.cn';
assert.strictEqual(config.getApiBaseUrl(), 'http://test-genealogy-api.ddxcjp.cn');
console.log('config tests passed');
+45
View File
@@ -0,0 +1,45 @@
const assert = require('assert');
const FeedPages = require('../public/js/feed-pages.js');
function run() {
assert.deepStrictEqual(FeedPages.normalizeList({ rows: [{ feedId: 1 }] }), [{ feedId: 1 }]);
assert.deepStrictEqual(FeedPages.normalizeList({ records: [{ feedId: 2 }] }), [{ feedId: 2 }]);
assert.deepStrictEqual(FeedPages.buildFeedBody({
content: ' 家族聚会 ',
mediaOssIds: ' 1,2 ',
visibility: '',
status: ''
}), {
content: '家族聚会',
mediaOssIds: '1,2',
visibility: '2',
status: '0'
});
assert.deepStrictEqual(FeedPages.buildCommentBody({
parentCommentId: '',
replyUserId: ' 9001 ',
content: ' 写得好 '
}), {
parentCommentId: undefined,
replyUserId: 9001,
content: '写得好'
});
assert.strictEqual(
FeedPages.buildFeedRow({
feedId: 901,
content: '家族聚会',
authorName: '汤明',
likeCount: 3,
commentCount: 2,
createTime: '2026-07-09'
}),
'<div class="module-row feed-row" data-feed-id="901"><div><h3>汤明</h3><p>家族聚会</p><small>2026-07-09 · 3 赞 · 2 评论</small></div><div class="row-actions"><button class="pill" type="button" data-feed-like="901">点赞</button><button class="pill" type="button" data-feed-comment="901">评论</button></div></div>'
);
console.log('feed-pages tests passed');
}
run();
+33
View File
@@ -0,0 +1,33 @@
const assert = require('assert');
const FeedbackPages = require('../public/js/feedback-pages.js');
function run() {
assert.deepStrictEqual(FeedbackPages.buildFeedbackBody({
feedbackType: 'bug',
feedbackTitle: '登录异常',
feedbackContent: '页面打不开',
contactInfo: '13800000000'
}), {
feedbackType: 'bug',
feedbackContent: '【登录异常】页面打不开',
contactInfo: '13800000000'
});
assert.deepStrictEqual(FeedbackPages.normalizeList({
rows: [{ id: 1 }]
}), [{ id: 1 }]);
assert.strictEqual(
FeedbackPages.buildFeedbackRow({
feedbackType: 'bug',
feedbackContent: '页面打不开',
status: '已受理',
createTime: '2026-07-09'
}),
'<div class="module-row"><div><h3>bug</h3><p>已受理 · 2026-07-09 · 页面打不开</p></div><span class="pill">查看</span></div>'
);
console.log('feedback-pages tests passed');
}
run();
+70
View File
@@ -0,0 +1,70 @@
const assert = require('assert');
const GenealogyPages = require('../public/js/genealogy-pages.js');
function run() {
assert.deepStrictEqual(GenealogyPages.buildGenealogyBody({
genealogyName: '四川武胜汤氏族谱',
surname: '汤',
regionCode: '',
addressDetail: '四川武胜',
intro: '家族简介'
}), {
genealogyName: '四川武胜汤氏族谱',
surname: '汤',
regionCode: '',
addressDetail: '四川武胜',
intro: '家族简介',
visibility: '1',
joinMode: '1'
});
assert.strictEqual(GenealogyPages.buildGenealogyBody({
genealogyName: '成都陈氏族谱',
surname: '陈',
regionCode: '510104'
}).regionCode, '510104');
assert.strictEqual(GenealogyPages.pick({
genealogyName: '汤氏族谱',
name: '备用名称'
}, ['genealogyName', 'name'], '未命名'), '汤氏族谱');
assert.deepStrictEqual(GenealogyPages.normalizeList({ rows: [{ id: 1 }] }), [{ id: 1 }]);
assert.deepStrictEqual(GenealogyPages.normalizeList([{ id: 2 }]), [{ id: 2 }]);
assert.strictEqual(GenealogyPages.getQueryParam('?id=900001001&from=plaza', 'id'), '900001001');
assert.strictEqual(GenealogyPages.getQueryParam('?genealogyId=42', 'id'), '');
assert.deepStrictEqual(GenealogyPages.buildJoinApplyBody({
applicantName: ' 汤小明 ',
phone: ' 13800000000 ',
relationDesc: ' 本族成员 ',
applyReason: ''
}), {
applicantName: '汤小明',
phone: '13800000000',
relationDesc: '本族成员',
applyReason: '申请加入家谱',
inviterUserId: undefined
});
assert.strictEqual(
GenealogyPages.buildFamilyHeroTitle({
genealogyName: '汤氏族谱'
}),
'汤氏族谱'
);
assert.strictEqual(GenealogyPages.formatJoinMode('0'), '关闭加入');
assert.strictEqual(GenealogyPages.formatJoinMode('1'), '审核加入');
assert.strictEqual(GenealogyPages.formatJoinMode('2'), '邀请码加入');
assert.strictEqual(
GenealogyPages.buildMyGenealogyRow({
genealogyId: 9,
genealogyName: '汤氏族谱',
roleName: '创建者',
memberCount: 18
}),
'<a class="module-row" href="profile-family-home.html?id=9"><div><h3>汤氏族谱</h3><p>创建者 · 共修入 18 人</p></div><span class="pill">进入主页</span></a>'
);
console.log('genealogy-pages tests passed');
}
run();
+52
View File
@@ -0,0 +1,52 @@
const assert = require('assert');
const GenerationPages = require('../public/js/generation-pages.js');
function run() {
assert.deepStrictEqual(GenerationPages.normalizeList({ rows: [{ id: 1 }] }), [{ id: 1 }]);
assert.deepStrictEqual(GenerationPages.normalizeList({ records: [{ id: 2 }] }), [{ id: 2 }]);
assert.deepStrictEqual(GenerationPages.buildGenerationBody({
generationNo: ' 5 ',
generationText: ' 忠 ',
description: ' 第五代 ',
sortOrder: '',
status: ''
}), {
generationNo: 5,
generationText: '忠',
description: '第五代',
sortOrder: 5,
status: '0'
});
assert.strictEqual(
GenerationPages.buildGenerationRow({
poemId: 77,
generationNo: 5,
generationText: '忠',
memberCount: 8,
description: '第五代'
}, '900001001'),
'<div class="module-row generation-row" data-generation-id="77"><div><h3>第 5 代:忠</h3><p>8 人 · 第五代</p></div><button class="pill" type="button" data-generation-edit="77" data-generation-no="5" data-generation-text="忠" data-genealogy-id="900001001">编辑</button></div>'
);
assert.deepStrictEqual(GenerationPages.buildGenerationBatchBody({
content: ' 德承家亦 ',
stopMissingOldGeneration: 'on'
}), {
content: '德承家亦',
stopMissingOldGeneration: true
});
assert.deepStrictEqual(GenerationPages.buildGenerationBatchBody({
content: '',
stopMissingOldGeneration: ''
}), {
content: '',
stopMissingOldGeneration: false
});
console.log('generation-pages tests passed');
}
run();
+43
View File
@@ -0,0 +1,43 @@
const assert = require('assert');
const GrowthPages = require('../public/js/growth-pages.js');
function run() {
assert.deepStrictEqual(GrowthPages.normalizeList({ rows: [{ recordId: 1 }] }), [{ recordId: 1 }]);
assert.deepStrictEqual(GrowthPages.normalizeList({ records: [{ recordId: 2 }] }), [{ recordId: 2 }]);
assert.deepStrictEqual(GrowthPages.buildGrowthBody({
lineagePersonId: ' 900020001 ',
recordType: '',
recordTitle: ' 出生记录 ',
recordContent: ' 成长内容 ',
recordDate: '2026-07-09',
remindTime: '',
mediaOssIds: ' 2060001 ',
sortOrder: '',
status: ''
}), {
lineagePersonId: 900020001,
recordType: 'growth',
recordTitle: '出生记录',
recordContent: '成长内容',
recordDate: '2026-07-09',
remindTime: undefined,
mediaOssIds: '2060001',
sortOrder: 1,
status: '0'
});
assert.strictEqual(
GrowthPages.buildGrowthRow({
recordId: 701,
recordTitle: '出生记录',
recordType: 'birth',
recordDate: '2026-07-09'
}, '900001001'),
'<a class="module-row growth-row" href="profile-growth-edit.html?recordId=701&genealogyId=900001001"><div><h3>出生记录</h3><p>birth · 2026-07-09</p></div><span class="pill">编辑</span></a>'
);
console.log('growth-pages tests passed');
}
run();
+29
View File
@@ -0,0 +1,29 @@
const assert = require('assert');
const HelpPages = require('../public/js/help-pages.js');
function run() {
assert.deepStrictEqual(HelpPages.normalizeList({ rows: [{ id: 1 }] }), [{ id: 1 }]);
assert.deepStrictEqual(HelpPages.normalizeList({ records: [{ id: 2 }] }), [{ id: 2 }]);
assert.strictEqual(
HelpPages.buildHelpItem({
helpId: 88,
title: '如何创建家谱?',
summary: '填写名称、姓氏、地区即可创建。'
}, 0),
'<details open class="tilt-card" data-help-id="88" data-help-loaded="false"><summary>如何创建家谱?</summary><p>填写名称、姓氏、地区即可创建。</p></details>'
);
assert.strictEqual(
HelpPages.buildHelpItem({
id: 89,
articleTitle: '资料填错了怎么办?',
content: '<b>可以编辑成员资料</b>'
}, 1),
'<details class="tilt-card" data-help-id="89" data-help-loaded="true"><summary>资料填错了怎么办?</summary><p>&lt;b&gt;可以编辑成员资料&lt;/b&gt;</p></details>'
);
console.log('help-pages tests passed');
}
run();
+44
View File
@@ -0,0 +1,44 @@
const assert = require('assert');
const JoinApplyPages = require('../public/js/join-apply-pages.js');
function run() {
assert.deepStrictEqual(JoinApplyPages.normalizeList({ rows: [{ id: 1 }] }), [{ id: 1 }]);
assert.deepStrictEqual(JoinApplyPages.normalizeList({ records: [{ id: 2 }] }), [{ id: 2 }]);
assert.strictEqual(JoinApplyPages.formatApplyStatus('0'), '待审核');
assert.strictEqual(JoinApplyPages.formatApplyStatus('1'), '已通过');
assert.strictEqual(JoinApplyPages.formatApplyStatus('2'), '已拒绝');
assert.deepStrictEqual(JoinApplyPages.buildAuditBody('approve', ''), {
status: '1',
auditRemark: '信息核验通过'
});
assert.deepStrictEqual(JoinApplyPages.buildAuditBody('reject', '资料不完整'), {
status: '2',
auditRemark: '资料不完整'
});
assert.strictEqual(
JoinApplyPages.buildPendingRow({
applyId: 66,
applicantName: '汤小明',
relationDesc: '本族成员',
createTime: '2026-07-09'
}, '900001001'),
'<div class="module-row join-apply-row" data-join-apply-id="66"><div><h3>汤小明申请加入家谱</h3><p>2026-07-09 · 本族成员</p></div><div class="bottom-actions"><button class="btn primary" type="button" data-join-audit="approve" data-genealogy-id="900001001" data-apply-id="66">同意</button><button class="btn ghost" type="button" data-join-audit="reject" data-genealogy-id="900001001" data-apply-id="66">拒绝</button></div></div>'
);
assert.strictEqual(
JoinApplyPages.buildMineRow({
applyId: 67,
genealogyName: '汤氏族谱',
status: '0',
relationDesc: '本族成员'
}),
'<div class="module-row join-apply-row" data-join-apply-id="67"><div><h3>汤氏族谱</h3><p>待审核 · 本族成员</p></div><button class="btn ghost" type="button" data-join-cancel="67">撤销申请</button></div>'
);
console.log('join-apply-pages tests passed');
}
run();
+77
View File
@@ -0,0 +1,77 @@
const assert = require('assert');
const LineagePages = require('../public/js/lineage-pages.js');
function run() {
assert.deepStrictEqual(LineagePages.normalizeList({ rows: [{ personId: 1 }] }), [{ personId: 1 }]);
assert.deepStrictEqual(LineagePages.normalizeList({ records: [{ personId: 2 }] }), [{ personId: 2 }]);
assert.deepStrictEqual(LineagePages.buildLineagePersonBody({
appUserId: '',
personNo: ' P001 ',
personName: ' 汤明 ',
aliasName: ' 明公 ',
sex: '0',
generationNo: ' 3 ',
generationName: '忠',
birthDate: '1990-01-02',
deathDate: '',
introduction: ' 三世成员 '
}), {
appUserId: undefined,
personNo: 'P001',
personName: '汤明',
aliasName: '明公',
sex: '0',
generationNo: 3,
generationName: '忠',
avatarOssId: undefined,
birthDate: '1990-01-02',
deathDate: undefined,
introduction: '三世成员'
});
assert.strictEqual(LineagePages.formatSex('0'), '男');
assert.strictEqual(LineagePages.formatSex('1'), '女');
assert.strictEqual(LineagePages.formatSex('9'), '未知');
assert.strictEqual(
LineagePages.buildPersonRow({
personId: 7001,
personName: '汤明',
generationNo: 3,
generationName: '忠',
sex: '0',
boundAccount: true
}),
'<button class="module-row lineage-row" type="button" data-lineage-person="7001"><div><h3>汤明</h3><p>第 3 世 · 忠字辈 · 男 · 已绑定账号</p></div><span class="pill">查看资料</span></button>'
);
assert.strictEqual(
LineagePages.buildTreeNode({
personId: 7001,
personName: '汤明',
generationNo: 3,
children: [
{
personId: 7002,
personName: '汤小明',
generationNo: 4
}
]
}),
'<li><button type="button" class="lineage-node" data-lineage-person="7001"><strong>汤明</strong><span>第 3 世</span></button><ul><li><button type="button" class="lineage-node" data-lineage-person="7002"><strong>汤小明</strong><span>第 4 世</span></button></li></ul></li>'
);
assert.strictEqual(
LineagePages.buildHomeSummary({
genealogyNo: 'JP001',
hallName: '敦本堂',
memberCount: 18
}, 3),
'JP001 · 堂号 敦本堂 · 共 18 人 · 3 个世系根节点'
);
console.log('lineage-pages tests passed');
}
run();
+49
View File
@@ -0,0 +1,49 @@
const assert = require('assert');
const MemberAdminPages = require('../public/js/member-admin-pages.js');
function run() {
assert.deepStrictEqual(MemberAdminPages.normalizeList({ rows: [{ memberId: 1 }] }), [{ memberId: 1 }]);
assert.deepStrictEqual(MemberAdminPages.normalizeList({ records: [{ memberId: 2 }] }), [{ memberId: 2 }]);
assert.deepStrictEqual(MemberAdminPages.buildMemberUpdateBody({
memberName: ' 汤明 ',
relationName: ' 族亲 ',
roleType: ' admin ',
lineagePersonId: ' 90002 '
}), {
memberName: '汤明',
relationName: '族亲',
roleType: 'admin',
lineagePersonId: 90002
});
assert.strictEqual(
MemberAdminPages.buildOverviewSummary({
genealogyName: '汤氏族谱',
memberCount: 18,
articleCount: 3,
albumCount: 2
}),
'汤氏族谱 · 18 位成员 · 3 篇谱文 · 2 个相册'
);
assert.strictEqual(
MemberAdminPages.buildMemberRow({
memberId: 501,
memberName: '汤明',
relationName: '族亲',
roleType: 'admin',
lineagePersonName: '忠字辈'
}),
'<div class="module-row member-admin-row" data-member-id="501"><div><h3>汤明</h3><p>族亲 · 管理员 · 绑定:忠字辈</p></div><div class="row-actions"><button class="pill" type="button" data-member-edit="501" data-member-name="汤明" data-member-relation="族亲" data-member-role="admin">权限</button><button class="pill is-danger" type="button" data-member-remove="501">移除</button></div></div>'
);
assert.strictEqual(
MemberAdminPages.buildInviteLink('https://example.test/profile-invite.html?id=900001001', '900001001'),
'https://example.test/join-genealogy.html?id=900001001'
);
console.log('member-admin-pages tests passed');
}
run();
+39
View File
@@ -0,0 +1,39 @@
const assert = require('assert');
const MemoPages = require('../public/js/memo-pages.js');
function run() {
assert.deepStrictEqual(MemoPages.normalizeList({ rows: [{ memoId: 1 }] }), [{ memoId: 1 }]);
assert.deepStrictEqual(MemoPages.normalizeList({ records: [{ memoId: 2 }] }), [{ memoId: 2 }]);
assert.deepStrictEqual(MemoPages.buildMemoBody({
memoTitle: ' 修谱事项 ',
memoContent: ' 联系族亲 ',
remindTime: '',
completed: '',
mediaOssIds: ' 2060001 ',
sortOrder: '',
status: ''
}), {
memoTitle: '修谱事项',
memoContent: '联系族亲',
remindTime: undefined,
completed: '0',
mediaOssIds: '2060001',
sortOrder: 1,
status: '0'
});
assert.strictEqual(
MemoPages.buildMemoRow({
memoId: 801,
memoTitle: '修谱事项',
completed: '0',
remindTime: '2026-08-01'
}, '900001001'),
'<a class="module-row memo-row" href="profile-memo-edit.html?memoId=801&genealogyId=900001001"><div><h3>修谱事项</h3><p>未完成 · 2026-08-01</p></div><span class="pill">编辑</span></a>'
);
console.log('memo-pages tests passed');
}
run();
+32
View File
@@ -0,0 +1,32 @@
const assert = require('assert');
const NotificationPages = require('../public/js/notification-pages.js');
function run() {
assert.strictEqual(NotificationPages.formatNotificationStatus({
readStatus: '0'
}), '未读');
assert.strictEqual(NotificationPages.formatNotificationStatus({
readStatus: '1'
}), '已读');
assert.strictEqual(NotificationPages.formatNotificationStatus({
readFlag: true
}), '已读');
assert.strictEqual(
NotificationPages.buildNotificationRow({
notificationId: 77,
title: '族人申请加入汤氏族谱',
content: '需要管理员审核',
createTime: '2026-07-09 12:00:00',
readStatus: '0'
}),
'<div class="module-row notification-row is-unread" data-notification-id="77"><div><h3>族人申请加入汤氏族谱</h3><p>2026-07-09 12:00:00 · 需要管理员审核</p></div><button class="btn ghost" type="button" data-notification-read="77">标记已读</button></div>'
);
assert.deepStrictEqual(NotificationPages.normalizeList({ rows: [{ id: 1 }] }), [{ id: 1 }]);
assert.deepStrictEqual(NotificationPages.normalizeList({ records: [{ id: 2 }] }), [{ id: 2 }]);
console.log('notification-pages tests passed');
}
run();
+67
View File
@@ -0,0 +1,67 @@
const assert = require('assert');
const ProfilePages = require('../public/js/profile-pages.js');
function run() {
assert.strictEqual(ProfilePages.pick({
nickName: '汤小明',
userName: '备用'
}, ['nickName', 'userName'], '未设置'), '汤小明');
assert.strictEqual(ProfilePages.getAvatarText({
nickName: '汤小明'
}), '汤');
assert.deepStrictEqual(ProfilePages.buildProfileView({
nickName: '汤小明',
phone: '13800000000',
sex: '1',
birthday: '2026-07-09',
provinceCode: '510000',
cityCode: '510100',
districtCode: '510104'
}), {
displayName: '汤小明',
avatarText: '汤',
phone: '13800000000',
sex: '1',
birthday: '2026-07-09',
regionText: '510000 / 510100 / 510104'
});
assert.deepStrictEqual(ProfilePages.buildProfileUpdateBody({
nickName: ' 汤小明 ',
avatarOssId: '2060001',
sex: '1',
birthday: '2026-07-09',
provinceCode: '510000',
cityCode: '510100',
districtCode: '510104'
}), {
nickName: '汤小明',
avatarOssId: 2060001,
sex: '1',
birthday: '2026-07-09',
provinceCode: '510000',
cityCode: '510100',
districtCode: '510104'
});
assert.deepStrictEqual(ProfilePages.buildProfileUpdateBody({
nickName: ' ',
avatarOssId: '',
sex: '',
birthday: ''
}), {
nickName: undefined,
avatarOssId: undefined,
sex: undefined,
birthday: undefined,
provinceCode: undefined,
cityCode: undefined,
districtCode: undefined
});
console.log('profile-pages tests passed');
}
run();
+53
View File
@@ -0,0 +1,53 @@
const assert = require('assert');
const PromotionPages = require('../public/js/promotion-pages.js');
function run() {
const promotions = [
{
promotionId: 11,
promotionTitle: '平台公告',
promotionContent: '<p>公开展示规范</p>',
summary: '保护家族资料安全',
coverUrl: 'cover.jpg',
linkUrl: 'download.html',
publishTime: '2026-07-09'
}
];
assert.deepStrictEqual(PromotionPages.normalizeList({ rows: promotions }), promotions);
assert.deepStrictEqual(PromotionPages.normalizeList({ records: promotions }), promotions);
assert.strictEqual(
PromotionPages.buildPromotionCard(promotions[0]),
'<a class="promotion-card" href="download.html" data-promotion-id="11"><img src="cover.jpg" alt="平台公告" /><div><h3>平台公告</h3><p>保护家族资料安全</p><span>2026-07-09</span></div></a>'
);
assert.strictEqual(
PromotionPages.buildDownloadCard({
promotionId: 12,
title: 'Android 安装包',
description: '适用于安卓手机',
linkUrl: 'https://example.test/app.apk'
}),
'<a class="card soft download-card tilt-card" href="https://example.test/app.apk" data-promotion-id="12"><h3>Android 安装包</h3><p>适用于安卓手机</p></a>'
);
assert.deepStrictEqual(
PromotionPages.pickPromotion(promotions, '11'),
promotions[0]
);
assert.deepStrictEqual(
PromotionPages.buildNoticeDetail(promotions[0]),
{
title: '平台公告',
summary: '保护家族资料安全',
content: '<p>公开展示规范</p>',
time: '2026-07-09'
}
);
console.log('promotion-pages tests passed');
}
run();
+50
View File
@@ -0,0 +1,50 @@
const assert = require('assert');
const RegionPages = require('../public/js/region-pages.js');
function run() {
assert.deepStrictEqual(RegionPages.normalizeList({ rows: [{ regionCode: '51' }] }), [{ regionCode: '51' }]);
assert.deepStrictEqual(RegionPages.normalizeList({ records: [{ regionCode: '510100' }] }), [{ regionCode: '510100' }]);
assert.strictEqual(
RegionPages.buildRegionOption({
regionCode: '510000',
regionName: '四川省'
}),
'<option value="510000">四川省</option>'
);
assert.strictEqual(
RegionPages.buildPathText([
{ regionName: '四川省' },
{ regionName: '成都市' },
{ name: '锦江区' }
]),
'四川省 / 成都市 / 锦江区'
);
assert.strictEqual(RegionPages.getFinalRegionCode({
provinceCode: '510000',
cityCode: '510100',
districtCode: '510104'
}), '510104');
assert.strictEqual(RegionPages.getFinalRegionCode({
provinceCode: '510000',
cityCode: '',
districtCode: ''
}), '510000');
assert.deepStrictEqual(RegionPages.buildProfileRegionBody({
provinceCode: '510000',
cityCode: '510100',
districtCode: ''
}), {
provinceCode: '510000',
cityCode: '510100',
districtCode: undefined
});
console.log('region-pages tests passed');
}
run();
+51
View File
@@ -0,0 +1,51 @@
const assert = require('assert');
const SecurityPages = require('../public/js/security-pages.js');
function hash(value) {
return `hashed:${value}`;
}
function run() {
assert.deepStrictEqual(SecurityPages.buildPasswordChangeBody({
oldPassword: '123456',
newPassword: 'abcdef'
}, hash), {
oldPassword: 'hashed:123456',
newPassword: 'hashed:abcdef'
});
assert.deepStrictEqual(SecurityPages.buildPhoneChangeBody({
newPhone: ' 13900000000 ',
smsCode: ' 123456 ',
validToken: ' ticket '
}), {
newPhone: '13900000000',
smsCode: '123456',
validToken: 'ticket'
});
assert.deepStrictEqual(SecurityPages.buildSmsLoginBody({
phone: '13900000000',
smsCode: '123456',
validToken: ''
}), {
phone: '13900000000',
smsCode: '123456',
validToken: undefined
});
assert.deepStrictEqual(SecurityPages.buildDeactivateBody({
password: '123456',
reason: ' 不再使用 '
}, hash), {
password: 'hashed:123456',
reason: '不再使用'
});
assert.strictEqual(SecurityPages.isDangerConfirmed('确认注销'), true);
assert.strictEqual(SecurityPages.isDangerConfirmed('注销'), false);
console.log('security-pages tests passed');
}
run();
+33
View File
@@ -0,0 +1,33 @@
const assert = require('assert');
const UploadPages = require('../public/js/upload-pages.js');
function run() {
assert.deepStrictEqual(UploadPages.normalizeUploadResult({
ossId: 2060001,
fileName: 'cover.jpg',
url: 'https://cdn.example.test/cover.jpg'
}), {
ossId: '2060001',
fileName: 'cover.jpg',
url: 'https://cdn.example.test/cover.jpg'
});
assert.deepStrictEqual(UploadPages.normalizeUploadResult({
id: 2060002,
originalName: 'old.png',
fileUrl: '/old.png'
}), {
ossId: '2060002',
fileName: 'old.png',
url: '/old.png'
});
assert.strictEqual(UploadPages.mergeOssIds('', '2060001', false), '2060001');
assert.strictEqual(UploadPages.mergeOssIds('2060001', '2060002', true), '2060001,2060002');
assert.strictEqual(UploadPages.mergeOssIds('2060001', '2060001', true), '2060001');
assert.strictEqual(UploadPages.buildUploadStatus('cover.jpg', '2060001'), 'cover.jpg 上传完成,OSS ID2060001');
console.log('upload-pages tests passed');
}
run();
+54
View File
@@ -0,0 +1,54 @@
const assert = require('assert');
const StorageUtil = require('../utils/StorageUtil.js');
const FormUtil = require('../utils/FormUtil.js');
const RequestUtil = require('../utils/RequestUtil.js');
assert.strictEqual(FormUtil.trimOrUndefined(' 张三 '), '张三');
assert.strictEqual(FormUtil.trimOrUndefined(' '), undefined);
assert.strictEqual(FormUtil.toNumberOrUndefined('12'), 12);
assert.strictEqual(FormUtil.toNumberOrUndefined(''), undefined);
assert.strictEqual(FormUtil.toBoolean('on'), true);
assert.strictEqual(FormUtil.toBoolean('0'), false);
const store = {};
const storage = {
getItem: (key) => Object.prototype.hasOwnProperty.call(store, key) ? store[key] : null,
setItem: (key, value) => {
store[key] = String(value);
},
removeItem: (key) => {
delete store[key];
}
};
StorageUtil.write(storage, 'token', 'abc');
assert.strictEqual(StorageUtil.read(storage, 'token'), 'abc');
StorageUtil.remove(storage, 'token');
assert.strictEqual(StorageUtil.read(storage, 'token'), null);
const calls = [];
const requester = RequestUtil.createRequester({
baseUrl: 'https://test-genealogy-api.ddxcjp.cn',
clientId: 'pc-client',
tokenHeaderName: 'Authorization',
getToken: () => 'token-x',
fetchImpl: async (url, options) => {
calls.push({ url, options });
return {
ok: true,
status: 200,
json: async () => ({ code: 200, data: { ok: true } })
};
}
});
requester('GET', '/genealogy/pc/auth/profile', {
query: { keyword: '汤 氏' }
}).then((data) => {
assert.deepStrictEqual(data, { ok: true });
assert.strictEqual(calls[0].url, 'https://test-genealogy-api.ddxcjp.cn/genealogy/pc/auth/profile?keyword=%E6%B1%A4+%E6%B0%8F');
assert.strictEqual(calls[0].options.headers.clientid, 'pc-client');
assert.strictEqual(calls[0].options.headers.Authorization, 'Bearer token-x');
console.log('utils tests passed');
});
+52
View File
@@ -0,0 +1,52 @@
const assert = require('assert');
const VipPages = require('../public/js/vip-pages.js');
function run() {
assert.deepStrictEqual(VipPages.normalizeList({ rows: [{ packageId: 1 }] }), [{ packageId: 1 }]);
assert.deepStrictEqual(VipPages.normalizeList({ records: [{ packageId: 2 }] }), [{ packageId: 2 }]);
assert.deepStrictEqual(VipPages.buildVipOrderBody({
packageId: ' 900060001 ',
genealogyId: ' 900001001 ',
payType: 'wechat'
}), {
packageId: 900060001,
genealogyId: 900001001,
payType: 'wechat'
});
assert.deepStrictEqual(VipPages.buildVipOrderBody({
packageId: '900060002',
genealogyId: '',
payType: ''
}), {
packageId: 900060002,
genealogyId: undefined,
payType: undefined
});
assert.strictEqual(
VipPages.buildPackageCard({
packageId: 900060001,
packageName: '年度会员',
price: 199,
durationDays: 365,
description: '适合长期维护家谱'
}),
'<button class="vip-package-card" type="button" data-vip-package-id="900060001"><span class="vip-package-name">年度会员</span><strong>¥199</strong><small>365天</small><p>适合长期维护家谱</p></button>'
);
assert.strictEqual(
VipPages.buildOrderRow({
orderNo: 'VIP202607090001',
packageName: '年度会员',
payType: 'wechat',
orderStatus: 'created'
}),
'<div class="module-row vip-order-row"><div><h3>年度会员</h3><p>VIP202607090001 · 微信支付 · created</p></div><span class="pill">订单</span></div>'
);
console.log('vip-pages tests passed');
}
run();