Files
NapCatQQ/packages/napcat-test/qzoneCommentsData.test.ts
T
TanTan f954252a34
Build NapCat Artifacts / Type Check & Test (push) Waiting to run
Release Tag / validate-version (push) Waiting to run
NapCatQQ source snapshot
2026-11-13 09:01:47 +08:00

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);
});
});