Integrate the current development branch, preserve the album comment fixes, and align the changes with repository formatting and dependency tooling.
115 lines
5.0 KiB
TypeScript
115 lines
5.0 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest';
|
|
import { atAll, chain, face, fromCQString, message, parseSegments, text, toCQString } from '../src';
|
|
|
|
describe('MessageChain', () => {
|
|
it.each([undefined, true])(
|
|
'preserves animation results through builders and CQ with large=%s',
|
|
(large) => {
|
|
const options = { resultId: '0', ...(large !== undefined ? { large } : {}) };
|
|
const expected = [{ type: 'face', data: { id: '114', ...options } }];
|
|
expect(face(114, options).toSegments()).toEqual(expected);
|
|
expect(chain().face(114, options).toSegments()).toEqual(expected);
|
|
expect(message.face(114, options)).toEqual(expected[0]);
|
|
expect(parseSegments(toCQString(face(114, options)))).toEqual(expected);
|
|
},
|
|
);
|
|
|
|
it.each([false, true])(
|
|
'preserves face animation selection through builders and CQ, large=%s',
|
|
(large) => {
|
|
const expected = [{ type: 'face', data: { id: '451', large } }];
|
|
const built = face(451, { large });
|
|
expect(built.toSegments()).toEqual(expected);
|
|
expect(chain().face(451, { large }).toSegments()).toEqual(expected);
|
|
expect(message.face(451, { large })).toEqual(expected[0]);
|
|
expect(parseSegments(toCQString(built))).toEqual(expected);
|
|
expect(parseSegments(`[CQ:face,id=451,large=${large ? 1 : 0}]`)).toEqual(expected);
|
|
},
|
|
);
|
|
|
|
it('rejects an invalid face animation choice instead of discarding it', () => {
|
|
expect(() => parseSegments('[CQ:face,id=451,large=invalid]')).toThrow('face.large');
|
|
});
|
|
|
|
it('builds fluent segment chains', () => {
|
|
const chain = text('hello').at(10001).br().image('/tmp/a.png');
|
|
|
|
expect(chain.length).toBe(4);
|
|
expect(chain.toSegments()).toEqual([
|
|
{ type: 'text', data: { text: 'hello' } },
|
|
{ type: 'at', data: { qq: '10001' } },
|
|
{ type: 'text', data: { text: '\n' } },
|
|
{ type: 'image', data: { file: '/tmp/a.png' } },
|
|
]);
|
|
});
|
|
|
|
it('keeps the old segment builder API available', () => {
|
|
expect(message.text('hello')).toEqual({ type: 'text', data: { text: 'hello' } });
|
|
expect(atAll().toSegments()).toEqual([{ type: 'at', data: { qq: 'all' } }]);
|
|
});
|
|
|
|
it('allows reply only once in a chain at runtime', () => {
|
|
const chain = text('hello').reply(1);
|
|
expect(() => (chain as any).reply(2)).toThrow(/reply/);
|
|
expect(() => chain.append(message.reply(2))).toThrow(/reply/);
|
|
});
|
|
|
|
it('appends other messages and converts to CQ string', () => {
|
|
const chain = text('hello').append([message.at('all'), message.face(14)]);
|
|
expect(toCQString(chain)).toBe('hello[CQ:at,qq=all][CQ:face,id=14]');
|
|
});
|
|
|
|
it('parses CQ strings into chains', () => {
|
|
const chain = fromCQString('hi[CQ:at,qq=all][CQ:image,file=a,b]');
|
|
expect(chain.toSegments()).toEqual([
|
|
{ type: 'text', data: { text: 'hi' } },
|
|
{ type: 'at', data: { qq: 'all' } },
|
|
{
|
|
type: 'image',
|
|
data: { file: 'a,b', url: undefined, type: undefined, summary: undefined },
|
|
},
|
|
]);
|
|
expect(parseSegments('[CQ:reply,id=7]')).toEqual([{ type: 'reply', data: { id: '7' } }]);
|
|
});
|
|
|
|
it.each([
|
|
message.image('literal,,.png'),
|
|
message.customReply('literal , [CQ:at,qq=all] &[],'),
|
|
message.raw('extension', { value: '[],& &[],' }),
|
|
])('decodes CQ parameters exactly once for $type', (segment) => {
|
|
expect(parseSegments(toCQString(segment))).toEqual([segment]);
|
|
});
|
|
|
|
it('preserves string custom replies and their metadata through CQ conversion', () => {
|
|
const reply = message.customReply('hello[CQ:at,qq=all] & goodbye', {
|
|
id: '-123',
|
|
qq: '10001',
|
|
time: '1700000000',
|
|
seq: '42',
|
|
});
|
|
expect(parseSegments(toCQString(reply))).toEqual([reply]);
|
|
const inline = message.customReply('quoted');
|
|
expect(parseSegments(toCQString(inline))).toEqual([inline]);
|
|
});
|
|
|
|
it.each([[message.text('quoted')], message.text('quoted')].map((content) => ({ content })))(
|
|
'rejects nested custom replies during CQ conversion ($content)',
|
|
({ content }) => {
|
|
expect(() => toCQString(message.customReply(content))).toThrow(/must be sent as JSON/);
|
|
},
|
|
);
|
|
|
|
it('can send itself through a compatible client', async () => {
|
|
const client = {
|
|
sendGroupMessage: vi.fn().mockResolvedValue({ message_id: 1 }),
|
|
sendPrivateMessage: vi.fn().mockResolvedValue({ message_id: 2 }),
|
|
};
|
|
|
|
await text('hi').sendToGroup(client, 123);
|
|
await text('yo').sendToPrivate(client, 456);
|
|
|
|
expect(client.sendGroupMessage).toHaveBeenCalledWith(123, text('hi'), undefined);
|
|
expect(client.sendPrivateMessage).toHaveBeenCalledWith(456, text('yo'), undefined);
|
|
});
|
|
});
|