feat(component-test): 完善组件测试功能与界面交互

master
钟良源 2 months ago
parent 000d985420
commit bba9318e9d

@ -11,6 +11,7 @@ const BreadcrumbItem = Breadcrumb.Item;
const ComponentTest = () => {
const [selectedIdentifier, setSelectedIdentifier] = useState<string>('');
const [selectedParentId, setSelectedParentId] = useState<string>('');
const [refreshKey, setRefreshKey] = useState<number>(0);
const [count, setCount] = useState({ total: 0, passed: 0, failed: 0 });
const [currentView, setCurrentView] = useState<'list' | 'test'>('list');
@ -24,8 +25,9 @@ const ComponentTest = () => {
];
// 处理子节点选择
const handleNodeSelect = (identifier: string) => {
const handleNodeSelect = (identifier: string, parentId: string) => {
setSelectedIdentifier(identifier);
setSelectedParentId(parentId);
// 每次选择都更新 refreshKey,确保即使选择同一个节点也会触发刷新
setRefreshKey(prev => prev + 1);
};
@ -106,6 +108,7 @@ const ComponentTest = () => {
</>
) : (
<TestInstance
parentId={selectedParentId}
instance={selectedInstance}
onBack={handleBackToList}
/>

@ -64,6 +64,7 @@ const SideBar = ({ onNodeSelect, getCount }) => {
classifyMap.set(classify, {
title: classify,
key: `classify-${classify}`,
id: item.id,
children: []
});
}
@ -72,6 +73,7 @@ const SideBar = ({ onNodeSelect, getCount }) => {
classifyMap.get(classify).children.push({
title: item.name,
key: item.identifier,
id: item.id,
data: item
});
});
@ -116,8 +118,9 @@ const SideBar = ({ onNodeSelect, getCount }) => {
}
}
else if (selectedKey) {
// 子节点的key就是identifier
onNodeSelect(selectedKey);
const parentId = info.node.props.id;
onNodeSelect(selectedKey, parentId);
}
};

@ -207,10 +207,32 @@
padding: 16px;
.node-function {
display: flex;
align-items: center;
gap: 8px;
font-size: 15px;
font-weight: 500;
color: #1d2129;
margin-bottom: 10px;
opacity: 0.4;
transition: opacity 0.3s;
.function-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: #86909c;
flex-shrink: 0;
transition: background 0.3s;
}
&.active {
opacity: 1;
.function-dot {
background: #165dff;
}
}
}
.node-params {
@ -223,18 +245,30 @@
display: flex;
align-items: center;
gap: 8px;
opacity: 0.4;
transition: opacity 0.3s;
.param-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #86909c;
flex-shrink: 0;
transition: background 0.3s;
}
.param-label {
font-size: 14px;
color: #4e5969;
}
&.active {
opacity: 1;
.param-dot {
background: #165dff;
}
}
}
}
@ -243,18 +277,31 @@
align-items: center;
justify-content: flex-end;
gap: 8px;
margin-bottom: 7px;
opacity: 0.4;
transition: opacity 0.3s;
.param-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #86909c;
flex-shrink: 0;
transition: background 0.3s;
}
.param-label {
font-size: 14px;
color: #4e5969;
}
&.active {
opacity: 1;
.param-dot {
background: #165dff;
}
}
}
}
}

@ -1,22 +1,65 @@
import React, { useEffect, useState } from 'react';
import { Button, Space, Tree, Collapse } from '@arco-design/web-react';
import { Button, Space, Tree, Collapse, Divider, Message } from '@arco-design/web-react';
import { IconLeft, IconPlus, IconEdit, IconDelete, IconLink, IconSend } from '@arco-design/web-react/icon';
import styles from './style/testInstance.module.less';
import { getComponentDesign } from '@/api/componentDevelopProcess';
import { getComponentTestCaseList, submitTestCase } from '@/api/componentTestCase';
import TestCaseModal from './TestCaseModal';
const CollapseItem = Collapse.Item;
const TestInstance = ({ instance, onBack }: { instance: any; onBack: () => void }) => {
const TestInstance = ({ instance, parentId, onBack }: { instance: any; parentId: string; onBack: () => void }) => {
const [activeTab, setActiveTab] = useState('link');
const [testCaseList, setTestCaseList] = useState([]);
const [design, setDesign] = useState(null);
const [modalVisible, setModalVisible] = useState(false);
const [selectedOperationIdent, setSelectedOperationIdent] = useState('');
const [activeOperationIdent, setActiveOperationIdent] = useState('');
const getDesign = async () => {
const res: any = await getComponentDesign(instance.id);
console.log('res:', res);
const res: any = await getComponentDesign(parentId);
if (res.code === 200) {
setDesign(res.data);
// 默认激活第一个 operation
if (res.data?.operates?.length > 0) {
setActiveOperationIdent(res.data.operates[0].ident);
}
}
};
const getTestCaseList = async () => {
const res: any = await getComponentTestCaseList({ componentBaseId: parentId, identifier: instance.identifier });
if (res.code === 200) setTestCaseList(res.data);
};
useEffect(() => {
getTestCaseList();
getDesign();
}, [instance]);
}, [parentId, instance]);
const handleAddTestCase = (operationIdent: string) => {
setSelectedOperationIdent(operationIdent);
setModalVisible(true);
};
const handleModalOk = async (values: any) => {
const params = {
...values,
componentBaseId: parentId,
identifier: instance.identifier
};
const res: any = await submitTestCase(params);
if (res.code === 200) {
Message.success('添加测试用例成功');
getTestCaseList();
}
else Message.error(res.msg);
setModalVisible(false);
};
const handleModalCancel = () => {
setModalVisible(false);
};
return (
<div className={styles['test-instance']}>
@ -68,35 +111,44 @@ const TestInstance = ({ instance, onBack }: { instance: any; onBack: () => void
<span></span>
</div>
<div className={styles['tree-container']}>
<Collapse defaultActiveKey={['1']} bordered={false}>
<CollapseItem header="监听组件" name="1" extra={<IconPlus />}>
<div className={styles['tree-list']}>
<div className={styles['tree-item']}>
<span className={styles['item-text']}></span>
<div className={styles['item-actions']}>
<IconSend />
<IconEdit />
<IconDelete />
<Collapse defaultActiveKey={['1']} bordered={false} accordion>
{testCaseList.map((item: any, index: number) => (
<CollapseItem
header={
<div onClick={() => setActiveOperationIdent(item.operationIdent)}>
{item.operationIdent}
</div>
}
name={index.toString()}
key={index}
extra={
<IconPlus
onClick={(e) => {
e.stopPropagation();
handleAddTestCase(item.operationIdent);
}}
style={{ cursor: 'pointer' }}
/>
}
>
<div className={styles['tree-list']}>
{item.children.map((child: any, index1: number) => (
<div
className={styles['tree-item']}
key={index1}
onClick={() => setActiveOperationIdent(item.operationIdent)}
>
<span className={styles['item-text']}>{child.testCaseName}</span>
<div className={styles['item-actions']}>
<IconSend />
<IconEdit />
<IconDelete />
</div>
</div>
))}
</div>
<div className={styles['tree-item']}>
<span className={styles['item-text']}>jck|ssss</span>
<div className={styles['item-actions']}>
<IconSend />
<IconEdit />
<IconDelete />
</div>
</div>
<div className={styles['tree-item']}>
<span className={styles['item-text']}></span>
<div className={styles['item-actions']}>
<IconSend />
<IconEdit />
<IconDelete />
</div>
</div>
</div>
</CollapseItem>
</CollapseItem>
))}
</Collapse>
</div>
</div>
@ -108,7 +160,6 @@ const TestInstance = ({ instance, onBack }: { instance: any; onBack: () => void
<div className={styles['flow-node']}>
<div className={`${styles['node-box']} ${styles['node-start']}`}>
<div className={styles['node-header']}>
<span className={styles['node-icon']}>🔧</span>
<span className={styles['node-title']}></span>
</div>
<div className={styles['node-label']}></div>
@ -119,38 +170,56 @@ const TestInstance = ({ instance, onBack }: { instance: any; onBack: () => void
</div>
</div>
{/* 组件节点 */}
{/* 组件节点 - 单个节点展示所有接口 */}
<div className={styles['flow-node']}>
<div className={`${styles['node-box']} ${styles['node-component']}`}>
<div className={styles['node-header']}>
<span className={styles['node-icon']}></span>
<span className={styles['node-title']}>#1</span>
<span className={styles['node-title']}>{instance.identifier}</span>
</div>
<div className={styles['node-body']}>
<div className={styles['node-function']}>lineMove</div>
<div className={styles['node-function']}>circleMove</div>
<div className={styles['node-params']}>
<div className={styles['param-item']}>
<span className={styles['param-dot']}></span>
<span className={styles['param-label']}>input</span>
</div>
<div className={styles['param-item']}>
<span className={styles['param-dot']}></span>
<span className={styles['param-label']}>strpos ARR</span>
</div>
<div className={styles['param-item']}>
<span className={styles['param-dot']}></span>
<span className={styles['param-label']}>ufNum INT</span>
</div>
<div className={styles['param-item']}>
<span className={styles['param-dot']}></span>
<span className={styles['param-label']}>ufNum INT</span>
{/* 显示所有接口函数 - 固定在上方 */}
{design?.operates?.map((operation: any, index: number) => (
<div
key={index}
className={`${styles['node-function']} ${
activeOperationIdent === operation.ident ? styles['active'] : ''
}`}
>
<span className={styles['function-dot']}></span>
{operation.ident}
</div>
</div>
<div className={styles['param-item-right']}>
<span className={styles['param-label']}>output</span>
<span className={styles['param-dot']}></span>
</div>
))}
{/* 分割线 */}
<Divider />
{/* 显示激活接口的参数 */}
{design?.operates?.map((operation: any, index: number) => (
activeOperationIdent === operation.ident && (
<div key={index}>
<div className={styles['node-params']}>
{/* Input 参数 - 显示 parameters */}
{operation.parameters?.map((param: any, paramIndex: number) => (
<div className={`${styles['param-item']} ${styles['active']}`} key={paramIndex}>
<span className={styles['param-dot']}></span>
<span className={styles['param-label']}>
{param.ident} {param.type}
</span>
</div>
))}
</div>
{/* Output 参数 - 显示 responses */}
{operation.responses?.map((response: any, respIndex: number) => (
<div className={`${styles['param-item-right']} ${styles['active']}`} key={respIndex}>
<span className={styles['param-label']}>
{response.ident} {response.type}
</span>
<span className={styles['param-dot']}></span>
</div>
))}
</div>
)
))}
</div>
</div>
<div className={styles['node-connector']}>
@ -163,7 +232,6 @@ const TestInstance = ({ instance, onBack }: { instance: any; onBack: () => void
<div className={styles['flow-node']}>
<div className={`${styles['node-box']} ${styles['node-end']}`}>
<div className={styles['node-header']}>
<span className={styles['node-icon']}>📍</span>
<span className={styles['node-title']}></span>
</div>
<div className={styles['node-label']}></div>
@ -192,6 +260,14 @@ const TestInstance = ({ instance, onBack }: { instance: any; onBack: () => void
</div>
</div>
</div>
{modalVisible && <TestCaseModal
design={design.operates}
visible={modalVisible}
operationIdent={selectedOperationIdent}
onCancel={handleModalCancel}
onOk={handleModalOk}
/>}
</div>
);
};

Loading…
Cancel
Save