feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { renderHook, act } from '@testing-library/react-hooks';
|
||||
import { type ShortCutCommand } from '@coze-agent-ide/tool-config';
|
||||
|
||||
import {
|
||||
useLoadMore,
|
||||
getNextActiveItem,
|
||||
getPreviousItem,
|
||||
} from '../../../src/hooks/shortcut-bar/use-load-more';
|
||||
|
||||
describe('useLoadMore', () => {
|
||||
it('should initialize with default values', async () => {
|
||||
const { result } = renderHook(() =>
|
||||
useLoadMore<ShortCutCommand>({
|
||||
getId: item => item.command_id,
|
||||
listRef: { current: null },
|
||||
getMoreListService: async () =>
|
||||
Promise.resolve({ list: [], hasMore: false }),
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.current.activeId).toBe('');
|
||||
expect(result.current.loadingMore).toBe(false);
|
||||
expect(result.current.data).toEqual({ list: [], hasMore: false });
|
||||
expect(result.current.loading).toBe(true);
|
||||
|
||||
await act(() => {});
|
||||
expect(result.current.loading).toBe(false);
|
||||
});
|
||||
|
||||
it('should load more when reaching limit', async () => {
|
||||
const getMoreListService = vi
|
||||
.fn()
|
||||
.mockResolvedValue({ list: [{ id: '2' }], hasMore: false });
|
||||
const { result, waitForNextUpdate } = renderHook(() =>
|
||||
useLoadMore({
|
||||
getId: item => item.id,
|
||||
listRef: { current: null },
|
||||
getMoreListService,
|
||||
defaultList: [{ id: '1' }],
|
||||
}),
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.goNext();
|
||||
});
|
||||
|
||||
await waitForNextUpdate();
|
||||
|
||||
expect(getMoreListService).toHaveBeenCalled();
|
||||
expect(result.current.data.list).toEqual([{ id: '1' }, { id: '2' }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getNextActiveItem', () => {
|
||||
it('should return next item and reach limit flag', () => {
|
||||
const result = getNextActiveItem({
|
||||
curItem: { id: '1' },
|
||||
list: [{ id: '1' }, { id: '2' }, { id: '3' }],
|
||||
getId: item => item.id,
|
||||
});
|
||||
|
||||
expect(result).toEqual({ reachLimit: true, item: { id: '2' } });
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPreviousItem', () => {
|
||||
it('should return previous item and reach limit flag', () => {
|
||||
const result = getPreviousItem({
|
||||
curItem: { id: '2' },
|
||||
list: [{ id: '1' }, { id: '2' }, { id: '3' }],
|
||||
getId: item => item.id,
|
||||
});
|
||||
|
||||
expect(result).toEqual({ reachLimit: false, item: { id: '1' } });
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,318 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ContentType, useSendTextMessage } from '@coze-common/chat-area';
|
||||
import { sendTeaEvent, EVENT_NAMES } from '@coze-arch/bot-tea';
|
||||
import { type ShortCutCommand } from '@coze-agent-ide/tool-config';
|
||||
|
||||
import {
|
||||
useSendTextQueryMessage,
|
||||
useSendUseToolMessage,
|
||||
getTemplateQuery,
|
||||
getImageAndFileList,
|
||||
} from '../../src/hooks/shortcut';
|
||||
|
||||
const sendTextMessageMock = vi.fn();
|
||||
const sendMultimodalMessage = vi.fn();
|
||||
vi.mock('@coze-arch/bot-tea', () => ({
|
||||
sendTeaEvent: vi.fn(),
|
||||
EVENT_NAMES: {
|
||||
page_view: 'page_view',
|
||||
},
|
||||
}));
|
||||
vi.mock('../../src/shortcut-tool/shortcut-edit/method', () => ({
|
||||
enableSendTypePanelHideTemplate: vi.fn(),
|
||||
}));
|
||||
vi.mock('@coze-common/chat-area', () => ({
|
||||
useSendTextMessage: () => sendTextMessageMock,
|
||||
useSendMultimodalMessage: () => sendMultimodalMessage,
|
||||
ContentType: {
|
||||
Image: 'image',
|
||||
File: 'file',
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('@coze-common/chat-core', () => ({
|
||||
default: () => vi.fn(),
|
||||
getFileInfo: vi.fn().mockImplementation(file => {
|
||||
if (file.type === 'image/png') {
|
||||
return {
|
||||
fileType: 'image',
|
||||
};
|
||||
}
|
||||
return {
|
||||
fileType: 'file',
|
||||
};
|
||||
}),
|
||||
}));
|
||||
|
||||
const mockShortcut: ShortCutCommand = {
|
||||
command_id: '7374755905893793836',
|
||||
command_name: 'muti',
|
||||
components_list: [
|
||||
{
|
||||
name: 'news',
|
||||
description: 'Keywords to search for news, must in English',
|
||||
input_type: 0,
|
||||
parameter: 'q',
|
||||
options: [],
|
||||
},
|
||||
],
|
||||
description: '',
|
||||
object_id: '7374633552917479468',
|
||||
plugin_api_name: 'getNews',
|
||||
plugin_id: '7373521805258014764',
|
||||
send_type: 1,
|
||||
shortcut_command: '/muti',
|
||||
template_query: '查询{{news}}',
|
||||
tool_type: 2,
|
||||
tool_info: {
|
||||
tool_name: 'News',
|
||||
tool_params_list: [
|
||||
{
|
||||
default_value: '',
|
||||
desc: 'Keywords to search for news, must in English',
|
||||
name: 'q',
|
||||
refer_component: true,
|
||||
required: true,
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
// @ts-expect-error -- test ignore
|
||||
tool_type: 2,
|
||||
},
|
||||
work_flow_id: '',
|
||||
};
|
||||
|
||||
describe('useSendTextQueryMessage', () => {
|
||||
it('should send text message with query template', () => {
|
||||
const sendTextMessage = useSendTextMessage();
|
||||
const sendTextQueryMessage = useSendTextQueryMessage();
|
||||
mockShortcut.tool_type = undefined;
|
||||
sendTextQueryMessage({
|
||||
queryTemplate: 'test',
|
||||
shortcut: mockShortcut,
|
||||
});
|
||||
expect(sendTextMessage).toHaveBeenCalledWith(
|
||||
{ text: 'test', mentionList: [] },
|
||||
'shortcut',
|
||||
{
|
||||
extendFiled: {
|
||||
device_id: expect.any(String),
|
||||
},
|
||||
},
|
||||
);
|
||||
expect(sendTeaEvent).toHaveBeenCalledWith(EVENT_NAMES.shortcut_use, {
|
||||
show_panel: undefined,
|
||||
tool_type: undefined,
|
||||
use_components: true,
|
||||
});
|
||||
});
|
||||
it('should send modify message with onBeforeSend', () => {
|
||||
const sendTextQueryMessage = useSendTextQueryMessage();
|
||||
const defaultOptions = {
|
||||
extendFiled: {
|
||||
device_id: expect.any(String),
|
||||
},
|
||||
};
|
||||
const onBeforeSendMock = vi.fn().mockReturnValue({
|
||||
message: {
|
||||
payload: {
|
||||
text: 'modified query template',
|
||||
mention_list: [{ id: 123 }],
|
||||
},
|
||||
},
|
||||
options: {
|
||||
...defaultOptions,
|
||||
test: '123',
|
||||
},
|
||||
});
|
||||
sendTextQueryMessage({
|
||||
queryTemplate: 'test',
|
||||
onBeforeSend: onBeforeSendMock,
|
||||
shortcut: mockShortcut,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('useSendUseToolMessage', () => {
|
||||
it('should send multimodal message with shortcut command', () => {
|
||||
const sendUseToolMessage = useSendUseToolMessage();
|
||||
const shortcut = {
|
||||
command_id: '7374755905893793836',
|
||||
command_name: 'muti',
|
||||
components_list: [
|
||||
{
|
||||
name: 'news',
|
||||
description: 'Keywords to search for news, must in English',
|
||||
input_type: 0,
|
||||
parameter: 'q',
|
||||
options: [],
|
||||
},
|
||||
],
|
||||
description: '',
|
||||
object_id: '7374633552917479468',
|
||||
plugin_api_name: 'getNews',
|
||||
plugin_id: '7373521805258014764',
|
||||
send_type: 1,
|
||||
tool_type: 2,
|
||||
shortcut_command: '/muti',
|
||||
template_query: '查询{{news}}',
|
||||
tool_info: {
|
||||
tool_name: 'News',
|
||||
tool_params_list: [
|
||||
{
|
||||
default_value: '',
|
||||
desc: 'Keywords to search for news, must in English',
|
||||
name: 'q',
|
||||
refer_component: true,
|
||||
required: true,
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
tool_type: 2,
|
||||
},
|
||||
work_flow_id: '',
|
||||
};
|
||||
const componentsFormValues = { news: '查询北京news' };
|
||||
// @ts-expect-error --单测忽略
|
||||
sendUseToolMessage({ shortcut, componentsFormValues });
|
||||
expect(sendMultimodalMessage).toHaveBeenCalled();
|
||||
expect(sendTeaEvent).toHaveBeenCalledWith(EVENT_NAMES.shortcut_use, {
|
||||
show_panel: true,
|
||||
tool_type: 2,
|
||||
use_components: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getTemplateQuery', () => {
|
||||
it('should return query from template', () => {
|
||||
const shortcut = {
|
||||
command_id: '7374755905893793836',
|
||||
command_name: 'muti',
|
||||
components_list: [
|
||||
{
|
||||
name: 'news',
|
||||
description: 'Keywords to search for news, must in English',
|
||||
input_type: 0,
|
||||
parameter: 'q',
|
||||
options: [],
|
||||
},
|
||||
],
|
||||
description: '',
|
||||
object_id: '7374633552917479468',
|
||||
plugin_api_name: 'getNews',
|
||||
plugin_id: '7373521805258014764',
|
||||
send_type: 1,
|
||||
shortcut_command: '/muti',
|
||||
template_query: '查询{{news}}',
|
||||
tool_info: {
|
||||
tool_name: 'News',
|
||||
tool_params_list: [
|
||||
{
|
||||
default_value: '',
|
||||
desc: 'Keywords to search for news, must in English',
|
||||
name: 'q',
|
||||
refer_component: true,
|
||||
required: true,
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
tool_type: 2,
|
||||
},
|
||||
work_flow_id: '',
|
||||
};
|
||||
const componentsFormValues = { news: '北京新闻' };
|
||||
// @ts-expect-error --单测忽略
|
||||
const result = getTemplateQuery(shortcut, componentsFormValues);
|
||||
expect(result).toBe('查询北京新闻');
|
||||
});
|
||||
|
||||
it('should throw error when template_query is not defined', () => {
|
||||
const shortcut = {
|
||||
command_id: '7374755905893793836',
|
||||
command_name: 'muti',
|
||||
components_list: [
|
||||
{
|
||||
name: 'news',
|
||||
description: 'Keywords to search for news, must in English',
|
||||
input_type: 0,
|
||||
parameter: 'q',
|
||||
options: [],
|
||||
},
|
||||
],
|
||||
description: '',
|
||||
object_id: '7374633552917479468',
|
||||
plugin_api_name: 'getNews',
|
||||
plugin_id: '7373521805258014764',
|
||||
send_type: 1,
|
||||
shortcut_command: '/muti',
|
||||
tool_info: {
|
||||
tool_name: 'News',
|
||||
tool_params_list: [
|
||||
{
|
||||
default_value: '',
|
||||
desc: 'Keywords to search for news, must in English',
|
||||
name: 'q',
|
||||
refer_component: true,
|
||||
required: true,
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
tool_type: 2,
|
||||
},
|
||||
work_flow_id: '',
|
||||
};
|
||||
const componentsFormValues = { news: '北京新闻' };
|
||||
// @ts-expect-error --单测忽略
|
||||
expect(() => getTemplateQuery(shortcut, componentsFormValues)).toThrowError(
|
||||
'template_query is not defined',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getImageAndFileList', () => {
|
||||
it('should return list of images and files', () => {
|
||||
const componentsFormValues = {
|
||||
image: {
|
||||
fileInstance: new File([''], 'filename', { type: 'image/png' }),
|
||||
url: 'http://example.com/image.png',
|
||||
width: 100,
|
||||
height: 100,
|
||||
},
|
||||
file: {
|
||||
fileInstance: new File([''], 'filename', { type: 'text/plain' }),
|
||||
url: 'http://example.com/file.txt',
|
||||
},
|
||||
};
|
||||
const result = getImageAndFileList(componentsFormValues);
|
||||
expect(result).toEqual([
|
||||
{
|
||||
type: ContentType.Image,
|
||||
file: componentsFormValues.image.fileInstance,
|
||||
uri: componentsFormValues.image.url,
|
||||
width: componentsFormValues.image.width,
|
||||
height: componentsFormValues.image.height,
|
||||
},
|
||||
{
|
||||
type: ContentType.File,
|
||||
file: componentsFormValues.file.fileInstance,
|
||||
uri: componentsFormValues.file.url,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type WorkFlowItemType } from '@coze-studio/bot-detail-store';
|
||||
import { type PluginApi, ToolType } from '@coze-arch/bot-api/playground_api';
|
||||
|
||||
import {
|
||||
initToolInfoByToolApi,
|
||||
initToolInfoByWorkFlow,
|
||||
initToolInfoByPlugin,
|
||||
MAX_TOOL_PARAMS_COUNT,
|
||||
} from '../../../../../../src/shortcut-tool/shortcut-edit/action-switch-area/skill-switch/method';
|
||||
|
||||
describe('initToolInfoByToolApi', () => {
|
||||
it('returns null when no toolApi is provided', () => {
|
||||
expect(initToolInfoByToolApi()).toBeNull();
|
||||
});
|
||||
|
||||
it('initializes tool info by workflow when workflow_id is present', () => {
|
||||
// @ts-expect-error -- workflow_id is not required
|
||||
const workflow: WorkFlowItemType = {
|
||||
workflow_id: '1',
|
||||
name: 'Test Workflow',
|
||||
parameters: [],
|
||||
};
|
||||
|
||||
const result = initToolInfoByToolApi(workflow);
|
||||
expect(result?.tool_type).toBe(ToolType.ToolTypeWorkFlow);
|
||||
});
|
||||
|
||||
it('initializes tool info by plugin when workflow_id is not present', () => {
|
||||
const plugin: PluginApi = {
|
||||
name: 'Test Plugin',
|
||||
plugin_name: 'Test Plugin',
|
||||
parameters: [],
|
||||
};
|
||||
|
||||
const result = initToolInfoByToolApi(plugin);
|
||||
expect(result?.tool_type).toBe(ToolType.ToolTypePlugin);
|
||||
});
|
||||
|
||||
it('sorts parameters by required field and limits to MAX_TOOL_PARAMS_COUNT', () => {
|
||||
const parameters = Array(MAX_TOOL_PARAMS_COUNT + 2)
|
||||
.fill(null)
|
||||
.map((_, index) => ({
|
||||
name: `param${index}`,
|
||||
desc: `desc${index}`,
|
||||
required: index < MAX_TOOL_PARAMS_COUNT,
|
||||
type: 'string',
|
||||
}));
|
||||
|
||||
const plugin: PluginApi = {
|
||||
name: 'Test Plugin',
|
||||
plugin_name: 'Test Plugin',
|
||||
parameters,
|
||||
};
|
||||
|
||||
const result = initToolInfoByToolApi(plugin);
|
||||
expect(result?.tool_params_list.length).toBe(MAX_TOOL_PARAMS_COUNT + 2);
|
||||
// 前10个是required=true的参数
|
||||
expect(
|
||||
result?.tool_params_list
|
||||
.slice(0, MAX_TOOL_PARAMS_COUNT)
|
||||
.every(param => param.required),
|
||||
).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('initToolInfoByWorkFlow', () => {
|
||||
it('initializes tool info from a workflow item', () => {
|
||||
// @ts-expect-error -- workflow_id is not required
|
||||
const workflow: WorkFlowItemType = {
|
||||
workflow_id: '1',
|
||||
name: 'Test Workflow',
|
||||
parameters: [],
|
||||
};
|
||||
|
||||
const result = initToolInfoByWorkFlow(workflow);
|
||||
expect(result.tool_type).toBe(ToolType.ToolTypeWorkFlow);
|
||||
expect(result.tool_name).toBe(workflow.name);
|
||||
expect(result.work_flow_id).toBe(workflow.workflow_id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('initToolInfoByPlugin', () => {
|
||||
it('initializes tool info from a plugin item', () => {
|
||||
const plugin: PluginApi = {
|
||||
name: 'Test Plugin',
|
||||
plugin_name: 'Test Plugin',
|
||||
parameters: [],
|
||||
};
|
||||
|
||||
const result = initToolInfoByPlugin(plugin);
|
||||
expect(result.tool_type).toBe(ToolType.ToolTypePlugin);
|
||||
expect(result.tool_name).toBe(plugin.plugin_name);
|
||||
expect(result.plugin_api_name).toBe(plugin.name);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { InputType } from '@coze-arch/bot-api/playground_api';
|
||||
|
||||
import { type ShortcutEditFormValues } from '../../../../../src/shortcut-tool/types';
|
||||
import {
|
||||
initComponentsByToolParams,
|
||||
getUnusedComponents,
|
||||
} from '../../../../../src/shortcut-tool/shortcut-edit/action-switch-area/method';
|
||||
|
||||
describe('initComponentsByToolParams', () => {
|
||||
it('should initialize components correctly', () => {
|
||||
const params = [
|
||||
{ name: 'param1', desc: 'description1', refer_component: true },
|
||||
{ name: 'param2', desc: 'description2', refer_component: false },
|
||||
];
|
||||
const expected = [
|
||||
{
|
||||
name: 'param1',
|
||||
parameter: 'param1',
|
||||
description: 'description1',
|
||||
input_type: InputType.TextInput,
|
||||
default_value: { value: '' },
|
||||
hide: false,
|
||||
},
|
||||
{
|
||||
name: 'param2',
|
||||
parameter: 'param2',
|
||||
description: 'description2',
|
||||
input_type: InputType.TextInput,
|
||||
default_value: { value: '' },
|
||||
hide: true,
|
||||
},
|
||||
];
|
||||
expect(initComponentsByToolParams(params)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should handle empty params', () => {
|
||||
expect(initComponentsByToolParams([])).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getUnusedComponents', () => {
|
||||
it('should return unused components', () => {
|
||||
// @ts-expect-error -- hide is missing
|
||||
const shortcut: ShortcutEditFormValues = {
|
||||
components_list: [
|
||||
{ name: 'comp1', hide: false },
|
||||
{ name: 'comp2', hide: false },
|
||||
],
|
||||
template_query: '{{comp1}}',
|
||||
};
|
||||
const expected = [{ name: 'comp2', hide: false }];
|
||||
expect(getUnusedComponents(shortcut)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should handle empty components_list', () => {
|
||||
// @ts-expect-error -- hide is missing
|
||||
const shortcut: ShortcutEditFormValues = {
|
||||
components_list: [],
|
||||
template_query: '',
|
||||
};
|
||||
expect(getUnusedComponents(shortcut)).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle no unused components', () => {
|
||||
// @ts-expect-error -- hide is missing
|
||||
const shortcut: ShortcutEditFormValues = {
|
||||
components_list: [{ name: 'comp1', hide: false }],
|
||||
template_query: '{{comp1}}',
|
||||
};
|
||||
expect(getUnusedComponents(shortcut)).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
InputType,
|
||||
// eslint-disable-next-line camelcase
|
||||
type shortcut_command,
|
||||
} from '@coze-arch/bot-api/playground_api';
|
||||
|
||||
import {
|
||||
type ComponentsWithId,
|
||||
type ComponentTypeItem,
|
||||
} from '../../../../../src/shortcut-tool/shortcut-edit/components-table/types';
|
||||
import {
|
||||
attachIdToComponents,
|
||||
checkDuplicateName,
|
||||
formatSubmitValues,
|
||||
getComponentTypeFormBySubmitField,
|
||||
getComponentTypeSelectFormInitValues,
|
||||
getSubmitFieldFromComponentTypeForm,
|
||||
isUploadType,
|
||||
modifyComponentWhenSwitchChange,
|
||||
type SubmitComponentTypeFields,
|
||||
} from '../../../../../src/shortcut-tool/shortcut-edit/components-table/method';
|
||||
|
||||
describe('attachIdToComponents', () => {
|
||||
it('should attach unique id to each component', () => {
|
||||
// eslint-disable-next-line camelcase
|
||||
const components: shortcut_command.Components[] = [
|
||||
{ input_type: InputType.TextInput },
|
||||
{ input_type: InputType.Select },
|
||||
];
|
||||
const result = attachIdToComponents(components);
|
||||
expect(result[0]?.id).toBeDefined();
|
||||
expect(result[1]?.id).toBeDefined();
|
||||
expect(result[0]?.id).not.toEqual(result[1]?.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatSubmitValues', () => {
|
||||
it('should format values correctly', () => {
|
||||
const values: ComponentsWithId[] = [
|
||||
{ id: '1', input_type: InputType.TextInput, options: ['option1'] },
|
||||
{
|
||||
id: '2',
|
||||
input_type: InputType.Select,
|
||||
options: ['option1', 'option2'],
|
||||
},
|
||||
];
|
||||
const result = formatSubmitValues(values);
|
||||
expect(result[0]?.options).toEqual([]);
|
||||
expect(result[1]?.options).toEqual(['option1', 'option2']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkDuplicateName', () => {
|
||||
it('should return true if duplicate names exist', () => {
|
||||
const values: ComponentsWithId[] = [
|
||||
{ id: '1', name: 'component1' },
|
||||
{ id: '2', name: 'component1' },
|
||||
];
|
||||
const formApi = { setError: vi.fn() };
|
||||
const result = checkDuplicateName(values, formApi as any);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if no duplicate names exist', () => {
|
||||
const values: ComponentsWithId[] = [
|
||||
{ id: '1', name: 'component1' },
|
||||
{ id: '2', name: 'component2' },
|
||||
];
|
||||
const formApi = { setError: vi.fn() };
|
||||
const result = checkDuplicateName(values, formApi as any);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getComponentTypeSelectFormInitValues', () => {
|
||||
it('should return initial values', () => {
|
||||
const result = getComponentTypeSelectFormInitValues();
|
||||
expect(result).toEqual({ type: 'text' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSubmitFieldFromComponentTypeForm', () => {
|
||||
it('returns TextInput type for text', () => {
|
||||
const values: ComponentTypeItem = { type: 'text' };
|
||||
const result = getSubmitFieldFromComponentTypeForm(values);
|
||||
expect(result).toEqual({ input_type: InputType.TextInput });
|
||||
});
|
||||
|
||||
it('returns Select type with options for select', () => {
|
||||
const values: ComponentTypeItem = {
|
||||
type: 'select',
|
||||
options: ['option1', 'option2'],
|
||||
};
|
||||
const result = getSubmitFieldFromComponentTypeForm(values);
|
||||
expect(result).toEqual({
|
||||
input_type: InputType.Select,
|
||||
options: ['option1', 'option2'],
|
||||
});
|
||||
});
|
||||
|
||||
it('returns MixUpload type with upload options for multiple upload types', () => {
|
||||
const values: ComponentTypeItem = {
|
||||
type: 'upload',
|
||||
uploadTypes: [InputType.UploadImage, InputType.UploadDoc],
|
||||
};
|
||||
const result = getSubmitFieldFromComponentTypeForm(values);
|
||||
expect(result).toEqual({
|
||||
input_type: InputType.MixUpload,
|
||||
upload_options: [InputType.UploadImage, InputType.UploadDoc],
|
||||
});
|
||||
});
|
||||
|
||||
it('returns specific Upload type for single upload type', () => {
|
||||
const values: ComponentTypeItem = {
|
||||
type: 'upload',
|
||||
uploadTypes: [InputType.UploadImage],
|
||||
};
|
||||
const result = getSubmitFieldFromComponentTypeForm(values);
|
||||
expect(result).toEqual({ input_type: InputType.UploadImage });
|
||||
});
|
||||
|
||||
it('returns TextInput type for unrecognized type', () => {
|
||||
// @ts-expect-error -- 无视
|
||||
const values: ComponentTypeItem = { type: 'unknown' };
|
||||
const result = getSubmitFieldFromComponentTypeForm(values);
|
||||
expect(result).toEqual({ input_type: InputType.TextInput });
|
||||
});
|
||||
});
|
||||
|
||||
describe('isUploadType', () => {
|
||||
it('should return true for upload types', () => {
|
||||
const result = isUploadType(InputType.UploadImage);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for non-upload types', () => {
|
||||
const result = isUploadType(InputType.TextInput);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getComponentTypeFormBySubmitField', () => {
|
||||
it('returns initial values when input_type is not provided', () => {
|
||||
const values: SubmitComponentTypeFields = {};
|
||||
const result = getComponentTypeFormBySubmitField(values);
|
||||
expect(result).toEqual({ type: 'text' });
|
||||
});
|
||||
|
||||
it('returns correct form for TextInput type', () => {
|
||||
const values: SubmitComponentTypeFields = {
|
||||
input_type: InputType.TextInput,
|
||||
};
|
||||
const result = getComponentTypeFormBySubmitField(values);
|
||||
expect(result).toEqual({ type: 'text' });
|
||||
});
|
||||
|
||||
it('returns correct form for Select type with options', () => {
|
||||
const values: SubmitComponentTypeFields = {
|
||||
input_type: InputType.Select,
|
||||
options: ['option1', 'option2'],
|
||||
};
|
||||
const result = getComponentTypeFormBySubmitField(values);
|
||||
expect(result).toEqual({ type: 'select', options: ['option1', 'option2'] });
|
||||
});
|
||||
|
||||
it('returns correct form for Upload type with upload options', () => {
|
||||
const values: SubmitComponentTypeFields = {
|
||||
input_type: InputType.UploadImage,
|
||||
upload_options: [InputType.UploadAudio, InputType.VIDEO],
|
||||
};
|
||||
const result = getComponentTypeFormBySubmitField(values);
|
||||
expect(result).toEqual({
|
||||
type: 'upload',
|
||||
uploadTypes: [InputType.UploadAudio, InputType.VIDEO],
|
||||
});
|
||||
});
|
||||
|
||||
it('returns initial values when input_type is not recognized', () => {
|
||||
const values: SubmitComponentTypeFields = {
|
||||
input_type: 'unknown' as unknown as InputType,
|
||||
};
|
||||
const result = getComponentTypeFormBySubmitField(values);
|
||||
expect(result).toEqual({ type: 'text' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('isUploadType', () => {
|
||||
it('returns true for upload types', () => {
|
||||
expect(isUploadType(InputType.UploadImage)).toBeTruthy();
|
||||
expect(isUploadType(InputType.UploadDoc)).toBeTruthy();
|
||||
expect(isUploadType(InputType.UploadTable)).toBeTruthy();
|
||||
expect(isUploadType(InputType.UploadAudio)).toBeTruthy();
|
||||
expect(isUploadType(InputType.CODE)).toBeTruthy();
|
||||
expect(isUploadType(InputType.ARCHIVE)).toBeTruthy();
|
||||
expect(isUploadType(InputType.PPT)).toBeTruthy();
|
||||
expect(isUploadType(InputType.VIDEO)).toBeTruthy();
|
||||
expect(isUploadType(InputType.TXT)).toBeTruthy();
|
||||
expect(isUploadType(InputType.MixUpload)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('returns false for non-upload types', () => {
|
||||
expect(isUploadType(InputType.TextInput)).toBeFalsy();
|
||||
expect(isUploadType(InputType.Select)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
describe('modifyComponentWhenSwitchChange', () => {
|
||||
it('should modify component hide property correctly', () => {
|
||||
const components: ComponentsWithId[] = [
|
||||
{ id: '1', hide: false },
|
||||
{ id: '2', hide: false },
|
||||
];
|
||||
const record: ComponentsWithId = { id: '1', hide: false };
|
||||
const result = modifyComponentWhenSwitchChange({
|
||||
components,
|
||||
record,
|
||||
checked: false,
|
||||
});
|
||||
expect(result[0]?.hide).toBe(true);
|
||||
expect(result[1]?.hide).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
vi.stubGlobal('IS_OVERSEA', false);
|
||||
vi.stubGlobal('IS_RELEASE_VERSION', false);
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type ItemType } from '../../src/utils/data-helper';
|
||||
|
||||
describe('ItemType', () => {
|
||||
it('returns array item type for array input', () => {
|
||||
type Result = ItemType<string[]>;
|
||||
const result: Result = 'test';
|
||||
expect(typeof result).to.equal('string');
|
||||
});
|
||||
|
||||
it('returns same type for non-array input', () => {
|
||||
type Result = ItemType<number>;
|
||||
const result: Result = 123;
|
||||
expect(typeof result).to.equal('number');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { getUIModeByBizScene } from '../../src/utils/get-ui-mode-by-biz-scene';
|
||||
|
||||
describe('ItemType', () => {
|
||||
it('returns UIMode correctly', () => {
|
||||
const res1 = getUIModeByBizScene({
|
||||
bizScene: 'agentApp',
|
||||
showBackground: false,
|
||||
});
|
||||
|
||||
const res2 = getUIModeByBizScene({
|
||||
bizScene: 'home',
|
||||
showBackground: false,
|
||||
});
|
||||
|
||||
const res3 = getUIModeByBizScene({
|
||||
bizScene: 'agentApp',
|
||||
showBackground: false,
|
||||
});
|
||||
|
||||
expect(res1).toBe('grey');
|
||||
expect(res2).toBe('white');
|
||||
expect(res3).toBe('grey');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { isApiError } from '../../src/utils/handle-error';
|
||||
|
||||
describe('isApiError', () => {
|
||||
it('identifies ApiError correctly', () => {
|
||||
const error = { name: 'ApiError' };
|
||||
const result = isApiError(error);
|
||||
expect(result).to.be.true;
|
||||
});
|
||||
|
||||
it('returns false for non-ApiError', () => {
|
||||
const error = { name: 'OtherError' };
|
||||
const result = isApiError(error);
|
||||
expect(result).to.be.false;
|
||||
});
|
||||
|
||||
it('returns false for error without name', () => {
|
||||
const error = { message: 'An error occurred' };
|
||||
const result = isApiError(error);
|
||||
expect(result).to.be.false;
|
||||
});
|
||||
|
||||
it('handles null and undefined', () => {
|
||||
expect(isApiError(null)).to.be.false;
|
||||
expect(isApiError(undefined)).to.be.false;
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { getQueryFromTemplate } from '../../src/utils/shortcut-query';
|
||||
|
||||
describe('getQueryFromTemplate', () => {
|
||||
it('should replace placeholders with corresponding values', () => {
|
||||
const templateQuery = 'Hello, {{name}}!';
|
||||
const values = { name: 'John' };
|
||||
const result = getQueryFromTemplate(templateQuery, values);
|
||||
expect(result).to.equal('Hello, John!');
|
||||
});
|
||||
|
||||
it('should handle multiple placeholders', () => {
|
||||
const templateQuery = '{{greeting}}, {{name}}!';
|
||||
const values = { greeting: 'Hi', name: 'John' };
|
||||
const result = getQueryFromTemplate(templateQuery, values);
|
||||
expect(result).to.equal('Hi, John!');
|
||||
});
|
||||
|
||||
it('should leave unreplaced placeholders intact', () => {
|
||||
const templateQuery = 'Hello, {{name}}!';
|
||||
const values = { greeting: 'Hi' };
|
||||
const result = getQueryFromTemplate(templateQuery, values);
|
||||
expect(result).to.equal('Hello, {{name}}!');
|
||||
});
|
||||
|
||||
it('should handle empty values object', () => {
|
||||
const templateQuery = 'Hello, {{name}}!';
|
||||
const values = {};
|
||||
const result = getQueryFromTemplate(templateQuery, values);
|
||||
expect(result).to.equal('Hello, {{name}}!');
|
||||
});
|
||||
|
||||
it('should handle empty template string', () => {
|
||||
const templateQuery = '';
|
||||
const values = { name: 'John' };
|
||||
const result = getQueryFromTemplate(templateQuery, values);
|
||||
expect(result).to.equal('');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user