kt-template-admin/test/views/qqbot/account/components/MessagePushTargetPicker.spec.tsx

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',
},
]);
});
});