Explore Test Automation (#320)

* config jest and add setup for unittest

* config jest coverage

* example of unit testing a Status widget/component

* add license for test file

* add test/run command in the .travis.yml

* add mock store and i18n for vue jest

* add mock file missing

* add mock router

* add lincence to mock file & decodeHtml to setup file

* add mock axios instance & fix eslint on tests folder

* add test components > views > ActionButton component

* fix for test coverage success

* refactor test file

* add test Views > Autogenview.vue (Navigation Guard, Watchers, Computed)

* history mode mockRouter, refactor test code, test Autogenview > fetchData (routeName)

* test Views > AutogenView.vue (processing 31%)

* add mock router exception & test Views > AutogenView.vue (processing 43%)

* test Views > AutogenView (processing 65%), add test onSearch, closeAction, execAction, listUuidOpts

* refactor and add comment test files

* test Views > AutogenView (processing 91%)

* add comment file AutogenView.spec.js

* test Views > AutogenView.vue (handleSubmit method)

Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
This commit is contained in:
Hoang Nguyen 2021-01-20 07:42:25 +07:00 committed by Rohit Yadav
parent 64d95fb6e3
commit 338de72665
23 changed files with 5053 additions and 23 deletions

7
ui/.babelrc Normal file
View File

@ -0,0 +1,7 @@
{
"env": {
"test": {
"plugins": ["require-context-hook"]
}
}
}

View File

@ -22,3 +22,4 @@ cache:
npm: false npm: false
script: script:
- npm run lint && npm run build - npm run lint && npm run build
- npm run test:unit

View File

@ -15,10 +15,11 @@
// specific language governing permissions and limitations // specific language governing permissions and limitations
// under the License. // under the License.
module.exports = { const babelConfig = {
presets: [ presets: [
'@vue/app' '@vue/app'
] ],
plugins: []
// if your use import on Demand, Use this code // if your use import on Demand, Use this code
// , // ,
// plugins: [ // plugins: [
@ -29,3 +30,9 @@ module.exports = {
// } ] // } ]
// ] // ]
} }
if (process.env.NODE_ENV === 'test') {
babelConfig.plugins.push('require-context-hook')
}
module.exports = babelConfig

View File

@ -16,6 +16,8 @@
// under the License. // under the License.
module.exports = { module.exports = {
testURL: 'http://localhost/',
setupFiles: ['<rootDir>/tests/setup.js'],
moduleFileExtensions: [ moduleFileExtensions: [
'js', 'js',
'jsx', 'jsx',
@ -24,11 +26,13 @@ module.exports = {
], ],
transform: { transform: {
'^.+\\.vue$': 'vue-jest', '^.+\\.vue$': 'vue-jest',
'.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub', '.+\\.(css|styl|less|sass|scss|png|svg|jpg|ttf|woff|woff2)?$': 'jest-transform-stub',
'^.+\\.jsx?$': 'babel-jest' '^.+\\.jsx?$': 'babel-jest'
}, },
moduleNameMapper: { moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1' '.+\\.svg?.+$': 'jest-transform-stub',
'^@/(.*)$': '<rootDir>/src/$1',
'^@public/(.*)$': '<rootDir>/public/$1'
}, },
snapshotSerializers: [ snapshotSerializers: [
'jest-serializer-vue' 'jest-serializer-vue'
@ -36,5 +40,14 @@ module.exports = {
testMatch: [ testMatch: [
'**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)' '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
], ],
testURL: 'http://localhost/' transformIgnorePatterns: [
'<rootDir>/node_modules/(?!ant-design-vue|vue)'
],
collectCoverage: true,
collectCoverageFrom: [
'<rootDir>/src/**/*.{js,vue}',
'!**/node_modules/**',
'!<rootDir>/src/locales/*.{js, json}'
],
coverageReporters: ['html', 'text-summary']
} }

63
ui/package-lock.json generated
View File

@ -3803,6 +3803,7 @@
"vue-loader": "^15.9.2", "vue-loader": "^15.9.2",
"vue-style-loader": "^4.1.2", "vue-style-loader": "^4.1.2",
"webpack": "^4.0.0", "webpack": "^4.0.0",
"webpack-bundle-analyzer": "^3.8.0",
"webpack-chain": "^6.4.0", "webpack-chain": "^6.4.0",
"webpack-dev-server": "^3.11.0", "webpack-dev-server": "^3.11.0",
"webpack-merge": "^4.2.2" "webpack-merge": "^4.2.2"
@ -10190,9 +10191,9 @@
} }
}, },
"duplexer": { "duplexer": {
"version": "0.1.1", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz",
"integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==",
"dev": true "dev": true
}, },
"duplexer3": { "duplexer3": {
@ -18055,9 +18056,9 @@
} }
}, },
"opener": { "opener": {
"version": "1.5.1", "version": "1.5.2",
"resolved": "https://registry.npmjs.org/opener/-/opener-1.5.1.tgz", "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz",
"integrity": "sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==", "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==",
"dev": true "dev": true
}, },
"opn": { "opn": {
@ -24873,6 +24874,56 @@
} }
} }
}, },
"webpack-bundle-analyzer": {
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.9.0.tgz",
"integrity": "sha512-Ob8amZfCm3rMB1ScjQVlbYYUEJyEjdEtQ92jqiFUYt5VkEeO2v5UMbv49P/gnmCZm3A6yaFQzCBvpZqN4MUsdA==",
"dev": true,
"requires": {
"acorn": "^7.1.1",
"acorn-walk": "^7.1.1",
"bfj": "^6.1.1",
"chalk": "^2.4.1",
"commander": "^2.18.0",
"ejs": "^2.6.1",
"express": "^4.16.3",
"filesize": "^3.6.1",
"gzip-size": "^5.0.0",
"lodash": "^4.17.19",
"mkdirp": "^0.5.1",
"opener": "^1.5.1",
"ws": "^6.0.0"
},
"dependencies": {
"acorn": {
"version": "7.4.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
"integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
"dev": true
},
"acorn-walk": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz",
"integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==",
"dev": true
},
"commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true
},
"mkdirp": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"dev": true,
"requires": {
"minimist": "^1.2.5"
}
}
}
},
"webpack-chain": { "webpack-chain": {
"version": "6.5.1", "version": "6.5.1",
"resolved": "https://registry.npmjs.org/webpack-chain/-/webpack-chain-6.5.1.tgz", "resolved": "https://registry.npmjs.org/webpack-chain/-/webpack-chain-6.5.1.tgz",

View File

@ -39,6 +39,7 @@
"ant-design-vue": "~1.6.2", "ant-design-vue": "~1.6.2",
"antd-theme-webpack-plugin": "^1.3.4", "antd-theme-webpack-plugin": "^1.3.4",
"axios": "^0.19.2", "axios": "^0.19.2",
"babel-plugin-require-context-hook": "^1.0.0",
"core-js": "^3.6.5", "core-js": "^3.6.5",
"enquire.js": "^2.1.6", "enquire.js": "^2.1.6",
"js-cookie": "^2.2.1", "js-cookie": "^2.2.1",
@ -153,6 +154,9 @@
"autoprefixer": {} "autoprefixer": {}
} }
}, },
"jest": {
"testEnvironment": "node"
},
"browserslist": [ "browserslist": [
"> 1%", "> 1%",
"last 2 versions", "last 2 versions",

View File

@ -80,7 +80,7 @@ export default {
}, },
data () { data () {
return { return {
actionBadge: [] actionBadge: {}
} }
}, },
mounted () { mounted () {
@ -132,7 +132,7 @@ export default {
this.$emit('exec-action', action) this.$emit('exec-action', action)
}, },
handleShowBadge () { handleShowBadge () {
const dataBadge = {} this.actionBadge = {}
const arrAsync = [] const arrAsync = []
const actionBadge = this.actions.filter(action => action.showBadge === true) const actionBadge = this.actions.filter(action => action.showBadge === true)
@ -157,7 +157,7 @@ export default {
} }
} }
if (json[responseJsonName].count && json[responseJsonName].count > 0) { if (json[responseJsonName] && json[responseJsonName].count && json[responseJsonName].count > 0) {
response.count = json[responseJsonName].count response.count = json[responseJsonName].count
} }
@ -170,12 +170,10 @@ export default {
Promise.all(arrAsync).then(response => { Promise.all(arrAsync).then(response => {
for (let j = 0; j < response.length; j++) { for (let j = 0; j < response.length; j++) {
this.$set(dataBadge, response[j].api, {}) this.$set(this.actionBadge, response[j].api, {})
this.$set(dataBadge[response[j].api], 'badgeNum', response[j].count) this.$set(this.actionBadge[response[j].api], 'badgeNum', response[j].count)
} }
}) }).catch(() => {})
this.actionBadge = dataBadge
} }
} }
} }

View File

@ -164,19 +164,19 @@ export default {
jobId: jobid, jobId: jobid,
successMessage: `${this.$t('message.success.migrating')} ${this.resource.name}`, successMessage: `${this.$t('message.success.migrating')} ${this.resource.name}`,
successMethod: () => { successMethod: () => {
this.$parent.$parent.close() this.$emit('close-action')
}, },
errorMessage: this.$t('message.migrating.failed'), errorMessage: this.$t('message.migrating.failed'),
errorMethod: () => { errorMethod: () => {
this.$parent.$parent.close() this.$emit('close-action')
}, },
loadingMessage: `${this.$t('message.migrating.processing')} ${this.resource.name}`, loadingMessage: `${this.$t('message.migrating.processing')} ${this.resource.name}`,
catchMessage: this.$t('error.fetching.async.job.result'), catchMessage: this.$t('error.fetching.async.job.result'),
catchMethod: () => { catchMethod: () => {
this.$parent.$parent.close() this.$emit('close-action')
} }
}) })
this.$parent.$parent.close() this.$emit('close-action')
}).catch(error => { }).catch(error => {
this.$notification.error({ this.$notification.error({
message: this.$t('message.request.failed'), message: this.$t('message.request.failed'),

88
ui/tests/common/index.js Normal file
View File

@ -0,0 +1,88 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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 mockI18n from '../mock/mockI18n'
import mockStore from '../mock/mockStore'
import mockRouter from '../mock/mockRouter'
import localVue from '../setup'
import { mount } from '@vue/test-utils'
import { pollJobPlugin, notifierPlugin } from '@/utils/plugins'
localVue.use(pollJobPlugin)
localVue.use(notifierPlugin)
function createMockRouter (newRoutes = []) {
let routes = []
if (!newRoutes || Object.keys(newRoutes).length === 0) {
return mockRouter.mock(routes)
}
routes = [...newRoutes]
return mockRouter.mock(routes)
}
function createMockI18n (locale = 'en', messages = {}) {
return mockI18n.mock(locale, messages)
}
function createMockStore (state = {}, actions = {}) {
return mockStore.mock(state, actions)
}
function decodeHtml (html) {
const text = document.createElement('textarea')
text.innerHTML = html
return text.value
}
function createFactory (component, options) {
var {
router = null,
i18n = null,
store = null,
props = {},
data = {},
mocks = {}
} = options
if (!router) router = createMockRouter()
if (!i18n) i18n = createMockI18n()
if (!store) store = createMockStore()
return mount(component, {
localVue,
router,
i18n,
store,
propsData: props,
mocks,
data () {
return { ...data }
}
})
}
export default {
createFactory,
createMockRouter,
createMockI18n,
createMockStore,
decodeHtml
}

View File

@ -0,0 +1,22 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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.
const mockAxios = jest.genMockFromModule('axios')
mockAxios.create = jest.fn(() => mockAxios)
export default mockAxios

29
ui/tests/mock/mockI18n.js Normal file
View File

@ -0,0 +1,29 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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 VueI18n from 'vue-i18n'
const mockI18n = {
mock: (locale = 'en', message = {}) => {
return new VueI18n({
locale: locale,
messages: message
})
}
}
export default mockI18n

View File

@ -0,0 +1,64 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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 VueRouter from 'vue-router'
const mockRouter = {
routes: [
{
path: '/',
name: 'home',
meta: { icon: 'home' },
children: []
}
],
mock: (routes = []) => {
mockRouter.routes[0].children = [
{
path: '/exception',
name: 'exception',
children: [
{
path: '/exception/403',
name: 403,
hidden: true,
meta: { icon: 'icon-error-test' }
},
{
path: '/exception/404',
name: 404,
hidden: true,
meta: { icon: 'icon-error-test' }
},
{
path: '/exception/500',
name: 500,
hidden: true,
meta: { icon: 'icon-error-test' }
}
]
}
]
if (routes && routes.length > 0) {
mockRouter.routes[0].children = [...mockRouter.routes[0].children, ...routes]
}
return new VueRouter({ routes: mockRouter.routes, mode: 'history' })
}
}
export default mockRouter

View File

@ -0,0 +1,50 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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 Vuex from 'vuex'
const mockStore = {
state: {},
mock: (state, actions) => {
mockStore.state = {
app: {
device: 'desktop'
},
user: {},
permission: {}
}
if (state && Object.keys(state).length > 0) {
mockStore.state = { ...mockStore.state, ...state }
}
if (!actions) {
actions = {}
}
return new Vuex.Store({
state: mockStore.state,
getters: {
apis: () => mockStore.state.user.apis,
userInfo: () => mockStore.state.user.info
},
actions
})
}
}
export default mockStore

View File

@ -0,0 +1,30 @@
{
"messages": {
"en": { "label.action": "action-en" },
"de": { "label.action": "action-de" }
},
"apis": {
"test-api-case-1": {},
"test-api-case-2": {},
"test-api-case-3": {},
"test-api-case-4": {},
"test-api-case-5": {},
"test-api-case-6": {}
},
"routes": [
{
"name": "testRouter1",
"path": "/test-router-1",
"meta": {
"name": "systemvm"
}
},
{
"name": "testRouter2",
"path": "/test-router-2",
"meta": {
"name": "test-name"
}
}
]
}

View File

@ -0,0 +1,198 @@
{
"messages": {
"en": {
"labelname": "test-name-en",
"displaytext": "description-en",
"label.column1": "column1-en",
"label.column2": "column2-en",
"label.column3": "column3-en",
"label.id": "uuid-en",
"label.name": "name-en",
"label.domainid": "domain-en",
"label.self": "self-en",
"label.all": "all-en",
"label.tags": "tags-en",
"label.account": "account-en",
"label.domainids": "domainids-en",
"label.keypair": "keypair-en",
"label.filterby": "filterby-en",
"label.refresh": "refresh-en",
"message.error.required.input": "required-en",
"message.error.select": "select-en",
"label.search": "search-en",
"label.quota.configuration": "quota-configuration-en",
"label.quota.value": "quota-value-en",
"label.quota.tariff.effectivedate": "quota-effectivedate-en",
"label.confirmpassword": "confirmpassword-en",
"label.confirmpassword.description": "confirmpassword-description-en",
"label.open.documentation": "open",
"label.metrics": "metrics",
"label.showing": "Showing",
"label.of": "of",
"label.items": "items",
"label.page": "page",
"label.view.console": "view-console-en",
"error.fetching.async.job.result": "Error encountered while fetching async job result",
"label.cancel": "cancel",
"label.ok": "ok"
},
"de": {
"labelname": "test-name-de",
"displaytext": "description-de",
"label.column1": "column1-de",
"label.column2": "column2-de",
"label.column3": "column3-de",
"label.id": "uuid-de",
"label.name": "name-de",
"label.domainid": "domain-de",
"label.self": "self-de",
"label.all": "all-de",
"label.tags": "tags-de",
"label.account": "account-de",
"label.domainids": "domainids-de",
"label.keypair": "keypair-de",
"label.filterby": "filterby-de",
"label.refresh": "refresh-de",
"message.error.required.input": "required-de",
"message.error.select": "select-de",
"label.search": "search-de",
"label.quota.configuration": "quota-configuration-de",
"label.quota.value": "quota-value-de",
"label.quota.tariff.effectivedate": "quota-effectivedate-de",
"label.confirmpassword": "confirmpassword-de",
"label.confirmpassword.description": "confirmpassword-description-de",
"label.open.documentation": "open",
"label.metrics": "metrics",
"label.showing": "Showing",
"label.of": "of",
"label.items": "items",
"label.page": "page",
"label.view.console": "view-console-de",
"error.fetching.async.job.result": "Error encountered while fetching async job result",
"label.cancel": "cancel",
"label.ok": "ok"
}
},
"apis": {
"testApiNameCase1": {
"params": {},
"response": []
},
"testApiNameCase2": {
"params": {},
"response": []
},
"testApiNameCase3": {
"params": {},
"response": []
},
"testApiNameCase4": {
"params": {},
"response": [
{
"name": "column2",
"type": "string"
},
{
"name": "column1",
"type": "string"
},
{
"name": "column3",
"type": "string"
}
]
},
"testApiNameCase5": {
"params": [
{
"name": "column2",
"type": "string"
},
{
"name": "column1",
"type": "string"
},
{
"name": "column3",
"type": "string"
},
{
"name": "name",
"type": "string"
},
{
"name": "id",
"type": "string"
}
],
"response": []
},
"testApiNameCase6": {
"params": [
{
"name": "id",
"type": "uuid"
},
{
"name": "tags",
"type": "list"
},
{
"name": "column1",
"type": "list"
},
{
"name": "column2",
"type": "string"
},
{
"name": "account",
"type": "string"
},
{
"name": "confirmpassword",
"type": "string"
}
],
"response": []
},
"listTemplates": {
"params": {},
"response": []
},
"listIsos": {
"params": {},
"response": []
},
"listRoles": {
"params": {},
"response": []
},
"listHosts": {
"params": {},
"response": []
},
"listTestApiNames": {
"params": {},
"response": []
},
"createAccount": {
"params": {},
"response": []
},
"addAccountToProject": {
"params": {},
"response": []
},
"quotaEmailTemplateList": {
"params": {},
"response": []
}
},
"info": {
"roletype": "Normal",
"account": "test-account",
"domainid": "test-domain-id"
}
}

View File

@ -0,0 +1,24 @@
{
"messages": {
"en": {
"name": "name-en",
"Suitability": "Suitability-en",
"cpuused": "cpuused-en",
"memused": "memused-en",
"select": "select-en",
"ok": "ok-en",
"message.load.host.failed": "Failed to load hosts",
"message.migrating.vm.to.host.failed": "Failed to migrate VM to host"
},
"de": {
"name": "name-de",
"Suitability": "Suitability-de",
"cpuused": "cpuused-de",
"memused": "memused-de",
"select": "select-de",
"ok": "ok-de",
"message.load.host.failed": "Failed to load hosts",
"message.migrating.vm.to.host.failed": "Failed to migrate VM to host"
}
}
}

View File

@ -0,0 +1,38 @@
{
"messages": {
"en": {
"state.running": "Running",
"state.migrating": "Migrating",
"state.stopped": "Stopped",
"state.starting": "Starting",
"state.stopping": "Stopping",
"state.suspended": "Suspended",
"state.pending": "Pending",
"state.expunging": "Expunging",
"state.error": "Error",
"message.publicip.state.allocated": "Allocated",
"message.publicip.state.created": "Created",
"message.vmsnapshot.state.active": "Active",
"message.vm.state.active": "Active",
"message.volume.state.active": "Active",
"message.guestnetwork.state.active": "Active",
"message.publicip.state.active": "Active",
"Created": "Created",
"Active": "Active",
"Allocated": "Allocated",
"Error": "Error",
"Expunging": "Expunging",
"Suspended": "Suspended",
"Pending": "Pending",
"Running": "Running",
"Starting": "Starting",
"Another": "Another",
"Ready": "Ready",
"Disabled": "Disabled",
"Migrating": "Migrating",
"Stopping": "Stopping",
"Alert": "Alert",
"Stopped": "Stopped"
}
}
}

49
ui/tests/setup.js Normal file
View File

@ -0,0 +1,49 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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 Vue from 'vue'
import Vuex from 'vuex'
import Antd from 'ant-design-vue'
import VueRouter from 'vue-router'
import VueI18n from 'vue-i18n'
import VueStorage from 'vue-ls'
import VueClipboard from 'vue-clipboard2'
import config from '@/config/settings'
import { createLocalVue } from '@vue/test-utils'
import registerRequireContextHook from 'babel-plugin-require-context-hook/register'
const localVue = createLocalVue()
Vue.use(Antd)
Vue.use(VueStorage, config.storageOptions)
localVue.use(VueRouter)
localVue.use(VueI18n)
localVue.use(Vuex)
localVue.use(VueClipboard)
registerRequireContextHook()
window.matchMedia = window.matchMedia || function () {
return {
matches: false,
addListener: function () {},
removeListener: function () {}
}
}
module.exports = localVue

View File

@ -0,0 +1,337 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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 mockAxios from '../../../mock/mockAxios'
import ActionButton from '@/components/view/ActionButton'
import common from '../../../common'
import mockData from '../../../mockData/ActionButton.mock.json'
jest.mock('axios', () => mockAxios)
let router, store, i18n
const state = {
user: {
apis: mockData.apis
}
}
router = common.createMockRouter(mockData.routes)
store = common.createMockStore(state)
i18n = common.createMockI18n('en', mockData.messages)
const factory = (opts = {}) => {
router = opts.router || router
store = opts.store || store
i18n = opts.i18n || i18n
return common.createFactory(ActionButton, {
router,
store,
i18n,
props: opts.props || {},
data: opts.data || {}
})
}
describe('Components > View > ActionButton.vue', () => {
beforeEach(() => {
jest.clearAllMocks()
})
describe('Template', () => {
it('Button action is show', () => {
const expected = '<i aria-label="icon: plus" class="anticon anticon-plus">'
const wrapper = factory()
wrapper.vm.$nextTick(() => {
const received = wrapper.html()
expect(received).not.toContain(expected)
})
})
it('Normal button action is show', () => {
const expected = '<i aria-label="icon: plus" class="anticon anticon-plus">'
const propsData = {
actions: [
{
label: 'label.action',
api: 'test-api-case-1',
showBadge: false,
icon: 'plus',
dataView: false,
listView: true
}
],
dataView: false,
listView: true
}
const wrapper = factory({ props: propsData })
wrapper.vm.$nextTick(() => {
const received = wrapper.html()
expect(received).toContain(expected)
})
})
it('Badge button action is show', (done) => {
const expected = '<span class="button-action-badge ant-badge">'
const propsData = {
actions: [
{
label: 'label.action',
api: 'test-api-case-2',
showBadge: true,
icon: 'plus',
dataView: true
}
],
dataView: true
}
const dataMock = {
testapinameresponse: {
count: 0,
testapiname: []
}
}
mockAxios.mockImplementation(() => Promise.resolve(dataMock))
const wrapper = factory({ props: propsData })
wrapper.vm.$nextTick(() => {
const wrapperHtml = wrapper.html()
const received = common.decodeHtml(wrapperHtml)
expect(received).toContain(expected)
done()
})
})
})
describe('Method', () => {
describe('handleShowBadge()', () => {
it('check the api is called and returned is not null', (done) => {
const postData = new URLSearchParams()
const expected = { 'test-api-case-3': { badgeNum: 2 } }
const dataMock = { testapinameresponse: { count: 2 } }
const propsData = {
actions: [
{
label: 'label.action',
api: 'test-api-case-3',
showBadge: true,
icon: 'plus',
dataView: true
}
],
dataView: true
}
mockAxios.mockResolvedValue(dataMock)
const wrapper = factory({ props: propsData })
setTimeout(() => {
expect(mockAxios).toHaveBeenCalledTimes(1)
expect(mockAxios).toHaveBeenCalledWith({
data: postData,
method: 'GET',
params: {
command: 'test-api-case-3',
response: 'json'
},
url: '/'
})
expect(wrapper.vm.actionBadge).toEqual(expected)
done()
})
})
it('check the api is called returned is null', (done) => {
const postData = new URLSearchParams()
const expected = { 'test-api-case-4': { badgeNum: 0 } }
const dataMock = { data: [] }
const propsData = {
actions: [
{
label: 'label.action',
api: 'test-api-case-4',
showBadge: true,
icon: 'plus',
dataView: true
}
],
dataView: true
}
mockAxios.mockResolvedValue(dataMock)
const wrapper = factory({ props: propsData })
setTimeout(() => {
expect(mockAxios).toHaveBeenCalledTimes(1)
expect(mockAxios).toHaveBeenCalledWith({
data: postData,
method: 'GET',
params: {
command: 'test-api-case-4',
response: 'json'
},
url: '/'
})
expect(wrapper.vm.actionBadge).toEqual(expected)
done()
})
})
it('check the api is called and throws error', (done) => {
const postData = new URLSearchParams()
const propsData = {
actions: [
{
label: 'label.action',
api: 'test-api-case-5',
showBadge: true,
icon: 'plus',
dataView: true
}
],
dataView: true
}
const errorMessage = 'errMethodMessage'
mockAxios.mockImplementationOnce(() => Promise.reject(errorMessage))
const wrapper = factory({ props: propsData })
setTimeout(() => {
expect(mockAxios).toHaveBeenCalledTimes(1)
expect(mockAxios).toHaveBeenCalledWith({
data: postData,
method: 'GET',
params: {
command: 'test-api-case-5',
response: 'json'
},
url: '/'
})
expect(wrapper.vm.actionBadge).toEqual({})
done()
})
})
})
describe('execAction()', () => {
it('check emitted events are executed', async () => {
const expected = {
icon: 'plus',
label: 'label.action',
api: 'test-api-case-6',
showBadge: false,
dataView: true,
resource: {
id: 'test-resource-id'
}
}
const propsData = {
actions: [
{
icon: 'plus',
label: 'label.action',
api: 'test-api-case-6',
showBadge: false,
dataView: true
}
],
dataView: true,
resource: {
id: 'test-resource-id'
}
}
const wrapper = factory({ props: propsData })
await wrapper.find('button').trigger('click')
await wrapper.vm.$nextTick()
expect(wrapper.emitted()['exec-action'][0]).toEqual([expected])
})
})
})
describe('Watcher', () => {
describe('handleShowBadge()', () => {
it('check handleShowBadge() is not called with empty resource', async () => {
const wrapper = factory({
props: {
resource: {
id: 'test-resource-id'
}
}
})
const handleShowBadge = jest.spyOn(wrapper.vm, 'handleShowBadge')
wrapper.setProps({
resource: null
})
await wrapper.vm.$nextTick()
expect(handleShowBadge).not.toBeCalled()
})
it('check handleShowBadge() is not called with resource containing id null', async () => {
const wrapper = factory({
props: {
resource: {
id: 'test-resource-id'
}
}
})
const handleShowBadge = jest.spyOn(wrapper.vm, 'handleShowBadge')
wrapper.setProps({
resource: { id: null }
})
await wrapper.vm.$nextTick()
expect(handleShowBadge).not.toBeCalled()
})
it('check handleShowBadge() is not called with changed resource data', async () => {
const wrapper = factory({
props: {
resource: {
id: 'test-resource-id-1'
}
}
})
wrapper.setProps({
resource: {
id: 'test-resource-id-2'
}
})
const handleShowBadge = jest.spyOn(wrapper.vm, 'handleShowBadge')
await wrapper.vm.$nextTick()
expect(handleShowBadge).toHaveBeenCalledTimes(1)
})
})
})
})

View File

@ -0,0 +1,395 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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 Status from '@/components/widgets/Status'
import common from '../../../common'
import mockData from '../../../mockData/Status.mock.json'
let router, i18n
router = common.createMockRouter()
i18n = common.createMockI18n('en', mockData.messages)
const factory = (opts = {}) => {
router = opts.router || router
i18n = opts.i18n || i18n
return common.createFactory(Status, {
router,
i18n,
props: opts.props || {},
data: opts.data || {}
})
}
describe('Components > Widgets > Status.vue', () => {
describe('Methods', () => {
describe('getText()', () => {
it('getText() is called and the value returned is null', () => {
const propsData = {
text: 'Running',
displayText: false
}
const wrapper = factory({ props: propsData })
const received = wrapper.html()
const expected = '<span class="ant-badge-status-text"></span>'
expect(received).toContain(expected)
})
it('getText() is called with state equal Running', () => {
const propsData = {
text: 'Running',
displayText: true
}
const wrapper = factory({ props: propsData })
const received = wrapper.html()
const expected = '<span class="ant-badge-status-text">Running</span>'
expect(received).toContain(expected)
})
it('getText() is called with state equal Stopped', () => {
const propsData = {
text: 'Stopped',
displayText: true
}
const wrapper = factory({ props: propsData })
const received = wrapper.html()
const expected = '<span class="ant-badge-status-text">Stopped</span>'
expect(received).toContain(expected)
})
it('getText() is called with state equal Starting', () => {
const propsData = {
text: 'Starting',
displayText: true
}
const wrapper = factory({ props: propsData })
const received = wrapper.html()
const expected = '<span class="ant-badge-status-text">Starting</span>'
expect(received).toContain(expected)
})
it('getText() is called with state equal Stopping', () => {
const propsData = {
text: 'Stopping',
displayText: true
}
const wrapper = factory({ props: propsData })
const received = wrapper.html()
const expected = '<span class="ant-badge-status-text">Stopping</span>'
expect(received).toContain(expected)
})
it('getText() is called with state equal Suspended', () => {
const propsData = {
text: 'Suspended',
displayText: true
}
const wrapper = factory({ props: propsData })
const received = wrapper.html()
const expected = '<span class="ant-badge-status-text">Suspended</span>'
expect(received).toContain(expected)
})
it('getText() is called with state equal Pending', () => {
const propsData = {
text: 'Pending',
displayText: true
}
const wrapper = factory({ props: propsData })
const received = wrapper.html()
const expected = '<span class="ant-badge-status-text">Pending</span>'
expect(received).toContain(expected)
})
it('getText() is called with state equal Expunging', () => {
const propsData = {
text: 'Expunging',
displayText: true
}
const wrapper = factory({ props: propsData })
const received = wrapper.html()
const expected = '<span class="ant-badge-status-text">Expunging</span>'
expect(received).toContain(expected)
})
it('getText() is called with state equal Error', () => {
const propsData = {
text: 'Error',
displayText: true
}
const wrapper = factory({ props: propsData })
const received = wrapper.html()
const expected = '<span class="ant-badge-status-text">Error</span>'
expect(received).toContain(expected)
})
})
describe('getBadgeStatus()', () => {
it('getBadgeStatus() is called and the value returned is default status', () => {
const propsData = {
text: 'Another',
displayText: true
}
const wrapper = factory({ props: propsData })
const received = wrapper.html()
const expected = '<span class="ant-badge-status-dot ant-badge-status-default"></span>'
expect(received).toContain(expected)
})
it('getBadgeStatus() is called and the value returned is success status', () => {
const propsData = {
text: 'Active',
displayText: true
}
const wrapper = factory({ props: propsData })
const received = wrapper.html()
const expected = '<span class="ant-badge-status-dot ant-badge-status-success"></span>'
expect(received).toContain(expected)
})
it('getBadgeStatus() is called and the value returned is error status', () => {
const propsData = {
text: 'Disabled',
displayText: true
}
const wrapper = factory({ props: propsData })
const received = wrapper.html()
const expected = '<span class="ant-badge-status-dot ant-badge-status-error"></span>'
expect(received).toContain(expected)
})
it('getBadgeStatus() is called and the value returned is processing status', () => {
const propsData = {
text: 'Migrating',
displayText: true
}
const wrapper = factory({ props: propsData })
const received = wrapper.html()
const expected = '<span class="ant-badge-status-dot ant-badge-status-processing"></span>'
expect(received).toContain(expected)
})
it('getBadgeStatus() is called and the value returned is error status', () => {
const propsData = {
text: 'Alert',
displayText: true
}
const wrapper = factory({ props: propsData })
const received = wrapper.html()
const expected = '<span class="ant-badge ant-badge-status ant-badge-not-a-wrapper" style="display: inline-flex;"><span class="ant-badge-status-dot ant-badge-status-error"></span><span class="ant-badge-status-text">Alert</span></span>'
expect(received).toContain(expected)
})
it('getBadgeStatus() is called and the value returned is warning status with state equal Allocated', () => {
const propsData = {
text: 'Allocated',
displayText: true
}
const wrapper = factory({ props: propsData })
const received = wrapper.html()
const expected = '<span class="ant-badge ant-badge-status ant-badge-not-a-wrapper" style="display: inline-flex;"><span class="ant-badge-status-dot ant-badge-status-warning"></span><span class="ant-badge-status-text">Allocated</span></span>'
expect(received).toContain(expected)
})
it('getBadgeStatus() is called and the value returned is success status with state equal Allocated', () => {
const propsData = {
text: 'Allocated',
displayText: true
}
router = common.createMockRouter([{
name: 'testRouter1',
path: '/publicip',
meta: {
icon: 'test-router-1'
}
}])
router.push({ name: 'testRouter1' })
const wrapper = factory({ router: router, props: propsData })
const received = wrapper.html()
const expected = '<span class="ant-badge ant-badge-status ant-badge-not-a-wrapper" style="display: inline-flex;"><span class="ant-badge-status-dot ant-badge-status-success"></span><span class="ant-badge-status-text">Allocated</span></span>'
expect(received).toContain(expected)
})
it('getBadgeStatus() is called and the value returned is warning status with state equal Created', () => {
const propsData = {
text: 'Created',
displayText: true
}
const wrapper = factory({ props: propsData })
const received = wrapper.html()
const expected = '<span class="ant-badge ant-badge-status ant-badge-not-a-wrapper" style="display: inline-flex;"><span class="ant-badge-status-dot ant-badge-status-warning"></span><span class="ant-badge-status-text">Created</span></span>'
expect(received).toContain(expected)
})
})
describe('getTooltip()', () => {
it('getTooltip() is called with `$route.path` equal `/vmsnapshot`', () => {
const propsData = {
text: 'Active',
displayText: true
}
router = common.createMockRouter([{
name: 'testRouter1',
path: '/vmsnapshot',
meta: {
icon: 'test-router-1'
}
}])
router.push({ name: 'testRouter1' })
const wrapper = factory({ router: router, props: propsData })
const received = wrapper.html()
const expected = '<span class="ant-badge ant-badge-status ant-badge-not-a-wrapper" style="display: inline-flex;"><span class="ant-badge-status-dot ant-badge-status-success"></span><span class="ant-badge-status-text">Active</span></span>'
expect(received).toContain(expected)
})
it('getTooltip() is called with `$route.path` equal `/vm`', () => {
const propsData = {
text: 'Active',
displayText: true
}
router = common.createMockRouter([{
name: 'testRouter1',
path: '/vm',
meta: {
icon: 'test-router-1'
}
}])
router.push({ name: 'testRouter1' })
const wrapper = factory({ router: router, props: propsData })
const received = wrapper.html()
const expected = '<span class="ant-badge ant-badge-status ant-badge-not-a-wrapper" style="display: inline-flex;"><span class="ant-badge-status-dot ant-badge-status-success"></span><span class="ant-badge-status-text">Active</span></span>'
expect(received).toContain(expected)
})
it('getTooltip() is called with `$route.path` equal `/volume`', () => {
const propsData = {
text: 'Active',
displayText: true
}
router = common.createMockRouter([{
name: 'testRouter1',
path: '/volume',
meta: {
icon: 'test-router-1'
}
}])
router.push({ name: 'testRouter1' })
const wrapper = factory({ router: router, props: propsData })
const received = wrapper.html()
const expected = '<span class="ant-badge ant-badge-status ant-badge-not-a-wrapper" style="display: inline-flex;"><span class="ant-badge-status-dot ant-badge-status-success"></span><span class="ant-badge-status-text">Active</span></span>'
expect(received).toContain(expected)
})
it('getTooltip() is called with `$route.path` equal `/guestnetwork`', () => {
const propsData = {
text: 'Active',
displayText: true
}
router = common.createMockRouter([{
name: 'testRouter1',
path: '/guestnetwork',
meta: {
icon: 'test-router-1'
}
}])
router.push({ name: 'testRouter1' })
const wrapper = factory({ router: router, props: propsData })
const received = wrapper.html()
const expected = '<span class="ant-badge ant-badge-status ant-badge-not-a-wrapper" style="display: inline-flex;"><span class="ant-badge-status-dot ant-badge-status-success"></span><span class="ant-badge-status-text">Active</span></span>'
expect(received).toContain(expected)
})
it('getTooltip() is called with `$route.path` equal `/publicip`', () => {
const propsData = {
text: 'Active',
displayText: true
}
router = common.createMockRouter([{
name: 'testRouter1',
path: '/publicip',
meta: {
icon: 'test-router-1'
}
}])
router.push({ name: 'testRouter1' })
const wrapper = factory({ router: router, props: propsData })
const received = wrapper.html()
const expected = '<span class="ant-badge ant-badge-status ant-badge-not-a-wrapper" style="display: inline-flex;"><span class="ant-badge-status-dot ant-badge-status-success"></span><span class="ant-badge-status-text">Active</span></span>'
expect(received).toContain(expected)
})
})
})
})

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,686 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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 mockAxios from '../../../mock/mockAxios'
import MigrateWizard from '@/views/compute/MigrateWizard'
import common from '../../../common'
import mockData from '../../../mockData/MigrateWizard.mock'
jest.mock('axios', () => mockAxios)
let wrapper, i18n, store, mocks
const state = {}
const actions = {
AddAsyncJob: jest.fn((jobObject) => {})
}
mocks = {
$message: {
error: jest.fn((message) => {})
},
$notification: {
error: jest.fn((message) => {})
},
$pollJob: jest.fn((obj) => {
switch (obj.jobId) {
case 'test-job-id-case-1':
if ('successMethod' in obj) {
obj.successMethod()
}
break
case 'test-job-id-case-2':
if ('errorMethod' in obj) {
obj.errorMethod()
}
break
case 'test-job-id-case-3':
if ('catchMethod' in obj) {
obj.catchMethod()
}
break
}
})
}
i18n = common.createMockI18n('en', mockData.messages)
store = common.createMockStore(state, actions)
const factory = (opts = {}) => {
i18n = opts.i18n || i18n
store = opts.store || store
mocks = opts.mocks || mocks
return common.createFactory(MigrateWizard, {
i18n,
store,
mocks,
props: opts.props || {},
data: opts.data || {}
})
}
describe('Views > compute > MigrateWizard.vue', () => {
jest.spyOn(console, 'warn').mockImplementation(() => {})
beforeEach(() => {
jest.clearAllMocks()
if (wrapper) {
wrapper.destroy()
}
if (i18n.locale !== 'en') {
i18n.locale = 'en'
}
})
describe('Methods', () => {
describe('fetchData()', () => {
it('check api is called with resource is empty and searchQuery is null', () => {
const mockData = {
findhostsformigrationresponse: {
count: 0,
host: []
}
}
mockAxios.mockResolvedValue(mockData)
wrapper = factory({
props: {
resource: {}
}
})
wrapper.vm.$nextTick(() => {
expect(mockAxios).toHaveBeenCalled()
expect(mockAxios).toHaveBeenCalledWith({
url: '/',
method: 'GET',
data: new URLSearchParams(),
params: {
command: 'findHostsForMigration',
virtualmachineid: undefined,
keyword: '',
page: 1,
pagesize: 10,
response: 'json'
}
})
})
})
it('check api is called with resource.id is null and searchQuery is null', () => {
const mockData = {
findhostsformigrationresponse: {
count: 0,
host: []
}
}
mockAxios.mockResolvedValue(mockData)
wrapper = factory({
props: {
resource: { id: null }
}
})
wrapper.vm.$nextTick(() => {
expect(mockAxios).toHaveBeenCalled()
expect(mockAxios).toHaveBeenCalledWith({
url: '/',
method: 'GET',
data: new URLSearchParams(),
params: {
command: 'findHostsForMigration',
virtualmachineid: null,
keyword: '',
page: 1,
pagesize: 10,
response: 'json'
}
})
})
})
it('check api is called with resource.id is not null and searchQuery is null', () => {
const mockData = {
findhostsformigrationresponse: {
count: 0,
host: []
}
}
mockAxios.mockResolvedValue(mockData)
wrapper = factory({
props: {
resource: { id: 'test-id-value' }
}
})
wrapper.vm.$nextTick(() => {
expect(mockAxios).toHaveBeenCalled()
expect(mockAxios).toHaveBeenCalledWith({
url: '/',
method: 'GET',
data: new URLSearchParams(),
params: {
command: 'findHostsForMigration',
virtualmachineid: 'test-id-value',
keyword: '',
page: 1,
pagesize: 10,
response: 'json'
}
})
})
})
it('check api is called with resource.id is not null and searchQuery is not null', () => {
const mockData = {
findhostsformigrationresponse: {
count: 0,
host: []
}
}
mockAxios.mockResolvedValue(mockData)
wrapper = factory({
props: { resource: { id: 'test-id-value' } },
data: { searchQuery: 'test-query-value' }
})
wrapper.vm.$nextTick(() => {
expect(mockAxios).toHaveBeenCalled()
expect(mockAxios).toHaveBeenCalledWith({
url: '/',
method: 'GET',
data: new URLSearchParams(),
params: {
command: 'findHostsForMigration',
virtualmachineid: 'test-id-value',
keyword: 'test-query-value',
page: 1,
pagesize: 10,
response: 'json'
}
})
})
})
it('check api is called with params assign by resource, searchQuery, page, pageSize', () => {
const mockData = {
findhostsformigrationresponse: {
count: 0,
host: []
}
}
mockAxios.mockResolvedValue(mockData)
wrapper = factory({
props: { resource: { id: 'test-id-value' } },
data: {
searchQuery: 'test-query-value',
page: 2,
pageSize: 20
}
})
wrapper.vm.$nextTick(() => {
expect(mockAxios).toHaveBeenCalled()
expect(mockAxios).toHaveBeenCalledWith({
url: '/',
method: 'GET',
data: new URLSearchParams(),
params: {
command: 'findHostsForMigration',
virtualmachineid: 'test-id-value',
keyword: 'test-query-value',
page: 2,
pagesize: 20,
response: 'json'
}
})
})
})
it('check hosts, totalCount when api is called with response result is empty', async (done) => {
const mockData = {
findhostsformigrationresponse: {
count: 0,
host: []
}
}
mockAxios.mockResolvedValue(mockData)
wrapper = factory({ props: { resource: {} } })
await wrapper.vm.$nextTick()
setTimeout(() => {
expect(wrapper.vm.hosts).toEqual([])
expect(wrapper.vm.totalCount).toEqual(0)
done()
})
})
it('check hosts, totalCount when api is called with response result is not empty', async (done) => {
const mockData = {
findhostsformigrationresponse: {
count: 1,
host: [{
id: 'test-host-id',
name: 'test-host-name',
suitability: 'test-host-suitability',
cpuused: 'test-host-cpuused',
memused: 'test-host-memused',
select: 'test-host-select'
}]
}
}
mockAxios.mockResolvedValue(mockData)
wrapper = factory({ props: { resource: {} } })
await wrapper.vm.$nextTick()
setTimeout(() => {
expect(wrapper.vm.hosts).toEqual([{
id: 'test-host-id',
name: 'test-host-name',
suitability: 'test-host-suitability',
cpuused: 'test-host-cpuused',
memused: 'test-host-memused',
select: 'test-host-select'
}])
expect(wrapper.vm.totalCount).toEqual(1)
done()
})
})
it('check $message.error is called when api is called with throw error', async (done) => {
const mockError = 'Error: throw error message'
console.error = jest.fn()
mockAxios.mockRejectedValue(mockError)
wrapper = factory({ props: { resource: {} } })
await wrapper.vm.$nextTick()
setTimeout(() => {
expect(mocks.$message.error).toHaveBeenCalled()
expect(mocks.$message.error).toHaveBeenCalledWith(`${i18n.t('message.load.host.failed')}: ${mockError}`)
done()
})
})
})
describe('submitForm()', () => {
it('check api is called when selectedHost.requiresStorageMotion is true', async (done) => {
const mockData = {
migratevirtualmachineresponse: {
jobid: 'test-job-id'
},
queryasyncjobresultresponse: {
jobstatus: 1,
jobresult: {
name: 'test-name-value'
}
}
}
wrapper = factory({
props: {
resource: {
id: 'test-resource-id',
name: 'test-resource-name'
}
},
data: {
selectedHost: {
requiresStorageMotion: true,
id: 'test-host-id',
name: 'test-host-name'
}
}
})
jest.spyOn(wrapper.vm, 'fetchData').mockImplementation(() => {})
mockAxios.mockResolvedValue(mockData)
await wrapper.vm.$nextTick()
await wrapper.vm.submitForm()
setTimeout(() => {
expect(mockAxios).toHaveBeenCalled()
expect(mockAxios).toHaveBeenCalledWith({
url: '/',
method: 'GET',
data: new URLSearchParams(),
params: {
command: 'migrateVirtualMachineWithVolume',
hostid: 'test-host-id',
virtualmachineid: 'test-resource-id',
response: 'json'
}
})
done()
})
})
it('check api is called when selectedHost.requiresStorageMotion is false', async (done) => {
const mockData = {
migratevirtualmachineresponse: {
jobid: 'test-job-id'
},
queryasyncjobresultresponse: {
jobstatus: 1,
jobresult: {
name: 'test-name-value'
}
}
}
wrapper = factory({
props: {
resource: {
id: 'test-resource-id',
name: 'test-resource-name'
}
},
data: {
selectedHost: {
requiresStorageMotion: false,
id: 'test-host-id',
name: 'test-host-name'
}
}
})
jest.spyOn(wrapper.vm, 'fetchData').mockImplementation(() => {})
mockAxios.mockResolvedValue(mockData)
await wrapper.vm.$nextTick()
await wrapper.vm.submitForm()
setTimeout(() => {
expect(mockAxios).toHaveBeenCalled()
expect(mockAxios).toHaveBeenCalledWith({
url: '/',
method: 'GET',
data: new URLSearchParams(),
params: {
command: 'migrateVirtualMachine',
hostid: 'test-host-id',
virtualmachineid: 'test-resource-id',
response: 'json'
}
})
done()
})
})
it('check store dispatch `AddAsyncJob` and $pollJob have successMethod() is called with requiresStorageMotion is true', async (done) => {
const mockData = {
migratevirtualmachinewithvolumeresponse: {
jobid: 'test-job-id-case-1'
},
queryasyncjobresultresponse: {
jobstatus: 1,
jobresult: {
name: 'test-name-value'
}
}
}
wrapper = factory({
props: {
resource: {
id: 'test-resource-id',
name: 'test-resource-name'
}
},
data: {
selectedHost: {
requiresStorageMotion: true,
id: 'test-host-id',
name: 'test-host-name'
}
}
})
jest.spyOn(wrapper.vm, 'fetchData').mockImplementation(() => {})
mockAxios.mockResolvedValue(mockData)
await wrapper.vm.$nextTick()
await wrapper.vm.submitForm()
setTimeout(() => {
expect(actions.AddAsyncJob).toHaveBeenCalled()
expect(mocks.$pollJob).toHaveBeenCalled()
expect(wrapper.emitted()['close-action'][0]).toEqual([])
done()
})
})
it('check store dispatch `AddAsyncJob` and $pollJob have successMethod() is called with requiresStorageMotion is false', async (done) => {
const mockData = {
migratevirtualmachineresponse: {
jobid: 'test-job-id-case-2'
},
queryasyncjobresultresponse: {
jobstatus: 1,
jobresult: {
name: 'test-name-value'
}
}
}
wrapper = factory({
props: {
resource: {
id: 'test-resource-id',
name: 'test-resource-name'
}
},
data: {
selectedHost: {
requiresStorageMotion: false,
id: 'test-host-id',
name: 'test-host-name'
}
}
})
jest.spyOn(wrapper.vm, 'fetchData').mockImplementation(() => {})
mockAxios.mockResolvedValue(mockData)
await wrapper.vm.$nextTick()
await wrapper.vm.submitForm()
setTimeout(() => {
expect(actions.AddAsyncJob).toHaveBeenCalled()
expect(mocks.$pollJob).toHaveBeenCalled()
expect(wrapper.emitted()['close-action'][0]).toEqual([])
done()
})
})
it('check store dispatch `AddAsyncJob` and $pollJob have errorMethod() is called', async (done) => {
const mockData = {
migratevirtualmachinewithvolumeresponse: {
jobid: 'test-job-id-case-3'
},
queryasyncjobresultresponse: {
jobstatus: 2,
jobresult: {
errortext: 'test-error-message'
}
}
}
wrapper = factory({
props: {
resource: {
id: 'test-resource-id',
name: 'test-resource-name'
}
},
data: {
selectedHost: {
requiresStorageMotion: true,
id: 'test-host-id',
name: 'test-host-name'
}
}
})
jest.spyOn(wrapper.vm, 'fetchData').mockImplementation(() => {})
mockAxios.mockResolvedValue(mockData)
await wrapper.vm.$nextTick()
await wrapper.vm.submitForm()
setTimeout(() => {
expect(actions.AddAsyncJob).toHaveBeenCalled()
expect(mocks.$pollJob).toHaveBeenCalled()
expect(wrapper.emitted()['close-action'][0]).toEqual([])
done()
})
})
it('check store dispatch `AddAsyncJob` and $pollJob have catchMethod() is called', async (done) => {
const mockData = {
migratevirtualmachinewithvolumeresponse: {
jobid: 'test-job-id-case-4'
}
}
wrapper = factory({
props: {
resource: {
id: 'test-resource-id',
name: 'test-resource-name'
}
},
data: {
selectedHost: {
requiresStorageMotion: true,
id: 'test-host-id',
name: 'test-host-name'
}
}
})
jest.spyOn(wrapper.vm, 'fetchData').mockImplementation(() => {})
mockAxios.mockResolvedValue(mockData)
await wrapper.vm.$nextTick()
await wrapper.vm.submitForm()
setTimeout(() => {
expect(actions.AddAsyncJob).toHaveBeenCalled()
expect(mocks.$pollJob).toHaveBeenCalled()
expect(wrapper.emitted()['close-action'][0]).toEqual([])
done()
})
})
it('check $message.error is called when api is called with throw error', async (done) => {
const mockError = {
message: 'Error: throw error message'
}
wrapper = factory({
props: {
resource: {}
},
data: {
selectedHost: {
requiresStorageMotion: true,
id: 'test-host-id',
name: 'test-host-name'
}
}
})
jest.spyOn(wrapper.vm, 'fetchData').mockImplementation(() => {})
mockAxios.mockRejectedValue(mockError)
await wrapper.vm.$nextTick()
await wrapper.vm.submitForm()
setTimeout(() => {
expect(mocks.$notification.error).toHaveBeenCalled()
expect(mocks.$notification.error).toHaveBeenCalledWith({
message: i18n.t('message.request.failed'),
description: 'Error: throw error message',
duration: 0
})
done()
})
})
})
describe('handleChangePage()', () => {
it('check page, pageSize and fetchData() when handleChangePage() is called', () => {
wrapper = factory({
props: {
resource: {}
},
data: {
page: 1,
pageSize: 10
}
})
const spyFetchData = jest.spyOn(wrapper.vm, 'fetchData').mockImplementation(() => {})
wrapper.vm.$nextTick(() => {
wrapper.vm.handleChangePage(2, 20)
expect(wrapper.vm.page).toEqual(2)
expect(wrapper.vm.pageSize).toEqual(20)
expect(spyFetchData).toBeCalled()
})
})
})
describe('handleChangePageSize()', () => {
it('check page, pageSize and fetchData() when handleChangePageSize() is called', () => {
wrapper = factory({
props: {
resource: {}
},
data: {
page: 1,
pageSize: 10
}
})
const spyFetchData = jest.spyOn(wrapper.vm, 'fetchData').mockImplementation(() => {})
wrapper.vm.$nextTick(() => {
wrapper.vm.handleChangePageSize(2, 20)
expect(wrapper.vm.page).toEqual(2)
expect(wrapper.vm.pageSize).toEqual(20)
expect(spyFetchData).toBeCalled()
})
})
})
})
})