From 338de726655c96d4df2af28b78aadb2fddfe9bdc Mon Sep 17 00:00:00 2001 From: Hoang Nguyen Date: Wed, 20 Jan 2021 07:42:25 +0700 Subject: [PATCH] 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 --- ui/.babelrc | 7 + ui/.travis.yml | 1 + ui/babel.config.js | 11 +- ui/jest.config.js | 19 +- ui/package-lock.json | 63 +- ui/package.json | 4 + ui/src/components/view/ActionButton.vue | 14 +- ui/src/views/compute/MigrateWizard.vue | 8 +- ui/tests/{unit => }/.eslintrc.js | 0 ui/tests/common/index.js | 88 + ui/tests/mock/mockAxios.js | 22 + ui/tests/mock/mockI18n.js | 29 + ui/tests/mock/mockRouter.js | 64 + ui/tests/mock/mockStore.js | 50 + ui/tests/mockData/ActionButton.mock.json | 30 + ui/tests/mockData/AutogenView.mock.json | 198 ++ ui/tests/mockData/MigrateWizard.mock.json | 24 + ui/tests/mockData/Status.mock.json | 38 + ui/tests/setup.js | 49 + .../unit/components/view/ActionButton.spec.js | 337 ++ .../unit/components/widgets/Status.spec.js | 395 +++ ui/tests/unit/views/AutogenView.spec.js | 2939 +++++++++++++++++ .../unit/views/compute/MigrateWizard.spec.js | 686 ++++ 23 files changed, 5053 insertions(+), 23 deletions(-) create mode 100644 ui/.babelrc rename ui/tests/{unit => }/.eslintrc.js (100%) create mode 100644 ui/tests/common/index.js create mode 100644 ui/tests/mock/mockAxios.js create mode 100644 ui/tests/mock/mockI18n.js create mode 100644 ui/tests/mock/mockRouter.js create mode 100644 ui/tests/mock/mockStore.js create mode 100644 ui/tests/mockData/ActionButton.mock.json create mode 100644 ui/tests/mockData/AutogenView.mock.json create mode 100644 ui/tests/mockData/MigrateWizard.mock.json create mode 100644 ui/tests/mockData/Status.mock.json create mode 100644 ui/tests/setup.js create mode 100644 ui/tests/unit/components/view/ActionButton.spec.js create mode 100644 ui/tests/unit/components/widgets/Status.spec.js create mode 100644 ui/tests/unit/views/AutogenView.spec.js create mode 100644 ui/tests/unit/views/compute/MigrateWizard.spec.js diff --git a/ui/.babelrc b/ui/.babelrc new file mode 100644 index 00000000000..39fd9996751 --- /dev/null +++ b/ui/.babelrc @@ -0,0 +1,7 @@ +{ + "env": { + "test": { + "plugins": ["require-context-hook"] + } + } +} \ No newline at end of file diff --git a/ui/.travis.yml b/ui/.travis.yml index b90555be82a..02a2c81e1a4 100644 --- a/ui/.travis.yml +++ b/ui/.travis.yml @@ -22,3 +22,4 @@ cache: npm: false script: - npm run lint && npm run build + - npm run test:unit diff --git a/ui/babel.config.js b/ui/babel.config.js index a5fbcb83fee..68a21dbdacd 100644 --- a/ui/babel.config.js +++ b/ui/babel.config.js @@ -15,10 +15,11 @@ // specific language governing permissions and limitations // under the License. -module.exports = { +const babelConfig = { presets: [ '@vue/app' - ] + ], + plugins: [] // if your use import on Demand, Use this code // , // plugins: [ @@ -29,3 +30,9 @@ module.exports = { // } ] // ] } + +if (process.env.NODE_ENV === 'test') { + babelConfig.plugins.push('require-context-hook') +} + +module.exports = babelConfig diff --git a/ui/jest.config.js b/ui/jest.config.js index 1c3356b0e89..7fcb5df4c79 100644 --- a/ui/jest.config.js +++ b/ui/jest.config.js @@ -16,6 +16,8 @@ // under the License. module.exports = { + testURL: 'http://localhost/', + setupFiles: ['/tests/setup.js'], moduleFileExtensions: [ 'js', 'jsx', @@ -24,11 +26,13 @@ module.exports = { ], transform: { '^.+\\.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' }, moduleNameMapper: { - '^@/(.*)$': '/src/$1' + '.+\\.svg?.+$': 'jest-transform-stub', + '^@/(.*)$': '/src/$1', + '^@public/(.*)$': '/public/$1' }, snapshotSerializers: [ 'jest-serializer-vue' @@ -36,5 +40,14 @@ module.exports = { testMatch: [ '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)' ], - testURL: 'http://localhost/' + transformIgnorePatterns: [ + '/node_modules/(?!ant-design-vue|vue)' + ], + collectCoverage: true, + collectCoverageFrom: [ + '/src/**/*.{js,vue}', + '!**/node_modules/**', + '!/src/locales/*.{js, json}' + ], + coverageReporters: ['html', 'text-summary'] } diff --git a/ui/package-lock.json b/ui/package-lock.json index 2b40790b1df..00113b83cd2 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -3803,6 +3803,7 @@ "vue-loader": "^15.9.2", "vue-style-loader": "^4.1.2", "webpack": "^4.0.0", + "webpack-bundle-analyzer": "^3.8.0", "webpack-chain": "^6.4.0", "webpack-dev-server": "^3.11.0", "webpack-merge": "^4.2.2" @@ -10190,9 +10191,9 @@ } }, "duplexer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", - "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", "dev": true }, "duplexer3": { @@ -18055,9 +18056,9 @@ } }, "opener": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.1.tgz", - "integrity": "sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", "dev": true }, "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": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/webpack-chain/-/webpack-chain-6.5.1.tgz", diff --git a/ui/package.json b/ui/package.json index afe12fb11d1..4b9aab8f7ba 100644 --- a/ui/package.json +++ b/ui/package.json @@ -39,6 +39,7 @@ "ant-design-vue": "~1.6.2", "antd-theme-webpack-plugin": "^1.3.4", "axios": "^0.19.2", + "babel-plugin-require-context-hook": "^1.0.0", "core-js": "^3.6.5", "enquire.js": "^2.1.6", "js-cookie": "^2.2.1", @@ -153,6 +154,9 @@ "autoprefixer": {} } }, + "jest": { + "testEnvironment": "node" + }, "browserslist": [ "> 1%", "last 2 versions", diff --git a/ui/src/components/view/ActionButton.vue b/ui/src/components/view/ActionButton.vue index df36a16ceb7..570dae1b205 100644 --- a/ui/src/components/view/ActionButton.vue +++ b/ui/src/components/view/ActionButton.vue @@ -80,7 +80,7 @@ export default { }, data () { return { - actionBadge: [] + actionBadge: {} } }, mounted () { @@ -132,7 +132,7 @@ export default { this.$emit('exec-action', action) }, handleShowBadge () { - const dataBadge = {} + this.actionBadge = {} const arrAsync = [] 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 } @@ -170,12 +170,10 @@ export default { Promise.all(arrAsync).then(response => { for (let j = 0; j < response.length; j++) { - this.$set(dataBadge, response[j].api, {}) - this.$set(dataBadge[response[j].api], 'badgeNum', response[j].count) + this.$set(this.actionBadge, response[j].api, {}) + this.$set(this.actionBadge[response[j].api], 'badgeNum', response[j].count) } - }) - - this.actionBadge = dataBadge + }).catch(() => {}) } } } diff --git a/ui/src/views/compute/MigrateWizard.vue b/ui/src/views/compute/MigrateWizard.vue index 87c4d638fe3..46346e941a1 100644 --- a/ui/src/views/compute/MigrateWizard.vue +++ b/ui/src/views/compute/MigrateWizard.vue @@ -164,19 +164,19 @@ export default { jobId: jobid, successMessage: `${this.$t('message.success.migrating')} ${this.resource.name}`, successMethod: () => { - this.$parent.$parent.close() + this.$emit('close-action') }, errorMessage: this.$t('message.migrating.failed'), errorMethod: () => { - this.$parent.$parent.close() + this.$emit('close-action') }, loadingMessage: `${this.$t('message.migrating.processing')} ${this.resource.name}`, catchMessage: this.$t('error.fetching.async.job.result'), catchMethod: () => { - this.$parent.$parent.close() + this.$emit('close-action') } }) - this.$parent.$parent.close() + this.$emit('close-action') }).catch(error => { this.$notification.error({ message: this.$t('message.request.failed'), diff --git a/ui/tests/unit/.eslintrc.js b/ui/tests/.eslintrc.js similarity index 100% rename from ui/tests/unit/.eslintrc.js rename to ui/tests/.eslintrc.js diff --git a/ui/tests/common/index.js b/ui/tests/common/index.js new file mode 100644 index 00000000000..544c66eeff6 --- /dev/null +++ b/ui/tests/common/index.js @@ -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 +} diff --git a/ui/tests/mock/mockAxios.js b/ui/tests/mock/mockAxios.js new file mode 100644 index 00000000000..9463ecf1c17 --- /dev/null +++ b/ui/tests/mock/mockAxios.js @@ -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 diff --git a/ui/tests/mock/mockI18n.js b/ui/tests/mock/mockI18n.js new file mode 100644 index 00000000000..869b6aa5a8e --- /dev/null +++ b/ui/tests/mock/mockI18n.js @@ -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 diff --git a/ui/tests/mock/mockRouter.js b/ui/tests/mock/mockRouter.js new file mode 100644 index 00000000000..206393470aa --- /dev/null +++ b/ui/tests/mock/mockRouter.js @@ -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 diff --git a/ui/tests/mock/mockStore.js b/ui/tests/mock/mockStore.js new file mode 100644 index 00000000000..6d94906f0fb --- /dev/null +++ b/ui/tests/mock/mockStore.js @@ -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 diff --git a/ui/tests/mockData/ActionButton.mock.json b/ui/tests/mockData/ActionButton.mock.json new file mode 100644 index 00000000000..c0934d00b3e --- /dev/null +++ b/ui/tests/mockData/ActionButton.mock.json @@ -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" + } + } + ] +} \ No newline at end of file diff --git a/ui/tests/mockData/AutogenView.mock.json b/ui/tests/mockData/AutogenView.mock.json new file mode 100644 index 00000000000..d1df58fccba --- /dev/null +++ b/ui/tests/mockData/AutogenView.mock.json @@ -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" + } +} \ No newline at end of file diff --git a/ui/tests/mockData/MigrateWizard.mock.json b/ui/tests/mockData/MigrateWizard.mock.json new file mode 100644 index 00000000000..0c581f17109 --- /dev/null +++ b/ui/tests/mockData/MigrateWizard.mock.json @@ -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" + } + } +} \ No newline at end of file diff --git a/ui/tests/mockData/Status.mock.json b/ui/tests/mockData/Status.mock.json new file mode 100644 index 00000000000..179cb63dc61 --- /dev/null +++ b/ui/tests/mockData/Status.mock.json @@ -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" + } + } +} \ No newline at end of file diff --git a/ui/tests/setup.js b/ui/tests/setup.js new file mode 100644 index 00000000000..0f532ff483a --- /dev/null +++ b/ui/tests/setup.js @@ -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 diff --git a/ui/tests/unit/components/view/ActionButton.spec.js b/ui/tests/unit/components/view/ActionButton.spec.js new file mode 100644 index 00000000000..db7bd816684 --- /dev/null +++ b/ui/tests/unit/components/view/ActionButton.spec.js @@ -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 = '' + const wrapper = factory() + + wrapper.vm.$nextTick(() => { + const received = wrapper.html() + + expect(received).not.toContain(expected) + }) + }) + + it('Normal button action is show', () => { + const expected = '' + 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 = '' + 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) + }) + }) + }) +}) diff --git a/ui/tests/unit/components/widgets/Status.spec.js b/ui/tests/unit/components/widgets/Status.spec.js new file mode 100644 index 00000000000..a2de5efe7d3 --- /dev/null +++ b/ui/tests/unit/components/widgets/Status.spec.js @@ -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 = '' + + 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 = 'Running' + + 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 = 'Stopped' + + 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 = 'Starting' + + 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 = 'Stopping' + + 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 = 'Suspended' + + 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 = 'Pending' + + 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 = 'Expunging' + + 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 = 'Error' + + 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 = '' + + 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 = '' + + 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 = '' + + 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 = '' + + 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 = 'Alert' + + 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 = 'Allocated' + + 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 = 'Allocated' + + 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 = 'Created' + + 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 = 'Active' + + 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 = 'Active' + + 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 = 'Active' + + 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 = 'Active' + + 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 = 'Active' + + expect(received).toContain(expected) + }) + }) + }) +}) diff --git a/ui/tests/unit/views/AutogenView.spec.js b/ui/tests/unit/views/AutogenView.spec.js new file mode 100644 index 00000000000..66c347bb0a2 --- /dev/null +++ b/ui/tests/unit/views/AutogenView.spec.js @@ -0,0 +1,2939 @@ +// 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 AutogenView from '@/views/AutogenView' +import user from '@/store/modules/user' +import common from '../../common' +import mockData from '../../mockData/AutogenView.mock.json' + +jest.mock('axios', () => mockAxios) +user.state.apis = mockData.apis + +let router, store, i18n, mocks + +const state = { + user: { + apis: mockData.apis, + info: mockData.info + } +} + +store = common.createMockStore(state) +i18n = common.createMockI18n('en', mockData.messages) + +const actions = { + AddAsyncJob: jest.fn((jobId) => {}) +} +const spyConsole = { + log: null, + warn: null +} +mocks = { + $notifyError: jest.fn((error) => { + return error + }), + $notification: { + info: jest.fn((option) => { + return { + message: option.message, + description: 'test-description', + duration: option.duration + } + }), + success: jest.fn((option) => { + return { + message: option.message, + description: option.description + } + }) + }, + $message: { + success: jest.fn((obj) => { + return obj + }) + } +} + +const factory = (opts = {}) => { + router = opts.router || router + i18n = opts.i18n || i18n + store = opts.store || store + mocks = opts.mocks || mocks + + return common.createFactory(AutogenView, { + router, + i18n, + store, + mocks, + props: opts.props || {}, + data: opts.data || {} + }) +} + +describe('Views > AutogenView.vue', () => { + let wrapper + + beforeEach(() => { + jest.clearAllMocks() + + if (wrapper) wrapper.destroy() + if (router && router.currentRoute.name !== 'home') { + router.replace({ name: 'home' }) + } + state.user.info.roletype = 'Normal' + if (i18n.locale !== 'en') i18n.locale = 'en' + if (spyConsole.log) { + spyConsole.log.mockClear() + spyConsole.log.mockRestore() + } + if (spyConsole.warn) { + spyConsole.warn.mockClear() + spyConsole.warn.mockRestore() + } + }) + + describe('Navigation Guard', () => { + it('check beforeRouteUpdate() is called', () => { + router = common.createMockRouter([{ + name: 'testRouter1', + path: '/test-router-1', + meta: { + icon: 'test-router-1' + } + }]) + wrapper = factory({ router: router }) + router.push({ name: 'testRouter1' }) + + const beforeRouteUpdate = wrapper.vm.$options.beforeRouteUpdate + const nextFun = jest.fn() + + beforeRouteUpdate[0].call(wrapper.vm, {}, {}, nextFun) + + wrapper.vm.$nextTick(() => { + expect(wrapper.vm.currentPath).toEqual('/test-router-1') + expect(nextFun).toHaveBeenCalled() + }) + }) + + it('check beforeRouteLeave() is called', () => { + router = common.createMockRouter([{ + name: 'testRouter1', + path: '/test-router-1', + meta: { + icon: 'test-router-1' + } + }]) + wrapper = factory({ router: router }) + router.push({ name: 'testRouter1' }) + + const beforeRouteLeave = wrapper.vm.$options.beforeRouteLeave + const nextFun = jest.fn() + + beforeRouteLeave[0].call(wrapper.vm, {}, {}, nextFun) + + wrapper.vm.$nextTick(() => { + expect(wrapper.vm.currentPath).toEqual('/test-router-1') + expect(nextFun).toHaveBeenCalled() + }) + }) + }) + + describe('Watchers', () => { + describe('$route', () => { + it('The wrapper data does not change when $router do not change', () => { + wrapper = factory() + + const spy = jest.spyOn(wrapper.vm, 'fetchData') + + wrapper.setData({ + page: 2, + itemCount: 10 + }) + + wrapper.vm.$nextTick(() => { + expect(wrapper.vm.page).toEqual(2) + expect(wrapper.vm.itemCount).toEqual(10) + expect(spy).not.toBeCalled() + }) + }) + + it('The wrapper data changes when $router changes', () => { + router = common.createMockRouter([{ + name: 'testRouter2', + path: '/test-router-2', + meta: { + icon: 'test-router-2' + } + }]) + wrapper = factory({ router: router }) + + const spy = jest.spyOn(wrapper.vm, 'fetchData') + + wrapper.setData({ + page: 2, + itemCount: 10 + }) + + router.push({ name: 'testRouter2' }) + + wrapper.vm.$nextTick(() => { + expect(wrapper.vm.page).toEqual(1) + expect(wrapper.vm.itemCount).toEqual(0) + expect(spy).toBeCalled() + }) + }) + }) + + describe('$i18n.locale', () => { + it('Test language and fetchData() when not changing locale', () => { + wrapper = factory() + + const spy = jest.spyOn(wrapper.vm, 'fetchData') + + wrapper.vm.$nextTick(() => { + expect(wrapper.vm.$t('labelname')).toEqual('test-name-en') + expect(spy).not.toBeCalled() + }) + }) + + it('Test languages and fetchData() when changing locale', async () => { + wrapper = factory() + + i18n.locale = 'de' + const spy = jest.spyOn(wrapper.vm, 'fetchData') + + wrapper.vm.$nextTick(() => { + expect(wrapper.vm.$t('labelname')).toEqual('test-name-de') + expect(spy).toBeCalled() + }) + }) + }) + }) + + describe('Methods', () => { + describe('fetchData()', () => { + it('check routeName when fetchData() is called with $route.name is not empty', () => { + router = common.createMockRouter([{ + name: 'testRouter1', + path: '/test-router-1', + meta: { + icon: 'test-router-1' + } + }]) + wrapper = factory({ router: router }) + + router.push({ name: 'testRouter1' }) + + wrapper.vm.$nextTick(() => { + expect(wrapper.vm.routeName).toEqual('testRouter1') + expect(wrapper.vm.items).toEqual([]) + }) + }) + + it('check routeName when fetchData() is called with $route.name is empty', () => { + router = common.createMockRouter([{ + path: '/test-router-3', + meta: { + icon: 'test-router-3' + } + }]) + wrapper = factory({ router: router }) + + router.replace('/test-router-3') + + wrapper.vm.$nextTick(() => { + expect(wrapper.vm.routeName).toEqual('home') + }) + }) + + it('check resource, dataView when fetchData() is called with $route.meta.params is not empty', () => { + router = common.createMockRouter([{ + name: 'testRouter4', + path: '/test-router-4/:id', + meta: { + icon: 'test-router-4' + } + }]) + wrapper = factory({ router: router }) + + router.push({ name: 'testRouter4', params: { id: 'test-id' } }) + + wrapper.vm.$nextTick(() => { + expect(wrapper.vm.resource).toEqual({}) + expect(wrapper.vm.dataView).toBeTruthy() + }) + }) + + it('check columnKeys, actions when fetchData() is called with $route.meta.actions, route.meta.columns is not empty', () => { + router = common.createMockRouter([{ + name: 'testRouter5', + path: '/test-router-5', + meta: { + icon: 'test-router-5', + permission: ['testApiNameCase1'], + columns: ['column1', 'column2', 'column3'], + actions: [ + { + name: 'labelname', + icon: 'plus', + listView: true + } + ] + } + }]) + wrapper = factory({ router: router }) + const mockData = { + testapinamecase1response: { + count: 0, + testapinamecase1: [] + } + } + + mockAxios.mockImplementation(() => Promise.resolve(mockData)) + router.push({ name: 'testRouter5' }) + + wrapper.vm.$nextTick(() => { + expect(wrapper.vm.columnKeys.length).toEqual(3) + expect(wrapper.vm.actions.length).toEqual(1) + expect(wrapper.vm.columnKeys).toEqual(['column1', 'column2', 'column3']) + expect(wrapper.vm.actions).toEqual([{ + name: 'labelname', + icon: 'plus', + listView: true + }]) + }) + }) + + it('check columnKeys assign by store.getters.apis when fetchData() is called', () => { + router = common.createMockRouter([{ + name: 'testRouter6', + path: 'test-router-6', + meta: { + icon: 'test-router-6', + permission: ['testApiNameCase4'] + } + }]) + wrapper = factory({ router: router }) + + const mockData = { + testapinamecase4response: { + count: 0, + testapinamecase4: [] + } + } + + mockAxios.mockImplementation(() => Promise.resolve(mockData)) + router.push({ name: 'testRouter6' }) + + wrapper.vm.$nextTick(() => { + expect(wrapper.vm.columnKeys.length).toEqual(3) + expect(wrapper.vm.columnKeys).toEqual(['column1', 'column2', 'column3']) + }) + }) + + it('check columnKeys assign by $route.meta.columns when fetchData() is called', () => { + router = common.createMockRouter([{ + name: 'testRouter7', + path: 'test-router-7', + meta: { + icon: 'test-router-7', + permission: ['testApiNameCase1'], + columns: [{ name: 'string' }] + } + }]) + wrapper = factory({ router: router }) + + const mockData = { + testapinamecase1response: { + count: 0, + testapinamecase1: [] + } + } + + mockAxios.mockImplementation(() => Promise.resolve(mockData)) + router.push({ name: 'testRouter7' }) + + wrapper.vm.$nextTick(() => { + expect(wrapper.vm.columns.length).toEqual(1) + expect(wrapper.vm.columns[0].title).toEqual('name-en') + expect(wrapper.vm.columns[0].dataIndex).toEqual('name') + expect(wrapper.vm.columns[0].scopedSlots).toEqual({ customRender: 'name' }) + expect(typeof wrapper.vm.columns[0].sorter).toBe('function') + }) + }) + + it('check api is called with params assign by $route.query', (done) => { + router = common.createMockRouter([{ + name: 'testRouter8', + path: '/test-router-8', + meta: { + icon: 'test-router-8', + permission: ['testApiNameCase2'] + } + }]) + wrapper = factory({ router: router }) + + const postData = new URLSearchParams() + const mockData = { + testapinamecase2response: { + count: 0, + testapinamecase2: [] + } + } + + mockAxios.mockImplementation(() => Promise.resolve(mockData)) + router.push({ name: 'testRouter8', query: { key: 'test-value' } }) + + wrapper.vm.$nextTick(() => { + expect(mockAxios).toHaveBeenCalledTimes(1) + expect(mockAxios).toHaveBeenCalledWith({ + data: postData, + method: 'GET', + params: { + command: 'testApiNameCase2', + listall: true, + key: 'test-value', + page: 1, + pagesize: 20, + response: 'json' + }, + url: '/' + }) + + done() + }) + }) + + it('check api is called with params assign by $route.meta.params', (done) => { + router = common.createMockRouter([{ + name: 'testRouter9', + path: '/test-router-9', + meta: { + icon: 'test-router-9', + permission: ['testApiNameCase3'], + params: { + key: 'test-value' + } + } + }]) + wrapper = factory({ router: router }) + + const postData = new URLSearchParams() + const mockData = { + testapinamecase3response: { + count: 0, + testapinamecase3: [] + } + } + + mockAxios.mockImplementation(() => Promise.resolve(mockData)) + router.push({ name: 'testRouter9' }) + + wrapper.vm.$nextTick(() => { + expect(mockAxios).toHaveBeenCalledTimes(1) + expect(mockAxios).toHaveBeenCalledWith({ + data: postData, + method: 'GET', + params: { + command: 'testApiNameCase3', + listall: true, + key: 'test-value', + page: 1, + pagesize: 20, + response: 'json' + }, + url: '/' + }) + + done() + }) + }) + + it('check api is called with params has item id, name when $route.path startWith /ssh/', (done) => { + router = common.createMockRouter([{ + name: 'testRouter17', + path: '/ssh/:id', + meta: { + icon: 'test-router-17', + permission: ['testApiNameCase1'] + } + }]) + wrapper = factory({ router: router }) + + const mockData = { + testapinamecase1response: { + count: 0, + testapinamecase1: [] + } + } + + router.push({ name: 'testRouter17', params: { id: 'test-id' } }) + mockAxios.mockResolvedValue(mockData) + + wrapper.vm.$nextTick(() => { + expect(mockAxios).toHaveBeenCalledTimes(1) + expect(mockAxios).toHaveBeenCalledWith({ + url: '/', + method: 'GET', + data: new URLSearchParams(), + params: { + command: 'testApiNameCase1', + listall: true, + id: 'test-id', + name: 'test-id', + page: 1, + pagesize: 20, + response: 'json' + } + }) + + done() + }) + }) + + it('check api is called with params has item id, hostname when $route.path startWith /ldapsetting/', (done) => { + router = common.createMockRouter([{ + name: 'testRouter18', + path: '/ldapsetting/:id', + meta: { + icon: 'test-router-18', + permission: ['testApiNameCase1'] + } + }]) + wrapper = factory({ router: router }) + + const mockData = { + testapinamecase1response: { + count: 0, + testapinamecase1: [] + } + } + + router.push({ name: 'testRouter18', params: { id: 'test-id' } }) + mockAxios.mockResolvedValue(mockData) + + wrapper.vm.$nextTick(() => { + expect(mockAxios).toHaveBeenCalledTimes(1) + expect(mockAxios).toHaveBeenCalledWith({ + url: '/', + method: 'GET', + data: new URLSearchParams(), + params: { + command: 'testApiNameCase1', + listall: true, + id: 'test-id', + hostname: 'test-id', + page: 1, + pagesize: 20, + response: 'json' + } + }) + + done() + }) + }) + + it('check items, resource when api is called with result is not empty', (done) => { + router = common.createMockRouter([{ + name: 'testRouter19', + path: '/templates', + meta: { + icon: 'test-router-19', + permission: ['listTemplates'] + } + }]) + wrapper = factory({ router: router }) + + const mockData = { + listtemplatesresponse: { + count: 2, + templates: [{ + id: 'uuid1', + templateid: 'templateid-1', + name: 'template-test-1' + }, { + id: 'uuid2', + templateid: 'templateid-2', + name: 'template-test-2' + }] + } + } + + router.push({ name: 'testRouter19' }) + mockAxios.mockResolvedValue(mockData) + + setTimeout(() => { + expect(mockAxios).toHaveBeenCalledTimes(1) + expect(mockAxios).toHaveBeenLastCalledWith({ + url: '/', + method: 'GET', + data: new URLSearchParams(), + params: { + command: 'listTemplates', + listall: true, + page: 1, + pagesize: 20, + response: 'json' + } + }) + + expect(wrapper.vm.items.length).toEqual(2) + expect(wrapper.vm.items).toEqual([ + { + id: 'uuid1', + templateid: 'templateid-1', + name: 'template-test-1', + key: 0 + }, + { + id: 'uuid2', + templateid: 'templateid-2', + name: 'template-test-2', + key: 1 + } + ]) + expect(wrapper.vm.resource).toEqual({ + id: 'uuid1', + templateid: 'templateid-1', + name: 'template-test-1', + key: 0 + }) + + done() + }) + }) + + it('check items, resource when api is called and $route.meta.columns has function', (done) => { + router = common.createMockRouter([{ + name: 'testRouter20', + path: '/test-router-20', + meta: { + icon: 'test-router-20', + permission: ['testApiNameCase1'], + columns: [ + 'id', + 'name', + { + column1: (record) => { + return record.name + } + } + ] + } + }]) + wrapper = factory({ router: router }) + + const mockData = { + testapinamecase1response: { + count: 1, + testapinamecase1: [{ + id: 'test-id', + name: 'test-name-value' + }] + } + } + + router.push({ name: 'testRouter20' }) + mockAxios.mockResolvedValue(mockData) + + setTimeout(() => { + expect(wrapper.vm.items).toEqual([{ + id: 'test-id', + name: 'test-name-value', + key: 0, + column1: 'test-name-value' + }]) + expect(wrapper.vm.resource).toEqual({ + id: 'test-id', + name: 'test-name-value', + key: 0, + column1: 'test-name-value' + }) + + done() + }) + }) + + it('check items, resource when api is called and $route.path startWith /ssh', (done) => { + router = common.createMockRouter([{ + name: 'testRouter21', + path: '/ssh', + meta: { + icon: 'test-router-21', + permission: ['testApiNameCase1'] + } + }]) + wrapper = factory({ router: router }) + + const mockData = { + testapinamecase1response: { + count: 1, + testapinamecase1: [{ + name: 'test-name-value' + }] + } + } + + router.push({ name: 'testRouter21' }) + mockAxios.mockResolvedValue(mockData) + + setTimeout(() => { + expect(wrapper.vm.items).toEqual([{ + id: 'test-name-value', + name: 'test-name-value', + key: 0 + }]) + expect(wrapper.vm.resource).toEqual({ + id: 'test-name-value', + name: 'test-name-value', + key: 0 + }) + + done() + }) + }) + + it('check items, resource when api is called and $route.path startWith /ldapsetting', (done) => { + router = common.createMockRouter([{ + name: 'testRouter22', + path: '/ldapsetting', + meta: { + icon: 'test-router-22', + permission: ['testApiNameCase1'] + } + }]) + wrapper = factory({ router: router }) + + const mockData = { + testapinamecase1response: { + count: 1, + testapinamecase1: [{ + name: 'test-name-value', + hostname: 'test-hostname-value' + }] + } + } + + router.push({ name: 'testRouter22' }) + mockAxios.mockResolvedValue(mockData) + + setTimeout(() => { + expect(wrapper.vm.items).toEqual([{ + id: 'test-hostname-value', + name: 'test-name-value', + hostname: 'test-hostname-value', + key: 0 + }]) + expect(wrapper.vm.resource).toEqual({ + id: 'test-hostname-value', + name: 'test-name-value', + hostname: 'test-hostname-value', + key: 0 + }) + + done() + }) + }) + + it('check $notifyError is called when api is called with throw error', (done) => { + router = common.createMockRouter([{ + name: 'testRouter22', + path: '/test-router-22', + meta: { + icon: 'test-router-22', + permission: ['testApiNameCase1'] + } + }]) + + wrapper = factory({ router: router }) + + const errorMock = { + response: {}, + message: 'Error: throw exception error' + } + router.push({ name: 'testRouter22' }) + mockAxios.mockRejectedValue(errorMock) + + setTimeout(() => { + expect(mocks.$notifyError).toHaveBeenCalledTimes(1) + expect(mocks.$notifyError).toHaveBeenCalledWith(errorMock) + done() + }) + }) + + it('check $notifyError is called and router path = /exception/404 when api is called with throw error', (done) => { + router = common.createMockRouter([{ + name: 'testRouter23', + path: '/test-router-23', + meta: { + icon: 'test-router-23', + permission: ['testApiNameCase1'] + } + }]) + + wrapper = factory({ router: router }) + + const errorMock = { + response: { + status: 430 + }, + message: 'Error: Request Header Fields Too Large' + } + router.push({ name: 'testRouter23' }) + mockAxios.mockRejectedValue(errorMock) + + setTimeout(() => { + expect(mocks.$notifyError).toHaveBeenCalledTimes(1) + expect(mocks.$notifyError).toHaveBeenCalledWith(errorMock) + expect(router.currentRoute.path).toEqual('/exception/404') + + done() + }) + }) + + it('check $notifyError is called and router path = /exception/500 when api is called with throw error', (done) => { + router = common.createMockRouter([{ + name: 'testRouter23', + path: '/test-router-23', + meta: { + icon: 'test-router-23', + permission: ['testApiNameCase1'] + } + }]) + + wrapper = factory({ router: router }) + + const errorMock = { + response: { + status: 530 + }, + message: 'Error: Site is frozen' + } + router.push({ name: 'testRouter23' }) + mockAxios.mockRejectedValue(errorMock) + + setTimeout(() => { + expect(mocks.$notifyError).toHaveBeenCalledTimes(1) + expect(mocks.$notifyError).toHaveBeenCalledWith(errorMock) + expect(router.currentRoute.path).toEqual('/exception/500') + + done() + }) + }) + }) + + describe('onSearch()', () => { + it('check fetchData() is called when onSearch() is called', async () => { + router = common.createMockRouter([{ + name: 'testRouter24', + path: '/test-router-24', + meta: { + icon: 'test-router-24' + } + }]) + wrapper = factory({ router: router }) + router.push({ name: 'testRouter24', query: { page: 1, pagesize: 20 } }) + const spy = jest.spyOn(wrapper.vm, 'fetchData') + + await wrapper.vm.$nextTick() + wrapper.vm.onSearch() + expect(spy).toHaveBeenCalled() + }) + + it('check onSearch() is called with searchParams have item', async () => { + router = common.createMockRouter([{ + name: 'testRouter24', + path: '/test-router-24', + meta: { + icon: 'test-router-24' + } + }]) + wrapper = factory({ router: router }) + + router.push({ name: 'testRouter24' }) + await wrapper.vm.$nextTick() + wrapper.vm.onSearch() + expect(router.currentRoute.query).toEqual({ + page: 1, + pagesize: 20 + }) + }) + + it('check onSearch() is called with searchQuery not in opts', async () => { + router = common.createMockRouter([{ + name: 'testRouter25', + path: '/test-router-25', + meta: { + icon: 'test-router-25' + } + }]) + wrapper = factory({ router: router }) + + router.push({ name: 'testRouter25' }) + await wrapper.vm.$nextTick() + wrapper.vm.onSearch({ + key1: 'key1-value' + }) + expect(router.currentRoute.query).toEqual({ + key1: 'key1-value', + page: 1, + pagesize: 20 + }) + }) + + it('check onSearch() is called with searchQuery in opts but this is empty', async () => { + router = common.createMockRouter([{ + name: 'testRouter26', + path: '/test-router-26', + meta: { + icon: 'test-router-26' + }, + query: {} + }]) + wrapper = factory({ router: router }) + + router.push({ name: 'testRouter26' }) + await wrapper.vm.$nextTick() + wrapper.vm.onSearch({ + searchQuery: null + }) + expect(router.currentRoute.query).toEqual({ + page: 1, + pagesize: 20 + }) + }) + + it('check onSearch() is called with searchQuery in opts', async () => { + router = common.createMockRouter([{ + name: 'testRouter26', + path: '/test-router-26', + meta: { + icon: 'test-router-26' + }, + query: {} + }]) + wrapper = factory({ router: router }) + + router.push({ name: 'testRouter26' }) + await wrapper.vm.$nextTick() + wrapper.vm.onSearch({ + searchQuery: 'test-query' + }) + expect(router.currentRoute.query).toEqual({ + keyword: 'test-query', + q: 'test-query', + page: 1, + pagesize: 20 + }) + }) + + it('check onSearch() is called with searchQuery in opts and route.name equal `role`', async () => { + router = common.createMockRouter([{ + name: 'role', + path: '/test-router-26', + meta: { + icon: 'test-router-26' + }, + query: {} + }]) + wrapper = factory({ router: router }) + + router.push({ name: 'role' }) + await wrapper.vm.$nextTick() + wrapper.vm.onSearch({ + searchQuery: 'test-query' + }) + expect(router.currentRoute.query).toEqual({ + name: 'test-query', + q: 'test-query', + page: 1, + pagesize: 20 + }) + }) + + it('check onSearch() is called with searchQuery in opts and route.name equal `templatetype`', async () => { + router = common.createMockRouter([{ + name: 'quotaemailtemplate', + path: '/test-router-26', + meta: { + icon: 'test-router-26' + }, + query: {} + }]) + wrapper = factory({ router: router }) + + router.push({ name: 'quotaemailtemplate' }) + await wrapper.vm.$nextTick() + wrapper.vm.onSearch({ + searchQuery: 'test-query' + }) + expect(router.currentRoute.query).toEqual({ + templatetype: 'test-query', + q: 'test-query', + page: 1, + pagesize: 20 + }) + }) + + it('check onSearch() is called with searchQuery in opts and route.name equal `globalsetting`', async () => { + router = common.createMockRouter([{ + name: 'globalsetting', + path: '/test-router-26', + meta: { + icon: 'test-router-26' + }, + query: {} + }]) + wrapper = factory({ router: router }) + + router.push({ name: 'globalsetting' }) + await wrapper.vm.$nextTick() + wrapper.vm.onSearch({ + searchQuery: 'test-query' + }) + expect(router.currentRoute.query).toEqual({ + name: 'test-query', + q: 'test-query', + page: 1, + pagesize: 20 + }) + }) + }) + + describe('closeAction()', () => { + it('check currentAction, showAction when closeAction() is called', () => { + const data = { + currentAction: { + loading: true + }, + showAction: true + } + wrapper = factory({ data: data }) + + expect(wrapper.vm.currentAction).toEqual({ loading: true }) + expect(wrapper.vm.showAction).toBeTruthy() + + wrapper.vm.$nextTick(() => { + wrapper.vm.closeAction() + + expect(wrapper.vm.currentAction).toEqual({}) + expect(wrapper.vm.showAction).toBeFalsy() + }) + }) + }) + + describe('execAction()', () => { + it('check showAction, actionData and router name when execAction() is called', () => { + router = common.createMockRouter([{ + name: 'testRouter26', + path: '/test-router-26', + meta: { + icon: 'test-router-26' + } + }]) + const data = { + actionData: { + name: 'test-add-action' + } + } + wrapper = factory({ router: router, data: data }) + + expect(router.currentRoute.name).toEqual('home') + + wrapper.vm.$nextTick(() => { + wrapper.vm.execAction({ + label: 'labelname', + icon: 'plus', + component: () => jest.fn(), + api: 'testRouter26', + popup: false + }) + + expect(wrapper.vm.showAction).toBeFalsy() + expect(router.currentRoute.name).toEqual('testRouter26') + }) + }) + + it('check currentAction params and paramsField when execAction() is called', () => { + wrapper = factory() + + wrapper.vm.$nextTick(() => { + wrapper.vm.execAction({ + api: 'testApiNameCase5' + }) + + expect(wrapper.vm.currentAction.params).toEqual([ + { name: 'id', type: 'string' }, + { name: 'name', type: 'string' }, + { name: 'column1', type: 'string' }, + { name: 'column2', type: 'string' }, + { name: 'column3', type: 'string' } + ]) + expect(wrapper.vm.currentAction.paramFields).toEqual([]) + expect(wrapper.vm.showAction).toBeTruthy() + }) + }) + + it('check currentAction params and paramsField when execAction() is called with args is exists', () => { + wrapper = factory() + + wrapper.vm.$nextTick(() => { + wrapper.vm.execAction({ + api: 'testApiNameCase5', + args: ['column1', 'column2', 'column3'] + }) + + expect(wrapper.vm.currentAction.params).toEqual([ + { name: 'column1', type: 'string' }, + { name: 'column2', type: 'string' }, + { name: 'column3', type: 'string' }, + { name: 'name', type: 'string' }, + { name: 'id', type: 'string' } + ]) + expect(wrapper.vm.currentAction.paramFields).toEqual([ + { name: 'column1', type: 'string' }, + { name: 'column2', type: 'string' }, + { name: 'column3', type: 'string' } + ]) + expect(wrapper.vm.showAction).toBeTruthy() + }) + }) + + it('check currentAction params and paramsField when execAction() is called with args is function', () => { + wrapper = factory() + + wrapper.vm.$nextTick(() => { + wrapper.vm.execAction({ + api: 'testApiNameCase5', + resource: { id: 'test-id-value', name: 'test-name-value' }, + args: (record, store) => { + return ['Admin'].includes(store.userInfo.roletype) ? ['column1', 'column2', 'column3'] : ['id', 'name'] + } + }) + + expect(wrapper.vm.currentAction.params).toEqual([ + { name: 'id', type: 'string' }, + { name: 'name', type: 'string' }, + { name: 'column1', type: 'string' }, + { name: 'column2', type: 'string' }, + { name: 'column3', type: 'string' } + ]) + expect(wrapper.vm.currentAction.paramFields).toEqual([ + { name: 'id', type: 'string' }, + { name: 'name', type: 'string' } + ]) + expect(wrapper.vm.showAction).toBeTruthy() + }) + }) + + it('check currentAction paramsField and listUuidOpts() is called when execAction() is called', () => { + wrapper = factory() + const spy = jest.spyOn(wrapper.vm, 'listUuidOpts') + + wrapper.vm.$nextTick(() => { + wrapper.vm.execAction({ + api: 'testApiNameCase6', + args: ['id', 'tags', 'column1', 'column2', 'account'], + mapping: { + column2: () => { + return 'test-value' + } + } + }) + + expect(wrapper.vm.currentAction.paramFields).toEqual([ + { name: 'id', type: 'uuid' }, + { name: 'tags', type: 'string' }, + { name: 'column1', type: 'list' }, + { name: 'column2', type: 'string' }, + { name: 'account', type: 'string' } + ]) + expect(wrapper.vm.showAction).toBeTruthy() + expect(spy).toHaveBeenCalled() + expect(spy).toHaveBeenCalledWith({ name: 'id', type: 'uuid' }) + expect(spy).toHaveBeenCalledWith({ name: 'column1', type: 'list' }) + expect(spy).toHaveBeenCalledWith({ name: 'column2', type: 'string' }) + expect(spy).toHaveBeenCalledWith({ name: 'account', type: 'string' }) + }) + }) + + it('check fillEditFormFieldValues() is called when execAction() is called', () => { + wrapper = factory() + const spy = jest.spyOn(wrapper.vm, 'fillEditFormFieldValues') + + wrapper.vm.$nextTick(() => { + wrapper.vm.execAction({ + api: 'testApiNameCase6', + dataView: true, + icon: 'edit' + }) + + expect(spy).toHaveBeenCalled() + }) + }) + + it('check currentAction paramFields when execAction() is called args has confirmpassword field', () => { + wrapper = factory() + + wrapper.vm.$nextTick(() => { + wrapper.vm.execAction({ + api: 'testApiNameCase6', + args: ['confirmpassword'], + mapping: {} + }) + + expect(wrapper.vm.currentAction.paramFields).toEqual([ + { name: 'confirmpassword', type: 'password', required: true, description: 'confirmpassword-description-en' } + ]) + }) + }) + }) + + describe('listUuidOpts()', () => { + it('check api not called when listUuidOpts() is called with currentAction.mapping.id is null', (done) => { + wrapper = factory({ + data: { + currentAction: { + mapping: { + id: () => { return '' } + } + } + } + }) + + wrapper.vm.listUuidOpts({ name: 'id', type: 'uuid' }) + + setTimeout(() => { + expect(mockAxios).not.toHaveBeenCalled() + done() + }) + }) + + it('check api not called when listUuidOpts() is called with currentAction.mapping is empty', (done) => { + wrapper = factory({ + data: { + currentAction: { + mapping: {} + } + } + }) + + wrapper.vm.listUuidOpts({ name: 'test-name', type: 'uuid' }) + + setTimeout(() => { + expect(mockAxios).not.toHaveBeenCalled() + done() + }) + }) + + it('check api is called and param.opts when listUuidOpts() is called with currentAction.mapping[param.name].api', (done) => { + const param = { name: 'template', type: 'uuid' } + const mockData = { + testapinamecase1response: { + count: 1, + testapinamecase1: [{ + id: 'test-id-value', + name: 'test-name-value' + }] + } + } + + wrapper = factory({ + data: { + currentAction: { + mapping: { + template: { + api: 'testApiNameCase1' + } + } + } + } + }) + + mockAxios.mockResolvedValue(mockData) + wrapper.vm.listUuidOpts(param) + + setTimeout(() => { + expect(mockAxios).toHaveBeenCalledTimes(1) + expect(mockAxios).toHaveBeenCalledWith({ + url: '/', + method: 'GET', + data: new URLSearchParams(), + params: { + command: 'testApiNameCase1', + listall: true, + response: 'json' + } + }) + expect(param).toEqual({ + name: 'template', + type: 'uuid', + loading: false, + opts: [{ + id: 'test-id-value', + name: 'test-name-value' + }] + }) + + done() + }) + }) + + it('check api is called when listUuidOpts() is called with store apis has api startWith param.name', (done) => { + const param = { name: 'testapiname', type: 'uuid' } + const mockData = { + listtestapinamesresponse: { + count: 1, + testapiname: [{ + id: 'test-id-value', + name: 'test-name-value' + }] + } + } + + wrapper = factory() + + mockAxios.mockResolvedValue(mockData) + wrapper.vm.listUuidOpts(param) + + setTimeout(() => { + expect(mockAxios).toHaveBeenCalledTimes(1) + expect(mockAxios).toHaveBeenCalledWith({ + url: '/', + method: 'GET', + data: new URLSearchParams(), + params: { + command: 'listTestApiNames', + listall: true, + response: 'json' + } + }) + expect(param).toEqual({ + name: 'testapiname', + type: 'uuid', + loading: false, + opts: [{ + id: 'test-id-value', + name: 'test-name-value' + }] + }) + + done() + }) + }) + + it('check api is called with params has item name and value assign by resource', (done) => { + const param = { name: 'template', type: 'uuid' } + const mockData = { + testapinamecase1response: { + count: 0, + testapinamecase1: [{ + id: 'test-id-value', + name: 'test-name-value' + }] + } + } + + wrapper = factory({ + data: { + currentAction: { + mapping: { + template: { + api: 'testApiNameCase1', + params: (record) => { + return { + name: record.name + } + } + } + } + } + } + }) + + mockAxios.mockResolvedValue(mockData) + wrapper.setData({ + resource: { + id: 'test-id-value', + name: 'test-name-value' + } + }) + wrapper.vm.listUuidOpts(param) + + setTimeout(() => { + expect(mockAxios).toHaveBeenCalledTimes(1) + expect(mockAxios).toHaveBeenCalledWith({ + url: '/', + method: 'GET', + data: new URLSearchParams(), + params: { + command: 'testApiNameCase1', + listall: true, + name: 'test-name-value', + response: 'json' + } + }) + expect(param).toEqual({ + name: 'template', + type: 'uuid', + loading: false, + opts: [{ + id: 'test-id-value', + name: 'test-name-value' + }] + }) + + done() + }) + }) + + it('check api is called with params has item templatefilter when apiName is listTemplates', (done) => { + const param = { name: 'id', type: 'uuid' } + const mockData = { + listtemplateresponse: { + count: 1, + templates: [{ + id: 'test-id-value', + name: 'test-name-value' + }] + } + } + + wrapper = factory() + + mockAxios.mockResolvedValue(mockData) + wrapper.setData({ + apiName: 'listTemplates' + }) + wrapper.vm.listUuidOpts(param) + + setTimeout(() => { + expect(mockAxios).toHaveBeenCalledTimes(1) + expect(mockAxios).toHaveBeenCalledWith({ + url: '/', + method: 'GET', + data: new URLSearchParams(), + params: { + command: 'listTemplates', + listall: true, + templatefilter: 'executable', + response: 'json' + } + }) + expect(param).toEqual({ + name: 'id', + type: 'uuid', + loading: false, + opts: [{ + id: 'test-id-value', + name: 'test-name-value' + }] + }) + + done() + }) + }) + + it('check api is called with params has item isofilter when apiName is listIsos', (done) => { + const param = { name: 'id', type: 'uuid' } + const mockData = { + listisosresponse: { + count: 1, + iso: [{ + id: 'test-id-value', + name: 'test-name-value' + }] + } + } + + wrapper = factory() + + mockAxios.mockResolvedValue(mockData) + wrapper.setData({ + apiName: 'listIsos' + }) + wrapper.vm.listUuidOpts(param) + + setTimeout(() => { + expect(mockAxios).toHaveBeenCalledTimes(1) + expect(mockAxios).toHaveBeenCalledWith({ + url: '/', + method: 'GET', + data: new URLSearchParams(), + params: { + command: 'listIsos', + listall: true, + isofilter: 'executable', + response: 'json' + } + }) + expect(param).toEqual({ + name: 'id', + type: 'uuid', + loading: false, + opts: [{ + id: 'test-id-value', + name: 'test-name-value' + }] + }) + + done() + }) + }) + + it('check api is called with params has item type = routing when apiName is listHosts', (done) => { + const param = { name: 'id', type: 'uuid' } + const mockData = { + listhostresponse: { + count: 1, + hosts: [{ + id: 'test-id-value', + name: 'test-name-value' + }] + } + } + + wrapper = factory() + + mockAxios.mockResolvedValue(mockData) + wrapper.setData({ + apiName: 'listHosts' + }) + wrapper.vm.listUuidOpts(param) + + setTimeout(() => { + expect(mockAxios).toHaveBeenCalledTimes(1) + expect(mockAxios).toHaveBeenCalledWith({ + url: '/', + method: 'GET', + data: new URLSearchParams(), + params: { + command: 'listHosts', + listall: true, + type: 'routing', + response: 'json' + } + }) + expect(param).toEqual({ + name: 'id', + type: 'uuid', + loading: false, + opts: [{ + id: 'test-id-value', + name: 'test-name-value' + }] + }) + + done() + }) + }) + + it('check api is called and param.opts is empty when api throw error', (done) => { + const param = { name: 'id', type: 'uuid', loading: true } + const errorMock = { + response: {}, + stack: 'Error: throw exception error' + } + + wrapper = factory() + + spyConsole.log = jest.spyOn(console, 'log').mockImplementation(() => {}) + mockAxios.mockRejectedValue(errorMock) + wrapper.setData({ + apiName: 'testApiNameCase1' + }) + wrapper.vm.listUuidOpts(param) + + setTimeout(() => { + expect(mockAxios).toHaveBeenCalledTimes(1) + expect(mockAxios).toHaveBeenCalledWith({ + url: '/', + method: 'GET', + data: new URLSearchParams(), + params: { + command: 'testApiNameCase1', + listall: true, + response: 'json' + } + }) + expect(param).toEqual({ + name: 'id', + type: 'uuid', + loading: false, + opts: [] + }) + + done() + }) + }) + }) + + describe('pollActionCompletion()', () => { + it('check $notification, fetchData() when pollActionCompletion() is called with action is empty', (done) => { + const mockData = { + queryasyncjobresultresponse: { + jobstatus: 1, + jobresult: { + name: 'test-name-value' + } + } + } + + wrapper = factory() + + const jobId = 'test-job-id' + const action = {} + const spy = jest.spyOn(wrapper.vm, 'fetchData') + + mockAxios.mockResolvedValue(mockData) + wrapper.vm.pollActionCompletion(jobId, action) + + setTimeout(() => { + expect(spy).toHaveBeenCalled() + expect(mocks.$notification.info).not.toHaveBeenCalled() + expect(mockAxios).toHaveBeenCalled() + expect(mockAxios).toHaveBeenCalledWith({ + url: '/', + method: 'GET', + data: new URLSearchParams(), + params: { + command: 'queryAsyncJobResult', + jobId: jobId, + response: 'json' + } + }) + + done() + }) + }) + + it('check $notification, fetchData() when pollActionCompletion() is called with action is not empty', (done) => { + const mockData = { + queryasyncjobresultresponse: { + jobstatus: 1, + jobresult: { + name: 'test-name-value' + } + } + } + + wrapper = factory() + + const jobId = 'test-job-id' + const action = { + label: 'labelname', + response: (jobResult) => { + return jobResult.name + } + } + const spy = jest.spyOn(wrapper.vm, 'fetchData') + + mockAxios.mockResolvedValue(mockData) + wrapper.vm.pollActionCompletion(jobId, action) + + setTimeout(() => { + expect(spy).toHaveBeenCalled() + expect(mocks.$notification.info).toHaveBeenCalled() + expect(mocks.$notification.info).toHaveLastReturnedWith({ + message: 'test-name-en', + description: 'test-description', + duration: 0 + }) + expect(mockAxios).toHaveBeenCalled() + expect(mockAxios).toHaveBeenCalledWith({ + url: '/', + method: 'GET', + data: new URLSearchParams(), + params: { + command: 'queryAsyncJobResult', + jobId: jobId, + response: 'json' + } + }) + + done() + }) + }) + + it('check fetchData() is called when $pollJob error response', (done) => { + const mockData = { + queryasyncjobresultresponse: { + jobstatus: 2, + jobresult: { + errortext: 'test-error-message' + } + } + } + + wrapper = factory() + + const jobId = 'test-job-id' + const action = { + label: 'labelname', + response: (jobResult) => { + return jobResult.name + } + } + const spy = jest.spyOn(wrapper.vm, 'fetchData') + + mockAxios.mockResolvedValue(mockData) + wrapper.vm.pollActionCompletion(jobId, action) + + setTimeout(() => { + expect(spy).toHaveBeenCalled() + expect(mockAxios).toHaveBeenCalled() + expect(mockAxios).toHaveBeenCalledWith({ + url: '/', + method: 'GET', + data: new URLSearchParams(), + params: { + command: 'queryAsyncJobResult', + jobId: jobId, + response: 'json' + } + }) + + done() + }) + }) + }) + + describe('fillEditFormFieldValues()', () => { + it('check form getFieldDecorator() is called and formModel when currentAction.paramFields has item type = list', (done) => { + wrapper = factory({ + data: { + currentAction: { + paramFields: [ + { name: 'domainids', type: 'list' } + ], + mapping: { + column1: () => { return 'test-column' } + } + }, + resource: { + domainname: ['test-domain-value-1', 'test-domain-value-2'] + } + } + }) + + const spy = jest.spyOn(wrapper.vm.form, 'getFieldDecorator') + + wrapper.vm.fillEditFormFieldValues() + + wrapper.vm.$nextTick(() => { + expect(spy).toHaveBeenCalled() + expect(spy).toBeCalledWith('domainids', { + initialValue: ['test-domain-value-1', 'test-domain-value-2'] + }) + expect(wrapper.vm.formModel).toEqual({ domainids: ['test-domain-value-1', 'test-domain-value-2'] }) + + done() + }) + }) + + it('check form getFieldDecorator() is called and formModel when currentAction.paramFields has item name = account', (done) => { + wrapper = factory({ + data: { + currentAction: { + paramFields: [ + { name: 'account', type: 'string' } + ], + mapping: { + column1: () => { return 'test-column' } + } + }, + resource: { + account: 'test-account-value' + } + } + }) + + const spy = jest.spyOn(wrapper.vm.form, 'getFieldDecorator') + + wrapper.vm.fillEditFormFieldValues() + + wrapper.vm.$nextTick(() => { + expect(spy).toHaveBeenCalled() + expect(spy).toBeCalledWith('account', { + initialValue: 'test-account-value' + }) + expect(wrapper.vm.formModel).toEqual({ account: 'test-account-value' }) + + done() + }) + }) + + it('check form getFieldDecorator() is called and formModel when currentAction.paramFields has item exists in currentAction. mapping', (done) => { + wrapper = factory({ + data: { + currentAction: { + paramFields: [ + { name: 'column1', type: 'string' } + ], + mapping: { + column1: () => { return 'test-column' } + } + }, + resource: { + column1: 'test-column-value' + } + } + }) + + const spy = jest.spyOn(wrapper.vm.form, 'getFieldDecorator') + + wrapper.vm.fillEditFormFieldValues() + + wrapper.vm.$nextTick(() => { + expect(spy).toHaveBeenCalled() + expect(spy).toBeCalledWith('column1', { + initialValue: 'test-column-value' + }) + expect(wrapper.vm.formModel).toEqual({ column1: 'test-column-value' }) + + done() + }) + }) + + it('check form getFieldDecorator() is called and formModel when currentAction.paramFields has item exists in resource', (done) => { + wrapper = factory({ + data: { + currentAction: { + paramFields: [ + { name: 'column1', type: 'string' } + ] + }, + resource: { + column1: 'test-column-value' + } + } + }) + + const spy = jest.spyOn(wrapper.vm.form, 'getFieldDecorator') + + wrapper.vm.$nextTick(() => { + wrapper.vm.fillEditFormFieldValues() + + expect(spy).toHaveBeenCalled() + expect(spy).toBeCalledWith('column1', { + initialValue: 'test-column-value' + }) + expect(wrapper.vm.formModel).toEqual({ column1: 'test-column-value' }) + + done() + }) + }) + + it('check form getFieldDecorator() is called and formModel when currentAction.paramFields have not item in resource', (done) => { + wrapper = factory({ + data: { + currentAction: { + paramFields: [ + { name: 'column1', type: 'string' } + ] + }, + resource: {} + } + }) + + const spy = jest.spyOn(wrapper.vm.form, 'getFieldDecorator') + + wrapper.vm.$nextTick(() => { + wrapper.vm.fillEditFormFieldValues() + + expect(spy).not.toHaveBeenCalled() + expect(wrapper.vm.formModel).toEqual({}) + + done() + }) + }) + + it('check form getFieldDecorator() is not called when field value is null', (done) => { + wrapper = factory({ + data: { + currentAction: { + paramFields: [ + { name: 'column1', type: 'string' } + ] + }, + resource: { + column1: null + } + } + }) + + const spy = jest.spyOn(wrapper.vm.form, 'getFieldDecorator') + + wrapper.vm.$nextTick(() => { + wrapper.vm.fillEditFormFieldValues() + + expect(spy).not.toHaveBeenCalled() + + done() + }) + }) + }) + + describe('changeFilter()', () => { + it('check `route.query` when changeFilter() is called with filter', async () => { + wrapper = factory() + + await wrapper.vm.$nextTick() + wrapper.vm.changeFilter('test') + + expect(router.currentRoute.query).toEqual({ + filter: 'test', + page: 1, + pagesize: 20 + }) + }) + + it('check `route.query` when changeFilter() is called with `$route.name` equal `template`', async () => { + router = common.createMockRouter([{ + name: 'template', + path: '/test-router-1', + meta: { + icon: 'test' + } + }]) + wrapper = factory({ router: router }) + + router.push({ name: 'template' }) + await wrapper.vm.$nextTick() + wrapper.vm.changeFilter('test') + + expect(router.currentRoute.query).toEqual({ + templatefilter: 'test', + filter: 'test', + page: 1, + pagesize: 20 + }) + }) + + it('check `route.query` when changeFilter() is called with `$route.name` equal `iso`', async () => { + router = common.createMockRouter([{ + name: 'iso', + path: '/test-router-1', + meta: { + icon: 'test' + } + }]) + wrapper = factory({ router: router }) + + router.push({ name: 'iso' }) + await wrapper.vm.$nextTick() + wrapper.vm.changeFilter('test') + + expect(router.currentRoute.query).toEqual({ + isofilter: 'test', + filter: 'test', + page: 1, + pagesize: 20 + }) + }) + + it('check `route.query` when changeFilter() is called with `$route.name` equal `vm` and `filter` equal `self`', async () => { + router = common.createMockRouter([{ + name: 'vm', + path: '/test-router-1', + meta: { + icon: 'test' + } + }]) + wrapper = factory({ router: router }) + + router.push({ name: 'vm' }) + await wrapper.vm.$nextTick() + wrapper.vm.changeFilter('self') + + expect(router.currentRoute.query).toEqual({ + account: 'test-account', + domainid: 'test-domain-id', + filter: 'self', + page: 1, + pagesize: 20 + }) + }) + + it('check `route.query` when changeFilter() is called with `$route.name` equal `vm` and `filter` equal `running`', async () => { + router = common.createMockRouter([{ + name: 'vm', + path: '/test-router-1', + meta: { + icon: 'test' + } + }]) + wrapper = factory({ router: router }) + + router.push({ name: 'vm' }) + await wrapper.vm.$nextTick() + wrapper.vm.changeFilter('running') + + expect(router.currentRoute.query).toEqual({ + state: 'running', + filter: 'running', + page: 1, + pagesize: 20 + }) + }) + }) + + describe('changePage()', () => { + it('check page, pageSize when changePage() is called', () => { + wrapper = factory() + + wrapper.vm.$nextTick(() => { + expect(router.currentRoute.query).toEqual({}) + wrapper.vm.changePage(1, 10) + expect(router.currentRoute.query).toEqual({ + page: 1, + pagesize: 10 + }) + }) + }) + }) + + describe('changePageSize()', () => { + it('check page, pageSize and fetchData() when changePageSize() is called', () => { + wrapper = factory() + + wrapper.vm.$nextTick(() => { + expect(router.currentRoute.query).toEqual({ + page: 1, + pagesize: 10 + }) + wrapper.vm.changePageSize(2, 20) + expect(router.currentRoute.query).toEqual({ + page: 2, + pagesize: 20 + }) + }) + }) + }) + + describe('start()', () => { + it('check loading, selectedRowKeys, fetchData() when start() is called', async (done) => { + wrapper = factory() + + const spy = jest.spyOn(wrapper.vm, 'fetchData') + await wrapper.vm.$nextTick() + await wrapper.vm.start() + + expect(spy).toBeCalled() + + setTimeout(() => { + expect(wrapper.vm.loading).toBeFalsy() + expect(wrapper.vm.selectedRowKeys).toEqual([]) + + done() + }, 1000) + }) + }) + + describe('toggleLoading()', () => { + it('check loading when toggleLoading() is called', () => { + wrapper = factory({ + data: { + loading: false + } + }) + + wrapper.vm.$nextTick(() => { + expect(wrapper.vm.loading).toBeFalsy() + + wrapper.vm.toggleLoading() + + expect(wrapper.vm.loading).toBeTruthy() + }) + }) + }) + + describe('startLoading()', () => { + it('check loading when startLoading() is called', () => { + wrapper = factory({ + data: { + loading: false + } + }) + + wrapper.vm.$nextTick(() => { + expect(wrapper.vm.loading).toBeFalsy() + + wrapper.vm.startLoading() + + expect(wrapper.vm.loading).toBeTruthy() + }) + }) + }) + + describe('finishLoading()', () => { + it('check loading when finishLoading() is called', () => { + wrapper = factory({ + data: { + loading: true + } + }) + + wrapper.vm.$nextTick(() => { + expect(wrapper.vm.loading).toBeTruthy() + + wrapper.vm.finishLoading() + + expect(wrapper.vm.loading).toBeFalsy() + }) + }) + }) + + describe('execSubmit()', () => { + it('check error from validateFields', (done) => { + wrapper = factory({ + data: { + showAction: true, + currentAction: { + loading: false, + label: 'labelname', + params: [ + { name: 'id', type: 'uuid' } + ], + paramFields: [ + { name: 'id', type: 'uuid', description: '', required: true } + ], + mapping: {} + }, + resource: { + id: 'test-id-value' + } + } + }) + + spyConsole.warn = jest.spyOn(console, 'warn').mockImplementation(() => {}) + spyConsole.log = jest.spyOn(console, 'log').mockImplementation(() => {}) + + wrapper.vm.$nextTick(() => { + const event = document.createEvent('Event') + wrapper.vm.execSubmit(event) + + expect(mockAxios).not.toBeCalled() + done() + }) + }) + + it('check api is called with params has item id equal resource.id', (done) => { + wrapper = factory({ + data: { + showAction: true, + currentAction: { + api: 'testApiNameCase1', + loading: false, + label: 'labelname', + params: [ + { name: 'id', type: 'uuid' } + ], + paramFields: [ + { name: 'id', type: 'uuid', description: '', required: false } + ], + mapping: {} + }, + resource: { + id: 'test-id-value' + } + } + }) + + const mockData = { + testapinamecase1response: { + testapinamecase1: {} + } + } + mockAxios.mockResolvedValue(mockData) + spyConsole.log = jest.spyOn(console, 'log').mockImplementation(() => {}) + + wrapper.vm.$nextTick(() => { + const event = document.createEvent('Event') + wrapper.vm.execSubmit(event) + + expect(mockAxios).toHaveBeenCalledTimes(1) + expect(mockAxios).toHaveBeenCalledWith({ + url: '/', + method: 'GET', + data: new URLSearchParams(), + params: { + command: 'testApiNameCase1', + id: 'test-id-value', + response: 'json' + } + }) + + done() + }) + }) + + it('check api is called when form has input key not exist in currentAction.params', (done) => { + wrapper = factory({ + data: { + showAction: true, + currentAction: { + api: 'testApiNameCase1', + loading: false, + label: 'labelname', + params: [ + { name: 'id', type: 'uuid' } + ], + paramFields: [ + { name: 'name', type: 'string', description: '', required: false }, + { name: 'id', type: 'uuid', description: '', required: false } + ], + mapping: {} + }, + resource: { + id: 'test-id-value' + } + } + }) + + const mockData = { + testapinamecase1response: { + testapinamecase1: {} + } + } + mockAxios.mockResolvedValue(mockData) + spyConsole.log = jest.spyOn(console, 'log').mockImplementation(() => {}) + + wrapper.vm.$nextTick(() => { + const event = document.createEvent('Event') + wrapper.vm.execSubmit(event) + + expect(mockAxios).toHaveBeenCalledTimes(1) + expect(mockAxios).toHaveBeenCalledWith({ + url: '/', + method: 'GET', + data: new URLSearchParams(), + params: { + command: 'testApiNameCase1', + id: 'test-id-value', + response: 'json' + } + }) + + done() + }) + }) + + it('check api is called when form has input key exist in currentAction.params, type is boolean and value is undefined', (done) => { + wrapper = factory({ + data: { + showAction: true, + currentAction: { + api: 'testApiNameCase1', + loading: false, + label: 'labelname', + params: [ + { name: 'column1', type: 'boolean' } + ], + paramFields: [ + { name: 'column1', type: 'boolean', description: '', required: false } + ], + mapping: {} + }, + resource: {} + } + }) + + const mockData = { + testapinamecase1response: { + testapinamecase1: {} + } + } + mockAxios.mockResolvedValue(mockData) + spyConsole.log = jest.spyOn(console, 'log').mockImplementation(() => {}) + + wrapper.vm.$nextTick(() => { + const event = document.createEvent('Event') + wrapper.vm.execSubmit(event) + + expect(mockAxios).toHaveBeenCalledTimes(1) + expect(mockAxios).toHaveBeenCalledWith({ + url: '/', + method: 'GET', + data: new URLSearchParams(), + params: { + command: 'testApiNameCase1', + column1: false, + response: 'json' + } + }) + + done() + }) + }) + + it('check api is called when form has input key exist in currentAction.params, type is boolean and value is null', (done) => { + wrapper = factory({ + data: { + showAction: true, + currentAction: { + api: 'testApiNameCase1', + loading: false, + label: 'labelname', + params: [ + { name: 'column1', type: 'boolean' } + ], + paramFields: [ + { name: 'column1', type: 'boolean', description: '', required: false } + ], + mapping: {} + }, + resource: {} + } + }) + + const mockData = { + testapinamecase1response: { + testapinamecase1: {} + } + } + mockAxios.mockResolvedValue(mockData) + spyConsole.log = jest.spyOn(console, 'log').mockImplementation(() => {}) + + wrapper.vm.$nextTick(() => { + wrapper.vm.form.getFieldDecorator('column1', { initialValue: null }) + const event = document.createEvent('Event') + wrapper.vm.execSubmit(event) + + expect(mockAxios).toHaveBeenCalledTimes(1) + expect(mockAxios).toHaveBeenCalledWith({ + url: '/', + method: 'GET', + data: new URLSearchParams(), + params: { + command: 'testApiNameCase1', + column1: false, + response: 'json' + } + }) + + done() + }) + }) + + it('check api is called when form has input key exist in currentAction.mapping', (done) => { + wrapper = factory({ + data: { + showAction: true, + currentAction: { + api: 'testApiNameCase1', + loading: false, + label: 'labelname', + params: [ + { name: 'column1', type: 'list' } + ], + paramFields: [ + { name: 'column1', type: 'list', description: '', required: false } + ], + mapping: { + column1: { + options: ['column-value1', 'column-value2'] + } + } + }, + resource: {} + } + }) + + const mockData = { + testapinamecase1response: { + testapinamecase1: {} + } + } + mockAxios.mockResolvedValue(mockData) + spyConsole.log = jest.spyOn(console, 'log').mockImplementation(() => {}) + + wrapper.vm.$nextTick(() => { + wrapper.vm.form.getFieldDecorator('column1', { initialValue: 1 }) + const event = document.createEvent('Event') + wrapper.vm.execSubmit(event) + + expect(mockAxios).toHaveBeenCalledTimes(1) + expect(mockAxios).toHaveBeenCalledWith({ + url: '/', + method: 'GET', + data: new URLSearchParams(), + params: { + command: 'testApiNameCase1', + column1: 'column-value2', + response: 'json' + } + }) + + done() + }) + }) + + it('check api is called when form has input key not exist in currentAction.mapping, type is list and currentAction.params[input] has id', (done) => { + wrapper = factory({ + data: { + showAction: true, + currentAction: { + api: 'testApiNameCase1', + loading: false, + label: 'labelname', + params: [ + { + name: 'column1', + type: 'list', + opts: [ + { id: 'test-id-1', value: 'test-value-1' }, + { id: 'test-id-2', value: 'test-value-2' }, + { id: 'test-id-3', value: 'test-value-3' } + ] + } + ], + paramFields: [ + { name: 'column1', type: 'list', description: '', required: false } + ], + mapping: { + } + }, + resource: {} + } + }) + + const mockData = { + testapinamecase1response: { + testapinamecase1: {} + } + } + mockAxios.mockResolvedValue(mockData) + spyConsole.log = jest.spyOn(console, 'log').mockImplementation(() => {}) + + wrapper.vm.$nextTick(() => { + wrapper.vm.form.getFieldDecorator('column1', { initialValue: [1, 2] }) + const event = document.createEvent('Event') + wrapper.vm.execSubmit(event) + + expect(mockAxios).toHaveBeenCalledTimes(1) + expect(mockAxios).toHaveBeenCalledWith({ + url: '/', + method: 'GET', + data: new URLSearchParams(), + params: { + command: 'testApiNameCase1', + column1: 'test-id-2,test-id-3', + response: 'json' + } + }) + + done() + }) + }) + + it('check api is called when form has input key has name = account, currentAction.api = createAccount', (done) => { + wrapper = factory({ + data: { + showAction: true, + currentAction: { + api: 'createAccount', + loading: false, + label: 'labelname', + params: [ + { + name: 'account', + type: 'string' + } + ], + paramFields: [ + { name: 'account', type: 'string', description: '', required: false } + ], + mapping: {} + }, + resource: {} + } + }) + + const mockData = { + testapinamecase1response: { + testapinamecase1: {} + } + } + mockAxios.mockResolvedValue(mockData) + spyConsole.log = jest.spyOn(console, 'log').mockImplementation(() => {}) + + wrapper.vm.$nextTick(() => { + wrapper.vm.form.getFieldDecorator('account', { initialValue: 'test-account-value' }) + const event = document.createEvent('Event') + wrapper.vm.execSubmit(event) + + expect(mockAxios).toHaveBeenCalledTimes(1) + expect(mockAxios).toHaveBeenCalledWith({ + url: '/', + method: 'GET', + data: new URLSearchParams(), + params: { + command: 'createAccount', + account: 'test-account-value', + response: 'json' + } + }) + + done() + }) + }) + + it('check api is called when form has input key has name = keypair, currentAction.api = addAccountToProject', (done) => { + wrapper = factory({ + data: { + showAction: true, + currentAction: { + api: 'addAccountToProject', + loading: false, + label: 'labelname', + params: [ + { + name: 'keypair', + type: 'string' + } + ], + paramFields: [ + { name: 'keypair', type: 'string', description: '', required: false } + ], + mapping: {} + }, + resource: {} + } + }) + + const mockData = { + testapinamecase1response: { + testapinamecase1: {} + } + } + mockAxios.mockResolvedValue(mockData) + spyConsole.log = jest.spyOn(console, 'log').mockImplementation(() => {}) + + wrapper.vm.$nextTick(() => { + wrapper.vm.form.getFieldDecorator('keypair', { initialValue: 'test-keypair-value' }) + const event = document.createEvent('Event') + wrapper.vm.execSubmit(event) + + expect(mockAxios).toHaveBeenCalledTimes(1) + expect(mockAxios).toHaveBeenCalledWith({ + url: '/', + method: 'GET', + data: new URLSearchParams(), + params: { + command: 'addAccountToProject', + keypair: 'test-keypair-value', + response: 'json' + } + }) + + done() + }) + }) + + it('check api is called when form has input key name = (account | keypair), currentAction.api != (addAccountToProject | createAccount)', (done) => { + wrapper = factory({ + data: { + showAction: true, + currentAction: { + api: 'testApiNameCase1', + loading: false, + label: 'labelname', + params: [ + { + name: 'keypair', + type: 'string', + opts: [ + { id: 'test-id-1', name: 'test-name-1' }, + { id: 'test-id-2', name: 'test-name-2' } + ] + } + ], + paramFields: [ + { name: 'keypair', type: 'string', description: '', required: false } + ], + mapping: {} + }, + resource: {} + } + }) + + const mockData = { + testapinamecase1response: { + testapinamecase1: {} + } + } + mockAxios.mockResolvedValue(mockData) + spyConsole.log = jest.spyOn(console, 'log').mockImplementation(() => {}) + + wrapper.vm.$nextTick(() => { + wrapper.vm.form.getFieldDecorator('keypair', { initialValue: 1 }) + const event = document.createEvent('Event') + wrapper.vm.execSubmit(event) + + expect(mockAxios).toHaveBeenCalledTimes(1) + expect(mockAxios).toHaveBeenCalledWith({ + url: '/', + method: 'GET', + data: new URLSearchParams(), + params: { + command: 'testApiNameCase1', + keypair: 'test-name-2', + response: 'json' + } + }) + + done() + }) + }) + + it('check api is called when form has input key do not fall under special condition.', (done) => { + wrapper = factory({ + data: { + showAction: true, + currentAction: { + api: 'testApiNameCase1', + loading: false, + label: 'labelname', + params: [ + { + name: 'column1', + type: 'string' + } + ], + paramFields: [ + { name: 'column1', type: 'string', description: '', required: false } + ], + mapping: {} + }, + resource: {} + } + }) + + const mockData = { + testapinamecase1response: { + testapinamecase1: {} + } + } + mockAxios.mockResolvedValue(mockData) + spyConsole.log = jest.spyOn(console, 'log').mockImplementation(() => {}) + + wrapper.vm.$nextTick(() => { + wrapper.vm.form.getFieldDecorator('column1', { initialValue: 'test-column-value' }) + const event = document.createEvent('Event') + wrapper.vm.execSubmit(event) + + expect(mockAxios).toHaveBeenCalledTimes(1) + expect(mockAxios).toHaveBeenCalledWith({ + url: '/', + method: 'GET', + data: new URLSearchParams(), + params: { + command: 'testApiNameCase1', + column1: 'test-column-value', + response: 'json' + } + }) + + done() + }) + }) + + it('check api is called when currentAction has defaultArgs', (done) => { + wrapper = factory({ + data: { + showAction: true, + currentAction: { + api: 'testApiNameCase1', + loading: false, + label: 'labelname', + params: [ + { name: 'column1', type: 'string' } + ], + paramFields: [ + { name: 'column1', type: 'string', description: '', required: false } + ], + mapping: {}, + defaultArgs: { + column2: 'test-column2-value' + } + }, + resource: {} + } + }) + + const mockData = { + testapinamecase1response: { + testapinamecase1: {} + } + } + mockAxios.mockResolvedValue(mockData) + spyConsole.log = jest.spyOn(console, 'log').mockImplementation(() => {}) + + wrapper.vm.$nextTick(() => { + wrapper.vm.form.getFieldDecorator('column1', { initialValue: 'test-column1-value' }) + const event = document.createEvent('Event') + wrapper.vm.execSubmit(event) + + expect(mockAxios).toHaveBeenCalledTimes(1) + expect(mockAxios).toHaveBeenCalledWith({ + url: '/', + method: 'GET', + data: new URLSearchParams(), + params: { + command: 'testApiNameCase1', + column1: 'test-column1-value', + column2: 'test-column2-value', + response: 'json' + } + }) + + done() + }) + }) + + it('check api is called when currentAction.mapping has value and value is function', (done) => { + wrapper = factory({ + data: { + showAction: true, + currentAction: { + api: 'testApiNameCase1', + loading: false, + label: 'labelname', + params: [ + { name: 'column1', type: 'string' } + ], + paramFields: [ + { name: 'column1', type: 'string', description: '', required: false } + ], + mapping: { + column2: { + value: (record, params) => { + return record.name + } + } + } + }, + resource: { + id: 'test-id-value', + name: 'test-name-value' + } + } + }) + + const mockData = { + testapinamecase1response: { + testapinamecase1: {} + } + } + mockAxios.mockResolvedValue(mockData) + spyConsole.log = jest.spyOn(console, 'log').mockImplementation(() => {}) + + wrapper.vm.$nextTick(() => { + wrapper.vm.form.getFieldDecorator('column1', { initialValue: 'test-column1-value' }) + const event = document.createEvent('Event') + wrapper.vm.execSubmit(event) + + expect(mockAxios).toHaveBeenCalledTimes(1) + expect(mockAxios).toHaveBeenCalledWith({ + url: '/', + method: 'GET', + data: new URLSearchParams(), + params: { + command: 'testApiNameCase1', + column1: 'test-column1-value', + column2: 'test-name-value', + response: 'json' + } + }) + + done() + }) + }) + + it('check router name when api is called and currentAction.icon = delete and dataView is true', async (done) => { + router = common.createMockRouter([{ + name: 'testRouter26', + path: '/test-router-26', + meta: { + icon: 'test-router-26' + } + }]) + wrapper = factory({ router: router }) + router.push({ name: 'testRouter26' }) + + const mockData = { + testapinamecase1response: { + count: 1, + testapinamecase1: [{ + id: 'test-id-value', + name: 'test-name-value' + }] + } + } + + mockAxios.mockResolvedValue(mockData) + spyConsole.log = jest.spyOn(console, 'log').mockImplementation(() => {}) + await wrapper.vm.$nextTick() + + expect(router.currentRoute.name).toEqual('testRouter26') + + wrapper.setData({ + currentAction: { + icon: 'delete', + api: 'testApiNameCase1', + loading: false, + label: 'labelname', + params: [ + { name: 'column1', type: 'string' } + ], + paramFields: [ + { name: 'column1', type: 'string', description: '', required: false } + ] + }, + dataView: true + }) + + wrapper.vm.form.getFieldDecorator('column1', { initialValue: 'test-column1-value' }) + const event = document.createEvent('Event') + await wrapper.vm.execSubmit(event) + + setTimeout(() => { + expect(router.currentRoute.name).toEqual('home') + done() + }, 1000) + }) + + it('check pollActionCompletion() and action AddAsyncJob is called when api is called and response have jobId result', async (done) => { + store = common.createMockStore(state, actions) + wrapper = factory({ + store: store, + data: { + showAction: true, + currentAction: { + api: 'testApiNameCase1', + loading: false, + label: 'labelname', + params: [ + { name: 'column1', type: 'string' } + ], + paramFields: [ + { name: 'column1', type: 'string', description: '', required: false } + ] + }, + resource: {} + } + }) + + const spyPollAction = jest.spyOn(wrapper.vm, 'pollActionCompletion').mockImplementation(() => {}) + const mockData = { + testapinamecase1response: { + jobid: 'test-job-id' + } + } + + mockAxios.mockResolvedValue(mockData) + spyConsole.log = jest.spyOn(console, 'log').mockImplementation(() => {}) + + await wrapper.vm.$nextTick() + wrapper.vm.form.getFieldDecorator('column1', { initialValue: 'test-column1-value' }) + const event = document.createEvent('Event') + wrapper.vm.execSubmit(event) + + setTimeout(() => { + expect(actions.AddAsyncJob).toHaveBeenCalled() + expect(spyPollAction).toHaveBeenCalled() + + done() + }) + }) + + it('check $notification when api is called and response have not jobId result', async (done) => { + wrapper = factory({ + data: { + showAction: true, + currentAction: { + api: 'testApiNameCase1', + loading: false, + label: 'labelname', + params: [ + { name: 'column1', type: 'string' } + ], + paramFields: [ + { name: 'column1', type: 'string', description: '', required: false } + ] + }, + resource: { + name: 'test-name-value' + } + } + }) + + const mockData = { + testapinamecase1response: { + count: 1, + testapinamecase1: [{ + id: 'test-id-value' + }] + } + } + + mockAxios.mockResolvedValue(mockData) + spyConsole.log = jest.spyOn(console, 'log').mockImplementation(() => {}) + + await wrapper.vm.$nextTick() + wrapper.vm.form.getFieldDecorator('column1', { initialValue: 'test-column1-value' }) + const event = document.createEvent('Event') + wrapper.vm.execSubmit(event) + + setTimeout(() => { + expect(mocks.$message.success).toHaveBeenCalled() + expect(mocks.$message.success).toHaveLastReturnedWith({ + content: 'test-name-en - test-name-value', + key: 'labelnametest-name-value', + duration: 2 + }) + + done() + }) + }) + + it('check $notifyError is called when api is called with throw error', async (done) => { + wrapper = factory() + + const errorMock = { + response: {}, + message: 'Error: throw exception error' + } + mockAxios.mockRejectedValue(errorMock) + spyConsole.log = jest.spyOn(console, 'log').mockImplementation(() => {}) + + await wrapper.vm.$nextTick() + const event = document.createEvent('Event') + await wrapper.vm.execSubmit(event) + + setTimeout(() => { + expect(mocks.$notifyError).toHaveBeenCalledTimes(1) + expect(mocks.$notifyError).toHaveBeenCalledWith(errorMock) + + done() + }) + }) + }) + }) +}) diff --git a/ui/tests/unit/views/compute/MigrateWizard.spec.js b/ui/tests/unit/views/compute/MigrateWizard.spec.js new file mode 100644 index 00000000000..14c8a899233 --- /dev/null +++ b/ui/tests/unit/views/compute/MigrateWizard.spec.js @@ -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() + }) + }) + }) + }) +})