320 lines
12 KiB
JavaScript
320 lines
12 KiB
JavaScript
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const test = require('node:test');
|
|
|
|
const FeedPages = require('../public/js/feed-pages.js');
|
|
const rootDir = path.resolve(__dirname, '..');
|
|
|
|
function read(file) {
|
|
return fs.readFileSync(path.join(rootDir, file), 'utf8');
|
|
}
|
|
|
|
test('动态表单只构造最新文档定义的 FamilyFeedBody 字段', () => {
|
|
assert.deepEqual(
|
|
FeedPages.buildFeedBody({
|
|
feedContent: '今天上传一张老照片。',
|
|
mediaOssIds: '101,102',
|
|
status: '0',
|
|
visibility: '2'
|
|
}),
|
|
{
|
|
feedType: 'text',
|
|
feedContent: '今天上传一张老照片。',
|
|
mediaOssIds: '101,102',
|
|
status: '0'
|
|
}
|
|
);
|
|
});
|
|
|
|
test('动态页只从 URL 读取真实家谱和动态编号', () => {
|
|
assert.equal(FeedPages.getCurrentGenealogyId('?genealogyId=900001001'), '900001001');
|
|
assert.equal(FeedPages.getCurrentGenealogyId(''), '');
|
|
assert.equal(FeedPages.getCurrentFeedId('?feedId=7001'), '7001');
|
|
});
|
|
|
|
test('动态列表要求服务端返回 feedId 和 feedContent', () => {
|
|
assert.deepEqual(FeedPages.normalizeFeed({ feedId: 7001, feedContent: '家谱动态' }), {
|
|
feedId: '7001',
|
|
feedContent: '家谱动态'
|
|
});
|
|
assert.equal(FeedPages.normalizeFeed({ feedId: 7001, content: '旧字段' }), null);
|
|
});
|
|
|
|
test('评论请求只提交 Apifox 定义的 commentContent 和父评论 ID', () => {
|
|
assert.deepEqual(FeedPages.buildCommentBody({
|
|
commentContent: '收到',
|
|
parentCommentId: '2060000000000000000',
|
|
replyUserId: '2060000000000000001'
|
|
}), {
|
|
commentContent: '收到',
|
|
parentCommentId: '2060000000000000000'
|
|
});
|
|
});
|
|
|
|
test('评论响应使用 commentContent,并保留已删除评论的占位状态', () => {
|
|
assert.deepEqual(FeedPages.normalizeComment({
|
|
commentId: '2060000000000000000',
|
|
commentContent: '收到',
|
|
appUserNickName: '小李',
|
|
replyCount: 2,
|
|
userDeleted: '0'
|
|
}), {
|
|
commentId: '2060000000000000000',
|
|
commentContent: '收到',
|
|
appUserNickName: '小李',
|
|
replyCount: 2,
|
|
userDeleted: false
|
|
});
|
|
assert.deepEqual(FeedPages.normalizeComment({
|
|
commentId: '2060000000000000001',
|
|
commentContent: null,
|
|
userDeleted: '1'
|
|
}), {
|
|
commentId: '2060000000000000001',
|
|
commentContent: '该评论已删除',
|
|
appUserNickName: undefined,
|
|
replyCount: undefined,
|
|
userDeleted: true
|
|
});
|
|
});
|
|
|
|
test('动态响应保留页面闭环所需的完整 FamilyFeedView 字段和字符串 ID', () => {
|
|
assert.deepEqual(FeedPages.normalizeFeed({
|
|
feedId: 2060000000000000000n,
|
|
genealogyId: '2061000000000000000',
|
|
genealogyNo: 'G20260728001',
|
|
genealogyName: '叶氏家谱',
|
|
publisherUserId: '2062000000000000000',
|
|
publisherNickName: '叶子',
|
|
publisherStatus: '0',
|
|
feedType: 'text',
|
|
feedContent: '今日修谱。',
|
|
likedByMe: true,
|
|
likeCount: 8,
|
|
commentCount: 3,
|
|
pinned: '1',
|
|
pinnedTime: '2026-07-28 10:00:00',
|
|
sortOrder: 2,
|
|
status: '0',
|
|
remark: '族长发布',
|
|
createTime: '2026-07-28 09:00:00',
|
|
updateTime: '2026-07-28 09:30:00'
|
|
}), {
|
|
feedId: '2060000000000000000',
|
|
genealogyId: '2061000000000000000',
|
|
genealogyNo: 'G20260728001',
|
|
genealogyName: '叶氏家谱',
|
|
publisherUserId: '2062000000000000000',
|
|
publisherNickName: '叶子',
|
|
publisherStatus: '0',
|
|
feedType: 'text',
|
|
feedContent: '今日修谱。',
|
|
likedByMe: true,
|
|
likeCount: 8,
|
|
commentCount: 3,
|
|
pinned: '1',
|
|
pinnedTime: '2026-07-28 10:00:00',
|
|
sortOrder: 2,
|
|
status: '0',
|
|
remark: '族长发布',
|
|
createTime: '2026-07-28 09:00:00',
|
|
updateTime: '2026-07-28 09:30:00'
|
|
});
|
|
});
|
|
|
|
test('评论响应保留回复、归属、层级和时间字段', () => {
|
|
assert.deepEqual(FeedPages.normalizeComment({
|
|
commentId: '2060000000000000001',
|
|
genealogyId: '2061000000000000000',
|
|
feedId: '2060000000000000000',
|
|
parentCommentId: '2060000000000000002',
|
|
appUserId: '2062000000000000000',
|
|
parentAppUserId: '2062000000000000003',
|
|
appUserNickName: '叶子',
|
|
appUserAvatar: '401',
|
|
parentAppUserNickName: '小叶',
|
|
commentContent: '收到',
|
|
replyCount: 2,
|
|
commentLevel: 'reply',
|
|
userDeleted: '0',
|
|
status: '0',
|
|
createTime: '2026-07-28 10:30:00'
|
|
}), {
|
|
commentId: '2060000000000000001',
|
|
genealogyId: '2061000000000000000',
|
|
feedId: '2060000000000000000',
|
|
parentCommentId: '2060000000000000002',
|
|
appUserId: '2062000000000000000',
|
|
parentAppUserId: '2062000000000000003',
|
|
appUserNickName: '叶子',
|
|
appUserAvatar: '401',
|
|
parentAppUserNickName: '小叶',
|
|
commentContent: '收到',
|
|
replyCount: 2,
|
|
commentLevel: 'reply',
|
|
userDeleted: false,
|
|
status: '0',
|
|
createTime: '2026-07-28 10:30:00'
|
|
});
|
|
});
|
|
|
|
test('动态、评论和回复分页只接受正整数页码并使用固定页面大小', () => {
|
|
assert.deepEqual(FeedPages.buildPageQuery('3'), { pageNum: 3, pageSize: 20 });
|
|
assert.deepEqual(FeedPages.buildPageQuery('-1'), { pageNum: 1, pageSize: 20 });
|
|
assert.deepEqual(FeedPages.buildPageQuery('1.5'), { pageNum: 1, pageSize: 20 });
|
|
});
|
|
|
|
test('动态列表、详情和编辑深链始终同时携带真实家谱和动态 ID', () => {
|
|
assert.equal(
|
|
FeedPages.buildFeedUrl('detail', '2061000000000000000', '2060000000000000000'),
|
|
'profile-feed-detail.html?genealogyId=2061000000000000000&feedId=2060000000000000000'
|
|
);
|
|
assert.equal(
|
|
FeedPages.buildFeedUrl('edit', '2061000000000000000', '2060000000000000000'),
|
|
'profile-feed-edit.html?genealogyId=2061000000000000000&feedId=2060000000000000000'
|
|
);
|
|
assert.equal(
|
|
FeedPages.buildFeedUrl('list', '2061000000000000000'),
|
|
'profile-feed.html?genealogyId=2061000000000000000'
|
|
);
|
|
});
|
|
|
|
test('正常动态保存后进入详情,停用动态保存后返回可读取的列表', () => {
|
|
assert.equal(
|
|
FeedPages.buildPostSaveUrl('2061000000000000000', '2060000000000000000', '0'),
|
|
'profile-feed-detail.html?genealogyId=2061000000000000000&feedId=2060000000000000000'
|
|
);
|
|
assert.equal(
|
|
FeedPages.buildPostSaveUrl('2061000000000000000', '2060000000000000000', '1'),
|
|
'profile-feed.html?genealogyId=2061000000000000000'
|
|
);
|
|
});
|
|
|
|
test('动态表单省略未填写的可选字段并拒绝契约外状态', () => {
|
|
assert.deepEqual(FeedPages.buildFeedBody({
|
|
feedContent: '仅发布文字'
|
|
}), {
|
|
feedType: 'text',
|
|
feedContent: '仅发布文字'
|
|
});
|
|
assert.throws(
|
|
() => FeedPages.buildFeedBody({ feedContent: '错误状态', status: 'draft' }),
|
|
/状态只能是 0 或 1/
|
|
);
|
|
});
|
|
|
|
test('同一动态动作在请求完成前只执行一次', async () => {
|
|
let resolveAction;
|
|
let calls = 0;
|
|
const action = new Promise((resolve) => {
|
|
resolveAction = resolve;
|
|
});
|
|
|
|
const first = FeedPages.withActionLock('like:2060', async () => {
|
|
calls += 1;
|
|
await action;
|
|
return 'ok';
|
|
});
|
|
const second = FeedPages.withActionLock('like:2060', async () => {
|
|
calls += 1;
|
|
});
|
|
|
|
assert.equal(first, second);
|
|
assert.equal(calls, 1);
|
|
resolveAction();
|
|
assert.equal(await first, 'ok');
|
|
});
|
|
|
|
test('家谱能力和当前用户决定编辑、管理与本人删除上下文', () => {
|
|
assert.deepEqual(
|
|
FeedPages.normalizeViewerContext(
|
|
{ canEditContent: true, canManage: false },
|
|
{ userId: 2062000000000000000n }
|
|
),
|
|
{
|
|
userId: '2062000000000000000',
|
|
canEditContent: true,
|
|
canManage: false
|
|
}
|
|
);
|
|
});
|
|
|
|
test('家族圈编辑能力只接受真实布尔权限,并允许管理者维护内容', () => {
|
|
assert.equal(FeedPages.canEditFeeds({ canEditContent: true }), true);
|
|
assert.equal(FeedPages.canEditFeeds({ canManage: true }), true);
|
|
assert.equal(FeedPages.canEditFeeds({ canEditContent: 'true', canManage: false }), false);
|
|
assert.equal(FeedPages.canEditFeeds({ canEditContent: false, canManage: 'true' }), false);
|
|
});
|
|
|
|
test('家族圈页面提供真实详情、分页和上传控件,不暴露 OSS ID 或草稿伪语义', () => {
|
|
const listPage = read('profile-feed.html');
|
|
const editPage = read('profile-feed-edit.html');
|
|
const detailPage = read('profile-feed-detail.html');
|
|
const script = read('public/js/feed-pages.js');
|
|
|
|
assert.match(listPage, /data-feed-page-prev/);
|
|
assert.match(listPage, /data-feed-page-next/);
|
|
assert.match(listPage, /data-feed-create-link[^>]+data-genealogy-context-link[^>]+hidden|data-feed-create-link[^>]+hidden[^>]+data-genealogy-context-link/);
|
|
assert.match(detailPage, /data-feed-detail-page/);
|
|
assert.match(detailPage, /data-feed-detail/);
|
|
assert.match(detailPage, /data-feed-detail-comments/);
|
|
assert.match(detailPage, /data-feed-comment-form novalidate/);
|
|
assert.match(detailPage, /name="commentContent"[^>]+required/);
|
|
assert.match(detailPage, /data-feed-comment-form-status/);
|
|
assert.match(detailPage, /data-feed-list-link[^>]+data-genealogy-context-link|data-genealogy-context-link[^>]+data-feed-list-link/);
|
|
assert.doesNotMatch(editPage, /type="text"[^>]+name="mediaOssIds"|name="mediaOssIds"[^>]+type="text"/);
|
|
assert.match(editPage, /name="mediaOssIds"[^>]+type="hidden"|type="hidden"[^>]+name="mediaOssIds"/);
|
|
assert.match(editPage, /multiple/);
|
|
assert.match(editPage, /data-feed-advanced[^>]+hidden/);
|
|
assert.match(editPage, /name="status"[^>]+disabled|disabled[^>]+name="status"/);
|
|
assert.doesNotMatch(editPage, /草稿|图片 OSS ID/);
|
|
assert.match(script, /feedCommentsPage/);
|
|
assert.match(script, /feedCommentRepliesPage/);
|
|
assert.match(script, /currentProfile/);
|
|
assert.match(script, /genealogyDetail/);
|
|
assert.match(script, /field\.disabled/);
|
|
});
|
|
|
|
test('评论和回复使用详情页表单与统一状态,不再使用浏览器提示框', () => {
|
|
const detailPage = read('profile-feed-detail.html');
|
|
const script = read('public/js/feed-pages.js');
|
|
|
|
assert.match(detailPage, /data-feed-comment-form-target/);
|
|
assert.match(detailPage, /data-feed-comment-cancel/);
|
|
assert.match(detailPage, /data-feed-detail[^>]*>[\s\S]*data-api-state="loading"/);
|
|
assert.match(detailPage, /data-feed-detail-comments[^>]*>[\s\S]*data-api-state="loading"/);
|
|
assert.match(script, /function openFeedCommentForm\(feedId, parentCommentId\)/);
|
|
assert.match(script, /function submitComment\(form\)/);
|
|
assert.match(script, /function renderFeedState\(type, message\)/);
|
|
assert.match(script, /function setReplyState\(parentCommentId, type, message\)/);
|
|
assert.match(script, /setFeedCommentFormStatus\('正在发布评论…', 'loading'\)/);
|
|
assert.match(script, /setFeedCommentFormEnabled\(false\)/);
|
|
assert.match(script, /setFeedContainerState\('\[data-feed-detail-comments\]', 'loading', '正在加载评论…'\)/);
|
|
assert.match(script, /setReplyState\(commentId, 'loading', '正在加载回复…'\)/);
|
|
assert.match(script, /当前账号无权查看回复。/);
|
|
assert.doesNotMatch(script, /api-empty/);
|
|
assert.doesNotMatch(script, /root\.prompt/);
|
|
});
|
|
|
|
test('动态发布表单明确必填项,并以真实家谱能力控制加载、无权限和保存失败反馈', () => {
|
|
const editPage = read('profile-feed-edit.html');
|
|
const script = read('public/js/feed-pages.js');
|
|
|
|
assert.match(editPage, /data-feed-form novalidate/);
|
|
assert.match(editPage, /class="field-required"/);
|
|
assert.match(editPage, /data-feed-form-status/);
|
|
assert.match(script, /root\.ProfileUI && root\.ProfileUI\.setApiState/);
|
|
assert.match(script, /setFeedContainerState\('\[data-feed-list\]', 'loading'/);
|
|
assert.match(script, /setFeedContainerState\('\[data-feed-detail\]', 'loading'/);
|
|
assert.match(script, /'暂无家族动态'/);
|
|
assert.match(script, /!viewerContext\.canEditContent/);
|
|
assert.match(script, /setFeedFormEnabled\(false\)/);
|
|
assert.match(script, /setFeedSubmitPending\(true\)/);
|
|
assert.match(script, /正在保存…/);
|
|
assert.match(script, /function canEditFeeds\(genealogy\)/);
|
|
assert.match(script, /setFeedContentEditingEnabled\(viewerContext\.canEditContent\)/);
|
|
assert.match(script, /if \(!viewerContext\.canEditContent\) \{\s*setFeedFormStatus\('当前账号无权发布或编辑该家谱的动态。', 'forbidden'\)/);
|
|
assert.match(editPage, /data-feed-editor-action hidden disabled/);
|
|
});
|