Commit b173e564 authored by Clement Ho's avatar Clement Ho

Merge branch '30998-add-babel-rewire-plugin' into 'master'

Resolve "Add some way to mock and spy on default ES modules"

Closes #30998

See merge request gitlab-org/gitlab-ce!18116
parents 8b41c406 091cab95
{
"presets": [["latest", { "es2015": { "modules": false } }], "stage-2"],
"env": {
"karma": {
"plugins": ["rewire"]
},
"coverage": {
"plugins": [
[
......@@ -14,7 +17,8 @@
{
"process.env.BABEL_ENV": "coverage"
}
]
],
"rewire"
]
}
}
......
......@@ -144,3 +144,6 @@ export * from './actions/tree';
export * from './actions/file';
export * from './actions/project';
export * from './actions/merge_request';
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
......@@ -56,3 +56,6 @@ export const allBlobs = state =>
.sort((a, b) => b.lastOpenedAt - a.lastOpenedAt);
export const getStagedFile = state => path => state.stagedFiles.find(f => f.path === path);
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
......@@ -185,3 +185,6 @@ export const commitChanges = ({ commit, state, getters, dispatch, rootState }) =
commit(types.UPDATE_LOADING, false);
});
};
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
......@@ -27,3 +27,6 @@ export const branchName = (state, getters, rootState) => {
return rootState.currentBranchId;
};
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
......@@ -315,3 +315,6 @@ export const scrollToNoteIfNeeded = (context, el) => {
scrollToElement(el);
}
};
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
......@@ -68,3 +68,6 @@ export const resolvedDiscussionCount = (state, getters) => {
return Object.keys(resolvedMap).length;
};
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
......@@ -35,3 +35,6 @@ export const deleteRegistry = ({ commit }, image) => Vue.http.delete(image.destr
export const setMainEndpoint = ({ commit }, data) => commit(types.SET_MAIN_ENDPOINT, data);
export const toggleLoading = ({ commit }) => commit(types.TOGGLE_MAIN_LOADING);
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
export const isLoading = state => state.isLoading;
export const repos = state => state.repos;
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
......@@ -33,7 +33,7 @@ webpackConfig.plugins.push(
})
);
webpackConfig.devtool = 'cheap-inline-source-map';
webpackConfig.devtool = process.env.BABEL_ENV !== 'coverage' && 'cheap-inline-source-map';
// Karma configuration
module.exports = function(config) {
......
......@@ -126,13 +126,51 @@ it('tests a promise rejection', (done) => {
});
```
#### Stubbing
#### Stubbing and Mocking
For unit tests, you should stub methods that are unrelated to the current unit you are testing.
If you need to use a prototype method, instantiate an instance of the class and call it there instead of mocking the instance completely.
Jasmine provides useful helpers `spyOn`, `spyOnProperty`, `jasmine.createSpy`,
and `jasmine.createSpyObject` to facilitate replacing methods with dummy
placeholders, and recalling when they are called and the arguments that are
passed to them. These tools should be used liberally, to test for expected
behavior, to mock responses, and to block unwanted side effects (such as a
method that would generate a network request or alter `window.location`). The
documentation for these methods can be found in the [jasmine introduction page](https://jasmine.github.io/2.0/introduction.html#section-Spies).
For integration tests, you should stub methods that will effect the stability of the test if they
execute their original behaviour. i.e. Network requests.
Sometimes you may need to spy on a method that is directly imported by another
module. GitLab has a custom `spyOnDependency` method which utilizes
[babel-plugin-rewire](https://github.com/speedskater/babel-plugin-rewire) to
achieve this. It can be used like so:
```javascript
// my_module.js
import { visitUrl } from '~/lib/utils/url_utility';
export default function doSomething() {
visitUrl('/foo/bar');
}
// my_module_spec.js
import doSomething from '~/my_module';
describe('my_module', () => {
it('does something', () => {
const visitUrl = spyOnDependency(doSomething, 'visitUrl');
doSomething();
expect(visitUrl).toHaveBeenCalledWith('/foo/bar');
});
});
```
Unlike `spyOn`, `spyOnDependency` expects its first parameter to be the default
export of a module who's import you want to stub, rather than an object which
contains a method you wish to stub (if the module does not have a default
export, one is be generated by the babel plugin). The second parameter is the
name of the import you wish to change. The result of the function is a Spy
object which can be treated like any other jasmine spy object.
Further documentation on the babel rewire pluign API can be found on
[its repository Readme doc](https://github.com/speedskater/babel-plugin-rewire#babel-plugin-rewire).
### Vue.js unit tests
......
......@@ -5,9 +5,9 @@
"eslint": "eslint --max-warnings 0 --ext .js,.vue .",
"eslint-fix": "eslint --max-warnings 0 --ext .js,.vue --fix .",
"eslint-report": "eslint --max-warnings 0 --ext .js,.vue --format html --output-file ./eslint-report.html .",
"karma": "karma start --single-run true config/karma.config.js",
"karma": "BABEL_ENV=${BABEL_ENV:=karma} karma start --single-run true config/karma.config.js",
"karma-coverage": "BABEL_ENV=coverage karma start --single-run true config/karma.config.js",
"karma-start": "karma start config/karma.config.js",
"karma-start": "BABEL_ENV=karma karma start config/karma.config.js",
"prettier-staged": "node ./scripts/frontend/prettier.js",
"prettier-staged-save": "node ./scripts/frontend/prettier.js save",
"prettier-all": "node ./scripts/frontend/prettier.js check-all",
......@@ -99,6 +99,9 @@
"axios-mock-adapter": "^1.10.0",
"babel-eslint": "^8.0.2",
"babel-plugin-istanbul": "^4.1.5",
"babel-plugin-rewire": "^1.1.0",
"babel-template": "^6.26.0",
"babel-types": "^6.26.0",
"commander": "^2.15.1",
"eslint": "^3.18.0",
"eslint-config-airbnb-base": "^10.0.1",
......
......@@ -18,6 +18,7 @@
"sandbox": false,
"setFixtures": false,
"setStyleFixtures": false,
"spyOnDependency": false,
"spyOnEvent": false,
"ClassSpecHelper": false
},
......
import $ from 'jquery';
import '~/behaviors/quick_submit';
describe('Quick Submit behavior', () => {
describe('Quick Submit behavior', function () {
const keydownEvent = (options = { keyCode: 13, metaKey: true }) => $.Event('keydown', options);
preloadFixtures('merge_requests/merge_request_with_task_list.html.raw');
......
import $ from 'jquery';
import BlobFileDropzone from '~/blob/blob_file_dropzone';
describe('BlobFileDropzone', () => {
describe('BlobFileDropzone', function () {
preloadFixtures('blob/show.html.raw');
beforeEach(() => {
......
import CommentTypeToggle from '~/comment_type_toggle';
import * as dropLabSrc from '~/droplab/drop_lab';
import InputSetter from '~/droplab/plugins/input_setter';
describe('CommentTypeToggle', function () {
......@@ -59,14 +58,14 @@ describe('CommentTypeToggle', function () {
this.droplab = jasmine.createSpyObj('droplab', ['init']);
spyOn(dropLabSrc, 'default').and.returnValue(this.droplab);
this.droplabConstructor = spyOnDependency(CommentTypeToggle, 'DropLab').and.returnValue(this.droplab);
spyOn(this.commentTypeToggle, 'setConfig').and.returnValue(this.config);
CommentTypeToggle.prototype.initDroplab.call(this.commentTypeToggle);
});
it('should instantiate a DropLab instance', function () {
expect(dropLabSrc.default).toHaveBeenCalled();
expect(this.droplabConstructor).toHaveBeenCalled();
});
it('should set .droplab', function () {
......
......@@ -4,7 +4,7 @@ import axios from '~/lib/utils/axios_utils';
import pipelinesTable from '~/commit/pipelines/pipelines_table.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('Pipelines table in Commits and Merge requests', () => {
describe('Pipelines table in Commits and Merge requests', function () {
const jsonFixtureName = 'pipelines/pipelines.json';
let pipeline;
let PipelinesTable;
......
import Hook from '~/droplab/hook';
import * as dropdownSrc from '~/droplab/drop_down';
describe('Hook', function () {
describe('class constructor', function () {
......@@ -10,7 +9,7 @@ describe('Hook', function () {
this.config = {};
this.dropdown = {};
spyOn(dropdownSrc, 'default').and.returnValue(this.dropdown);
this.dropdownConstructor = spyOnDependency(Hook, 'DropDown').and.returnValue(this.dropdown);
this.hook = new Hook(this.trigger, this.list, this.plugins, this.config);
});
......@@ -24,7 +23,7 @@ describe('Hook', function () {
});
it('should call DropDown constructor', function () {
expect(dropdownSrc.default).toHaveBeenCalledWith(this.list, this.config);
expect(this.dropdownConstructor).toHaveBeenCalledWith(this.list, this.config);
});
it('should set .type', function () {
......
import * as urlUtils from '~/lib/utils/url_utility';
import * as recentSearchesStoreSrc from '~/filtered_search/stores/recent_searches_store';
import RecentSearchesService from '~/filtered_search/services/recent_searches_service';
import RecentSearchesServiceError from '~/filtered_search/services/recent_searches_service_error';
import RecentSearchesRoot from '~/filtered_search/recent_searches_root';
......@@ -11,7 +9,7 @@ import FilteredSearchDropdownManager from '~/filtered_search/filtered_search_dro
import FilteredSearchManager from '~/filtered_search/filtered_search_manager';
import FilteredSearchSpecHelper from '../helpers/filtered_search_spec_helper';
describe('Filtered Search Manager', () => {
describe('Filtered Search Manager', function () {
let input;
let manager;
let tokensContainer;
......@@ -74,18 +72,19 @@ describe('Filtered Search Manager', () => {
describe('class constructor', () => {
const isLocalStorageAvailable = 'isLocalStorageAvailable';
let RecentSearchesStoreSpy;
beforeEach(() => {
spyOn(RecentSearchesService, 'isAvailable').and.returnValue(isLocalStorageAvailable);
spyOn(recentSearchesStoreSrc, 'default');
spyOn(RecentSearchesRoot.prototype, 'render');
RecentSearchesStoreSpy = spyOnDependency(FilteredSearchManager, 'RecentSearchesStore');
});
it('should instantiate RecentSearchesStore with isLocalStorageAvailable', () => {
manager = new FilteredSearchManager({ page });
expect(RecentSearchesService.isAvailable).toHaveBeenCalled();
expect(recentSearchesStoreSrc.default).toHaveBeenCalledWith({
expect(RecentSearchesStoreSpy).toHaveBeenCalledWith({
isLocalStorageAvailable,
allowedKeys: FilteredSearchTokenKeys.getKeys(),
});
......@@ -164,7 +163,7 @@ describe('Filtered Search Manager', () => {
it('should search with a single word', (done) => {
input.value = 'searchTerm';
spyOn(urlUtils, 'visitUrl').and.callFake((url) => {
spyOnDependency(FilteredSearchManager, 'visitUrl').and.callFake((url) => {
expect(url).toEqual(`${defaultParams}&search=searchTerm`);
done();
});
......@@ -175,7 +174,7 @@ describe('Filtered Search Manager', () => {
it('should search with multiple words', (done) => {
input.value = 'awesome search terms';
spyOn(urlUtils, 'visitUrl').and.callFake((url) => {
spyOnDependency(FilteredSearchManager, 'visitUrl').and.callFake((url) => {
expect(url).toEqual(`${defaultParams}&search=awesome+search+terms`);
done();
});
......@@ -186,7 +185,7 @@ describe('Filtered Search Manager', () => {
it('should search with special characters', (done) => {
input.value = '~!@#$%^&*()_+{}:<>,.?/';
spyOn(urlUtils, 'visitUrl').and.callFake((url) => {
spyOnDependency(FilteredSearchManager, 'visitUrl').and.callFake((url) => {
expect(url).toEqual(`${defaultParams}&search=~!%40%23%24%25%5E%26*()_%2B%7B%7D%3A%3C%3E%2C.%3F%2F`);
done();
});
......@@ -200,7 +199,7 @@ describe('Filtered Search Manager', () => {
${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug')}
`);
spyOn(urlUtils, 'visitUrl').and.callFake((url) => {
spyOnDependency(FilteredSearchManager, 'visitUrl').and.callFake((url) => {
expect(url).toEqual(`${defaultParams}&label_name[]=bug`);
done();
});
......
import RecentSearchesRoot from '~/filtered_search/recent_searches_root';
import * as vueSrc from 'vue';
describe('RecentSearchesRoot', () => {
describe('render', () => {
let recentSearchesRoot;
let data;
let template;
let VueSpy;
beforeEach(() => {
recentSearchesRoot = {
......@@ -14,7 +14,7 @@ describe('RecentSearchesRoot', () => {
},
};
spyOn(vueSrc, 'default').and.callFake((options) => {
VueSpy = spyOnDependency(RecentSearchesRoot, 'Vue').and.callFake((options) => {
data = options.data;
template = options.template;
});
......@@ -23,7 +23,7 @@ describe('RecentSearchesRoot', () => {
});
it('should instantiate Vue', () => {
expect(vueSrc.default).toHaveBeenCalled();
expect(VueSpy).toHaveBeenCalled();
expect(data()).toBe(recentSearchesRoot.store.state);
expect(template).toContain(':is-local-storage-available="isLocalStorageAvailable"');
});
......
/* eslint-disable comma-dangle, no-param-reassign, no-unused-expressions, max-len */
import $ from 'jquery';
import '~/gl_dropdown';
import GLDropdown from '~/gl_dropdown';
import '~/lib/utils/common_utils';
import * as urlUtils from '~/lib/utils/url_utility';
describe('glDropdown', function describeDropdown() {
preloadFixtures('static/gl_dropdown.html.raw');
......@@ -138,13 +137,13 @@ describe('glDropdown', function describeDropdown() {
expect(this.dropdownContainerElement).toHaveClass('open');
const randomIndex = Math.floor(Math.random() * (this.projectsData.length - 1)) + 0;
navigateWithKeys('down', randomIndex, () => {
spyOn(urlUtils, 'visitUrl').and.stub();
const visitUrl = spyOnDependency(GLDropdown, 'visitUrl').and.stub();
navigateWithKeys('enter', null, () => {
expect(this.dropdownContainerElement).not.toHaveClass('open');
const link = $(`${ITEM_SELECTOR}:eq(${randomIndex}) a`, this.$dropdownMenuElement);
expect(link).toHaveClass('is-active');
const linkedLocation = link.attr('href');
if (linkedLocation && linkedLocation !== '#') expect(urlUtils.visitUrl).toHaveBeenCalledWith(linkedLocation);
if (linkedLocation && linkedLocation !== '#') expect(visitUrl).toHaveBeenCalledWith(linkedLocation);
});
});
});
......
import $ from 'jquery';
import Vue from 'vue';
import * as utils from '~/lib/utils/url_utility';
import appComponent from '~/groups/components/app.vue';
import groupFolderComponent from '~/groups/components/group_folder.vue';
import groupItemComponent from '~/groups/components/group_item.vue';
......@@ -177,7 +176,7 @@ describe('AppComponent', () => {
it('should fetch groups for provided page details and update window state', (done) => {
spyOn(vm, 'fetchGroups').and.returnValue(returnServicePromise(mockGroups));
spyOn(vm, 'updateGroups').and.callThrough();
spyOn(utils, 'mergeUrlParams').and.callThrough();
const mergeUrlParams = spyOnDependency(appComponent, 'mergeUrlParams').and.callThrough();
spyOn(window.history, 'replaceState');
spyOn($, 'scrollTo');
......@@ -193,7 +192,7 @@ describe('AppComponent', () => {
setTimeout(() => {
expect(vm.isLoading).toBe(false);
expect($.scrollTo).toHaveBeenCalledWith(0);
expect(utils.mergeUrlParams).toHaveBeenCalledWith({ page: 2 }, jasmine.any(String));
expect(mergeUrlParams).toHaveBeenCalledWith({ page: 2 }, jasmine.any(String));
expect(window.history.replaceState).toHaveBeenCalledWith({
page: jasmine.any(String),
}, jasmine.any(String), jasmine.any(String));
......
import Vue from 'vue';
import * as urlUtils from '~/lib/utils/url_utility';
import groupItemComponent from '~/groups/components/group_item.vue';
import groupFolderComponent from '~/groups/components/group_folder.vue';
import eventHub from '~/groups/event_hub';
......@@ -135,13 +134,13 @@ describe('GroupItemComponent', () => {
const group = Object.assign({}, mockParentGroupItem);
group.childrenCount = 0;
const newVm = createComponent(group);
spyOn(urlUtils, 'visitUrl').and.stub();
const visitUrl = spyOnDependency(groupItemComponent, 'visitUrl').and.stub();
spyOn(eventHub, '$emit');
newVm.onClickRowGroup(event);
setTimeout(() => {
expect(eventHub.$emit).not.toHaveBeenCalled();
expect(urlUtils.visitUrl).toHaveBeenCalledWith(newVm.group.relativePath);
expect(visitUrl).toHaveBeenCalledWith(newVm.group.relativePath);
done();
}, 0);
});
......
......@@ -2,7 +2,7 @@
import './class_spec_helper';
describe('ClassSpecHelper', () => {
describe('ClassSpecHelper', function () {
describe('itShouldBeAStaticMethod', () => {
beforeEach(() => {
class TestClass {
......
import * as urlUtils from '~/lib/utils/url_utility';
import actions, { stageAllChanges, unstageAllChanges } from '~/ide/stores/actions';
import store from '~/ide/stores';
import * as actions from '~/ide/stores/actions';
import * as types from '~/ide/stores/mutation_types';
import router from '~/ide/ide_router';
import { resetStore, file } from '../helpers';
......@@ -17,12 +16,12 @@ describe('Multi-file store actions', () => {
describe('redirectToUrl', () => {
it('calls visitUrl', done => {
spyOn(urlUtils, 'visitUrl');
const visitUrl = spyOnDependency(actions, 'visitUrl');
store
.dispatch('redirectToUrl', 'test')
.then(() => {
expect(urlUtils.visitUrl).toHaveBeenCalledWith('test');
expect(visitUrl).toHaveBeenCalledWith('test');
done();
})
......@@ -298,7 +297,7 @@ describe('Multi-file store actions', () => {
store.state.changedFiles.push(file(), file('new'));
testAction(
actions.stageAllChanges,
stageAllChanges,
null,
store.state,
[
......@@ -316,7 +315,7 @@ describe('Multi-file store actions', () => {
store.state.stagedFiles.push(file(), file('new'));
testAction(
actions.unstageAllChanges,
unstageAllChanges,
null,
store.state,
[
......
import actions from '~/ide/stores/actions';
import store from '~/ide/stores';
import service from '~/ide/services';
import router from '~/ide/ide_router';
import * as urlUtils from '~/lib/utils/url_utility';
import eventHub from '~/ide/eventhub';
import * as consts from '~/ide/stores/modules/commit/constants';
import { resetStore, file } from 'spec/ide/helpers';
......@@ -307,8 +307,10 @@ describe('IDE commit module actions', () => {
});
describe('commitChanges', () => {
let visitUrl;
beforeEach(() => {
spyOn(urlUtils, 'visitUrl');
visitUrl = spyOnDependency(actions, 'visitUrl');
document.body.innerHTML += '<div class="flash-container"></div>';
......@@ -461,7 +463,7 @@ describe('IDE commit module actions', () => {
store
.dispatch('commit/commitChanges')
.then(() => {
expect(urlUtils.visitUrl).toHaveBeenCalledWith(
expect(visitUrl).toHaveBeenCalledWith(
`webUrl/merge_requests/new?merge_request[source_branch]=${
store.getters['commit/newBranchName']
}&merge_request[target_branch]=master`,
......
......@@ -2,7 +2,6 @@ import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import '~/behaviors/markdown/render_gfm';
import * as urlUtils from '~/lib/utils/url_utility';
import issuableApp from '~/issue_show/components/app.vue';
import eventHub from '~/issue_show/event_hub';
import setTimeoutPromise from 'spec/helpers/set_timeout_promise_helper';
......@@ -174,7 +173,7 @@ describe('Issuable output', () => {
});
it('does not redirect if issue has not moved', (done) => {
spyOn(urlUtils, 'visitUrl');
const visitUrl = spyOnDependency(issuableApp, 'visitUrl');
spyOn(vm.service, 'updateIssuable').and.callFake(() => new Promise((resolve) => {
resolve({
data: {
......@@ -187,16 +186,13 @@ describe('Issuable output', () => {
vm.updateIssuable();
setTimeout(() => {
expect(
urlUtils.visitUrl,
).not.toHaveBeenCalled();
expect(visitUrl).not.toHaveBeenCalled();
done();
});
});
it('redirects if returned web_url has changed', (done) => {
spyOn(urlUtils, 'visitUrl');
const visitUrl = spyOnDependency(issuableApp, 'visitUrl');
spyOn(vm.service, 'updateIssuable').and.callFake(() => new Promise((resolve) => {
resolve({
data: {
......@@ -209,10 +205,7 @@ describe('Issuable output', () => {
vm.updateIssuable();
setTimeout(() => {
expect(
urlUtils.visitUrl,
).toHaveBeenCalledWith('/testing-issue-move');
expect(visitUrl).toHaveBeenCalledWith('/testing-issue-move');
done();
});
});
......@@ -340,7 +333,7 @@ describe('Issuable output', () => {
describe('deleteIssuable', () => {
it('changes URL when deleted', (done) => {
spyOn(urlUtils, 'visitUrl');
const visitUrl = spyOnDependency(issuableApp, 'visitUrl');
spyOn(vm.service, 'deleteIssuable').and.callFake(() => new Promise((resolve) => {
resolve({
data: {
......@@ -352,16 +345,13 @@ describe('Issuable output', () => {
vm.deleteIssuable();
setTimeout(() => {
expect(
urlUtils.visitUrl,
).toHaveBeenCalledWith('/test');
expect(visitUrl).toHaveBeenCalledWith('/test');
done();
});
});
it('stops polling when deleting', (done) => {
spyOn(urlUtils, 'visitUrl');
spyOnDependency(issuableApp, 'visitUrl');
spyOn(vm.poll, 'stop').and.callThrough();
spyOn(vm.service, 'deleteIssuable').and.callFake(() => new Promise((resolve) => {
resolve({
......@@ -377,7 +367,6 @@ describe('Issuable output', () => {
expect(
vm.poll.stop,
).toHaveBeenCalledWith();
done();
});
});
......
import $ from 'jquery';
import Vue from 'vue';
import descriptionComponent from '~/issue_show/components/description.vue';
import * as taskList from '~/task_list';
import Description from '~/issue_show/components/description.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('Description component', () => {
......@@ -17,7 +16,7 @@ describe('Description component', () => {
};
beforeEach(() => {
DescriptionComponent = Vue.extend(descriptionComponent);
DescriptionComponent = Vue.extend(Description);
if (!document.querySelector('.issuable-meta')) {
const metaData = document.createElement('div');
......@@ -82,18 +81,20 @@ describe('Description component', () => {
});
describe('TaskList', () => {
let TaskList;
beforeEach(() => {
vm = mountComponent(DescriptionComponent, Object.assign({}, props, {
issuableType: 'issuableType',
}));
spyOn(taskList, 'default');
TaskList = spyOnDependency(Description, 'TaskList');
});
it('re-inits the TaskList when description changed', (done) => {
vm.descriptionHtml = 'changed';
setTimeout(() => {
expect(taskList.default).toHaveBeenCalled();
expect(TaskList).toHaveBeenCalled();
done();
});
});
......@@ -103,7 +104,7 @@ describe('Description component', () => {
vm.descriptionHtml = 'changed';
setTimeout(() => {
expect(taskList.default).not.toHaveBeenCalled();
expect(TaskList).not.toHaveBeenCalled();
done();
});
});
......@@ -112,7 +113,7 @@ describe('Description component', () => {
vm.descriptionHtml = 'changed';
setTimeout(() => {
expect(taskList.default).toHaveBeenCalledWith({
expect(TaskList).toHaveBeenCalledWith({
dataType: 'issuableType',
fieldName: 'description',
selector: '.detail-page-description',
......
......@@ -2,7 +2,6 @@ import $ from 'jquery';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import * as urlUtils from '~/lib/utils/url_utility';
import '~/lib/utils/datetime_utility';
import Job from '~/job';
import '~/breakpoints';
......@@ -22,7 +21,7 @@ describe('Job', () => {
beforeEach(() => {
loadFixtures('builds/build-with-artifacts.html.raw');
spyOn(urlUtils, 'visitUrl');
spyOnDependency(Job, 'visitUrl');
response = {};
......
import csrf from '~/lib/utils/csrf';
describe('csrf', () => {
describe('csrf', function () {
beforeEach(() => {
this.tokenKey = 'X-CSRF-Token';
this.token = 'pH1cvjnP9grx2oKlhWEDvUZnJ8x2eXsIs1qzyHkF3DugSG5yTxR76CWeEZRhML2D1IeVB7NEW0t5l/axE4iJpQ==';
......
import * as imageUtility from '~/lib/utils/image_utility';
import { isImageLoaded } from '~/lib/utils/image_utility';
describe('imageUtility', () => {
describe('isImageLoaded', () => {
......@@ -8,7 +8,7 @@ describe('imageUtility', () => {
naturalHeight: 100,
};
expect(imageUtility.isImageLoaded(element)).toEqual(false);
expect(isImageLoaded(element)).toEqual(false);
});
it('should return false when naturalHeight = 0', () => {
......@@ -17,7 +17,7 @@ describe('imageUtility', () => {
naturalHeight: 0,
};
expect(imageUtility.isImageLoaded(element)).toEqual(false);
expect(isImageLoaded(element)).toEqual(false);
});
it('should return true when image.complete and naturalHeight != 0', () => {
......@@ -26,7 +26,7 @@ describe('imageUtility', () => {
naturalHeight: 100,
};
expect(imageUtility.isImageLoaded(element)).toEqual(true);
expect(isImageLoaded(element)).toEqual(true);
});
});
});
......@@ -3,7 +3,6 @@
import $ from 'jquery';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import * as urlUtils from '~/lib/utils/url_utility';
import MergeRequestTabs from '~/merge_request_tabs';
import '~/commit/pipelines/pipelines_bundle';
import '~/breakpoints';
......@@ -356,7 +355,7 @@ import 'vendor/jquery.scrollTo';
describe('with note fragment hash', () => {
it('should expand and scroll to linked fragment hash #note_xxx', function (done) {
spyOn(urlUtils, 'getLocationHash').and.returnValue(noteId);
spyOnDependency(MergeRequestTabs, 'getLocationHash').and.returnValue(noteId);
this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
setTimeout(() => {
......@@ -372,7 +371,7 @@ import 'vendor/jquery.scrollTo';
});
it('should gracefully ignore non-existant fragment hash', function (done) {
spyOn(urlUtils, 'getLocationHash').and.returnValue('note_something-that-does-not-exist');
spyOnDependency(MergeRequestTabs, 'getLocationHash').and.returnValue('note_something-that-does-not-exist');
this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
setTimeout(() => {
......@@ -385,7 +384,7 @@ import 'vendor/jquery.scrollTo';
describe('with line number fragment hash', () => {
it('should gracefully ignore line number fragment hash', function () {
spyOn(urlUtils, 'getLocationHash').and.returnValue(noteLineNumId);
spyOnDependency(MergeRequestTabs, 'getLocationHash').and.returnValue(noteLineNumId);
this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
expect(noteLineNumId.length).toBeGreaterThan(0);
......@@ -422,7 +421,7 @@ import 'vendor/jquery.scrollTo';
describe('with note fragment hash', () => {
it('should expand and scroll to linked fragment hash #note_xxx', function (done) {
spyOn(urlUtils, 'getLocationHash').and.returnValue(noteId);
spyOnDependency(MergeRequestTabs, 'getLocationHash').and.returnValue(noteId);
this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
......@@ -439,7 +438,7 @@ import 'vendor/jquery.scrollTo';
});
it('should gracefully ignore non-existant fragment hash', function (done) {
spyOn(urlUtils, 'getLocationHash').and.returnValue('note_something-that-does-not-exist');
spyOnDependency(MergeRequestTabs, 'getLocationHash').and.returnValue('note_something-that-does-not-exist');
this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
setTimeout(() => {
......@@ -451,7 +450,7 @@ import 'vendor/jquery.scrollTo';
describe('with line number fragment hash', () => {
it('should gracefully ignore line number fragment hash', function () {
spyOn(urlUtils, 'getLocationHash').and.returnValue(noteLineNumId);
spyOnDependency(MergeRequestTabs, 'getLocationHash').and.returnValue(noteLineNumId);
this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
expect(noteLineNumId.length).toBeGreaterThan(0);
......
import MonitoringStore from '~/monitoring/stores/monitoring_store';
import MonitoringMock, { deploymentData } from './mock_data';
describe('MonitoringStore', () => {
describe('MonitoringStore', function () {
this.store = new MonitoringStore();
this.store.storeMetrics(MonitoringMock.data);
......
......@@ -3,7 +3,6 @@ import $ from 'jquery';
import _ from 'underscore';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import * as urlUtils from '~/lib/utils/url_utility';
import 'autosize';
import '~/gl_form';
import '~/lib/utils/text_utility';
......@@ -222,7 +221,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
});
it('sets target when hash matches', () => {
spyOn(urlUtils, 'getLocationHash').and.returnValue(hash);
spyOnDependency(Notes, 'getLocationHash').and.returnValue(hash);
Notes.updateNoteTargetSelector($note);
......@@ -231,7 +230,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
});
it('unsets target when hash does not match', () => {
spyOn(urlUtils, 'getLocationHash').and.returnValue('note_doesnotexist');
spyOnDependency(Notes, 'getLocationHash').and.returnValue('note_doesnotexist');
Notes.updateNoteTargetSelector($note);
......@@ -239,7 +238,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
});
it('unsets target when there is not a hash fragment anymore', () => {
spyOn(urlUtils, 'getLocationHash').and.returnValue(null);
spyOnDependency(Notes, 'getLocationHash').and.returnValue(null);
Notes.updateNoteTargetSelector($note);
......
/* global fixture */
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import * as utils from '~/lib/utils/url_utility';
import Pager from '~/pager';
describe('pager', () => {
......@@ -25,7 +23,7 @@ describe('pager', () => {
it('should use current url if data-href attribute not provided', () => {
const href = `${gl.TEST_HOST}/some_list`;
spyOn(utils, 'removeParams').and.returnValue(href);
spyOnDependency(Pager, 'removeParams').and.returnValue(href);
Pager.init();
expect(Pager.url).toBe(href);
});
......@@ -39,9 +37,9 @@ describe('pager', () => {
it('keeps extra query parameters from url', () => {
window.history.replaceState({}, null, '?filter=test&offset=100');
const href = `${gl.TEST_HOST}/some_list?filter=test`;
spyOn(utils, 'removeParams').and.returnValue(href);
const removeParams = spyOnDependency(Pager, 'removeParams').and.returnValue(href);
Pager.init();
expect(utils.removeParams).toHaveBeenCalledWith(['limit', 'offset']);
expect(removeParams).toHaveBeenCalledWith(['limit', 'offset']);
expect(Pager.url).toEqual(href);
});
});
......
......@@ -2,7 +2,6 @@ import Vue from 'vue';
import axios from '~/lib/utils/axios_utils';
import stopJobsModal from '~/pages/admin/jobs/index/components/stop_jobs_modal.vue';
import * as urlUtility from '~/lib/utils/url_utility';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
......@@ -24,7 +23,7 @@ describe('stop_jobs_modal.vue', () => {
describe('onSubmit', () => {
it('stops jobs and redirects to overview page', (done) => {
const responseURL = `${gl.TEST_HOST}/stop_jobs_modal.vue/jobs`;
const redirectSpy = spyOn(urlUtility, 'redirectTo');
const redirectSpy = spyOnDependency(stopJobsModal, 'redirectTo');
spyOn(axios, 'post').and.callFake((url) => {
expect(url).toBe(props.url);
return Promise.resolve({
......@@ -44,7 +43,7 @@ describe('stop_jobs_modal.vue', () => {
it('displays error if stopping jobs failed', (done) => {
const dummyError = new Error('stopping jobs failed');
const redirectSpy = spyOn(urlUtility, 'redirectTo');
const redirectSpy = spyOnDependency(stopJobsModal, 'redirectTo');
spyOn(axios, 'post').and.callFake((url) => {
expect(url).toBe(props.url);
return Promise.reject(dummyError);
......
......@@ -3,7 +3,6 @@ import Vue from 'vue';
import axios from '~/lib/utils/axios_utils';
import deleteMilestoneModal from '~/pages/milestones/shared/components/delete_milestone_modal.vue';
import eventHub from '~/pages/milestones/shared/event_hub';
import * as urlUtility from '~/lib/utils/url_utility';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
......@@ -40,7 +39,7 @@ describe('delete_milestone_modal.vue', () => {
},
});
});
const redirectSpy = spyOn(urlUtility, 'redirectTo');
const redirectSpy = spyOnDependency(deleteMilestoneModal, 'redirectTo');
vm.onSubmit()
.then(() => {
......@@ -60,7 +59,7 @@ describe('delete_milestone_modal.vue', () => {
eventHub.$emit.calls.reset();
return Promise.reject(dummyError);
});
const redirectSpy = spyOn(urlUtility, 'redirectTo');
const redirectSpy = spyOnDependency(deleteMilestoneModal, 'redirectTo');
vm.onSubmit()
.catch((error) => {
......
......@@ -6,7 +6,7 @@ const PipelineSchedulesCalloutComponent = Vue.extend(PipelineSchedulesCallout);
const cookieKey = 'pipeline_schedules_callout_dismissed';
const docsUrl = 'help/ci/scheduled_pipelines';
describe('Pipeline Schedule Callout', () => {
describe('Pipeline Schedule Callout', function () {
beforeEach(() => {
setFixtures(`
<div id='pipeline-schedules-callout' data-docs-url=${docsUrl}></div>
......
......@@ -9,8 +9,6 @@ import Sidebar from '~/right_sidebar';
(function() {
var $aside, $icon, $labelsIcon, $page, $toggle, assertSidebarState;
this.sidebar = null;
$aside = null;
$toggle = null;
......@@ -43,7 +41,7 @@ import Sidebar from '~/right_sidebar';
beforeEach(function() {
loadFixtures(fixtureName);
mock = new MockAdapter(axios);
this.sidebar = new Sidebar();
new Sidebar(); // eslint-disable-line no-new
$aside = $('.right-sidebar');
$page = $('.layout-page');
$icon = $aside.find('i');
......
......@@ -4,7 +4,6 @@ import $ from 'jquery';
import '~/gl_dropdown';
import SearchAutocomplete from '~/search_autocomplete';
import '~/lib/utils/common_utils';
import * as urlUtils from '~/lib/utils/url_utility';
describe('Search autocomplete dropdown', () => {
var assertLinks,
......@@ -129,9 +128,6 @@ describe('Search autocomplete dropdown', () => {
beforeEach(function() {
loadFixtures('static/search_autocomplete.html.raw');
// Prevent turbolinks from triggering within gl_dropdown
spyOn(urlUtils, 'visitUrl').and.returnValue(true);
window.gon = {};
window.gon.current_user_id = userId;
window.gon.current_username = userName;
......
import findAndFollowLink from '~/shortcuts_dashboard_navigation';
import * as urlUtility from '~/lib/utils/url_utility';
describe('findAndFollowLink', () => {
it('visits a link when the selector exists', () => {
const href = '/some/path';
const locationSpy = spyOn(urlUtility, 'visitUrl');
const visitUrl = spyOnDependency(findAndFollowLink, 'visitUrl');
setFixtures(`<a class="my-shortcut" href="${href}">link</a>`);
findAndFollowLink('.my-shortcut');
expect(locationSpy).toHaveBeenCalledWith(href);
expect(visitUrl).toHaveBeenCalledWith(href);
});
it('does not throw an exception when the selector does not exist', () => {
const locationSpy = spyOn(urlUtility, 'visitUrl');
const visitUrl = spyOnDependency(findAndFollowLink, 'visitUrl');
// this should not throw an exception
findAndFollowLink('.this-selector-does-not-exist');
expect(locationSpy).not.toHaveBeenCalled();
expect(visitUrl).not.toHaveBeenCalled();
});
});
......@@ -4,7 +4,7 @@ import ShortcutsIssuable from '~/shortcuts_issuable';
initCopyAsGFM();
describe('ShortcutsIssuable', () => {
describe('ShortcutsIssuable', function () {
const fixtureName = 'merge_requests/diff_comment.html.raw';
preloadFixtures(fixtureName);
beforeEach(() => {
......
import _ from 'underscore';
import Vue from 'vue';
import * as urlUtils from '~/lib/utils/url_utility';
import SidebarMediator from '~/sidebar/sidebar_mediator';
import SidebarStore from '~/sidebar/stores/sidebar_store';
import SidebarService from '~/sidebar/services/sidebar_service';
import Mock from './mock_data';
describe('Sidebar mediator', () => {
describe('Sidebar mediator', function() {
beforeEach(() => {
Vue.http.interceptors.push(Mock.sidebarMockInterceptor);
this.mediator = new SidebarMediator(Mock.mediator);
......@@ -87,12 +86,12 @@ describe('Sidebar mediator', () => {
const moveToProjectId = 7;
this.mediator.store.setMoveToProjectId(moveToProjectId);
spyOn(this.mediator.service, 'moveIssue').and.callThrough();
spyOn(urlUtils, 'visitUrl');
const visitUrl = spyOnDependency(SidebarMediator, 'visitUrl');
this.mediator.moveIssue()
.then(() => {
expect(this.mediator.service.moveIssue).toHaveBeenCalledWith(moveToProjectId);
expect(urlUtils.visitUrl).toHaveBeenCalledWith('/root/some-project/issues/5');
expect(visitUrl).toHaveBeenCalledWith('/root/some-project/issues/5');
})
.then(done)
.catch(done.fail);
......
......@@ -7,7 +7,7 @@ import SidebarService from '~/sidebar/services/sidebar_service';
import SidebarMoveIssue from '~/sidebar/lib/sidebar_move_issue';
import Mock from './mock_data';
describe('SidebarMoveIssue', () => {
describe('SidebarMoveIssue', function () {
beforeEach(() => {
Vue.http.interceptors.push(Mock.sidebarMockInterceptor);
this.mediator = new SidebarMediator(Mock.mediator);
......
......@@ -31,7 +31,7 @@ const PARTICIPANT_LIST = [
{ ...PARTICIPANT, id: 3 },
];
describe('Sidebar store', () => {
describe('Sidebar store', function () {
beforeEach(() => {
this.store = new SidebarStore({
currentUser: {
......
/* eslint-disable jasmine/no-global-setup */
/* eslint-disable jasmine/no-global-setup, jasmine/no-unsafe-spy, no-underscore-dangle */
import $ from 'jquery';
import 'vendor/jasmine-jquery';
import '~/commons';
......@@ -55,6 +56,17 @@ window.addEventListener('unhandledrejection', event => {
console.error(event.reason.stack || event.reason);
});
// Add global function to spy on a module's dependencies via rewire
window.spyOnDependency = (module, name) => {
const dependency = module.__GetDependency__(name);
const spy = jasmine.createSpy(name, dependency);
module.__Rewire__(name, spy);
return spy;
};
// Reset any rewired modules after each test (see babel-plugin-rewire)
afterEach(__rewire_reset_all__); // eslint-disable-line
// HACK: Chrome 59 disconnects if there are too many synchronous tests in a row
// because it appears to lock up the thread that communicates to Karma's socket
// This async beforeEach gets called on every spec and releases the JS thread long
......
import $ from 'jquery';
import * as urlUtils from '~/lib/utils/url_utility';
import Todos from '~/pages/dashboard/todos/index/todos';
import '~/lib/utils/common_utils';
......@@ -18,7 +17,7 @@ describe('Todos', () => {
it('opens the todo url', (done) => {
const todoLink = todoItem.dataset.url;
spyOn(urlUtils, 'visitUrl').and.callFake((url) => {
spyOnDependency(Todos, 'visitUrl').and.callFake((url) => {
expect(url).toEqual(todoLink);
done();
});
......@@ -33,7 +32,7 @@ describe('Todos', () => {
beforeEach(() => {
metakeyEvent = $.Event('click', { keyCode: 91, ctrlKey: true });
visitUrlSpy = spyOn(urlUtils, 'visitUrl').and.callFake(() => {});
visitUrlSpy = spyOnDependency(Todos, 'visitUrl').and.callFake(() => {});
windowOpenSpy = spyOn(window, 'open').and.callFake(() => {});
});
......
......@@ -3,7 +3,7 @@ import U2FAuthenticate from '~/u2f/authenticate';
import 'vendor/u2f';
import MockU2FDevice from './mock_u2f_device';
describe('U2FAuthenticate', () => {
describe('U2FAuthenticate', function () {
preloadFixtures('u2f/authenticate.html.raw');
beforeEach((done) => {
......
......@@ -3,7 +3,7 @@ import U2FRegister from '~/u2f/register';
import 'vendor/u2f';
import MockU2FDevice from './mock_u2f_device';
describe('U2FRegister', () => {
describe('U2FRegister', function () {
preloadFixtures('u2f/register.html.raw');
beforeEach((done) => {
......
import Vue from 'vue';
import * as urlUtils from '~/lib/utils/url_utility';
import deploymentComponent from '~/vue_merge_request_widget/components/deployment.vue';
import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
import { getTimeago } from '~/lib/utils/datetime_utility';
......@@ -117,13 +116,13 @@ describe('Deployment component', () => {
it('should show a confirm dialog and call service.stopEnvironment when confirmed', (done) => {
spyOn(window, 'confirm').and.returnValue(true);
spyOn(MRWidgetService, 'stopEnvironment').and.returnValue(returnPromise(true));
spyOn(urlUtils, 'visitUrl').and.returnValue(true);
const visitUrl = spyOnDependency(deploymentComponent, 'visitUrl').and.returnValue(true);
vm = mockStopEnvironment();
expect(window.confirm).toHaveBeenCalled();
expect(MRWidgetService.stopEnvironment).toHaveBeenCalledWith(deploymentMockData.stop_url);
setTimeout(() => {
expect(urlUtils.visitUrl).toHaveBeenCalledWith(url);
expect(visitUrl).toHaveBeenCalledWith(url);
done();
}, 333);
});
......
import Vue from 'vue';
import ReadyToMerge from '~/vue_merge_request_widget/components/states/ready_to_merge.vue';
import eventHub from '~/vue_merge_request_widget/event_hub';
import * as simplePoll from '~/lib/utils/simple_poll';
const commitMessage = 'This is the commit message';
const commitMessageWithDescription = 'This is the commit message description';
......@@ -355,9 +354,9 @@ describe('ReadyToMerge', () => {
describe('initiateMergePolling', () => {
it('should call simplePoll', () => {
spyOn(simplePoll, 'default');
const simplePoll = spyOnDependency(ReadyToMerge, 'simplePoll');
vm.initiateMergePolling();
expect(simplePoll.default).toHaveBeenCalled();
expect(simplePoll).toHaveBeenCalled();
});
});
......@@ -457,11 +456,11 @@ describe('ReadyToMerge', () => {
describe('initiateRemoveSourceBranchPolling', () => {
it('should emit event and call simplePoll', () => {
spyOn(eventHub, '$emit');
spyOn(simplePoll, 'default');
const simplePoll = spyOnDependency(ReadyToMerge, 'simplePoll');
vm.initiateRemoveSourceBranchPolling();
expect(eventHub.$emit).toHaveBeenCalledWith('SetBranchRemoveFlag', [true]);
expect(simplePoll.default).toHaveBeenCalled();
expect(simplePoll).toHaveBeenCalled();
});
});
......@@ -524,18 +523,20 @@ describe('ReadyToMerge', () => {
});
describe('when user can merge and can delete branch', () => {
let customVm;
beforeEach(() => {
this.customVm = createComponent({
customVm = createComponent({
mr: { canRemoveSourceBranch: true },
});
});
it('isRemoveSourceBranchButtonDisabled should be false', () => {
expect(this.customVm.isRemoveSourceBranchButtonDisabled).toBe(false);
expect(customVm.isRemoveSourceBranchButtonDisabled).toBe(false);
});
it('should be enabled in rendered output', () => {
const checkboxElement = this.customVm.$el.querySelector('#remove-source-branch-input');
const checkboxElement = customVm.$el.querySelector('#remove-source-branch-input');
expect(checkboxElement).not.toBeNull();
});
});
......
......@@ -670,6 +670,10 @@ babel-plugin-istanbul@^4.1.5:
istanbul-lib-instrument "^1.7.5"
test-exclude "^4.1.1"
babel-plugin-rewire@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/babel-plugin-rewire/-/babel-plugin-rewire-1.1.0.tgz#a6b966d9d8c06c03d95dcda2eec4e2521519549b"
babel-plugin-syntax-async-functions@^6.8.0:
version "6.13.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment