175 lines
8.2 KiB
TypeScript
175 lines
8.2 KiB
TypeScript
import { describe, expect, test } from 'vitest';
|
|
import { normalizeQzoneComment, normalizeQzoneReply } from '@/napcat-core/data/qzone-comments';
|
|
import { normalizeQzoneFeed, normalizeQzoneMediaImages, normalizeQzoneRichContent, type QzoneComment } from '@/napcat-core/data/qzone-rpc';
|
|
|
|
describe('Qzone comment and reply projections', () => {
|
|
test('keeps unknown comment values null without inventing history or counts', () => {
|
|
const comment = { future_field: { opaque: true } };
|
|
const normalized = normalizeQzoneComment(comment);
|
|
expect(normalized).toEqual({
|
|
comment_id: null,
|
|
user_id: null,
|
|
uid: null,
|
|
nickname: null,
|
|
time: null,
|
|
content: null,
|
|
images: null,
|
|
like_count: null,
|
|
liked: null,
|
|
reply_num: null,
|
|
has_more_replies: null,
|
|
replies: [],
|
|
comment,
|
|
});
|
|
expect(normalized.comment).toBe(comment);
|
|
});
|
|
|
|
test('normalizes the observed parent fields and preserves its complete raw object', () => {
|
|
const reply = {
|
|
id: 'reply-1',
|
|
user: { uin: '10002', uid: 'u_reply', nick: '回复者' },
|
|
target_user: { uin: '10001', uid: 'u_parent', nick: '作者' },
|
|
time: '1700000001',
|
|
content: [{ type: 0, content: '原始回复' }],
|
|
like: { id: 'reply-like-key', count: '3', status: 0 },
|
|
future_reply_field: 'keep',
|
|
};
|
|
const comment: QzoneComment = {
|
|
id: '0',
|
|
user: { uin: '10001', uid: 'u_parent', nick: '作者', portrait: 'https://example.com/avatar' },
|
|
time: 1700000000,
|
|
content: [{ type: 0, content: '第一行\n' }, { type: 1, name: 'Qiao', uin: '10003' }],
|
|
media_items: [{ type: 0, image: { photo_url: [{ spec: 6, url: { url: 'https://example.com/original' } }] } }],
|
|
like: { id: 'parent-like-key', count: 7, status: 1 },
|
|
reply_num: 12,
|
|
has_more_reply: true,
|
|
reply_attach_info: 'opaque-token',
|
|
replys: [reply],
|
|
unknown_field: [0, false, 'keep'],
|
|
};
|
|
const normalized = normalizeQzoneComment(comment);
|
|
expect(normalized).toMatchObject({
|
|
comment_id: '0',
|
|
user_id: '10001',
|
|
uid: 'u_parent',
|
|
nickname: '作者',
|
|
time: 1700000000,
|
|
content: '第一行\n@Qiao',
|
|
images: ['https://example.com/original'],
|
|
like_count: 7,
|
|
liked: true,
|
|
reply_num: 12,
|
|
has_more_replies: true,
|
|
});
|
|
expect(normalized.replies).toHaveLength(1);
|
|
expect(normalized.replies[0]).toMatchObject({
|
|
reply_id: 'reply-1',
|
|
user_id: '10002',
|
|
uid: 'u_reply',
|
|
nickname: '回复者',
|
|
time: 1700000001,
|
|
content: '原始回复',
|
|
like_count: 3,
|
|
liked: false,
|
|
target_user_id: '10001',
|
|
target_uid: 'u_parent',
|
|
target_nickname: '作者',
|
|
});
|
|
expect(normalized.comment).toBe(comment);
|
|
expect(normalized.replies[0]?.reply).toBe(reply);
|
|
expect(comment['reply_attach_info']).toBe('opaque-token');
|
|
expect(comment.user?.['portrait']).toBe('https://example.com/avatar');
|
|
expect(comment['unknown_field']).toEqual([0, false, 'keep']);
|
|
});
|
|
|
|
test('uses time rather than create_time and does not turn display defaults into facts', () => {
|
|
expect(normalizeQzoneComment({ create_time: 1700000000 }).time).toBeNull();
|
|
expect(normalizeQzoneReply({ create_time: 1700000000 }).time).toBeNull();
|
|
expect(normalizeQzoneComment({ time: 0, reply_num: 0, has_more_reply: false, like: { count: 0, status: 0 } }))
|
|
.toMatchObject({ time: 0, reply_num: 0, has_more_replies: false, like_count: 0, liked: false });
|
|
expect(normalizeQzoneReply({ time: '0', like: { count: '0', status: '1' } })).toMatchObject({ time: 0, like_count: 0, liked: null });
|
|
expect(normalizeQzoneComment({ has_more_reply: 1, like: { status: 2 } })).toMatchObject({ has_more_replies: null, liked: null });
|
|
});
|
|
|
|
test('accepts ID zero while never inventing a rounded numeric identity', () => {
|
|
for (const id of [0, '0']) {
|
|
const raw = { id };
|
|
expect(normalizeQzoneComment(raw).comment_id).toBe('0');
|
|
expect(normalizeQzoneReply(raw).reply_id).toBe('0');
|
|
expect(normalizeQzoneComment(raw).comment.id).toBe(id);
|
|
}
|
|
expect(normalizeQzoneReply({ id: '9007199254740993' }).reply_id).toBe('9007199254740993');
|
|
for (const id of [Number.MAX_SAFE_INTEGER + 1, -1, NaN, Infinity, 0.5, '', ' ']) {
|
|
expect(normalizeQzoneComment({ id }).comment_id).toBeNull();
|
|
expect(normalizeQzoneReply({ id }).reply_id).toBeNull();
|
|
}
|
|
});
|
|
|
|
test('preserves UID-only authors and separates author identity from reply target', () => {
|
|
const reply: QzoneComment = {
|
|
id: 0,
|
|
user: { uid: 'uid-only', nick: '' },
|
|
target_user: { uin: '9007199254740993', uid: 'target-uid', nick: '回复目标' },
|
|
content: [{ type: 0, content: '仅原文' }],
|
|
};
|
|
expect(normalizeQzoneReply(reply)).toMatchObject({
|
|
reply_id: '0',
|
|
user_id: null,
|
|
uid: 'uid-only',
|
|
nickname: '',
|
|
content: '仅原文',
|
|
target_user_id: '9007199254740993',
|
|
target_uid: 'target-uid',
|
|
target_nickname: '回复目标',
|
|
});
|
|
expect(normalizeQzoneComment({ user: { uin: '', uid: 'uid-only' } })).toMatchObject({ user_id: null, uid: 'uid-only' });
|
|
expect(normalizeQzoneReply({ user: { uin: '10001', uid: '' } })).toMatchObject({ user_id: '10001', uid: null, target_user_id: null });
|
|
expect(normalizeQzoneReply({ user: { uin: Number.MAX_SAFE_INTEGER + 1 } } as never).user_id).toBeNull();
|
|
});
|
|
|
|
test('does not infer reply totals or continuation from inline replies', () => {
|
|
const comment = { replys: [{ id: 'a' }, { id: 'b' }] };
|
|
expect(normalizeQzoneComment(comment)).toMatchObject({ reply_num: null, has_more_replies: null });
|
|
expect(normalizeQzoneComment(comment).replies).toHaveLength(2);
|
|
expect(normalizeQzoneComment({ replys: [] })).toMatchObject({ replies: [], reply_num: null, has_more_replies: null });
|
|
expect(normalizeQzoneComment({ reply_num: 20, has_more_reply: true })).toMatchObject({ replies: [], reply_num: 20, has_more_replies: true });
|
|
});
|
|
|
|
test('retains unknown reply media while projecting only known images', () => {
|
|
const video = { type: 1, video: { url: 'https://example.com/video', cover: { default_url: { url: 'https://example.com/cover' } } } };
|
|
const unknown = { type: 99, vendor_media: { raw: true } };
|
|
const reply = { media_items: [video, unknown], unknown_reply_field: { keep: true } };
|
|
const result = normalizeQzoneReply(reply);
|
|
expect(result.images).toBeNull();
|
|
expect(result.reply).toBe(reply);
|
|
expect(result.reply['media_items']).toEqual([video, unknown]);
|
|
expect(normalizeQzoneReply({ media_items: [video] }).images).toEqual([]);
|
|
expect(normalizeQzoneReply({ media_items: [] }).images).toEqual([]);
|
|
});
|
|
|
|
test('preserves unsupported text segments and invalid numbers as unknown projections', () => {
|
|
const reply = { content: [{ type: 99, content: 'future segment' }], time: 'unknown', like: { count: -1, status: true } };
|
|
expect(normalizeQzoneReply(reply)).toMatchObject({ content: null, time: null, like_count: null, liked: null });
|
|
expect(normalizeQzoneReply(reply).reply).toBe(reply);
|
|
expect(normalizeQzoneComment({ reply_num: '9007199254740993' }).reply_num).toBeNull();
|
|
});
|
|
|
|
test('does not silently drop malformed inline reply entries', () => {
|
|
expect(() => normalizeQzoneComment({ replys: [null] } as never)).toThrow('内联回复');
|
|
expect(() => normalizeQzoneComment({ replys: 'bad-shape' } as never)).toThrow('内联回复');
|
|
expect(() => normalizeQzoneReply(null as never)).toThrow('回复数据');
|
|
expect(() => normalizeQzoneComment(null as never)).toThrow('评论数据');
|
|
});
|
|
|
|
test('shares rich text and image projections with feed normalization without changing feed behavior', () => {
|
|
const content = [{ type: 0, content: 'same ' }, { type: 1, name: 'Qiao' }];
|
|
const media = [{ type: 0, image: { default_url: { url: '//example.com/image' } } }];
|
|
const feed = normalizeQzoneFeed({ cell_summary: { summary: content }, cell_media: { media_items: media } });
|
|
const comment = normalizeQzoneComment({ content, media_items: media });
|
|
expect(normalizeQzoneRichContent(content)).toBe(feed.content);
|
|
expect(normalizeQzoneMediaImages(media)).toEqual(feed.images);
|
|
expect(comment.content).toBe(feed.content);
|
|
expect(comment.images).toEqual(feed.images);
|
|
});
|
|
});
|