diff --git a/web-src/babel.config.js b/web-src/babel.config.js index 3f453d44..5775f29a 100644 --- a/web-src/babel.config.js +++ b/web-src/babel.config.js @@ -1,5 +1,10 @@ module.exports = { - presets: [ - '@vue/cli-plugin-babel/preset' - ] + presets: [ + '@vue/cli-plugin-babel/preset' + ], + env: { + 'test': { + 'plugins': ['rewire'] + } + } }; diff --git a/web-src/package.json b/web-src/package.json index 24d5bdf1..b4c9927a 100644 --- a/web-src/package.json +++ b/web-src/package.json @@ -17,34 +17,41 @@ "@stryker-mutator/babel-transpiler": "^1.3.1", "@stryker-mutator/core": "^1.3.1", "@stryker-mutator/javascript-mutator": "^1.3.1", - "@stryker-mutator/karma-runner": "^1.3.1", - "@stryker-mutator/mocha-framework": "^1.3.1", + "@stryker-mutator/karma-runner": "^3.1.0", + "@stryker-mutator/mocha-framework": "^3.1.0", "@stryker-mutator/webpack-transpiler": "^1.3.1", "@vue/cli-plugin-babel": "~4.2.0", - "@vue/cli-plugin-e2e-nightwatch": "~4.2.0", "@vue/cli-plugin-router": "~4.2.0", - "@vue/cli-plugin-unit-mocha": "~4.2.0", "@vue/cli-plugin-vuex": "~4.2.0", "@vue/cli-service": "~4.2.0", "@vue/test-utils": "1.0.0-beta.31", "axios-mock-adapter": "^1.18.1", "babel-plugin-rewire": "^1.2.0", - "chai": "^4.1.2", - "chromedriver": "80", + "chai": "^4.2.0", + "expect": "^25.3.0", "exports-loader": "^0.7.0", - "geckodriver": "^1.19.1", "http-proxy-middleware": "^1.0.3", + "jest-extended": "^0.11.5", "jquery": "^3.4.1", + "karma": "^4.4.1", + "karma-chrome-launcher": "^2.2.0", + "karma-mocha": "^1.3.0", + "karma-sourcemap-loader": "^0.3.7", + "karma-spec-reporter": "^0.0.32", + "karma-webpack": "^4.0.2", + "mocha": "^6.2.0", "mock-socket": "^9.0.2", "node-sass": "^4.13.1", "sass-loader": "^8.0.2", "sinon": "^7.5.0", + "vue-cli-plugin-unit-karmajs": "git@github.com:bugy/vue-cli-plugin-unit-karmajs.git", "vue-template-compiler": "^2.6.11" }, "scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build", "test:unit": "vue-cli-service test:unit", + "test:unit-ci": "vue-cli-service test:unit -b ChromeHeadless", "test:e2e": "vue-cli-service test:e2e", "stryker": "stryker run tests/stryker.conf.js" }, diff --git a/web-src/src/admin/store/script-config-module.js b/web-src/src/admin/store/script-config-module.js index 0eff9115..2398eb3b 100644 --- a/web-src/src/admin/store/script-config-module.js +++ b/web-src/src/admin/store/script-config-module.js @@ -1,6 +1,6 @@ import {contains, forEachKeyValue, isEmptyArray, isEmptyValue} from '@/common/utils/common'; import axios from 'axios'; -import clone from 'lodash.clone'; +import clone from 'lodash/clone'; import router from '../router/router' const allowedEmptyValuesInParam = ['name']; diff --git a/web-src/src/common/components/combobox.vue b/web-src/src/common/components/combobox.vue index 53482287..8d21c041 100644 --- a/web-src/src/common/components/combobox.vue +++ b/web-src/src/common/components/combobox.vue @@ -141,9 +141,11 @@ if (this.config.multiselect) { if (!Array.isArray(this.value)) { if (contains(allowedValues, this.value)) { - return false; + newValue = [this.value]; + } else { + newValue = []; } - newValue = [this.value]; + } else { newValue = []; for (var i = 0; i < this.value.length; i++) { diff --git a/web-src/src/common/components/file_dialog.vue b/web-src/src/common/components/file_dialog.vue index eae9d097..425707e7 100644 --- a/web-src/src/common/components/file_dialog.vue +++ b/web-src/src/common/components/file_dialog.vue @@ -63,7 +63,7 @@ } from '@/common/utils/common'; export default { - name: "file_dialog", + name: 'file_dialog', props: { onClose: { type: Function @@ -156,7 +156,12 @@ } this.$nextTick(() => { - const activeElements = this.$refs.filesList.getElementsByClassName('active'); + const filesList = this.$refs.filesList; + if (isNull(filesList)) { + return; + } + + const activeElements = filesList.getElementsByClassName('active'); if (activeElements.length === 0) { return; } diff --git a/web-src/src/common/materializecss/imports/global.js b/web-src/src/common/materializecss/imports/global.js index c340a524..cfbbc060 100644 --- a/web-src/src/common/materializecss/imports/global.js +++ b/web-src/src/common/materializecss/imports/global.js @@ -1,3 +1,6 @@ import 'materialize-css/js/cash'; import 'materialize-css/js/global'; -import 'materialize-css/js/waves' \ No newline at end of file +import 'materialize-css/js/waves' + +const Component = require('exports-loader?Component!materialize-css/js/component.js'); +global.Component = Component; diff --git a/web-src/src/main-app/store/scriptExecutionManager.js b/web-src/src/main-app/store/scriptExecutionManager.js index 4d816f7d..32cc65c1 100644 --- a/web-src/src/main-app/store/scriptExecutionManager.js +++ b/web-src/src/main-app/store/scriptExecutionManager.js @@ -1,6 +1,6 @@ import {deepCloneObject, forEachKeyValue, isEmptyArray, isEmptyString, isNull} from '@/common/utils/common'; import axios from 'axios'; -import clone from 'lodash.clone'; +import clone from 'lodash/clone'; import scriptExecutor, {STATUS_EXECUTING, STATUS_FINISHED, STATUS_INITIALIZING} from './scriptExecutor'; export default { diff --git a/web-src/tests/unit/admin/ParameterConfigForm_test.js b/web-src/tests/unit/admin/ParameterConfigForm_test.js index 6f8e0f71..a317e1a9 100644 --- a/web-src/tests/unit/admin/ParameterConfigForm_test.js +++ b/web-src/tests/unit/admin/ParameterConfigForm_test.js @@ -1,12 +1,12 @@ 'use strict'; +import ParameterConfigForm from '@/admin/components/scripts-config/ParameterConfigForm'; +import ChipsList from '@/common/components/ChipsList'; +import Combobox from '@/common/components/combobox'; +import TextArea from '@/common/components/TextArea'; +import {isBlankString, isNull, setInputValue} from '@/common/utils/common'; import {mount} from '@vue/test-utils'; import {assert, config as chaiConfig} from 'chai'; -import ParameterConfigForm from '../../js/admin/scripts-config/ParameterConfigForm'; -import {isBlankString, isNull, setInputValue} from '../../js/common'; -import ChipsList from '../../js/components/ChipsList'; -import Combobox from '../../js/components/combobox'; -import TextArea from '../../js/components/TextArea'; import {setChipListValue, vueTicks} from '../test_utils'; chaiConfig.truncateThreshold = 0; @@ -423,7 +423,7 @@ describe('Test ParameterConfigForm', function () { await _setValueByUser('Min', 5); - assertOutputValue('min', "5"); + assertOutputValue('min', '5'); }); it('Test update max', async function () { @@ -431,7 +431,7 @@ describe('Test ParameterConfigForm', function () { await _setValueByUser('Max', 5); - assertOutputValue('max', "5"); + assertOutputValue('max', '5'); }); it('Test update constant', async function () { diff --git a/web-src/tests/unit/admin/ParameterList_test.js b/web-src/tests/unit/admin/ParameterList_test.js index 8a268a1c..aba8a8e9 100644 --- a/web-src/tests/unit/admin/ParameterList_test.js +++ b/web-src/tests/unit/admin/ParameterList_test.js @@ -1,9 +1,9 @@ 'use strict'; +import ParamListItem from '@/admin/components/scripts-config/ParamListItem'; +import ScriptParamList from '@/admin/components/scripts-config/ScriptParamList'; +import {hasClass} from '@/common/utils/common'; import {assert, config as chaiConfig} from 'chai'; -import ParamListItem from '../../js/admin/scripts-config/ParamListItem'; -import ScriptParamList from '../../js/admin/scripts-config/ScriptParamList'; -import {hasClass} from '../../js/common'; import {createVue, timeout, triggerSingleClick, vueTicks} from '../test_utils'; import {setValueByUser} from './ParameterConfigForm_test'; diff --git a/web-src/tests/unit/admin/ScriptConfig_test.js b/web-src/tests/unit/admin/ScriptConfig_test.js index 81a560c7..37155504 100644 --- a/web-src/tests/unit/admin/ScriptConfig_test.js +++ b/web-src/tests/unit/admin/ScriptConfig_test.js @@ -1,10 +1,10 @@ 'use strict'; +import ScriptConfig from '@/admin/components/scripts-config/ScriptConfig'; +import ScriptConfigForm from '@/admin/components/scripts-config/ScriptConfigForm'; import {createLocalVue, mount} from '@vue/test-utils'; import {assert, config as chaiConfig} from 'chai'; import Vuex from 'vuex'; -import ScriptConfig from '../../js/admin/scripts-config/ScriptConfig'; -import ScriptConfigForm from '../../js/admin/scripts-config/ScriptConfigForm'; import {vueTicks} from '../test_utils'; import {findField, setValueByUser} from './ParameterConfigForm_test'; diff --git a/web-src/tests/unit/admin/script-module_test.js b/web-src/tests/unit/admin/script-module_test.js index 987aabaa..8b68941d 100644 --- a/web-src/tests/unit/admin/script-module_test.js +++ b/web-src/tests/unit/admin/script-module_test.js @@ -1,9 +1,9 @@ 'use strict'; +import scripts, {axiosInstance} from '@/admin/store/scripts-module' import {createLocalVue} from '@vue/test-utils'; import MockAdapter from 'axios-mock-adapter'; import {assert, config as chaiConfig} from 'chai'; import Vuex from 'vuex'; -import scripts, {axiosInstance} from '../../js/admin/scripts-config/scripts-module' chaiConfig.truncateThreshold = 0; diff --git a/web-src/tests/unit/combobox_test.js b/web-src/tests/unit/combobox_test.js index 038edf8c..55eeea1c 100644 --- a/web-src/tests/unit/combobox_test.js +++ b/web-src/tests/unit/combobox_test.js @@ -1,20 +1,16 @@ 'use strict'; +import Combobox from '@/common/components/combobox' +import {contains} from '@/common/utils/common'; import {mount} from '@vue/test-utils'; -import {assert, config as chaiConfig} from 'chai'; -import {contains, hasClass, isEmptyString, setInputValue} from '../js/common'; -import Combobox from '../js/components/combobox' import {setDeepProp, timeout, triggerSingleClick, vueTicks, wrapVModel} from './test_utils'; -chaiConfig.truncateThreshold = 0; describe('Test ComboBox', function () { + let comboBox; - before(function () { - - }); - beforeEach(function () { - this.comboBox = mount(Combobox, { + beforeEach(async function () { + comboBox = mount(Combobox, { attachToDocument: true, propsData: { config: { @@ -27,113 +23,105 @@ describe('Test ComboBox', function () { value: 'Value B' } }); - wrapVModel(this.comboBox); + comboBox.vm.$parent.$forceUpdate(); + await comboBox.vm.$nextTick(); + + wrapVModel(comboBox); }); afterEach(async function () { await vueTicks(); - this.comboBox.destroy(); + comboBox.destroy(); }); - after(function () { - }); + function assertListElements(expectedTexts, searchHeader = false) { + const listChildren = comboBox.findAll('li'); + expect(listChildren).toHaveLength(expectedTexts.length + 1); - function assertListElements(combobox, expectedTexts) { - const listChildren = $(combobox.element).find('li'); - assert.equal(expectedTexts.length, listChildren.length - 1); + const headerText = listChildren.at(0).text(); + if (!searchHeader) { + expect(headerText).toBe('Choose your option'); + } else { + expect(headerText.trim()).toBe('Search'); + } for (let i = 0; i < expectedTexts.length; i++) { const value = expectedTexts[i]; - assert.equal(value, listChildren.get(i + 1).innerText); + expect(listChildren.at(i + 1).text()).toBe(value); } } - async function openDropdown(combobox) { - const triggerInput = $(combobox.element).find('.dropdown-trigger').get(0); - - triggerSingleClick(triggerInput); + async function openDropdown() { + comboBox.get('.dropdown-trigger').trigger('click'); await timeout(50); } + function findSelectedOptions() { + return comboBox.findAll('option').filter(option => option.element.selected); + } + describe('Test config', function () { it('Test initial name', function () { - assert.equal('List param X', this.comboBox.find('select').element.id); - assert.equal('List param X', this.comboBox.find('label').element.innerText); + expect(comboBox.get('select').attributes('id')).toBe('List param X'); + expect(comboBox.get('label').text()).toBe('List param X'); }); it('Test change name', async function () { - setDeepProp(this.comboBox, 'config.name', 'testName1'); + setDeepProp(comboBox, 'config.name', 'testName1'); await vueTicks(); - assert.equal('testName1', this.comboBox.find('select').element.id); - assert.equal('testName1', this.comboBox.find('label').element.innerText); + expect(comboBox.get('select').attributes('id')).toBe('testName1'); + expect(comboBox.get('label').text()).toBe('testName1'); }); it('Test initial required', function () { - assert.equal(false, this.comboBox.find('select').element.required); + expect(comboBox.get('select').attributes('required')).toBeFalsy(); }); it('Test change required', async function () { - setDeepProp(this.comboBox, 'config.required', true); + setDeepProp(comboBox, 'config.required', true); await vueTicks(); - assert.equal(true, this.comboBox.find('select').element.required); + expect(comboBox.get('select').attributes('required')).toBe('required'); }); it('Test initial description', function () { - assert.equal('some param', this.comboBox.element.title); + expect(comboBox.element.title).toBe('some param'); }); it('Test change description', async function () { - setDeepProp(this.comboBox, 'config.description', 'My new desc'); + setDeepProp(comboBox, 'config.description', 'My new desc'); await vueTicks(); - assert.equal('My new desc', this.comboBox.element.title); + expect(comboBox.element.title).toBe('My new desc'); }); it('Test initial multiselect', function () { - assert.notExists(this.comboBox.find('select').attributes('multiple')); + expect(comboBox.find('select').attributes('multiple')).toBeNil(); - const listElement = $(this.comboBox.element).find('ul').get(0); - assert.equal(false, hasClass(listElement, 'multiple-select-dropdown')); + const listElement = comboBox.get('ul'); + expect(listElement.classes()).not.toContain('multiple-select-dropdown'); }); it('Test initial allowed values', function () { const values = ['Value A', 'Value B', 'Value C']; - const listChildren = $(this.comboBox.element).find('li'); - - assert.equal(values.length, listChildren.length - 1); - - assert.equal('Choose your option', listChildren.get(0).innerText); - - for (let i = 0; i < values.length; i++) { - const value = values[i]; - assert.equal(value, listChildren.get(i + 1).innerText); - } + assertListElements(values); }); it('Test change allowed values', async function () { const values = ['val1', 'val2', 'hello', 'another option']; - setDeepProp(this.comboBox, 'config.values', values); + setDeepProp(comboBox, 'config.values', values); await vueTicks(); - const listChildren = $(this.comboBox.element).find('li'); - assert.equal(values.length, listChildren.length - 1); - - assert.equal('Choose your option', listChildren.get(0).innerText); - - for (let i = 0; i < values.length; i++) { - const value = values[i]; - assert.equal(value, listChildren.get(i + 1).innerText); - } + assertListElements(values); }); }); @@ -142,316 +130,306 @@ describe('Test ComboBox', function () { it('Test initial value', async function () { await vueTicks(); - assert.equal(this.comboBox.vm.value, 'Value B'); + expect(comboBox.vm.value).toBe('Value B'); - const selectedOption = $(this.comboBox.element).find('.selected').text(); - assert.equal(selectedOption, 'Value B'); + const selectedOption = comboBox.find('.selected').text(); + expect(selectedOption).toBe('Value B'); }); it('Test external value change', async function () { - this.comboBox.setProps({value: 'Value C'}); + comboBox.setProps({value: 'Value C'}); await vueTicks(); - assert.equal(this.comboBox.vm.value, 'Value C'); + expect(comboBox.vm.value).toBe('Value C'); - const selectedOption = $(this.comboBox.element).find('.selected').text(); - assert.equal(selectedOption, 'Value C'); + const selectedOption = comboBox.get('.selected').text(); + expect(selectedOption).toBe('Value C'); }); it('Test select another value', async function () { - const selectElement = $(this.comboBox.element).find('select'); - selectElement.val('Value A'); - selectElement.trigger('change'); + comboBox.get('select').setValue('Value A'); await vueTicks(); - assert.equal(this.comboBox.vm.value, 'Value A'); - - const selectedOption = $(this.comboBox.element).find('.selected').text(); - assert.equal(selectedOption, 'Value A'); - }); - - it('Test set unknown value', async function () { - this.comboBox.setProps({value: 'Xyz'}); - - await vueTicks(); + expect(comboBox.vm.value).toBe('Value A'); - assert.isTrue(isEmptyString(this.comboBox.vm.value)); - assert.equal(1, $(this.comboBox.element).find(':selected').length); + const selectedOption = comboBox.get('.selected').text(); + expect(selectedOption).toBe('Value A'); }); it('Test set unknown value', async function () { - this.comboBox.setProps({value: 'Xyz'}); + comboBox.setProps({value: 'Xyz'}); await vueTicks(); - assert.isTrue(isEmptyString(this.comboBox.vm.value)); - assert.equal('Choose your option', $(this.comboBox.element).find('.selected').text()); + expect(comboBox.vm.value).toBeNull(); + expect(findSelectedOptions()).toHaveLength(1); + expect(comboBox.get('.selected').text()).toBe('Choose your option'); }); it('Test set multiselect single value', async function () { - setDeepProp(this.comboBox, 'config.multiselect', true); + setDeepProp(comboBox, 'config.multiselect', true); await vueTicks(); - this.comboBox.setProps({value: 'Value A'}); + comboBox.setProps({value: 'Value A'}); await vueTicks(); - assert.equal(['Value A'], this.comboBox.vm.value); - assert.equal('Value A', $(this.comboBox.element).find('.selected').text()); + expect(comboBox.vm.value).toEqual(['Value A']); + expect(comboBox.get('.selected').text()).toBe('Value A'); }); it('Test set multiselect multiple values', async function () { - setDeepProp(this.comboBox, 'config.multiselect', true); + setDeepProp(comboBox, 'config.multiselect', true); await vueTicks(); - this.comboBox.setProps({value: ['Value A', 'Value C']}); + comboBox.setProps({value: ['Value A', 'Value C']}); await vueTicks(); - assert.deepEqual(['Value A', 'Value C'], this.comboBox.vm.value); - let selectedElements = $(this.comboBox.element).find(':selected'); - assert.equal(2, selectedElements.length); - assert.equal('Value A', selectedElements.get(0).textContent); - assert.equal('Value C', selectedElements.get(1).textContent); + expect(comboBox.vm.value).toEqual(['Value A', 'Value C']); + + const selectedElements = findSelectedOptions(); + expect(selectedElements).toHaveLength(2); + expect(selectedElements.at(0).text()).toBe('Value A'); + expect(selectedElements.at(1).text()).toBe('Value C'); }); it('Test set multiselect single unknown value', async function () { - setDeepProp(this.comboBox, 'config.multiselect', true); + setDeepProp(comboBox, 'config.multiselect', true); await vueTicks(); - this.comboBox.setProps({value: ['Value X']}); + comboBox.setProps({value: ['Value X']}); await vueTicks(); - assert.deepEqual([], this.comboBox.vm.value); - assert.equal('Choose your option', $(this.comboBox.element).find('.selected').text()); + expect(comboBox.vm.value).toEqual([]); + expect(comboBox.get('.selected').text()).toBe('Choose your option'); }); it('Test set multiselect unknown value from multiple', async function () { - setDeepProp(this.comboBox, 'config.multiselect', true); + setDeepProp(comboBox, 'config.multiselect', true); await vueTicks(); - this.comboBox.setProps({value: ['Value A', 'Value X']}); + comboBox.setProps({value: ['Value A', 'Value X']}); await vueTicks(); - assert.deepEqual(['Value A'], this.comboBox.vm.value); - assert.equal('Value A', $(this.comboBox.element).find('.selected').text()); + expect(comboBox.vm.value).toEqual(['Value A']); + expect(comboBox.get('.selected').text()).toBe('Value A'); }); it('Test select multiple values in multiselect', async function () { - setDeepProp(this.comboBox, 'config.multiselect', true); + setDeepProp(comboBox, 'config.multiselect', true); await vueTicks(); const values = ['Value A', 'Value C']; - const selectElement = $(this.comboBox.element).find('select'); + const selectElement = $(comboBox.get('select').element); selectElement.val(values); selectElement.trigger('change'); await vueTicks(); - assert.deepEqual(values, this.comboBox.vm.value); - let selectedElements = $(this.comboBox.element).find(':selected'); - assert.equal(2, selectedElements.length); - assert.equal('Value A', selectedElements.get(0).textContent); - assert.equal('Value C', selectedElements.get(1).textContent); + expect(comboBox.vm.value).toEqual(values); + + const selectedElements = findSelectedOptions(); + expect(selectedElements).toHaveLength(2); + expect(selectedElements.at(0).text()).toBe('Value A'); + expect(selectedElements.at(1).text()).toBe('Value C'); }); it('Test change allowed values with matching value', async function () { - setDeepProp(this.comboBox, 'config.values', ['val1', 'val2', 'hello', 'Value B', 'another option']); + setDeepProp(comboBox, 'config.values', ['val1', 'val2', 'hello', 'Value B', 'another option']); await vueTicks(); - assert.equal('Value B', this.comboBox.vm.value); - assert.equal('Value B', $(this.comboBox.element).find('.selected').text()); + expect(comboBox.vm.value).toBe('Value B'); + expect(comboBox.get('.selected').text()).toBe('Value B'); }); it('Test change allowed values with unmatching value', async function () { - setDeepProp(this.comboBox, 'config.values', ['val1', 'val2', 'hello', 'another option']); + setDeepProp(comboBox, 'config.values', ['val1', 'val2', 'hello', 'another option']); await vueTicks(); - assert.isTrue(isEmptyString(this.comboBox.vm.value)); - assert.equal('Choose your option', $(this.comboBox.element).find('.selected').text()); + expect(comboBox.vm.value).toBeNull(); + expect(comboBox.get('.selected').text()).toBe('Choose your option'); }); it('Test change allowed values and then a value', async function () { - setDeepProp(this.comboBox, 'config.values', ['val1', 'val2', 'hello', 'another option']); - this.comboBox.setProps({value: 'val2'}); + setDeepProp(comboBox, 'config.values', ['val1', 'val2', 'hello', 'another option']); + comboBox.setProps({value: 'val2'}); await vueTicks(); - assert.equal('val2', this.comboBox.vm.value); - assert.equal('val2', $(this.comboBox.element).find('.selected').text()); + expect(comboBox.vm.value).toBe('val2'); + expect(comboBox.get('.selected').text()).toBe('val2'); }); }); describe('Test errors', function () { it('Test set external empty value when required', async function () { - setDeepProp(this.comboBox, 'config.required', true); + setDeepProp(comboBox, 'config.required', true); await vueTicks(); - this.comboBox.setProps({value: ''}); + comboBox.setProps({value: ''}); await vueTicks(); - assert.equal('required', this.comboBox.currentError); + expect(comboBox.currentError).toBe('required'); }); it('Test unselect combobox when required', async function () { - setDeepProp(this.comboBox, 'config.required', true); + setDeepProp(comboBox, 'config.required', true); await vueTicks(); - const selectElement = $(this.comboBox.element).find('select'); - selectElement.val(''); - selectElement.trigger('change'); + comboBox.get('select').setValue(''); await vueTicks(); - assert.equal('required', this.comboBox.currentError); + expect(comboBox.currentError).toBe('required'); }); it('Test set external value after empty', async function () { - setDeepProp(this.comboBox, 'config.required', true); - this.comboBox.setProps({value: ''}); + setDeepProp(comboBox, 'config.required', true); + comboBox.setProps({value: ''}); await vueTicks(); - this.comboBox.setProps({value: 'Value A'}); + comboBox.setProps({value: 'Value A'}); await vueTicks(); - assert.equal('', this.comboBox.currentError); + expect(comboBox.currentError).toBe(''); }); }); - function getSearchElement(comboBox) { - return getDropdownElement(comboBox).childNodes[0] + function getSearchElement() { + return getDropdownElement().childNodes[0] } - function getDropdownElement(combobox) { - return $(combobox.element).find('.dropdown-content').get(0); + function getDropdownElement() { + return comboBox.get('.dropdown-content').element; } describe('Test search', function () { - async function makeSearchable(combobox) { + async function makeSearchable() { const values = Array(20).fill(0).map((v, i) => 'Value ' + i); - setDeepProp(combobox, 'config.values', values); + setDeepProp(comboBox, 'config.values', values); await vueTicks(); - combobox.vm.comboboxWrapper.dropdown.options.inDuration = 1; - combobox.vm.comboboxWrapper.dropdown.options.outDuration = 1; + comboBox.vm.comboboxWrapper.dropdown.options.inDuration = 1; + comboBox.vm.comboboxWrapper.dropdown.options.outDuration = 1; return values; } - function assertVisible(element, visible, message) { + function assertVisible(element, visible) { const displayStyle = window.getComputedStyle(element).display; if (visible) { - assert.notEqual(displayStyle, 'none', message); + expect(displayStyle).not.toBe('none'); } else { - assert.equal(displayStyle, 'none', message); + expect(displayStyle).toBe('none'); } } function assertVisibleItems(combobox, expectedVisible) { - const [header, ...listItems] = $(combobox.element).find('li').toArray(); + const [header, ...listItems] = combobox.findAll('li').wrappers; - const headerVisible = !hasClass(header, 'search-hidden'); - assert.isTrue(headerVisible); + expect(header.classes()).not.toContain('search-hidden'); for (const listItem of listItems) { - const text = listItem.innerText; + const text = listItem.text(); const shouldBeVisible = contains(expectedVisible, text); - const visible = !hasClass(listItem, 'search-hidden'); - assert.equal(visible, shouldBeVisible, 'Item "' + text + '" has wrong visibility'); + if (shouldBeVisible) { + expect(listItem.classes()).not.toContain('search-hidden'); + } else { + expect(listItem.classes()).toContain('search-hidden'); + } } } it('Test show search field', async function () { - const values = await makeSearchable(this.comboBox); + const values = await makeSearchable(); - const searchText = $(this.comboBox.element).find('.dropdown-content li').get(0).innerText.trim(); - assert.equal('Search', searchText); - assertListElements(this.comboBox, values); + assertListElements(values, true); }); it('Test focus search field on open', async function () { - await makeSearchable(this.comboBox); + await makeSearchable(); - await openDropdown(this.comboBox); + await openDropdown(); - const searchInput = $(getSearchElement(this.comboBox)).find('input').get(0); - assert.equal(searchInput, document.activeElement); + const searchInput = comboBox.get('.dropdown-content input'); + expect(document.activeElement).toBe(searchInput.element); }); it('Test keep open on search click', async function () { - await makeSearchable(this.comboBox); + await makeSearchable(); - await openDropdown(this.comboBox); + await openDropdown(); - const searchInput = $(getSearchElement(this.comboBox)).find('input').get(0); - triggerSingleClick(searchInput); + const searchInput = comboBox.get('.dropdown-content input'); + searchInput.trigger('click'); await timeout(50); - assertVisible(getDropdownElement(this.comboBox), true); + assertVisible(getDropdownElement(), true); }); it('Test close on item click', async function () { - await makeSearchable(this.comboBox); + await makeSearchable(); - await openDropdown(this.comboBox); + await openDropdown(); - const firstItem = getDropdownElement(this.comboBox).childNodes[1]; + const firstItem = getDropdownElement().childNodes[1]; triggerSingleClick(firstItem); await timeout(50); - assertVisible(getDropdownElement(this.comboBox), false); + assertVisible(getDropdownElement(), false); }); it('Test filter on search', async function () { - await makeSearchable(this.comboBox); + await makeSearchable(); - await openDropdown(this.comboBox); + await openDropdown(); - const inputField = $(getSearchElement(this.comboBox)).find('input').get(0); - setInputValue(inputField, '2', true); + const searchInput = comboBox.get('.dropdown-content input'); + searchInput.setValue('2'); await vueTicks(); - assertVisibleItems(this.comboBox, ['Value 2', 'Value 12']); + assertVisibleItems(comboBox, ['Value 2', 'Value 12']); }); it('Test filter on search second input', async function () { - await makeSearchable(this.comboBox); + await makeSearchable(); - await openDropdown(this.comboBox); + await openDropdown(); - const inputField = $(getSearchElement(this.comboBox)).find('input').get(0); + const searchInput = comboBox.get('.dropdown-content input'); - setInputValue(inputField, '2', true); + searchInput.setValue('2'); await vueTicks(); - setInputValue(inputField, '12', true); + searchInput.setValue('12'); await vueTicks(); - assertVisibleItems(this.comboBox, ['Value 12']); + assertVisibleItems(comboBox, ['Value 12']); }); it('Test filter on search clear input', async function () { - const values = await makeSearchable(this.comboBox); + const values = await makeSearchable(); - await openDropdown(this.comboBox); + await openDropdown(); - const inputField = $(getSearchElement(this.comboBox)).find('input').get(0); + const searchInput = comboBox.get('.dropdown-content input'); - setInputValue(inputField, '2', true); + searchInput.setValue('2'); await vueTicks(); - setInputValue(inputField, '', true); + searchInput.setValue(''); await vueTicks(); - assertVisibleItems(this.comboBox, values); + assertVisibleItems(comboBox, values); }); }); }); \ No newline at end of file diff --git a/web-src/tests/unit/file_dialog_test.js b/web-src/tests/unit/file_dialog_test.js index 887dc3de..678fbff2 100644 --- a/web-src/tests/unit/file_dialog_test.js +++ b/web-src/tests/unit/file_dialog_test.js @@ -1,9 +1,9 @@ 'use strict'; +import FileDialog from '@/common/components/file_dialog' +import {hasClass, isEmptyArray, toMap} from '@/common/utils/common'; import {mount} from '@vue/test-utils'; import {assert, config as chaiConfig} from 'chai'; -import {hasClass, isEmptyArray, toMap} from '../js/common'; -import FileDialog from '../js/components/file_dialog' import {triggerDoubleClick, triggerKeyEvent, triggerSingleClick, vueTicks} from './test_utils'; chaiConfig.truncateThreshold = 0; diff --git a/web-src/tests/unit/history/execution-details_test.js b/web-src/tests/unit/history/execution-details_test.js index 7b9617f3..cab03dc5 100644 --- a/web-src/tests/unit/history/execution-details_test.js +++ b/web-src/tests/unit/history/execution-details_test.js @@ -1,10 +1,10 @@ 'use strict'; +import ExecutionDetails from '@/common/components/history/execution-details' +import historyModule, {axiosInstance} from '@/common/store/executions-module'; import {createLocalVue, mount} from '@vue/test-utils'; import MockAdapter from 'axios-mock-adapter'; import {assert, config as chaiConfig} from 'chai'; import Vuex from 'vuex'; -import ExecutionDetails from '../../js/history/execution-details' -import historyModule, {axiosInstance} from '../../js/history/executions-module'; import {vueTicks} from '../test_utils'; diff --git a/web-src/tests/unit/index.js b/web-src/tests/unit/index.js index b41cadb3..5e408a11 100644 --- a/web-src/tests/unit/index.js +++ b/web-src/tests/unit/index.js @@ -1,7 +1,15 @@ +import expect from 'expect'; +import extendedMatchers from 'jest-extended/dist/matchers' +import $ from 'jquery'; + +expect.extend(extendedMatchers); +window.expect = expect; +window.$ = $; + const context = require.context( '.', true, /_test.js$/ ); -context.keys().forEach(context); \ No newline at end of file +context.keys().forEach(context); diff --git a/web-src/tests/unit/karma.conf.js b/web-src/tests/unit/karma.conf.js deleted file mode 100644 index 85b817b1..00000000 --- a/web-src/tests/unit/karma.conf.js +++ /dev/null @@ -1,104 +0,0 @@ -"use strict"; - -var webpackConfig = require('../webpack.dev.js'); - -// we need to explicitly override entries to avoid full files compilation -// and empty entries are not allowed -webpackConfig.entry = {'some_entry': './tests/test_utils.js'}; -webpackConfig.devtool = 'inline-source-map'; -webpackConfig.mode = 'development'; -webpackConfig.watch = true; - -for (const rule of webpackConfig.module.rules) { - if (!rule.test) { - continue; - } - - if (/\.css/.test(rule.test)) { - // karma-webpack cannot handle MiniCssExtractPlugin, so just remove it - for (let i = 0; i < rule.loaders.length; i++) { - const loader = rule.loaders[i]; - - if (/mini-css-extract-plugin/.test(loader)) { - rule.loaders.splice(i, 1); - i--; - } - } - } else if (/\.js/.test(rule.test)) { - const options = rule.use.options; - if ((typeof options.plugins) === 'undefined') { - options.plugins = ['babel-plugin-rewire']; - } else { - options.plugins.push('babel-plugin-rewire'); - } - } -} - - -module.exports = function (config) { - config.set({ - - // base path that will be used to resolve all patterns (eg. files, exclude) - basePath: '..', - - - // frameworks to use - // available frameworks: https://npmjs.org/browse/keyword/karma-adapter - frameworks: ['mocha'], - - - // list of files / patterns to load in the browser - files: [ - 'tests/index.js', - 'node_modules/materialize-css/dist/css/materialize.css' - ], - - - exclude: [], - - - // preprocess matching files before serving them to the browser - // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor - preprocessors: { - '**/*.js': ['webpack', 'sourcemap'] - }, - - webpack: webpackConfig, - - // test results reporter to use - // possible values: 'dots', 'progress' - // available reporters: https://npmjs.org/browse/keyword/karma-reporter - reporters: ['spec'], - - - // web server port - port: 9876, - - - // enable / disable colors in the output (reporters and logs) - colors: true, - - - // level of logging - // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG - logLevel: config.LOG_INFO, - - - // enable / disable watching file and executing tests whenever any file changes - autoWatch: false, - - - // start these browsers - // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher - browsers: ['ChromeHeadless'], - - - // Continuous Integration mode - // if true, Karma captures browsers, runs the tests and exits - singleRun: true, - - // Concurrency level - // how many browser should be started simultaneous - concurrency: Infinity - }) -}; diff --git a/web-src/tests/unit/main-app/scripts/ScriptList_test.js b/web-src/tests/unit/main-app/scripts/ScriptList_test.js index 6847a5fa..16ea6581 100644 --- a/web-src/tests/unit/main-app/scripts/ScriptList_test.js +++ b/web-src/tests/unit/main-app/scripts/ScriptList_test.js @@ -1,12 +1,12 @@ 'use strict'; +import {hasClass, isBlankString} from '@/common/utils/common'; +import ScriptsList from '@/main-app/components/scripts/ScriptsList'; +import router from '@/main-app/router/router'; import {createLocalVue, mount} from '@vue/test-utils'; import {assert, config as chaiConfig} from 'chai'; import VueRouter from 'vue-router'; import Vuex from 'vuex'; -import {hasClass, isBlankString} from '../../../js/common'; -import router from '../../../js/main-app/router'; -import ScriptsList from '../../../js/main-app/scripts/ScriptsList'; import {triggerSingleClick, vueTicks} from '../../test_utils'; diff --git a/web-src/tests/unit/scriptConfig_test.js b/web-src/tests/unit/scriptConfig_test.js index 84765e16..5c54b0d4 100644 --- a/web-src/tests/unit/scriptConfig_test.js +++ b/web-src/tests/unit/scriptConfig_test.js @@ -1,8 +1,8 @@ +import {clearArray, removeElement, SocketClosedError} from '@/common/utils/common'; +import scriptConfig, {__RewireAPI__ as SocketRewireAPI} from '@/main-app/store/scriptConfig'; import {createLocalVue} from '@vue/test-utils'; import {assert} from 'chai'; import Vuex from 'vuex'; -import {clearArray, removeElement, SocketClosedError} from '../js/common'; -import scriptConfig, {__RewireAPI__ as SocketRewireAPI} from '../js/main-app/store/scriptConfig'; import {timeout} from './test_utils' @@ -309,7 +309,7 @@ describe('Test scriptConfig module', function () { const assertAllowedValues = (parameterName, expectedValues) => { const param = store.state.scriptConfig.parameters.find(p => p.name === parameterName); - assert.exists(param, "Couldn't find parameter for name: " + parameterName); + assert.exists(param, 'Couldn\'t find parameter for name: ' + parameterName); assert.deepEqual(param.values, expectedValues); }; diff --git a/web-src/tests/unit/scriptExecutor_test.js b/web-src/tests/unit/scriptExecutor_test.js index d4300618..b77009e7 100644 --- a/web-src/tests/unit/scriptExecutor_test.js +++ b/web-src/tests/unit/scriptExecutor_test.js @@ -1,3 +1,4 @@ +import scriptExecutor, {__RewireAPI__ as ExecutorRewireAPI} from '@/main-app/store/scriptExecutor'; import {createLocalVue} from '@vue/test-utils'; import axios from 'axios'; @@ -6,7 +7,6 @@ import {assert} from 'chai'; import {Server, WebSocket} from 'mock-socket'; import * as sinon from 'sinon'; import Vuex from 'vuex'; -import scriptExecutor, {__RewireAPI__ as ExecutorRewireAPI} from '../js/main-app/store/scriptExecutor'; import {timeout} from './test_utils' const axiosMock = new MockAdapter(axios); diff --git a/web-src/tests/unit/scriptSetup_test.js b/web-src/tests/unit/scriptSetup_test.js index 7ea9e5b4..f50418e9 100644 --- a/web-src/tests/unit/scriptSetup_test.js +++ b/web-src/tests/unit/scriptSetup_test.js @@ -1,7 +1,7 @@ +import scriptSetup from '@/main-app/store/scriptSetup'; import {createLocalVue} from '@vue/test-utils'; import {assert} from 'chai'; import Vuex from 'vuex'; -import scriptSetup from '../js/main-app/store/scriptSetup'; const localVue = createLocalVue(); @@ -19,7 +19,7 @@ function createStore(sentData) { } } } - }, + } }); } diff --git a/web-src/tests/unit/script_view_conf_test.js b/web-src/tests/unit/script_view_conf_test.js index 4393085a..3f0c1c94 100644 --- a/web-src/tests/unit/script_view_conf_test.js +++ b/web-src/tests/unit/script_view_conf_test.js @@ -1,8 +1,8 @@ 'use strict'; +import ScriptView from '@/main-app/components/scripts/script-view'; import {createLocalVue, mount} from '@vue/test-utils'; import {assert, config as chaiConfig} from 'chai'; import Vuex from 'vuex'; -import ScriptView from '../js/main-app/scripts/script-view'; chaiConfig.truncateThreshold = 0; diff --git a/web-src/tests/unit/terminal_model_test.js b/web-src/tests/unit/terminal_model_test.js index 4c035e7e..8628a811 100644 --- a/web-src/tests/unit/terminal_model_test.js +++ b/web-src/tests/unit/terminal_model_test.js @@ -1,9 +1,9 @@ 'use strict'; +import {Style, StyledRange, TerminalModel} from '@/common/components/terminal/terminal_model'; +import {clearArray, isNull} from '@/common/utils/common'; import {assert, config as chaiConfig, expect} from 'chai'; import * as sinon from 'sinon'; -import {clearArray, isNull} from '../js/common'; -import {Style, StyledRange, TerminalModel} from '../js/components/terminal/terminal_model'; import { clearFullLine, clearLine, diff --git a/web-src/tests/unit/terminal_view_test.js b/web-src/tests/unit/terminal_view_test.js index 92ac7bba..e4843fd3 100644 --- a/web-src/tests/unit/terminal_view_test.js +++ b/web-src/tests/unit/terminal_view_test.js @@ -1,9 +1,9 @@ 'use strict'; +import {TerminalModel} from '@/common/components/terminal/terminal_model'; +import {Terminal} from '@/common/components/terminal/terminal_view'; +import {removeElement} from '@/common/utils/common'; import {assert, config as chaiConfig} from 'chai'; -import {removeElement} from '../js/common'; -import {TerminalModel} from '../js/components/terminal/terminal_model'; -import {Terminal} from '../js/components/terminal/terminal_view'; import {clearScreen, clearScreenDown, clearScreenUp, format, moveCursorUp, moveToPosition} from './terminal_test_utils'; chaiConfig.truncateThreshold = 0; diff --git a/web-src/tests/unit/test_utils.js b/web-src/tests/unit/test_utils.js index 351b259e..eef5305f 100644 --- a/web-src/tests/unit/test_utils.js +++ b/web-src/tests/unit/test_utils.js @@ -1,5 +1,5 @@ +import {isNull} from '@/common/utils/common'; import Vue from 'vue'; -import {isNull} from '../js/common'; export async function vueTicks(count) { if (isNull(count)) { @@ -38,7 +38,7 @@ export function setDeepProp(wrapper, key, value) { } const rootKey = keys[0]; - const newRootElement = $.extend(true, {}, wrapper.props(rootKey)); + const newRootElement = Object.assign({}, wrapper.props(rootKey)); let currentElement = newRootElement; for (let i = 1; i < keys.length - 1; i++) { diff --git a/web-src/tests/unit/textfield_test.js b/web-src/tests/unit/textfield_test.js index 3ed0730d..db4d11f3 100644 --- a/web-src/tests/unit/textfield_test.js +++ b/web-src/tests/unit/textfield_test.js @@ -1,10 +1,10 @@ 'use strict'; +import Textfield from '@/common/components/textfield' +import {isEmptyString, setInputValue} from '@/common/utils/common'; import {mount} from '@vue/test-utils'; import {assert, config as chaiConfig} from 'chai'; -import {isEmptyString, setInputValue} from '../js/common'; -import Textfield from '../js/components/textfield' -import {mergeDeepProps, setDeepProp, vueTicks, wrapVModel} from './test_utils'; +import {setDeepProp, vueTicks, wrapVModel} from './test_utils'; chaiConfig.truncateThreshold = 0; diff --git a/web-src/vue.config.js b/web-src/vue.config.js index 541b0982..57fb01c5 100644 --- a/web-src/vue.config.js +++ b/web-src/vue.config.js @@ -51,11 +51,7 @@ module.exports = { } }, - configureWebpack: { - // webpack removes "class Component" during tree-shaking. Even if it's imported somewhere - // so we explicitly load it - plugins: [new webpack.ProvidePlugin({Component: 'exports-loader?Component!materialize-css/js/component.js'})] - }, + configureWebpack: {}, chainWebpack: config => { const options = module.exports; @@ -77,6 +73,24 @@ module.exports = { enforce: true })) } - }) + }); + }, + + pluginOptions: { + karma: { + files: ['tests/unit/index.js'], + customLaunchers: { + ChromeHeadless: { + base: 'Chrome', + flags: [ + '--headless', + '--disable-gpu', + '--no-sandbox', + '--remote-debugging-port=9222' + ] + } + }, + browsers: ['Chrome', 'Firefox'] + } } }; \ No newline at end of file