Commit 801cf923 authored by Phil Hughes's avatar Phil Hughes

Merge branch 'winh-promise-rejects-fail-tests' into 'master'

Make JavaScript tests fail for unhandled Promise rejections

Closes #33845 and #33623

See merge request !12264
parents 9c7bf123 925eea26
...@@ -17,7 +17,7 @@ export default { ...@@ -17,7 +17,7 @@ export default {
methods: { methods: {
submit(e) { submit(e) {
e.preventDefault(); e.preventDefault();
if (this.title.trim() === '') return; if (this.title.trim() === '') return Promise.resolve();
this.error = false; this.error = false;
...@@ -29,7 +29,10 @@ export default { ...@@ -29,7 +29,10 @@ export default {
assignees: [], assignees: [],
}); });
this.list.newIssue(issue) eventHub.$emit(`scroll-board-list-${this.list.id}`);
this.cancel();
return this.list.newIssue(issue)
.then(() => { .then(() => {
// Need this because our jQuery very kindly disables buttons on ALL form submissions // Need this because our jQuery very kindly disables buttons on ALL form submissions
$(this.$refs.submitButton).enable(); $(this.$refs.submitButton).enable();
...@@ -47,9 +50,6 @@ export default { ...@@ -47,9 +50,6 @@ export default {
// Show error message // Show error message
this.error = true; this.error = true;
}); });
eventHub.$emit(`scroll-board-list-${this.list.id}`);
this.cancel();
}, },
cancel() { cancel() {
this.title = ''; this.title = '';
......
...@@ -112,8 +112,7 @@ class List { ...@@ -112,8 +112,7 @@ class List {
.then((resp) => { .then((resp) => {
const data = resp.json(); const data = resp.json();
issue.id = data.iid; issue.id = data.iid;
})
.then(() => {
if (this.issuesSize > 1) { if (this.issuesSize > 1) {
const moveBeforeIid = this.issues[1].id; const moveBeforeIid = this.issues[1].id;
gl.boardService.moveIssue(issue.id, null, null, null, moveBeforeIid); gl.boardService.moveIssue(issue.id, null, null, null, moveBeforeIid);
......
...@@ -40,6 +40,10 @@ class FilteredSearchManager { ...@@ -40,6 +40,10 @@ class FilteredSearchManager {
return []; return [];
}) })
.then((searches) => { .then((searches) => {
if (!searches) {
return;
}
// Put any searches that may have come in before // Put any searches that may have come in before
// we fetched the saved searches ahead of the already saved ones // we fetched the saved searches ahead of the already saved ones
const resultantSearches = this.recentSearchesStore.setRecentSearches( const resultantSearches = this.recentSearchesStore.setRecentSearches(
......
...@@ -12,6 +12,7 @@ import './mock_data'; ...@@ -12,6 +12,7 @@ import './mock_data';
describe('Issue boards new issue form', () => { describe('Issue boards new issue form', () => {
let vm; let vm;
let list; let list;
let newIssueMock;
const promiseReturn = { const promiseReturn = {
json() { json() {
return { return {
...@@ -21,7 +22,11 @@ describe('Issue boards new issue form', () => { ...@@ -21,7 +22,11 @@ describe('Issue boards new issue form', () => {
}; };
const submitIssue = () => { const submitIssue = () => {
vm.$el.querySelector('.btn-success').click(); const dummySubmitEvent = {
preventDefault() {},
};
vm.$refs.submitButton = vm.$el.querySelector('.btn-success');
return vm.submit(dummySubmitEvent);
}; };
beforeEach((done) => { beforeEach((done) => {
...@@ -32,16 +37,10 @@ describe('Issue boards new issue form', () => { ...@@ -32,16 +37,10 @@ describe('Issue boards new issue form', () => {
gl.issueBoards.BoardsStore.create(); gl.issueBoards.BoardsStore.create();
gl.IssueBoardsApp = new Vue(); gl.IssueBoardsApp = new Vue();
setTimeout(() => {
list = new List(listObj); list = new List(listObj);
spyOn(gl.boardService, 'newIssue').and.callFake(() => new Promise((resolve, reject) => { newIssueMock = Promise.resolve(promiseReturn);
if (vm.title === 'error') { spyOn(list, 'newIssue').and.callFake(() => newIssueMock);
reject();
} else {
resolve(promiseReturn);
}
}));
vm = new BoardNewIssueComp({ vm = new BoardNewIssueComp({
propsData: { propsData: {
...@@ -49,12 +48,24 @@ describe('Issue boards new issue form', () => { ...@@ -49,12 +48,24 @@ describe('Issue boards new issue form', () => {
}, },
}).$mount(); }).$mount();
done(); Vue.nextTick()
}, 0); .then(done)
.catch(done.fail);
}); });
afterEach(() => { it('calls submit if submit button is clicked', (done) => {
Vue.http.interceptors = _.without(Vue.http.interceptors, boardsMockInterceptor); spyOn(vm, 'submit');
vm.title = 'Testing Title';
Vue.nextTick()
.then(() => {
vm.$el.querySelector('.btn-success').click();
expect(vm.submit.calls.count()).toBe(1);
expect(vm.$refs['submit-button']).toBe(vm.$el.querySelector('.btn-success'));
})
.then(done)
.catch(done.fail);
}); });
it('disables submit button if title is empty', () => { it('disables submit button if title is empty', () => {
...@@ -64,136 +75,122 @@ describe('Issue boards new issue form', () => { ...@@ -64,136 +75,122 @@ describe('Issue boards new issue form', () => {
it('enables submit button if title is not empty', (done) => { it('enables submit button if title is not empty', (done) => {
vm.title = 'Testing Title'; vm.title = 'Testing Title';
setTimeout(() => { Vue.nextTick()
.then(() => {
expect(vm.$el.querySelector('.form-control').value).toBe('Testing Title'); expect(vm.$el.querySelector('.form-control').value).toBe('Testing Title');
expect(vm.$el.querySelector('.btn-success').disabled).not.toBe(true); expect(vm.$el.querySelector('.btn-success').disabled).not.toBe(true);
})
done(); .then(done)
}, 0); .catch(done.fail);
}); });
it('clears title after clicking cancel', (done) => { it('clears title after clicking cancel', (done) => {
vm.$el.querySelector('.btn-default').click(); vm.$el.querySelector('.btn-default').click();
setTimeout(() => { Vue.nextTick()
.then(() => {
expect(vm.title).toBe(''); expect(vm.title).toBe('');
done(); })
}, 0); .then(done)
.catch(done.fail);
}); });
it('does not create new issue if title is empty', (done) => { it('does not create new issue if title is empty', (done) => {
submitIssue(); submitIssue()
.then(() => {
setTimeout(() => { expect(list.newIssue).not.toHaveBeenCalled();
expect(gl.boardService.newIssue).not.toHaveBeenCalled(); })
done(); .then(done)
}, 0); .catch(done.fail);
}); });
describe('submit success', () => { describe('submit success', () => {
it('creates new issue', (done) => { it('creates new issue', (done) => {
vm.title = 'submit title'; vm.title = 'submit title';
setTimeout(() => { Vue.nextTick()
submitIssue(); .then(submitIssue)
.then(() => {
expect(gl.boardService.newIssue).toHaveBeenCalled(); expect(list.newIssue).toHaveBeenCalled();
done(); })
}, 0); .then(done)
.catch(done.fail);
}); });
it('enables button after submit', (done) => { it('enables button after submit', (done) => {
vm.title = 'submit issue'; vm.title = 'submit issue';
setTimeout(() => { Vue.nextTick()
submitIssue(); .then(submitIssue)
.then(() => {
expect(vm.$el.querySelector('.btn-success').disabled).toBe(false); expect(vm.$el.querySelector('.btn-success').disabled).toBe(false);
done(); })
}, 0); .then(done)
.catch(done.fail);
}); });
it('clears title after submit', (done) => { it('clears title after submit', (done) => {
vm.title = 'submit issue'; vm.title = 'submit issue';
Vue.nextTick(() => { Vue.nextTick()
submitIssue(); .then(submitIssue)
.then(() => {
setTimeout(() => {
expect(vm.title).toBe(''); expect(vm.title).toBe('');
done(); })
}, 0); .then(done)
}); .catch(done.fail);
});
it('adds new issue to top of list after submit request', (done) => {
vm.title = 'submit issue';
setTimeout(() => {
submitIssue();
setTimeout(() => {
expect(list.issues.length).toBe(2);
expect(list.issues[0].title).toBe('submit issue');
expect(list.issues[0].subscribed).toBe(true);
done();
}, 0);
}, 0);
}); });
it('sets detail issue after submit', (done) => { it('sets detail issue after submit', (done) => {
expect(gl.issueBoards.BoardsStore.detail.issue.title).toBe(undefined); expect(gl.issueBoards.BoardsStore.detail.issue.title).toBe(undefined);
vm.title = 'submit issue'; vm.title = 'submit issue';
setTimeout(() => { Vue.nextTick()
submitIssue(); .then(submitIssue)
.then(() => {
setTimeout(() => {
expect(gl.issueBoards.BoardsStore.detail.issue.title).toBe('submit issue'); expect(gl.issueBoards.BoardsStore.detail.issue.title).toBe('submit issue');
done(); })
}, 0); .then(done)
}, 0); .catch(done.fail);
}); });
it('sets detail list after submit', (done) => { it('sets detail list after submit', (done) => {
vm.title = 'submit issue'; vm.title = 'submit issue';
setTimeout(() => { Vue.nextTick()
submitIssue(); .then(submitIssue)
.then(() => {
setTimeout(() => {
expect(gl.issueBoards.BoardsStore.detail.list.id).toBe(list.id); expect(gl.issueBoards.BoardsStore.detail.list.id).toBe(list.id);
done(); })
}, 0); .then(done)
}, 0); .catch(done.fail);
}); });
}); });
describe('submit error', () => { describe('submit error', () => {
it('removes issue', (done) => { beforeEach(() => {
newIssueMock = Promise.reject(new Error('My hovercraft is full of eels!'));
vm.title = 'error'; vm.title = 'error';
});
setTimeout(() => { it('removes issue', (done) => {
submitIssue(); Vue.nextTick()
.then(submitIssue)
setTimeout(() => { .then(() => {
expect(list.issues.length).toBe(1); expect(list.issues.length).toBe(1);
done(); })
}, 0); .then(done)
}, 0); .catch(done.fail);
}); });
it('shows error', (done) => { it('shows error', (done) => {
vm.title = 'error'; Vue.nextTick()
.then(submitIssue)
setTimeout(() => { .then(() => {
submitIssue();
setTimeout(() => {
expect(vm.error).toBe(true); expect(vm.error).toBe(true);
done(); })
}, 0); .then(done)
}, 0); .catch(done.fail);
}); });
}); });
}); });
...@@ -150,4 +150,41 @@ describe('List model', () => { ...@@ -150,4 +150,41 @@ describe('List model', () => {
expect(list.getIssues).toHaveBeenCalled(); expect(list.getIssues).toHaveBeenCalled();
}); });
}); });
describe('newIssue', () => {
beforeEach(() => {
spyOn(gl.boardService, 'newIssue').and.returnValue(Promise.resolve({
json() {
return {
iid: 42,
};
},
}));
});
it('adds new issue to top of list', (done) => {
list.issues.push(new ListIssue({
title: 'Testing',
iid: _.random(10000),
confidential: false,
labels: [list.label],
assignees: [],
}));
const dummyIssue = new ListIssue({
title: 'new issue',
iid: _.random(10000),
confidential: false,
labels: [list.label],
assignees: [],
});
list.newIssue(dummyIssue)
.then(() => {
expect(list.issues.length).toBe(2);
expect(list.issues[0]).toBe(dummyIssue);
})
.then(done)
.catch(done.fail);
});
});
}); });
...@@ -48,18 +48,23 @@ describe('Filtered Search Manager', () => { ...@@ -48,18 +48,23 @@ describe('Filtered Search Manager', () => {
</div> </div>
`); `);
spyOn(gl.FilteredSearchDropdownManager.prototype, 'setDropdown').and.callFake(() => {});
});
const initializeManager = () => {
/* eslint-disable jasmine/no-unsafe-spy */
spyOn(gl.FilteredSearchManager.prototype, 'loadSearchParamsFromURL').and.callFake(() => {}); spyOn(gl.FilteredSearchManager.prototype, 'loadSearchParamsFromURL').and.callFake(() => {});
spyOn(gl.FilteredSearchManager.prototype, 'tokenChange').and.callFake(() => {}); spyOn(gl.FilteredSearchManager.prototype, 'tokenChange').and.callFake(() => {});
spyOn(gl.FilteredSearchDropdownManager.prototype, 'setDropdown').and.callFake(() => {});
spyOn(gl.FilteredSearchDropdownManager.prototype, 'updateDropdownOffset').and.callFake(() => {}); spyOn(gl.FilteredSearchDropdownManager.prototype, 'updateDropdownOffset').and.callFake(() => {});
spyOn(gl.utils, 'getParameterByName').and.returnValue(null); spyOn(gl.utils, 'getParameterByName').and.returnValue(null);
spyOn(gl.FilteredSearchVisualTokens, 'unselectTokens').and.callThrough(); spyOn(gl.FilteredSearchVisualTokens, 'unselectTokens').and.callThrough();
/* eslint-enable jasmine/no-unsafe-spy */
input = document.querySelector('.filtered-search'); input = document.querySelector('.filtered-search');
tokensContainer = document.querySelector('.tokens-container'); tokensContainer = document.querySelector('.tokens-container');
manager = new gl.FilteredSearchManager(); manager = new gl.FilteredSearchManager();
manager.setup(); manager.setup();
}); };
afterEach(() => { afterEach(() => {
manager.cleanup(); manager.cleanup();
...@@ -67,33 +72,34 @@ describe('Filtered Search Manager', () => { ...@@ -67,33 +72,34 @@ describe('Filtered Search Manager', () => {
describe('class constructor', () => { describe('class constructor', () => {
const isLocalStorageAvailable = 'isLocalStorageAvailable'; const isLocalStorageAvailable = 'isLocalStorageAvailable';
let filteredSearchManager;
beforeEach(() => { beforeEach(() => {
spyOn(RecentSearchesService, 'isAvailable').and.returnValue(isLocalStorageAvailable); spyOn(RecentSearchesService, 'isAvailable').and.returnValue(isLocalStorageAvailable);
spyOn(recentSearchesStoreSrc, 'default'); spyOn(recentSearchesStoreSrc, 'default');
spyOn(RecentSearchesRoot.prototype, 'render'); spyOn(RecentSearchesRoot.prototype, 'render');
filteredSearchManager = new gl.FilteredSearchManager();
filteredSearchManager.setup();
return filteredSearchManager;
}); });
it('should instantiate RecentSearchesStore with isLocalStorageAvailable', () => { it('should instantiate RecentSearchesStore with isLocalStorageAvailable', () => {
manager = new gl.FilteredSearchManager();
expect(RecentSearchesService.isAvailable).toHaveBeenCalled(); expect(RecentSearchesService.isAvailable).toHaveBeenCalled();
expect(recentSearchesStoreSrc.default).toHaveBeenCalledWith({ expect(recentSearchesStoreSrc.default).toHaveBeenCalledWith({
isLocalStorageAvailable, isLocalStorageAvailable,
allowedKeys: gl.FilteredSearchTokenKeys.getKeys(), allowedKeys: gl.FilteredSearchTokenKeys.getKeys(),
}); });
}); });
});
describe('setup', () => {
beforeEach(() => {
manager = new gl.FilteredSearchManager();
});
it('should not instantiate Flash if an RecentSearchesServiceError is caught', () => { it('should not instantiate Flash if an RecentSearchesServiceError is caught', () => {
spyOn(RecentSearchesService.prototype, 'fetch').and.callFake(() => Promise.reject(new RecentSearchesServiceError())); spyOn(RecentSearchesService.prototype, 'fetch').and.callFake(() => Promise.reject(new RecentSearchesServiceError()));
spyOn(window, 'Flash'); spyOn(window, 'Flash');
filteredSearchManager = new gl.FilteredSearchManager(); manager.setup();
filteredSearchManager.setup();
expect(window.Flash).not.toHaveBeenCalled(); expect(window.Flash).not.toHaveBeenCalled();
}); });
...@@ -102,6 +108,7 @@ describe('Filtered Search Manager', () => { ...@@ -102,6 +108,7 @@ describe('Filtered Search Manager', () => {
describe('searchState', () => { describe('searchState', () => {
beforeEach(() => { beforeEach(() => {
spyOn(gl.FilteredSearchManager.prototype, 'search').and.callFake(() => {}); spyOn(gl.FilteredSearchManager.prototype, 'search').and.callFake(() => {});
initializeManager();
}); });
it('should blur button', () => { it('should blur button', () => {
...@@ -148,6 +155,10 @@ describe('Filtered Search Manager', () => { ...@@ -148,6 +155,10 @@ describe('Filtered Search Manager', () => {
describe('search', () => { describe('search', () => {
const defaultParams = '?scope=all&utf8=%E2%9C%93&state=opened'; const defaultParams = '?scope=all&utf8=%E2%9C%93&state=opened';
beforeEach(() => {
initializeManager();
});
it('should search with a single word', (done) => { it('should search with a single word', (done) => {
input.value = 'searchTerm'; input.value = 'searchTerm';
...@@ -197,6 +208,10 @@ describe('Filtered Search Manager', () => { ...@@ -197,6 +208,10 @@ describe('Filtered Search Manager', () => {
}); });
describe('handleInputPlaceholder', () => { describe('handleInputPlaceholder', () => {
beforeEach(() => {
initializeManager();
});
it('should render placeholder when there is no input', () => { it('should render placeholder when there is no input', () => {
expect(input.placeholder).toEqual(placeholder); expect(input.placeholder).toEqual(placeholder);
}); });
...@@ -223,6 +238,10 @@ describe('Filtered Search Manager', () => { ...@@ -223,6 +238,10 @@ describe('Filtered Search Manager', () => {
}); });
describe('checkForBackspace', () => { describe('checkForBackspace', () => {
beforeEach(() => {
initializeManager();
});
describe('tokens and no input', () => { describe('tokens and no input', () => {
beforeEach(() => { beforeEach(() => {
tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML( tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(
...@@ -260,6 +279,10 @@ describe('Filtered Search Manager', () => { ...@@ -260,6 +279,10 @@ describe('Filtered Search Manager', () => {
}); });
describe('removeToken', () => { describe('removeToken', () => {
beforeEach(() => {
initializeManager();
});
it('removes token even when it is already selected', () => { it('removes token even when it is already selected', () => {
tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML( tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(
FilteredSearchSpecHelper.createFilterVisualTokenHTML('milestone', 'none', true), FilteredSearchSpecHelper.createFilterVisualTokenHTML('milestone', 'none', true),
...@@ -291,6 +314,7 @@ describe('Filtered Search Manager', () => { ...@@ -291,6 +314,7 @@ describe('Filtered Search Manager', () => {
describe('removeSelectedTokenKeydown', () => { describe('removeSelectedTokenKeydown', () => {
beforeEach(() => { beforeEach(() => {
initializeManager();
tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML( tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(
FilteredSearchSpecHelper.createFilterVisualTokenHTML('milestone', 'none', true), FilteredSearchSpecHelper.createFilterVisualTokenHTML('milestone', 'none', true),
); );
...@@ -344,27 +368,39 @@ describe('Filtered Search Manager', () => { ...@@ -344,27 +368,39 @@ describe('Filtered Search Manager', () => {
spyOn(gl.FilteredSearchVisualTokens, 'removeSelectedToken').and.callThrough(); spyOn(gl.FilteredSearchVisualTokens, 'removeSelectedToken').and.callThrough();
spyOn(gl.FilteredSearchManager.prototype, 'handleInputPlaceholder').and.callThrough(); spyOn(gl.FilteredSearchManager.prototype, 'handleInputPlaceholder').and.callThrough();
spyOn(gl.FilteredSearchManager.prototype, 'toggleClearSearchButton').and.callThrough(); spyOn(gl.FilteredSearchManager.prototype, 'toggleClearSearchButton').and.callThrough();
manager.removeSelectedToken(); initializeManager();
}); });
it('calls FilteredSearchVisualTokens.removeSelectedToken', () => { it('calls FilteredSearchVisualTokens.removeSelectedToken', () => {
manager.removeSelectedToken();
expect(gl.FilteredSearchVisualTokens.removeSelectedToken).toHaveBeenCalled(); expect(gl.FilteredSearchVisualTokens.removeSelectedToken).toHaveBeenCalled();
}); });
it('calls handleInputPlaceholder', () => { it('calls handleInputPlaceholder', () => {
manager.removeSelectedToken();
expect(manager.handleInputPlaceholder).toHaveBeenCalled(); expect(manager.handleInputPlaceholder).toHaveBeenCalled();
}); });
it('calls toggleClearSearchButton', () => { it('calls toggleClearSearchButton', () => {
manager.removeSelectedToken();
expect(manager.toggleClearSearchButton).toHaveBeenCalled(); expect(manager.toggleClearSearchButton).toHaveBeenCalled();
}); });
it('calls update dropdown offset', () => { it('calls update dropdown offset', () => {
manager.removeSelectedToken();
expect(manager.dropdownManager.updateDropdownOffset).toHaveBeenCalled(); expect(manager.dropdownManager.updateDropdownOffset).toHaveBeenCalled();
}); });
}); });
describe('toggleInputContainerFocus', () => { describe('toggleInputContainerFocus', () => {
beforeEach(() => {
initializeManager();
});
it('toggles on focus', () => { it('toggles on focus', () => {
input.focus(); input.focus();
expect(document.querySelector('.filtered-search-box').classList.contains('focus')).toEqual(true); expect(document.querySelector('.filtered-search-box').classList.contains('focus')).toEqual(true);
......
...@@ -3,17 +3,9 @@ import '~/render_math'; ...@@ -3,17 +3,9 @@ import '~/render_math';
import '~/render_gfm'; import '~/render_gfm';
import issuableApp from '~/issue_show/components/app.vue'; import issuableApp from '~/issue_show/components/app.vue';
import eventHub from '~/issue_show/event_hub'; import eventHub from '~/issue_show/event_hub';
import Poll from '~/lib/utils/poll';
import issueShowData from '../mock_data'; import issueShowData from '../mock_data';
const issueShowInterceptor = data => (request, next) => {
next(request.respondWith(JSON.stringify(data), {
status: 200,
headers: {
'POLL-INTERVAL': 1,
},
}));
};
function formatText(text) { function formatText(text) {
return text.trim().replace(/\s\s+/g, ' '); return text.trim().replace(/\s\s+/g, ' ');
} }
...@@ -24,10 +16,10 @@ describe('Issuable output', () => { ...@@ -24,10 +16,10 @@ describe('Issuable output', () => {
let vm; let vm;
beforeEach(() => { beforeEach(() => {
const IssuableDescriptionComponent = Vue.extend(issuableApp);
Vue.http.interceptors.push(issueShowInterceptor(issueShowData.initialRequest));
spyOn(eventHub, '$emit'); spyOn(eventHub, '$emit');
spyOn(Poll.prototype, 'makeRequest');
const IssuableDescriptionComponent = Vue.extend(issuableApp);
vm = new IssuableDescriptionComponent({ vm = new IssuableDescriptionComponent({
propsData: { propsData: {
...@@ -54,9 +46,18 @@ describe('Issuable output', () => { ...@@ -54,9 +46,18 @@ describe('Issuable output', () => {
}); });
it('should render a title/description/edited and update title/description/edited on update', (done) => { it('should render a title/description/edited and update title/description/edited on update', (done) => {
setTimeout(() => { vm.poll.options.successCallback({
const editedText = vm.$el.querySelector('.edited-text'); json() {
return issueShowData.initialRequest;
},
});
let editedText;
Vue.nextTick()
.then(() => {
editedText = vm.$el.querySelector('.edited-text');
})
.then(() => {
expect(document.querySelector('title').innerText).toContain('this is a title (#1)'); expect(document.querySelector('title').innerText).toContain('this is a title (#1)');
expect(vm.$el.querySelector('.title').innerHTML).toContain('<p>this is a title</p>'); expect(vm.$el.querySelector('.title').innerHTML).toContain('<p>this is a title</p>');
expect(vm.$el.querySelector('.wiki').innerHTML).toContain('<p>this is a description!</p>'); expect(vm.$el.querySelector('.wiki').innerHTML).toContain('<p>this is a description!</p>');
...@@ -64,10 +65,16 @@ describe('Issuable output', () => { ...@@ -64,10 +65,16 @@ describe('Issuable output', () => {
expect(formatText(editedText.innerText)).toMatch(/Edited[\s\S]+?by Some User/); expect(formatText(editedText.innerText)).toMatch(/Edited[\s\S]+?by Some User/);
expect(editedText.querySelector('.author_link').href).toMatch(/\/some_user$/); expect(editedText.querySelector('.author_link').href).toMatch(/\/some_user$/);
expect(editedText.querySelector('time')).toBeTruthy(); expect(editedText.querySelector('time')).toBeTruthy();
})
Vue.http.interceptors.push(issueShowInterceptor(issueShowData.secondRequest)); .then(() => {
vm.poll.options.successCallback({
setTimeout(() => { json() {
return issueShowData.secondRequest;
},
});
})
.then(Vue.nextTick)
.then(() => {
expect(document.querySelector('title').innerText).toContain('2 (#1)'); expect(document.querySelector('title').innerText).toContain('2 (#1)');
expect(vm.$el.querySelector('.title').innerHTML).toContain('<p>2</p>'); expect(vm.$el.querySelector('.title').innerHTML).toContain('<p>2</p>');
expect(vm.$el.querySelector('.wiki').innerHTML).toContain('<p>42</p>'); expect(vm.$el.querySelector('.wiki').innerHTML).toContain('<p>42</p>');
...@@ -76,10 +83,9 @@ describe('Issuable output', () => { ...@@ -76,10 +83,9 @@ describe('Issuable output', () => {
expect(formatText(vm.$el.querySelector('.edited-text').innerText)).toMatch(/Edited[\s\S]+?by Other User/); expect(formatText(vm.$el.querySelector('.edited-text').innerText)).toMatch(/Edited[\s\S]+?by Other User/);
expect(editedText.querySelector('.author_link').href).toMatch(/\/other_user$/); expect(editedText.querySelector('.author_link').href).toMatch(/\/other_user$/);
expect(editedText.querySelector('time')).toBeTruthy(); expect(editedText.querySelector('time')).toBeTruthy();
})
done(); .then(done)
}); .catch(done.fail);
});
}); });
it('shows actions if permissions are correct', (done) => { it('shows actions if permissions are correct', (done) => {
...@@ -344,21 +350,23 @@ describe('Issuable output', () => { ...@@ -344,21 +350,23 @@ describe('Issuable output', () => {
describe('open form', () => { describe('open form', () => {
it('shows locked warning if form is open & data is different', (done) => { it('shows locked warning if form is open & data is different', (done) => {
Vue.http.interceptors.push(issueShowInterceptor(issueShowData.initialRequest)); vm.poll.options.successCallback({
json() {
return issueShowData.initialRequest;
},
});
Vue.nextTick() Vue.nextTick()
.then(() => new Promise((resolve) => {
setTimeout(resolve);
}))
.then(() => { .then(() => {
vm.openForm(); vm.openForm();
Vue.http.interceptors.push(issueShowInterceptor(issueShowData.secondRequest)); vm.poll.options.successCallback({
json() {
return new Promise((resolve) => { return issueShowData.secondRequest;
setTimeout(resolve); },
}); });
}) })
.then(Vue.nextTick)
.then(() => { .then(() => {
expect( expect(
vm.formState.lockedWarningVisible, vm.formState.lockedWarningVisible,
...@@ -367,9 +375,8 @@ describe('Issuable output', () => { ...@@ -367,9 +375,8 @@ describe('Issuable output', () => {
expect( expect(
vm.$el.querySelector('.alert'), vm.$el.querySelector('.alert'),
).not.toBeNull(); ).not.toBeNull();
done();
}) })
.then(done)
.catch(done.fail); .catch(done.fail);
}); });
}); });
......
...@@ -22,6 +22,19 @@ window.gl = window.gl || {}; ...@@ -22,6 +22,19 @@ window.gl = window.gl || {};
window.gl.TEST_HOST = 'http://test.host'; window.gl.TEST_HOST = 'http://test.host';
window.gon = window.gon || {}; window.gon = window.gon || {};
let hasUnhandledPromiseRejections = false;
window.addEventListener('unhandledrejection', (event) => {
hasUnhandledPromiseRejections = true;
console.error('Unhandled promise rejection:');
console.error(event.reason.stack || event.reason);
});
const checkUnhandledPromiseRejections = (done) => {
expect(hasUnhandledPromiseRejections).toBe(false);
done();
};
// HACK: Chrome 59 disconnects if there are too many synchronous tests in a row // 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 // 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 // This async beforeEach gets called on every spec and releases the JS thread long
...@@ -63,6 +76,10 @@ testsContext.keys().forEach(function (path) { ...@@ -63,6 +76,10 @@ testsContext.keys().forEach(function (path) {
} }
}); });
it('has no unhandled Promise rejections', (done) => {
setTimeout(checkUnhandledPromiseRejections(done), 1000);
});
// if we're generating coverage reports, make sure to include all files so // if we're generating coverage reports, make sure to include all files so
// that we can catch files with 0% coverage // that we can catch files with 0% coverage
// see: https://github.com/deepsweet/istanbul-instrumenter-loader/issues/15 // see: https://github.com/deepsweet/istanbul-instrumenter-loader/issues/15
......
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