feat(项目管理): 环境管理断言不包括响应体

This commit is contained in:
xinxin.wu 2024-03-08 11:53:46 +08:00 committed by 刘瑞斌
parent 45929cd675
commit b491c783ad
7 changed files with 254 additions and 133 deletions

View File

@ -1,7 +1,7 @@
<template>
<div>
<paramsTable
v-model:params="innerParams"
v-model:params="condition.assertions"
:selectable="false"
:columns="columns"
:scroll="{ minWidth: '700px' }"
@ -12,25 +12,32 @@
</template>
<script setup lang="ts">
import { defineModel } from 'vue';
import { useVModel } from '@vueuse/core';
import { statusCodeOptions } from '@/components/pure/ms-advance-filter/index';
import paramsTable, { type ParamTableColumn } from '@/views/api-test/components/paramTable.vue';
import type { ExecuteAssertionItem } from '@/models/apiTest/common';
interface Param {
[key: string]: any;
assertions: ExecuteAssertionItem[];
}
const innerParams = defineModel<Param[]>('modelValue', { default: [] });
const emit = defineEmits<{
(e: 'change', val: any[]): void; //
const props = defineProps<{
data: Param;
}>();
const emit = defineEmits<{
(e: 'change', data: Param): void;
}>();
const condition = useVModel(props, 'data', emit);
const defaultParamItem = {
responseHeader: '',
matchCondition: '',
matchValue: '',
header: '',
condition: '',
expectedValue: '',
enable: true,
};
@ -52,24 +59,24 @@
const columns: ParamTableColumn[] = [
{
title: 'ms.assertion.responseHeader', //
dataIndex: 'responseHeader',
slotName: 'responseHeader',
dataIndex: 'header',
slotName: 'header',
showInTable: true,
showDrag: true,
options: responseHeaderOption,
},
{
title: 'ms.assertion.matchCondition', //
dataIndex: 'matchCondition',
slotName: 'matchCondition',
dataIndex: 'condition',
slotName: 'condition',
showInTable: true,
showDrag: true,
options: statusCodeOptions,
},
{
title: 'ms.assertion.matchValue', //
dataIndex: 'matchValue',
slotName: 'matchValue',
dataIndex: 'expectedValue',
slotName: 'expectedValue',
showInTable: true,
showDrag: true,
},
@ -82,10 +89,11 @@
showDrag: true,
},
];
function handleParamTableChange(resultArr: any[], isInit?: boolean) {
innerParams.value = [...resultArr];
condition.value.assertions = [...resultArr];
if (!isInit) {
emit('change', resultArr);
emit('change', { ...condition.value });
}
}
</script>

View File

@ -4,29 +4,37 @@
<span class="text-[var(--color-text-1)]">{{ t('ms.assertion.responseTime') }}</span>
<span class="text-[var(--color-text-4)]">(ms)</span>
</div>
<a-input-number v-model:model-value="innerParams" :step="100" mode="button" />
<a-input-number
v-model="condition.expectedValue"
:step="100"
mode="button"
@blur="
emit('change', {
...condition,
})
"
/>
</div>
</template>
<script lang="ts" setup>
import { useVModel } from '@vueuse/core';
import { useI18n } from '@/hooks/useI18n';
import { ExecuteAssertion } from '../type';
const { t } = useI18n();
interface ResponseTimeTabProps {
responseTime: number;
}
const props = defineProps<{
value: ResponseTimeTabProps;
data: ExecuteAssertion;
}>();
const innerParams = ref(props.value.responseTime);
const emit = defineEmits<{
(e: 'change', val: ResponseTimeTabProps): void; //
(e: 'change', data: ExecuteAssertion): void;
}>();
watchEffect(() => {
emit('change', { responseTime: innerParams.value });
});
const condition = useVModel(props, 'data', emit);
</script>
<style lang="less" scoped></style>

View File

@ -3,12 +3,11 @@
<div>
<div class="mb-[8px]">{{ t('ms.assertion.statusCode') }}</div>
<a-select
v-model="selectValue"
v-model="condition.condition"
class="w-[157px]"
@change="
emit('change', {
statusCode: statusCode,
selectValue: selectValue,
...condition,
})
"
>
@ -17,15 +16,14 @@
</a-option>
</a-select>
</div>
<a-input-number
<a-input
v-if="showInput"
v-model:modelValue="statusCode"
v-model="condition.expectedValue"
hide-button
class="w-[157px]"
@change="
emit('change', {
statusCode: statusCode,
selectValue: selectValue,
...condition,
})
"
/>
@ -34,28 +32,30 @@
<script lang="ts" setup>
import { computed } from 'vue';
import { useVModel } from '@vueuse/core';
import { statusCodeOptions } from '@/components/pure/ms-advance-filter/index';
import { useI18n } from '@/hooks/useI18n';
const { t } = useI18n();
interface Param {
id: string;
name: string;
assertionType: string;
condition: string;
expectedValue: string;
}
const props = defineProps<{
value: {
statusCode: number;
selectValue: string;
};
data: Param;
}>();
const emit = defineEmits(['change']);
const selectValue = ref<string>('');
const statusCode = ref<number>(200);
const showInput = computed(() => selectValue.value !== 'none' && selectValue.value !== '');
const { t } = useI18n();
watchEffect(() => {
selectValue.value = props.value.selectValue;
statusCode.value = props.value.statusCode;
});
const emit = defineEmits<{
(e: 'change', data: Param): void;
}>();
const condition = useVModel(props, 'data', emit);
const showInput = computed(() => condition.value.condition !== 'none' && condition.value.condition !== '');
</script>
<style lang="less" scoped></style>

View File

@ -1,6 +1,6 @@
<template>
<paramsTable
v-model:params="innerParams"
v-model:params="condition.variableAssertionItems"
:selectable="false"
:columns="columns"
:scroll="{ minWidth: '700px' }"
@ -10,27 +10,30 @@
</template>
<script setup lang="ts">
import { useVModel } from '@vueuse/core';
import { statusCodeOptions } from '@/components/pure/ms-advance-filter/index';
import paramsTable, { type ParamTableColumn } from '@/views/api-test/components/paramTable.vue';
interface Param {
[key: string]: any;
variableAssertionItems: any[];
}
const props = defineProps<{
value?: Param[];
data: Param;
}>();
const innerParams = ref(props.value || []);
const emit = defineEmits<{
(e: 'change'): void; //
(e: 'change', data: Param): void;
}>();
const condition = useVModel(props, 'data', emit);
const defaultParamItem = {
responseHeader: '',
matchCondition: '',
matchValue: '',
variableName: '',
condition: '',
expectedValue: '',
enable: true,
};
@ -52,7 +55,7 @@
const columns: ParamTableColumn[] = [
{
title: 'ms.assertion.variableName', //
dataIndex: 'key',
dataIndex: 'variableName',
slotName: 'key',
showInTable: true,
showDrag: true,
@ -60,16 +63,16 @@
},
{
title: 'ms.assertion.matchCondition', //
dataIndex: 'matchCondition',
slotName: 'matchCondition',
dataIndex: 'condition',
slotName: 'condition',
showInTable: true,
showDrag: true,
options: statusCodeOptions,
},
{
title: 'ms.assertion.matchValue', //
dataIndex: 'value',
slotName: 'value',
dataIndex: 'expectedValue',
slotName: 'expectedValue',
showInTable: true,
showDrag: true,
},
@ -83,9 +86,9 @@
},
];
function handleParamTableChange(resultArr: any[], isInit?: boolean) {
innerParams.value = [...resultArr];
condition.value.variableAssertionItems = [...resultArr];
if (!isInit) {
emit('change');
emit('change', { ...condition.value });
}
}
</script>

View File

@ -12,9 +12,9 @@
</template>
</a-dropdown>
<div v-if="showBody" class="ms-assertion-body">
<VueDraggable v-model="selectItems" class="ms-assertion-body-left" ghost-class="ghost" handle=".sort-handle">
<VueDraggable v-model="assertions" class="ms-assertion-body-left" ghost-class="ghost" handle=".sort-handle">
<div
v-for="(item, index) in selectItems"
v-for="(item, index) in assertions"
:key="item.id"
class="ms-assertion-body-left-item"
:class="{
@ -25,7 +25,7 @@
>
<div class="ms-assertion-body-left-item-row">
<span class="ms-assertion-body-left-item-row-num">{{ index + 1 }}</span>
<span class="ms-assertion-body-left-item-row-title">{{ item.label }}</span>
<span class="ms-assertion-body-left-item-row-title">{{ item.name }}</span>
</div>
<div class="ms-assertion-body-left-item-switch">
<div class="ms-assertion-body-left-item-switch-action">
@ -50,40 +50,46 @@
</MsTableMoreAction>
</div>
<a-switch type="line" size="small" />
<a-switch v-model:model-value="item.enable" type="line" size="small" />
</div>
</div>
</VueDraggable>
<section class="ms-assertion-body-right">
<StatusCodeTab
v-if="valueKey === 'statusCode'"
:value="codeTabState.statusCode"
@change="(val) => handleChange(val, 'statusCode')"
/>
<!-- 响应头 -->
<ResponseHeaderTab
v-if="valueKey === 'responseHeader'"
:value="codeTabState.responseHeader"
@change="(val) => handleChange(val, 'responseHeader')"
v-if="valueKey === ResponseAssertionType.RESPONSE_HEADER"
v-model:data="getCurrentItemState"
@change="handleChange"
/>
<!-- 状态码 -->
<StatusCodeTab
v-if="valueKey === ResponseAssertionType.RESPONSE_CODE"
v-model:data="getCurrentItemState"
@change="handleChange"
/>
<!-- 响应体 -->
<ResponseBodyTab
v-if="valueKey === 'responseBody'"
:value="codeTabState.responseBody"
@change="(val) => handleChange(val, 'responseBody')"
v-if="valueKey === ResponseAssertionType.RESPONSE_BODY"
:value="getCurrentItemState"
@change="handleChange"
/>
<!-- 响应时间 -->
<ResponseTimeTab
v-if="valueKey === 'responseTime'"
:value="codeTabState.responseTime"
@="(val) => handleChange(val, 'responseTime')"
v-if="valueKey === ResponseAssertionType.RESPONSE_TIME"
v-model:data="getCurrentItemState"
@change="handleChange"
/>
<!-- 变量 -->
<VariableTab
v-if="valueKey === 'variable'"
:value="codeTabState.variable"
@change="(val) => handleChange(val, 'variable')"
v-if="valueKey === ResponseAssertionType.VARIABLE"
v-model:data="getCurrentItemState"
@change="handleChange"
/>
<!-- 脚本 -->
<ScriptTab
v-if="valueKey === 'script'"
:value="codeTabState.script"
@change="(val) => handleChange(val, 'script')"
v-if="valueKey === ResponseAssertionType.SCRIPT"
:value="getCurrentItemState"
@change="handleChange"
/>
</section>
</div>
@ -92,6 +98,7 @@
<script lang="ts" setup>
import { defineModel } from 'vue';
import { cloneDeep } from 'lodash-es';
import { VueDraggable } from 'vue-draggable-plus';
import MsButton from '@/components/pure/ms-button/index.vue';
@ -107,7 +114,9 @@
import { useI18n } from '@/hooks/useI18n';
import { MsAssertionItem, ValueObject } from './type';
import { ResponseAssertionType } from '@/enums/apiEnum';
import { ExecuteAssertion, MsAssertionItem } from './type';
defineOptions({
name: 'MsAssertion',
@ -116,26 +125,28 @@
const { t } = useI18n();
// key
const focusKey = ref<string>('');
//
const selectItems = defineModel<any[]>('params', { default: [] });
//
const assertions = defineModel<any[]>('params', { default: [] });
// Itemkey
const activeKey = ref<string>('');
// valueKey
const activeKey = ref<string>(assertions.value[0].id);
// value
const valueKey = computed(() => {
return activeKey.value && selectItems.value.find((item) => item.id === activeKey.value)?.value;
return activeKey.value && assertions.value.find((item) => item.id === activeKey.value)?.assertionType;
});
//
const codeTabState = computed({
//
const getCurrentItemState = computed({
get: () => {
return (selectItems.value.find((item) => item.id === activeKey.value)?.valueObj || {}) as ValueObject;
return assertions.value.find((item) => item.id === activeKey.value);
},
set: (val: ValueObject) => {
const currentIndex = selectItems.value.findIndex((item) => item.id === val.assertionType);
const tmpArr = selectItems.value;
tmpArr[currentIndex].valueObj = { ...val };
selectItems.value = tmpArr;
set: (val: ExecuteAssertion) => {
const currentIndex = assertions.value.findIndex((item) => item.id === activeKey.value);
const tmpArr = assertions.value;
tmpArr[currentIndex] = cloneDeep(val);
assertions.value = tmpArr;
},
});
const itemMoreActions: ActionsItem[] = [
{
label: 'common.copy',
@ -150,62 +161,111 @@
const assertOptionSource = [
{
label: t('ms.assertion.statusCode'),
value: 'statusCode',
value: ResponseAssertionType.RESPONSE_CODE,
},
{
label: t('ms.assertion.responseHeader'),
value: 'responseHeader',
value: ResponseAssertionType.RESPONSE_HEADER,
},
{
label: t('ms.assertion.responseBody'),
value: 'responseBody',
value: ResponseAssertionType.RESPONSE_BODY,
},
{
label: t('ms.assertion.responseTime'),
value: 'responseTime',
value: ResponseAssertionType.RESPONSE_TIME,
},
{
label: t('ms.assertion.param'),
value: 'variable',
value: ResponseAssertionType.VARIABLE,
},
{
label: t('ms.assertion.script'),
value: 'script',
value: ResponseAssertionType.SCRIPT,
},
];
//
const showBody = computed(() => {
return selectItems.value.length > 0;
return assertions.value.length > 0;
});
// dropdown
const handleSelect = (value: string | number | Record<string, any> | undefined) => {
const id = new Date().getTime().toString();
const tmpObj = {
label: assertOptionSource.find((item) => item.value === value)?.label || '',
value: value as string,
id: new Date().getTime().toString(),
name: assertOptionSource.find((item) => item.value === value)?.label || '',
assertionType: value,
id,
enable: true,
};
if (activeKey.value) {
const currentIndex = selectItems.value.findIndex((item) => item.id === activeKey.value);
const tmpArr = selectItems.value;
tmpArr.splice(currentIndex, 0, tmpObj);
selectItems.value = tmpArr;
} else {
selectItems.value.push(tmpObj);
switch (value) {
//
case ResponseAssertionType.RESPONSE_HEADER:
assertions.value.push({
...tmpObj,
assertions: [],
});
break;
//
case ResponseAssertionType.RESPONSE_CODE:
assertions.value.push({
...tmpObj,
condition: '',
expectedValue: '',
});
break;
case ResponseAssertionType.RESPONSE_BODY:
assertions.value.push({
...tmpObj,
assertionBodyType: '',
jsonPathAssertion: {
assertions: [],
},
xpathAssertion: {
assertions: [],
},
regexAssertion: {
assertions: [],
},
bodyAssertionDataByType: {},
});
break;
//
case ResponseAssertionType.RESPONSE_TIME:
assertions.value.push({
...tmpObj,
expectedValue: 0,
});
break;
case ResponseAssertionType.VARIABLE:
assertions.value.push({
...tmpObj,
condition: '',
expectedValue: '',
variableAssertionItems: [],
});
break;
case ResponseAssertionType.SCRIPT:
break;
default:
break;
}
activeKey.value = tmpObj.id;
activeKey.value = id;
};
const handleMoreActionSelect = (event: ActionsItem, item: MsAssertionItem) => {
const currentIndex = selectItems.value.findIndex((tmpItem) => tmpItem.id === item.id);
const currentIndex = assertions.value.findIndex((tmpItem) => tmpItem.id === item.id);
if (event.eventTag === 'delete') {
selectItems.value.splice(currentIndex, 1);
activeKey.value = currentIndex > 0 ? selectItems.value[currentIndex - 1].id : '';
assertions.value.splice(currentIndex, 1);
activeKey.value = currentIndex > 0 ? assertions.value[currentIndex - 1].id : '';
} else {
// copy item
const tmpObj = { ...selectItems.value[currentIndex], id: new Date().getTime().valueOf().toString() };
const tmpArr = selectItems.value;
const tmpObj = { ...assertions.value[currentIndex], id: new Date().getTime().valueOf().toString() };
const tmpArr = assertions.value;
tmpArr.splice(currentIndex, 0, tmpObj);
selectItems.value = tmpArr;
assertions.value = tmpArr;
activeKey.value = tmpObj.id;
}
};
@ -215,9 +275,33 @@
activeKey.value = item.id;
};
const handleChange = (val: any, key: string) => {
codeTabState[key] = { ...val, assertionType: key };
const handleChange = (val: any) => {
switch (val.assertionType) {
case ResponseAssertionType.RESPONSE_HEADER:
getCurrentItemState.value = { ...val };
break;
case ResponseAssertionType.RESPONSE_CODE:
getCurrentItemState.value = { ...val };
break;
case ResponseAssertionType.RESPONSE_BODY:
break;
case ResponseAssertionType.RESPONSE_TIME:
getCurrentItemState.value = { ...val };
break;
case ResponseAssertionType.VARIABLE:
getCurrentItemState.value = { ...val };
break;
case ResponseAssertionType.SCRIPT:
break;
default:
break;
}
};
watchEffect(() => {
console.log(getCurrentItemState.value);
});
</script>
<style lang="less" scoped>

View File

@ -1,3 +1,5 @@
import type { ExecuteAssertionItem } from '@/models/apiTest/common';
export interface ValueObject {
assertionType: string;
[key: string]: any;
@ -8,3 +10,14 @@ export interface MsAssertionItem {
value: string;
valueObj: ValueObject;
}
export interface ExecuteAssertion {
assertionType: string;
enable: boolean;
name: string;
id: string;
assertions: ExecuteAssertionItem[];
expectedValue: any;
condition: string;
variableAssertionItems: ExecuteAssertionItem[];
}

View File

@ -264,17 +264,22 @@
@change="handleTypeCheckingColChange(false)"
/>
</template>
<template #responseHeader="{ record, columnConfig, rowIndex }">
<a-select v-model="record.responseHeader" class="param-input" size="mini" @change="() => addTableLine(rowIndex)">
<!-- 响应头 -->
<template #header="{ record, columnConfig, rowIndex }">
<a-select v-model="record.header" class="param-input" size="mini" @change="() => addTableLine(rowIndex)">
<a-option v-for="item in columnConfig.options" :key="item.value">{{ t(item.label) }}</a-option>
</a-select>
</template>
<template #matchCondition="{ record, columnConfig }">
<!-- 匹配条件 -->
<template #condition="{ record, columnConfig }">
<a-select v-model="record.condition" size="mini" class="param-input">
<a-option v-for="item in columnConfig.options" :key="item.value">{{ t(item.label) }}</a-option>
<a-option v-for="item in columnConfig.options" :key="item.value" :value="item.value">{{
t(item.label)
}}</a-option>
</a-select>
</template>
<template #matchValue="{ record, rowIndex, columnConfig }">
<!-- 匹配值 -->
<template #expectedValue="{ record, rowIndex, columnConfig }">
<a-tooltip
v-if="columnConfig.hasRequired"
:content="t(record.required ? 'apiTestDebug.paramRequired' : 'apiTestDebug.paramNotRequired')"
@ -291,7 +296,7 @@
<div>*</div>
</MsButton>
</a-tooltip>
<a-input v-model="record.matchValue" size="mini" class="param-input" />
<a-input v-model="record.expectedValue" size="mini" class="param-input" />
</template>
<template #project="{ record, rowIndex }">
<a-select