Files
jiapu/tests/ceremony-pages.test.js
T
2026-08-03 16:49:38 +08:00

157 lines
6.1 KiB
JavaScript

const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const test = require('node:test');
const CeremonyPages = require('../public/js/ceremony-pages.js');
test('我的邀请响应保留完整展示字段并保持业务 ID 为字符串', () => {
assert.deepEqual(CeremonyPages.normalizeInvitation({
invitationId: '2060000000000000001',
genealogyId: '2060000000000000002',
ceremonyId: '2060000000000000003',
inviteeUserId: '2060000000000000004',
inviteStatus: 'PENDING',
inviteVersion: 2,
deliveredTime: '2026-07-29T09:00:00+08:00',
readTime: null,
responseTime: null,
ceremonyTitle: '家族答谢宴',
ceremonyTime: '2026-08-08T18:00:00+08:00',
location: '锦江厅',
locationAddress: '成都市锦江区示例路 8 号',
longitude: 104.0668,
latitude: 30.5728
}), {
invitationId: '2060000000000000001',
genealogyId: '2060000000000000002',
ceremonyId: '2060000000000000003',
inviteeUserId: '2060000000000000004',
inviteStatus: 'PENDING',
inviteVersion: 2,
deliveredTime: '2026-07-29T09:00:00+08:00',
readTime: '',
responseTime: '',
ceremonyTitle: '家族答谢宴',
ceremonyTime: '2026-08-08T18:00:00+08:00',
location: '锦江厅',
locationAddress: '成都市锦江区示例路 8 号',
longitude: 104.0668,
latitude: 30.5728
});
});
test('邀请响应拒绝缺少稳定 ID、非法状态和不安全数字长 ID', () => {
assert.equal(CeremonyPages.normalizeInvitation({
invitationId: '1',
genealogyId: '2',
inviteeUserId: '4',
inviteStatus: 'PENDING'
}), null);
assert.equal(CeremonyPages.normalizeInvitation({
invitationId: '1',
genealogyId: '2',
ceremonyId: '3',
inviteeUserId: '4',
inviteStatus: 'UNKNOWN'
}), null);
assert.equal(CeremonyPages.normalizeInvitation({
invitationId: Number('2060000000000000001'),
genealogyId: '2',
ceremonyId: '3',
inviteeUserId: '4',
inviteStatus: 'PENDING'
}), null);
});
test('YAML 未声明必返的 inviteeUserId 缺失时仍可用活动上下文响应', () => {
const invitation = CeremonyPages.normalizeInvitation({
invitationId: '1',
genealogyId: '2',
ceremonyId: '3',
inviteStatus: 'PENDING'
});
assert.notEqual(invitation, null);
assert.equal(invitation.inviteeUserId, undefined);
assert.equal(CeremonyPages.canRespondToInvitation(invitation), true);
});
test('响应邀请只允许提交 YAML 定义的 inviteStatus 字段', () => {
assert.deepEqual(CeremonyPages.buildInvitationResponseBody({
inviteStatus: 'ACCEPTED',
invitationId: '2060000000000000001',
inviteVersion: 2
}), {
inviteStatus: 'ACCEPTED'
});
assert.deepEqual(CeremonyPages.buildInvitationResponseBody({ inviteStatus: 'DECLINED' }), {
inviteStatus: 'DECLINED'
});
assert.equal(CeremonyPages.validateInvitationResponseBody({ inviteStatus: 'PENDING' }), '邀请只能选择接受或拒绝');
});
test('只有待响应邀请显示接受和拒绝动作', () => {
assert.equal(CeremonyPages.canRespondToInvitation({ inviteStatus: 'PENDING' }), true);
assert.equal(CeremonyPages.canRespondToInvitation({ inviteStatus: 'ACCEPTED' }), false);
assert.equal(CeremonyPages.canRespondToInvitation({ inviteStatus: 'DECLINED' }), false);
assert.equal(CeremonyPages.canRespondToInvitation({ inviteStatus: 'CANCELED' }), false);
});
test('邀请列表渲染转义内容、展示详情但不直接暴露内部 ID 和坐标', () => {
const invitation = CeremonyPages.normalizeInvitation({
invitationId: '2060000000000000001',
genealogyId: '2060000000000000002',
ceremonyId: '2060000000000000003',
inviteeUserId: '2060000000000000004',
inviteStatus: 'PENDING',
inviteVersion: 1,
ceremonyTitle: '<script>alert(1)</script>',
ceremonyTime: '2026-08-08T18:00:00+08:00',
location: '锦江厅',
locationAddress: '成都市锦江区示例路 8 号',
longitude: 104.0668,
latitude: 30.5728
});
const row = CeremonyPages.renderInvitationRow(invitation);
const detail = CeremonyPages.renderInvitationDetail(invitation);
assert.doesNotMatch(row, /<script>/);
assert.match(row, /接受邀请/);
assert.match(row, /拒绝邀请/);
assert.match(detail, /成都市锦江区示例路 8 号/);
assert.match(detail, /打开地图/);
assert.doesNotMatch(detail, /206000000000000000[1-4]/);
assert.doesNotMatch(detail, />104\.0668</);
assert.doesNotMatch(detail, />30\.5728</);
});
test('贺礼邀请页面只接入我的邀请闭环,不提供活动或献礼猜测表单', () => {
const page = fs.readFileSync(path.join(__dirname, '..', 'profile-gift.html'), 'utf8');
assert.doesNotMatch(page, /data-feature-status="pending"/);
assert.match(page, /data-my-invitation-list/);
assert.match(page, /data-my-invitation-detail/);
assert.match(page, /src="public\/js\/ceremony-pages\.js"/);
assert.doesNotMatch(page, /data-ceremony-gift-form/);
assert.doesNotMatch(page, /href="profile-gift-edit\.html"/);
});
test('我的邀请将列表、详情和响应结果写入统一状态组件', () => {
const root = path.join(__dirname, '..');
const page = fs.readFileSync(path.join(root, 'profile-gift.html'), 'utf8');
const source = fs.readFileSync(path.join(root, 'public', 'js', 'ceremony-pages.js'), 'utf8');
assert.match(page, /data-my-invitation-list[^>]*>[\s\S]*data-api-state="loading"/);
assert.match(page, /data-my-invitation-detail[^>]*>[\s\S]*data-api-state="empty"/);
assert.match(page, /data-my-invitation-status/);
assert.match(source, /root\.ProfileUI && root\.ProfileUI\.setApiState/);
assert.match(source, /setInvitationState\('\[data-my-invitation-list\]', 'loading', '正在加载我的邀请…'\)/);
assert.match(source, /setInvitationState\('\[data-my-invitation-list\]', 'empty', '暂无贺礼邀请'\)/);
assert.match(source, /当前账号无权查看邀请。/);
assert.match(source, /正在更新邀请状态…/);
assert.match(source, /当前账号无权响应该邀请。/);
assert.equal(CeremonyPages.isForbidden({ status: 403 }), true);
assert.equal(CeremonyPages.isForbidden({ status: 401 }), false);
});