feat(性能测试): 版本对比 (#8993)

* feat(性能测试): 版本对比

--user=郭雨琦 版本对比


Co-authored-by: guoyuqi <xiaomeinvG@126.com>
This commit is contained in:
metersphere-bot 2021-12-29 17:50:54 +08:00 committed by GitHub
parent 9fe0c329ee
commit 74bca78188
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 722 additions and 3 deletions

View File

@ -156,6 +156,7 @@
projectId() {
return getCurrentProjectID();
},
}
}
</script>

View File

@ -0,0 +1,226 @@
<template>
<div class="compare-class">
<el-card style="width: 50%;" ref="old">
<p>1</p>
<span>v1</span>
<h4>1</h4>
<el-row>
<el-col :span="12">
<el-form :inline="true">
<el-form-item :label="$t('load_test.name') ">
<el-input :disabled="true" :placeholder="$t('load_test.input_name')" v-model="oldData.name"
class="input-with-select"
size="small"
maxlength="30" show-word-limit/>
</el-form-item>
</el-form>
</el-col>
<el-col :span="12">
<el-tooltip :content="$t('commons.follow')" placement="bottom" effect="dark" v-if="!showFollow">
<i class="el-icon-star-off" style="color: #783987; font-size: 25px; margin-right: 15px;cursor: pointer;position: relative; top: 5px; " />
</el-tooltip>
<el-tooltip :content="$t('commons.cancel')" placement="bottom" effect="dark" v-if="showFollow">
<i class="el-icon-star-on" style="color: #783987; font-size: 28px; margin-right: 15px;cursor: pointer;position: relative; top: 5px; "/>
</el-tooltip>
</el-col>
</el-row>
<el-tabs v-model="active" @tab-click="clickTab">
<el-tab-pane :label="$t('load_test.basic_config')" class="advanced-config">
<performance-basic-config :is-read-only="true" :test="oldData" @fileChange="fileChange" ref="basicConfig" />
</el-tab-pane>
<el-tab-pane :label="$t('load_test.pressure_config')" class="advanced-config">
<performance-pressure-config :is-read-only="true" :test="oldData" :test-id="oldData.id" @fileChange="fileChange" ref="pressureConfig"/>
</el-tab-pane>
<el-tab-pane :label="$t('load_test.advanced_config')" class="advanced-config">
<performance-advanced-config :read-only="true" :test-id="oldData.id" ref="advancedConfig"/>
</el-tab-pane>
</el-tabs>
</el-card>
<el-card style="width: 50%;" ref="new">
<p>v1</p>
<span>1</span>
<h4>2</h4>
<el-row>
<el-col :span="12">
<el-form :inline="true">
<el-form-item :label="$t('load_test.name') ">
<el-input :disabled="true" :placeholder="$t('load_test.input_name')" v-model="newData.name"
class="input-with-select"
size="small"
maxlength="30" show-word-limit/>
</el-form-item>
</el-form>
</el-col>
<el-col :span="12">
<el-tooltip :content="$t('commons.follow')" placement="bottom" effect="dark" v-if="!newShowFollow">
<i class="el-icon-star-off" style="color: #783987; font-size: 25px; margin-right: 15px;cursor: pointer;position: relative; top: 5px; " />
</el-tooltip>
<el-tooltip :content="$t('commons.cancel')" placement="bottom" effect="dark" v-if="newShowFollow">
<i class="el-icon-star-on" style="color: #783987; font-size: 28px; margin-right: 15px;cursor: pointer;position: relative; top: 5px; "/>
</el-tooltip>
</el-col>
</el-row>
<el-tabs v-model="active" @tab-click="clickTab">
<el-tab-pane :label="$t('load_test.basic_config')" class="advanced-config">
<performance-basic-config :is-read-only="true" :test="newData" @fileChange="fileNewChange" ref="newBasicConfig" />
</el-tab-pane>
<el-tab-pane :label="$t('load_test.pressure_config')" class="advanced-config">
<performance-pressure-config :is-read-only="true" :test="newData" :test-id="newData.id" @fileChange="fileNewChange" ref="newPressureConfig" />
</el-tab-pane>
<el-tab-pane :label="$t('load_test.advanced_config')" class="advanced-config">
<performance-advanced-config :read-only="true" :test-id="newData.id" ref="newAdvancedConfig" />
</el-tab-pane>
</el-tabs>
</el-card>
<button @click="getDiff"></button>
</div>
</template>
<script>
import EditPerformanceTest from "@/business/components/performance/test/EditPerformanceTest";
import PerformancePressureConfig from "@/business/components/performance/test/components/PerformancePressureConfig";
import PerformanceBasicConfig from "@/business/components/performance/test/components/PerformanceBasicConfig";
import PerformanceAdvancedConfig from "@/business/components/performance/test/components/PerformanceAdvancedConfig";
import {patch} from "@/business/components/performance/v_node_diff";
import Vue from "vue";
const {diff} = require("@/business/components/performance/v_node_diff");
export default{
name:"DiffVersion",
components:{
EditPerformanceTest,
PerformancePressureConfig,
PerformanceBasicConfig,
PerformanceAdvancedConfig,
},
props:{
oldData:{
type:Object
},
newData:{
type:Object
},
showFollow:{
type:Boolean
},
newShowFollow:{
type:Boolean
}
},
data(){
return{
active: '0',
oldDataJson:{
},
newDataJson:{
},
}
},
methods:{
getDiff(){
let oldVnode = this.$refs.old
let vnode = this.$refs.new
//oldVnode.style.backgroundColor = "rgb(241,200,196)";
console.log(this.$refs.old)
console.log(this.$refs.new)
diff(oldVnode,vnode);
},
clickTab(tab) {
if (tab.index === '1') {
this.$refs.pressureConfig.calculateTotalChart();
}
},
fileChange(threadGroups) {
let handler = this.$refs.pressureConfig;
let csvSet = new Set;
threadGroups.forEach(tg => {
tg.threadNumber = tg.threadNumber || 10;
tg.duration = tg.duration || 10;
tg.durationHours = Math.floor(tg.duration / 3600);
tg.durationMinutes = Math.floor((tg.duration / 60 % 60));
tg.durationSeconds = Math.floor((tg.duration % 60));
tg.rampUpTime = tg.rampUpTime || 5;
tg.step = tg.step || 5;
tg.rpsLimit = tg.rpsLimit || 10;
tg.threadType = tg.threadType || 'DURATION';
tg.iterateNum = tg.iterateNum || 1;
tg.iterateRampUp = tg.iterateRampUp || 10;
if (tg.csvFiles) {
tg.csvFiles.map(item => csvSet.add(item));
}
});
let csvFiles = [];
for (const f of csvSet) {
csvFiles.push({name: f, csvSplit: false, csvHasHeader: true});
}
this.$set(handler, "threadGroups", threadGroups);
this.$refs.basicConfig.threadGroups = threadGroups;
this.$refs.pressureConfig.threadGroups = threadGroups;
this.$refs.advancedConfig.csvFiles = csvFiles;
this.$refs.pressureConfig.resourcePoolChange();
handler.calculateTotalChart();
},
fileNewChange(threadGroups) {
let handler = this.$refs.newPressureConfig;
let csvSet = new Set;
threadGroups.forEach(tg => {
tg.threadNumber = tg.threadNumber || 10;
tg.duration = tg.duration || 10;
tg.durationHours = Math.floor(tg.duration / 3600);
tg.durationMinutes = Math.floor((tg.duration / 60 % 60));
tg.durationSeconds = Math.floor((tg.duration % 60));
tg.rampUpTime = tg.rampUpTime || 5;
tg.step = tg.step || 5;
tg.rpsLimit = tg.rpsLimit || 10;
tg.threadType = tg.threadType || 'DURATION';
tg.iterateNum = tg.iterateNum || 1;
tg.iterateRampUp = tg.iterateRampUp || 10;
if (tg.csvFiles) {
tg.csvFiles.map(item => csvSet.add(item));
}
});
let csvFiles = [];
for (const f of csvSet) {
csvFiles.push({name: f, csvSplit: false, csvHasHeader: true});
}
this.$set(handler, "threadGroups", threadGroups);
this.$refs.newBasicConfig.threadGroups = threadGroups;
this.$refs.newPressureConfig.threadGroups = threadGroups;
this.$refs.newAdvancedConfig.csvFiles = csvFiles;
this.$refs.newPressureConfig.resourcePoolChange();
handler.calculateTotalChart();
},
},
created() {
}
}
</script>
<style scoped>
.compare-class{
display: flex;
justify-content:space-between;
}
</style>

View File

@ -76,6 +76,18 @@
<ms-change-history ref="changeHistory"/>
<el-dialog
:fullscreen="true"
:visible.sync="dialogVisible"
width="100%"
>
<diff-version :old-data="oldData" :show-follow="showFollow" :new-data="newData" :new-show-follow="newShowFollow" ></diff-version>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible=false"> </el-button>
<el-button type="primary" > </el-button>
</span>
</el-dialog>
</ms-main-container>
</ms-container>
</template>
@ -91,6 +103,7 @@ import MsScheduleConfig from "../../common/components/MsScheduleConfig";
import MsChangeHistory from "../../history/ChangeHistory";
import MsTableOperatorButton from "@/business/components/common/components/MsTableOperatorButton";
import MsTipButton from "@/business/components/common/components/MsTipButton";
import DiffVersion from "@/business/components/performance/test/DiffVersion";
const requireComponent = require.context('@/business/components/xpack/', true, /\.vue$/);
const versionHistory = requireComponent.keys().length > 0 ? requireComponent("./version/VersionHistory.vue") : {};
@ -108,14 +121,19 @@ export default {
MsMainContainer,
MsChangeHistory,
'MsVersionHistory': versionHistory.default,
DiffVersion
},
inject: [
'reload'
],
data() {
return {
dialogVisible:false,
result: {},
test: {schedule: {}, follows: []},
oldData: {schedule: {}, follows: []},
newData:{schedule: {}, follows: []},
newShowFollow:false,
savePath: "/performance/save",
editPath: "/performance/edit",
runPath: "/performance/run",
@ -183,7 +201,10 @@ export default {
},
importAPITest() {
let apiTest = this.$store.state.test;
console.log("输出vuex的test")
console.log(apiTest)
if (apiTest && apiTest.name) {
console.log("set test name")
this.$set(this.test, "name", apiTest.name);
if (apiTest.jmx.scenarioId) {
this.$refs.basicConfig.importScenario(apiTest.jmx.scenarioId);
@ -220,7 +241,10 @@ export default {
this.$store.commit("clearTest");
} else {
let scenarioJmxs = this.$store.state.scenarioJmxs;
console.log("输出vuex的scenarioJmxs")
console.log(scenarioJmxs)
if (scenarioJmxs && scenarioJmxs.name) {
console.log("set scenarioJmxs name")
this.$set(this.test, "name", scenarioJmxs.name);
let relateApiList = [];
if (scenarioJmxs.jmxs) {
@ -444,6 +468,7 @@ export default {
};
},
fileChange(threadGroups) {
console.log("zou")
let handler = this.$refs.pressureConfig;
let csvSet = new Set;
@ -524,7 +549,27 @@ export default {
});
},
compare(row) {
// console.log(row);
this.oldData = this.test;
this.$get('/performance/get/' + row.id+"/"+this.test.refId, response => {
this.$get('/performance/get/' + response.data.id, res => {
if (res.data) {
this.newData = res.data;
this.$get('/performance/test/follow/' + response.data.id, resp => {
if(resp.data&&resp.data.follows){
for (let i = 0; i <resp.data.follows.length; i++) {
if(resp.data.follows[i]===this.currentUser().id){
this.newShowFollow = true;
break;
}
}
}
});
}
});
});
if(this.newData){
this.dialogVisible = true;
}
},
checkout(row) {
//let test = this.versionData.filter(v => v.versionId === row.id)[0];
@ -580,4 +625,5 @@ export default {
margin-right: 25px;
margin-top: 5px;
}
</style>

View File

@ -0,0 +1,347 @@
function isUndef (v) {
return v === undefined || v === null
}
function isDef (v){
return v !== undefined && v !== null
}
function isTrue (v){
return v === true
}
function makeMap (str, expectsLowerCase){
const map = Object.create(null)
const list= str.split(',')
for (let i = 0; i < list.length; i++) {
map[list[i]] = true
}
return expectsLowerCase
? val => map[val.toLowerCase()]
: val => map[val]
}
const isTextInputType = makeMap('text,number,password,search,email,tel,url')
/**
* 该比较方法认为 12 -> 21 版本是有改变的
* @param oldDom 被比较的节点
* @param newDom 比较的节点
*/
export function diff (oldDom, newDom) {
let diffNode = {
oldNodeArray:[],
nodeArray:[],
}
//两个独立的节点传过来 首先单独比较一下他们最外层的 dom的_vnode 如果 _vnode 相同 则认为最外层节点相同,否则更改样式 return
let oldVnode = oldDom._vnode;
let newVnode = newDom._vnode;
if (isUndef(newVnode)) { //如果新节点不存在
//如果旧节点存在, 旧节点加背景颜色
if (isDef(oldVnode)) {
diffNode.oldNodeArray.push(oldVnode.elm)
}
return
}
if (isUndef(oldVnode)) {//如果旧节点不存在
//如果新节点存在, 新节点加背景颜色
if (isDef(newVnode)) {
diffNode.nodeArray.push(newVnode.elm)
}
return
}
if (sameVnode(oldVnode, newVnode)) {
//逐层比较节点 这里涉及到 children 的length 可能不同的情况
diffDetail(oldVnode, newVnode,diffNode)
} else {
diffNode.oldNodeArray.push(oldVnode.elm)
diffNode.nodeArray.push(newVnode.elm)
}
changeStyle(diffNode);
}
function changeStyle(diffNode){
console.log("查看结果");
console.log(diffNode.oldNodeArray);
console.log(diffNode.nodeArray);
for (let i = 0; i < diffNode.oldNodeArray.length; i++) {
if(diffNode.oldNodeArray[i]==='comment'||isUndef(diffNode.oldNodeArray[i].style)){
continue
}
if(diffNode.oldNodeArray[i].className==='cell'){
let rowVnodeElm = findRowVnodeElm(diffNode.oldNodeArray[i]);
rowVnodeElm.style.setProperty("background-color","rgb(241,200,196)",'important')
}else{
changeStyleBySubset(diffNode.oldNodeArray[i],"rgb(241,200,196)");
}
}
for (let i = 0; i < diffNode.nodeArray.length; i++) {
if(diffNode.nodeArray[i]==='comment'||isUndef(diffNode.nodeArray[i].style)){
continue
}
if(diffNode.nodeArray[i].className==='cell'){
let rowVnodeElm = findRowVnodeElm(diffNode.nodeArray[i]);
rowVnodeElm.style.setProperty("background-color","rgb(215, 243, 215)",'important')
}else{
changeStyleBySubset(diffNode.nodeArray[i],"rgb(215, 243, 215)");
}
}
}
function changeStyleBySubset(vnodeElm,color){
if(isDef(vnodeElm.children)&&vnodeElm.children.length>0){
if(isDef(vnodeElm.style)){
vnodeElm.style.setProperty("background-color",color,'important')
}
for (let i = 0; i < vnodeElm.children.length; i++) {
changeStyleBySubset(vnodeElm.children[i],color);
}
}else {
if(isDef(vnodeElm.style)){
vnodeElm.style.setProperty("background-color",color,'important')
}else {
vnodeElm.parentNode.style.setProperty("background-color",color,'important')
}
}
}
/**
*
* @param oldChildren Array<VNode>
* @param newChildren Array<VNode>
* @param diffNode
* @param isCompareChildren
*/
function diffChildren(oldChildren,newChildren,diffNode,isCompareChildren){
let oldLength = oldChildren.length;
let newLength = newChildren.length;
//如果isCompareChildren===true证明是需要轮巡比较table内容是否有重复的,方法是比较每个元素的最子dom,tr的td的length是相等的
if(isCompareChildren===true){
let oldIndexArray = [];
let newIndexArray = [];
//现在oldChildrennewChildren是tbody的所有数据
for (let i = 0; i < oldLength; i++) {
for (let j = 0; j < newLength; j++) {
let sameNode = {
nodeArray:[],
}
//找每行的数据但是行是确定的这里应该直接比较每个tr的 children
sameDetail(oldChildren[i],newChildren[j],sameNode)
if(sameNode.nodeArray.length>0){
//证明oldChildren[i]与 newChildren[j] 是相同的 内容
if(oldIndexArray.indexOf(i) === -1){
oldIndexArray.push(i);
}
if(newIndexArray.indexOf(j) === -1){
newIndexArray.push(j);
}
}
}
}
for (let j = 0; j < oldLength; j++) {
if(oldIndexArray.indexOf(j)===-1){
diffNode.oldNodeArray.push(oldChildren[j].elm);
}
}
for (let j = 0; j < newLength; j++) {
if(newIndexArray.indexOf(j)===-1){
diffNode.nodeArray.push(newChildren[j].elm);
}
}
}else {
//取二者公共长度
let childrenLength = Math.min(oldLength, newLength);
for (let i = 0; i < childrenLength; i++) {
let oldVnode = oldChildren[i]
let newVnode = newChildren[i]
diffDetail(oldVnode,newVnode,diffNode)
}
for (let i = childrenLength; i <= (oldLength - childrenLength); i++) {
if(oldChildren[i]){
diffNode.oldNodeArray.push(oldChildren[i].elm);
}
}
for (let i = childrenLength; i <= (newLength - childrenLength); i++) {
if(newChildren[i]){
diffNode.nodeArray.push(newChildren[i].elm);
}
}
}
}
function sameChildren(oldChildren,newChildren,sameNode){
let oldLength = oldChildren.length;
for (let i = 0; i < oldLength; i++) {
let oldVnode = oldChildren[i]
let newVnode = newChildren[i]
sameDetail(oldVnode,newVnode,sameNode)
if(sameNode.nodeArray.length===0){
return;
}
}
}
function sameDetail(oldVnode,newVnode,sameNode){
if(isDef(oldVnode.child)&&isDef(newVnode.child)){
let ovnode = oldVnode.child._vnode;
let nvnode = newVnode.child._vnode;
sameDetail(ovnode,nvnode,sameNode)
if(sameNode.nodeArray.length===0){
return;
}
}
if(isDef(oldVnode.children)&&isDef(newVnode.children)){
sameChildren(oldVnode.children,newVnode.children,sameNode)
}
//剩最后的子节点的时候,分类型做判断
if(isUndef(oldVnode.child)&&isUndef(newVnode.child)&&isUndef(oldVnode.children)&&isUndef(newVnode.children)){
if(isDef(oldVnode.text)&&isDef(newVnode.text)){
if(oldVnode.text===newVnode.text){
sameNode.nodeArray.push(newVnode.elm);
}else{
sameNode.nodeArray = [];
}
}else if(isDef(oldVnode.tag)&&isDef(newVnode.tag)){
if(oldVnode.tag==='input'&&newVnode.tag==='input'){
if(oldVnode.elm.value===newVnode.elm.value){
if(oldVnode.elm.checked!==undefined&&newVnode.elm.checked!==undefined){
if(oldVnode.elm.checked===newVnode.elm.checked){
sameNode.nodeArray.push(newVnode.elm);
}else {
sameNode.nodeArray = [];
}
}else {
sameNode.nodeArray.push(newVnode.elm);
}
}else{
sameNode.nodeArray = [];
}
}
}
else {
if(sameVnode(oldVnode,newVnode)){
sameNode.nodeArray.push(newVnode.elm);
}else{
sameNode.nodeArray = [];
}
}
}
}
function diffDetail(oldVnode,newVnode,diffNode){
if(isDef(oldVnode.child)&&isUndef(newVnode.child)){
diffNode.oldNodeArray.push(oldVnode.child._vnode.elm);
}
if(isDef(oldVnode.children)&&isUndef(newVnode.children)){
diffNode.oldNodeArray.push(oldVnode.elm);
}
if(isUndef(oldVnode.child)&&isDef(newVnode.child)){
diffNode.nodeArray.push(newVnode.child._vnode.elm);
}
if(isUndef(oldVnode.children)&&isDef(newVnode.children)){
diffNode.nodeArray.push(newVnode.elm);
}
if(isDef(oldVnode.child)&&isDef(newVnode.child)){
let ovnode = oldVnode.child._vnode;
let nvnode = newVnode.child._vnode;
diffDetail(ovnode,nvnode,diffNode)
}
if(isDef(oldVnode.children)&&isDef(newVnode.children)){
//处理节点数据结构为table的情况
let isCompareChildren = false;
if(oldVnode.tag==='tbody'&&newVnode.tag==='tbody'){
isCompareChildren = true;
}else
if(isDef(oldVnode.elm.className)&&isDef(newVnode.elm.className)){
if(oldVnode.elm.className==='el-collapse'&&newVnode.elm.className==='el-collapse'){
isCompareChildren = true;
}
}
diffChildren(oldVnode.children,newVnode.children,diffNode,isCompareChildren)
}
//剩最后的子节点的时候,分类型做判断
if(isUndef(oldVnode.child)&&isUndef(newVnode.child)&&isUndef(oldVnode.children)&&isUndef(newVnode.children)){
if(isDef(oldVnode.text)&&isDef(newVnode.text)){
if(oldVnode.text!==newVnode.text){
if(isDef(oldVnode.elm.style)){
diffNode.oldNodeArray.push(oldVnode.elm);
}else {
diffNode.oldNodeArray.push(oldVnode.elm.parentNode);
}
if(isDef(newVnode.elm.style)){
diffNode.nodeArray.push(newVnode.elm);
}else {
diffNode.nodeArray.push(newVnode.elm.parentNode);
}
}
}else if(isDef(oldVnode.tag)&&isDef(newVnode.tag)){
if(oldVnode.tag==='input'&&newVnode.tag==='input'){
if(oldVnode.elm.value!==newVnode.elm.value){
diffNode.oldNodeArray.push(oldVnode.elm);
diffNode.nodeArray.push(newVnode.elm);
}else {
if(oldVnode.elm.checked!==undefined&&newVnode.elm.checked!==undefined){
if(oldVnode.elm.checked!==newVnode.elm.checked){
diffNode.oldNodeArray.push(oldVnode.elm);
diffNode.nodeArray.push(newVnode.elm);
}
}
}
}
}
else {
if(!sameVnode(oldVnode,newVnode)){
diffNode.oldNodeArray.push(oldVnode.elm);
diffNode.nodeArray.push(newVnode.elm);
}
}
}
}
function sameVnode (a, b) {
return (
a.key === b.key &&
a.asyncFactory === b.asyncFactory && (
(
a.tag === b.tag &&
a.isComment === b.isComment &&
isDef(a.data) === isDef(b.data) &&
sameInputType(a, b)
) || (
isTrue(a.isAsyncPlaceholder) &&
isUndef(b.asyncFactory.error)
)
)
)
}
function findRowVnodeElm(nodeElm){
if(nodeElm.localName==="td"||nodeElm.className==="cell"){
return findRowVnodeElm(nodeElm.parentNode)
}else if(nodeElm.localName==="tr"){
return nodeElm;
}else {
return nodeElm;
}
}
function sameInputType (a, b) {
if (a.tag !== 'input') return true
let i
const typeA = isDef(i = a.data) && isDef(i = i.attrs) && i.type
const typeB = isDef(i = b.data) && isDef(i = i.attrs) && i.type
return typeA === typeB || isTextInputType(typeA) && isTextInputType(typeB)
}

View File

@ -0,0 +1,99 @@
/* @flow */
import {Component, ComponentOptions, VNodeComponentOptions, VNodeData} from "vue";
export default class VNode {
tag: string | void;
data: VNodeData | void;
children: VNode[];
text: string | void;
elm: Node | void;
ns: string | void;
context: Component | void; // rendered in this component's scope
key: string | number | void;
componentOptions: VNodeComponentOptions | void;
componentInstance: Component | void; // component instance
parent: VNode | void; // component placeholder node
// strictly internal
raw: boolean; // contains raw HTML? (server only)
isStatic: boolean; // hoisted static node
isRootInsert: boolean; // necessary for enter transition check
isComment: boolean; // empty comment placeholder?
isCloned: boolean; // is a cloned node?
isOnce: boolean; // is a v-once node?
asyncFactory: Function | void; // async component factory function
asyncMeta: Object | void;
isAsyncPlaceholder: boolean;
ssrContext: Object | void;
fnContext: Component | void; // real context vm for functional nodes
fnOptions: any; // for SSR caching
devtoolsMeta: Object; // used to store functional render context for devtools
fnScopeId: string; // functional scope id support
constructor(
tag?: string | void,
data?: VNodeData | void,
children?: VNode[],
text?: string | void,
elm?: Node | void,
context?: Component | void,
componentOptions?: VNodeComponentOptions | void,
asyncFactory?: Function | void
) {
this.tag = tag
this.data = data
this.children = children
this.text = text
this.elm = elm
this.ns = undefined
this.context = context
this.fnContext = undefined
this.fnOptions = undefined
this.fnScopeId = undefined
this.key = data && data.key
this.componentOptions = componentOptions
this.componentInstance = undefined
this.parent = undefined
this.raw = false
this.isStatic = false
this.isRootInsert = true
this.isComment = false
this.isCloned = false
this.isOnce = false
this.asyncFactory = asyncFactory
this.asyncMeta = undefined
this.isAsyncPlaceholder = false
}
// DEPRECATED: alias for componentInstance for backwards compat.
/* istanbul ignore next */
get child (): Component | void {
return this.componentInstance
}
}
export function cloneVNode (vnode: VNode): VNode {
const cloned = new VNode(
vnode.tag,
vnode.data,
// #7975
// clone children array to avoid mutating original in case of cloning
// a child.
vnode.children && vnode.children.slice(),
vnode.text,
vnode.elm,
vnode.context,
vnode.componentOptions,
vnode.asyncFactory
)
cloned.ns = vnode.ns
cloned.isStatic = vnode.isStatic
cloned.key = vnode.key
cloned.isComment = vnode.isComment
cloned.fnContext = vnode.fnContext
cloned.fnOptions = vnode.fnOptions
cloned.fnScopeId = vnode.fnScopeId
cloned.asyncMeta = vnode.asyncMeta
cloned.isCloned = true
return cloned
}