316 lines
9.2 KiB
TypeScript
316 lines
9.2 KiB
TypeScript
/* @vitest-environment happy-dom */
|
|
|
|
import type { QqbotMessagePushApi } from '#/api/qqbot/message-push';
|
|
|
|
import { flushPromises, mount } from '@vue/test-utils';
|
|
|
|
import MessagePushTargetPicker, {
|
|
isValidMessagePushTargetId,
|
|
} from '@test-source/apps/web-antdv-next/src/views/qqbot/account/components/MessagePushTargetPicker';
|
|
import Select from 'antdv-next/dist/select/index';
|
|
import { describe, expect, it } from 'vitest';
|
|
|
|
/** 创建群聊与私聊候选目标。 */
|
|
function createOptions(): QqbotMessagePushApi.QqbotMessagePushTargetOption[] {
|
|
return [
|
|
{
|
|
label: '帕鲁测试群',
|
|
targetId: '10000000000000001',
|
|
targetType: 'group',
|
|
},
|
|
{
|
|
label: '测试好友',
|
|
targetId: '20000000000000001',
|
|
targetType: 'private',
|
|
},
|
|
];
|
|
}
|
|
|
|
/** 按覆盖参数挂载消息推送目标选择器。 */
|
|
function mountPicker(
|
|
overrides: Partial<{
|
|
available: boolean;
|
|
disabled: boolean;
|
|
loading: boolean;
|
|
options: QqbotMessagePushApi.QqbotMessagePushTargetOption[];
|
|
reasonCode: null | string;
|
|
value: QqbotMessagePushApi.QqbotMessagePublishTargetInput[];
|
|
}> = {},
|
|
) {
|
|
return mount(MessagePushTargetPicker, {
|
|
props: {
|
|
available: true,
|
|
options: createOptions(),
|
|
reasonCode: null,
|
|
value: [],
|
|
...overrides,
|
|
},
|
|
});
|
|
}
|
|
|
|
/** 读取选择器最近一次提交的目标数组。 */
|
|
function latestValue(
|
|
wrapper: ReturnType<typeof mountPicker>,
|
|
): QqbotMessagePushApi.QqbotMessagePublishTargetInput[] {
|
|
const emitted = wrapper.emitted('update:value')?.at(-1)?.[0];
|
|
if (!Array.isArray(emitted)) throw new Error('Expected one target update');
|
|
return emitted as QqbotMessagePushApi.QqbotMessagePublishTargetInput[];
|
|
}
|
|
|
|
describe('message-push target picker', () => {
|
|
it.each([
|
|
['', false],
|
|
['01234', false],
|
|
['1234', false],
|
|
['12345', true],
|
|
['12345678901234567890', true],
|
|
['123456789012345678901', false],
|
|
])('validates the exact manual target matrix for %s', (targetId, valid) => {
|
|
expect(isValidMessagePushTargetId(targetId)).toBe(valid);
|
|
});
|
|
|
|
it('renders exactly two controlled tags Selects with type-scoped options', () => {
|
|
const wrapper = mountPicker();
|
|
const selects = wrapper.findAllComponents(Select);
|
|
|
|
expect(selects).toHaveLength(2);
|
|
expect(
|
|
wrapper.get('.qqbot-message-push-target-picker').attributes('style'),
|
|
).toContain('width: 100%');
|
|
expect(selects[0]?.props()).toMatchObject({
|
|
mode: 'tags',
|
|
value: [],
|
|
});
|
|
expect(selects[1]?.props()).toMatchObject({
|
|
mode: 'tags',
|
|
value: [],
|
|
});
|
|
expect(selects[0]?.props('labelInValue')).not.toBe(true);
|
|
expect(selects[1]?.props('labelInValue')).not.toBe(true);
|
|
expect(selects[0]?.vm.$.vnode.props?.style).toMatchObject({
|
|
width: '100%',
|
|
});
|
|
expect(selects[1]?.vm.$.vnode.props?.style).toMatchObject({
|
|
width: '100%',
|
|
});
|
|
expect(selects[0]?.props('options')).toEqual([
|
|
expect.objectContaining({
|
|
targetId: '10000000000000001',
|
|
value: '10000000000000001',
|
|
}),
|
|
]);
|
|
expect(selects[1]?.props('options')).toEqual([
|
|
expect.objectContaining({
|
|
targetId: '20000000000000001',
|
|
value: '20000000000000001',
|
|
}),
|
|
]);
|
|
});
|
|
|
|
it.each([
|
|
[0, '测试群'],
|
|
[0, '100000000000000'],
|
|
[1, '好友'],
|
|
[1, '200000000000000'],
|
|
])('searches selector %s by known label or string ID', (index, query) => {
|
|
const wrapper = mountPicker();
|
|
const select = wrapper.findAllComponents(Select)[index];
|
|
const option = (select?.props('options') || [])[0] as Record<
|
|
string,
|
|
unknown
|
|
>;
|
|
const filter = select?.props('filterOption') as (
|
|
input: string,
|
|
option: Record<string, unknown>,
|
|
) => boolean;
|
|
|
|
expect(filter(query as string, option)).toBe(true);
|
|
expect(filter('unrelated', option)).toBe(false);
|
|
});
|
|
|
|
it('uses actual controlled Select updates for multiple known/manual string IDs', async () => {
|
|
const wrapper = mountPicker();
|
|
const [group, privateSelect] = wrapper.findAllComponents(Select);
|
|
|
|
group?.vm.$emit('update:value', ['10000000000000001', '30000000000000001']);
|
|
await flushPromises();
|
|
expect(latestValue(wrapper)).toEqual([
|
|
{
|
|
targetId: '10000000000000001',
|
|
targetName: '帕鲁测试群',
|
|
targetType: 'group',
|
|
},
|
|
{ targetId: '30000000000000001', targetType: 'group' },
|
|
]);
|
|
|
|
await wrapper.setProps({ value: latestValue(wrapper) });
|
|
privateSelect?.vm.$emit('update:value', [
|
|
'20000000000000001',
|
|
'40000000000000001',
|
|
]);
|
|
await flushPromises();
|
|
const combined = latestValue(wrapper);
|
|
expect(combined).toEqual([
|
|
{
|
|
targetId: '10000000000000001',
|
|
targetName: '帕鲁测试群',
|
|
targetType: 'group',
|
|
},
|
|
{ targetId: '30000000000000001', targetType: 'group' },
|
|
{
|
|
targetId: '20000000000000001',
|
|
targetName: '测试好友',
|
|
targetType: 'private',
|
|
},
|
|
{ targetId: '40000000000000001', targetType: 'private' },
|
|
]);
|
|
expect(combined.every(({ targetId }) => typeof targetId === 'string')).toBe(
|
|
true,
|
|
);
|
|
});
|
|
|
|
it('rejects invalid/non-string values and remounts only that Select', async () => {
|
|
const wrapper = mountPicker({
|
|
value: [
|
|
{
|
|
targetId: '20000000000000001',
|
|
targetName: '测试好友',
|
|
targetType: 'private',
|
|
},
|
|
],
|
|
});
|
|
const initial = wrapper.findAllComponents(Select);
|
|
const groupKey = initial[0]?.vm.$.vnode.key;
|
|
const privateKey = initial[1]?.vm.$.vnode.key;
|
|
|
|
initial[0]?.vm.$emit('update:value', [
|
|
' 12345 ',
|
|
'01234',
|
|
12_345,
|
|
{ value: '99999' },
|
|
'12345',
|
|
]);
|
|
await flushPromises();
|
|
|
|
expect(latestValue(wrapper)).toEqual([
|
|
{ targetId: '12345', targetType: 'group' },
|
|
{
|
|
targetId: '20000000000000001',
|
|
targetName: '测试好友',
|
|
targetType: 'private',
|
|
},
|
|
]);
|
|
const next = wrapper.findAllComponents(Select);
|
|
expect(next[0]?.vm.$.vnode.key).not.toBe(groupKey);
|
|
expect(next[1]?.vm.$.vnode.key).toBe(privateKey);
|
|
expect(next[0]?.props('value')).toEqual([]);
|
|
});
|
|
|
|
it('shows the exact unavailable reason while manual input remains enabled', async () => {
|
|
const wrapper = mountPicker({
|
|
available: false,
|
|
options: [],
|
|
reasonCode: 'onebot_unavailable',
|
|
});
|
|
const [group, privateSelect] = wrapper.findAllComponents(Select);
|
|
|
|
expect(wrapper.text()).toContain('onebot_unavailable');
|
|
expect(group?.props('disabled')).toBe(false);
|
|
expect(privateSelect?.props('disabled')).toBe(false);
|
|
group?.vm.$emit('update:value', ['12345']);
|
|
await flushPromises();
|
|
expect(latestValue(wrapper)).toEqual([
|
|
{ targetId: '12345', targetType: 'group' },
|
|
]);
|
|
});
|
|
|
|
it('keeps names only for the same type and omits unknown manual names', async () => {
|
|
const equalId = '10000000000000001';
|
|
const wrapper = mountPicker({
|
|
options: [
|
|
{
|
|
label: '群名称',
|
|
targetId: equalId,
|
|
targetType: 'group',
|
|
},
|
|
{
|
|
label: '私聊名称',
|
|
targetId: equalId,
|
|
targetType: 'private',
|
|
},
|
|
],
|
|
});
|
|
const [group, privateSelect] = wrapper.findAllComponents(Select);
|
|
group?.vm.$emit('update:value', [equalId, '30000000000000001']);
|
|
await flushPromises();
|
|
await wrapper.setProps({ value: latestValue(wrapper) });
|
|
privateSelect?.vm.$emit('update:value', [equalId]);
|
|
await flushPromises();
|
|
|
|
expect(latestValue(wrapper)).toEqual([
|
|
{
|
|
targetId: equalId,
|
|
targetName: '群名称',
|
|
targetType: 'group',
|
|
},
|
|
{ targetId: '30000000000000001', targetType: 'group' },
|
|
{
|
|
targetId: equalId,
|
|
targetName: '私聊名称',
|
|
targetType: 'private',
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('preserves an existing same-type API name when candidates are unavailable', async () => {
|
|
const wrapper = mountPicker({
|
|
available: false,
|
|
options: [],
|
|
reasonCode: 'onebot_unavailable',
|
|
value: [
|
|
{
|
|
targetId: '10000000000000001',
|
|
targetName: '历史群名',
|
|
targetType: 'group',
|
|
},
|
|
],
|
|
});
|
|
const group = wrapper.findAllComponents(Select)[0];
|
|
|
|
group?.vm.$emit('update:value', ['10000000000000001', '30000000000000001']);
|
|
await flushPromises();
|
|
expect(latestValue(wrapper)).toEqual([
|
|
{
|
|
targetId: '10000000000000001',
|
|
targetName: '历史群名',
|
|
targetType: 'group',
|
|
},
|
|
{ targetId: '30000000000000001', targetType: 'group' },
|
|
]);
|
|
});
|
|
|
|
it('removing one type leaves the other controlled values untouched', async () => {
|
|
const wrapper = mountPicker({
|
|
value: [
|
|
{ targetId: '10000000000000001', targetType: 'group' },
|
|
{
|
|
targetId: '20000000000000001',
|
|
targetName: '测试好友',
|
|
targetType: 'private',
|
|
},
|
|
],
|
|
});
|
|
const group = wrapper.findAllComponents(Select)[0];
|
|
|
|
group?.vm.$emit('update:value', []);
|
|
await flushPromises();
|
|
expect(latestValue(wrapper)).toEqual([
|
|
{
|
|
targetId: '20000000000000001',
|
|
targetName: '测试好友',
|
|
targetType: 'private',
|
|
},
|
|
]);
|
|
});
|
|
});
|