Commit 793b0ffd authored by Enrique Alcántara's avatar Enrique Alcántara

Merge branch '27260-migrate-spec-javascripts-issue_show-to-jest' into 'master'

Migrate issue_show to Jest

Closes #27260

See merge request gitlab-org/gitlab!30720
parents 3e35595f 065cef41
...@@ -329,7 +329,7 @@ export default { ...@@ -329,7 +329,7 @@ export default {
}, },
deleteIssuable(payload) { deleteIssuable(payload) {
this.service return this.service
.deleteIssuable(payload) .deleteIssuable(payload)
.then(res => res.data) .then(res => res.data)
.then(data => { .then(data => {
...@@ -340,7 +340,7 @@ export default { ...@@ -340,7 +340,7 @@ export default {
}) })
.catch(() => { .catch(() => {
createFlash( createFlash(
sprintf(s__('Error deleting %{issuableType}'), { issuableType: this.issuableType }), sprintf(s__('Error deleting %{issuableType}'), { issuableType: this.issuableType }),
); );
}); });
}, },
...@@ -365,7 +365,12 @@ export default { ...@@ -365,7 +365,12 @@ export default {
:issuable-type="issuableType" :issuable-type="issuableType"
/> />
<recaptcha-modal v-show="showRecaptcha" :html="recaptchaHTML" @close="closeRecaptchaModal" /> <recaptcha-modal
v-show="showRecaptcha"
ref="recaptchaModal"
:html="recaptchaHTML"
@close="closeRecaptchaModal"
/>
</div> </div>
<div v-else> <div v-else>
<title-component <title-component
......
...@@ -8395,7 +8395,7 @@ msgstr "" ...@@ -8395,7 +8395,7 @@ msgstr ""
msgid "Error creating label." msgid "Error creating label."
msgstr "" msgstr ""
msgid "Error deleting %{issuableType}" msgid "Error deleting %{issuableType}"
msgstr "" msgstr ""
msgid "Error deleting project. Check logs for error details." msgid "Error deleting project. Check logs for error details."
......
/* eslint-disable no-unused-vars */
import Vue from 'vue'; import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import setTimeoutPromise from 'spec/helpers/set_timeout_promise_helper'; import { TEST_HOST } from 'helpers/test_constants';
import GLDropdown from '~/gl_dropdown';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import { visitUrl } from '~/lib/utils/url_utility';
import '~/behaviors/markdown/render_gfm'; import '~/behaviors/markdown/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';
...@@ -13,6 +12,9 @@ function formatText(text) { ...@@ -13,6 +12,9 @@ function formatText(text) {
return text.trim().replace(/\s\s+/g, ' '); return text.trim().replace(/\s\s+/g, ' ');
} }
jest.mock('~/lib/utils/url_utility');
jest.mock('~/issue_show/event_hub');
const REALTIME_REQUEST_STACK = [initialRequest, secondRequest]; const REALTIME_REQUEST_STACK = [initialRequest, secondRequest];
describe('Issuable output', () => { describe('Issuable output', () => {
...@@ -20,9 +22,10 @@ describe('Issuable output', () => { ...@@ -20,9 +22,10 @@ describe('Issuable output', () => {
let realtimeRequestCount = 0; let realtimeRequestCount = 0;
let vm; let vm;
beforeEach(done => { beforeEach(() => {
setFixtures(` setFixtures(`
<div> <div>
<title>Title</title>
<div class="detail-page-description content-block"> <div class="detail-page-description content-block">
<details open> <details open>
<summary>One</summary> <summary>One</summary>
...@@ -35,7 +38,6 @@ describe('Issuable output', () => { ...@@ -35,7 +38,6 @@ describe('Issuable output', () => {
<span id="task_status"></span> <span id="task_status"></span>
</div> </div>
`); `);
spyOn(eventHub, '$emit');
const IssuableDescriptionComponent = Vue.extend(issuableApp); const IssuableDescriptionComponent = Vue.extend(issuableApp);
...@@ -53,7 +55,7 @@ describe('Issuable output', () => { ...@@ -53,7 +55,7 @@ describe('Issuable output', () => {
canUpdate: true, canUpdate: true,
canDestroy: true, canDestroy: true,
endpoint: '/gitlab-org/gitlab-shell/-/issues/9/realtime_changes', endpoint: '/gitlab-org/gitlab-shell/-/issues/9/realtime_changes',
updateEndpoint: gl.TEST_HOST, updateEndpoint: TEST_HOST,
issuableRef: '#1', issuableRef: '#1',
initialTitleHtml: '', initialTitleHtml: '',
initialTitleText: '', initialTitleText: '',
...@@ -67,8 +69,6 @@ describe('Issuable output', () => { ...@@ -67,8 +69,6 @@ describe('Issuable output', () => {
issuableTemplateNamesPath: '/issuable-templates-path', issuableTemplateNamesPath: '/issuable-templates-path',
}, },
}).$mount(); }).$mount();
setTimeout(done);
}); });
afterEach(() => { afterEach(() => {
...@@ -79,9 +79,10 @@ describe('Issuable output', () => { ...@@ -79,9 +79,10 @@ describe('Issuable output', () => {
vm.$destroy(); vm.$destroy();
}); });
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', () => {
let editedText; let editedText;
Vue.nextTick() return axios
.waitForAll()
.then(() => { .then(() => {
editedText = vm.$el.querySelector('.edited-text'); editedText = vm.$el.querySelector('.edited-text');
}) })
...@@ -100,8 +101,8 @@ describe('Issuable output', () => { ...@@ -100,8 +101,8 @@ describe('Issuable output', () => {
}) })
.then(() => { .then(() => {
vm.poll.makeRequest(); vm.poll.makeRequest();
return axios.waitForAll();
}) })
.then(() => new Promise(resolve => setTimeout(resolve)))
.then(() => { .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>');
...@@ -115,312 +116,239 @@ describe('Issuable output', () => { ...@@ -115,312 +116,239 @@ describe('Issuable output', () => {
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();
expect(vm.state.lock_version).toEqual(2); expect(vm.state.lock_version).toEqual(2);
}) });
.then(done)
.catch(done.fail);
}); });
it('shows actions if permissions are correct', done => { it('shows actions if permissions are correct', () => {
vm.showForm = true; vm.showForm = true;
Vue.nextTick(() => { return vm.$nextTick().then(() => {
expect(vm.$el.querySelector('.btn')).not.toBeNull(); expect(vm.$el.querySelector('.btn')).not.toBeNull();
done();
}); });
}); });
it('does not show actions if permissions are incorrect', done => { it('does not show actions if permissions are incorrect', () => {
vm.showForm = true; vm.showForm = true;
vm.canUpdate = false; vm.canUpdate = false;
Vue.nextTick(() => { return vm.$nextTick().then(() => {
expect(vm.$el.querySelector('.btn')).toBeNull(); expect(vm.$el.querySelector('.btn')).toBeNull();
done();
}); });
}); });
it('does not update formState if form is already open', done => { it('does not update formState if form is already open', () => {
vm.updateAndShowForm(); vm.updateAndShowForm();
vm.state.titleText = 'testing 123'; vm.state.titleText = 'testing 123';
vm.updateAndShowForm(); vm.updateAndShowForm();
Vue.nextTick(() => { return vm.$nextTick().then(() => {
expect(vm.store.formState.title).not.toBe('testing 123'); expect(vm.store.formState.title).not.toBe('testing 123');
});
});
it('opens reCAPTCHA modal if update rejected as spam', () => {
let modal;
done(); jest.spyOn(vm.service, 'updateIssuable').mockResolvedValue({
data: {
recaptcha_html: '<div class="g-recaptcha">recaptcha_html</div>',
},
}); });
vm.canUpdate = true;
vm.showForm = true;
return vm
.$nextTick()
.then(() => {
vm.$refs.recaptchaModal.scriptSrc = '//scriptsrc';
return vm.updateIssuable();
})
.then(() => {
modal = vm.$el.querySelector('.js-recaptcha-modal');
expect(modal.style.display).not.toEqual('none');
expect(modal.querySelector('.g-recaptcha').textContent).toEqual('recaptcha_html');
expect(document.body.querySelector('.js-recaptcha-script').src).toMatch('//scriptsrc');
})
.then(() => {
modal.querySelector('.close').click();
return vm.$nextTick();
})
.then(() => {
expect(modal.style.display).toEqual('none');
expect(document.body.querySelector('.js-recaptcha-script')).toBeNull();
});
}); });
describe('updateIssuable', () => { describe('updateIssuable', () => {
it('fetches new data after update', done => { it('fetches new data after update', () => {
spyOn(vm, 'updateStoreState').and.callThrough(); const updateStoreSpy = jest.spyOn(vm, 'updateStoreState');
spyOn(vm.service, 'getData').and.callThrough(); const getDataSpy = jest.spyOn(vm.service, 'getData');
spyOn(vm.service, 'updateIssuable').and.returnValue( jest.spyOn(vm.service, 'updateIssuable').mockResolvedValue({
Promise.resolve({ data: { web_url: window.location.pathname },
data: { web_url: window.location.pathname }, });
}),
); return vm.updateIssuable().then(() => {
expect(updateStoreSpy).toHaveBeenCalled();
vm.updateIssuable() expect(getDataSpy).toHaveBeenCalled();
.then(() => { });
expect(vm.updateStoreState).toHaveBeenCalled();
expect(vm.service.getData).toHaveBeenCalled();
})
.then(done)
.catch(done.fail);
}); });
it('correctly updates issuable data', done => { it('correctly updates issuable data', () => {
spyOn(vm.service, 'updateIssuable').and.returnValue( const spy = jest.spyOn(vm.service, 'updateIssuable').mockResolvedValue({
Promise.resolve({ data: { web_url: window.location.pathname },
data: { web_url: window.location.pathname }, });
}),
);
vm.updateIssuable() return vm.updateIssuable().then(() => {
.then(() => { expect(spy).toHaveBeenCalledWith(vm.formState);
expect(vm.service.updateIssuable).toHaveBeenCalledWith(vm.formState); expect(eventHub.$emit).toHaveBeenCalledWith('close.form');
expect(eventHub.$emit).toHaveBeenCalledWith('close.form'); });
})
.then(done)
.catch(done.fail);
}); });
it('does not redirect if issue has not moved', done => { it('does not redirect if issue has not moved', () => {
const visitUrl = spyOnDependency(issuableApp, 'visitUrl'); jest.spyOn(vm.service, 'updateIssuable').mockResolvedValue({
spyOn(vm.service, 'updateIssuable').and.returnValue( data: {
Promise.resolve({ web_url: window.location.pathname,
data: { confidential: vm.isConfidential,
web_url: window.location.pathname, },
confidential: vm.isConfidential, });
},
}),
);
vm.updateIssuable();
setTimeout(() => { return vm.updateIssuable().then(() => {
expect(visitUrl).not.toHaveBeenCalled(); expect(visitUrl).not.toHaveBeenCalled();
done();
}); });
}); });
it('redirects if returned web_url has changed', done => { it('redirects if returned web_url has changed', () => {
const visitUrl = spyOnDependency(issuableApp, 'visitUrl'); jest.spyOn(vm.service, 'updateIssuable').mockResolvedValue({
spyOn(vm.service, 'updateIssuable').and.returnValue( data: {
Promise.resolve({ web_url: '/testing-issue-move',
data: { confidential: vm.isConfidential,
web_url: '/testing-issue-move', },
confidential: vm.isConfidential, });
},
}),
);
vm.updateIssuable(); vm.updateIssuable();
setTimeout(() => { return vm.updateIssuable().then(() => {
expect(visitUrl).toHaveBeenCalledWith('/testing-issue-move'); expect(visitUrl).toHaveBeenCalledWith('/testing-issue-move');
done();
}); });
}); });
describe('shows dialog when issue has unsaved changed', () => { describe('shows dialog when issue has unsaved changed', () => {
it('confirms on title change', done => { it('confirms on title change', () => {
vm.showForm = true; vm.showForm = true;
vm.state.titleText = 'title has changed'; vm.state.titleText = 'title has changed';
const e = { returnValue: null }; const e = { returnValue: null };
vm.handleBeforeUnloadEvent(e); vm.handleBeforeUnloadEvent(e);
Vue.nextTick(() => { return vm.$nextTick().then(() => {
expect(e.returnValue).not.toBeNull(); expect(e.returnValue).not.toBeNull();
done();
}); });
}); });
it('confirms on description change', done => { it('confirms on description change', () => {
vm.showForm = true; vm.showForm = true;
vm.state.descriptionText = 'description has changed'; vm.state.descriptionText = 'description has changed';
const e = { returnValue: null }; const e = { returnValue: null };
vm.handleBeforeUnloadEvent(e); vm.handleBeforeUnloadEvent(e);
Vue.nextTick(() => { return vm.$nextTick().then(() => {
expect(e.returnValue).not.toBeNull(); expect(e.returnValue).not.toBeNull();
done();
}); });
}); });
it('does nothing when nothing has changed', done => { it('does nothing when nothing has changed', () => {
const e = { returnValue: null }; const e = { returnValue: null };
vm.handleBeforeUnloadEvent(e); vm.handleBeforeUnloadEvent(e);
Vue.nextTick(() => { return vm.$nextTick().then(() => {
expect(e.returnValue).toBeNull(); expect(e.returnValue).toBeNull();
done();
}); });
}); });
}); });
describe('error when updating', () => { describe('error when updating', () => {
it('closes form on error', done => { it('closes form on error', () => {
spyOn(vm.service, 'updateIssuable').and.callFake(() => Promise.reject()); jest.spyOn(vm.service, 'updateIssuable').mockRejectedValue();
vm.updateIssuable(); return vm.updateIssuable().then(() => {
setTimeout(() => {
expect(eventHub.$emit).not.toHaveBeenCalledWith('close.form'); expect(eventHub.$emit).not.toHaveBeenCalledWith('close.form');
expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe( expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe(
`Error updating issue`, `Error updating issue`,
); );
done();
}); });
}); });
it('returns the correct error message for issuableType', done => { it('returns the correct error message for issuableType', () => {
spyOn(vm.service, 'updateIssuable').and.callFake(() => Promise.reject()); jest.spyOn(vm.service, 'updateIssuable').mockRejectedValue();
vm.issuableType = 'merge request'; vm.issuableType = 'merge request';
Vue.nextTick(() => { return vm
vm.updateIssuable(); .$nextTick()
.then(vm.updateIssuable)
setTimeout(() => { .then(() => {
expect(eventHub.$emit).not.toHaveBeenCalledWith('close.form'); expect(eventHub.$emit).not.toHaveBeenCalledWith('close.form');
expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe( expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe(
`Error updating merge request`, `Error updating merge request`,
); );
done();
}); });
});
}); });
it('shows error message from backend if exists', done => { it('shows error message from backend if exists', () => {
const msg = 'Custom error message from backend'; const msg = 'Custom error message from backend';
spyOn(vm.service, 'updateIssuable').and.callFake( jest
// eslint-disable-next-line prefer-promise-reject-errors .spyOn(vm.service, 'updateIssuable')
() => Promise.reject({ response: { data: { errors: [msg] } } }), .mockRejectedValue({ response: { data: { errors: [msg] } } });
);
vm.updateIssuable(); return vm.updateIssuable().then(() => {
setTimeout(() => {
expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe( expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe(
`${vm.defaultErrorMessage}. ${msg}`, `${vm.defaultErrorMessage}. ${msg}`,
); );
done();
}); });
}); });
}); });
}); });
it('opens reCAPTCHA modal if update rejected as spam', done => { describe('deleteIssuable', () => {
function mockScriptSrc() { it('changes URL when deleted', () => {
const recaptchaChild = vm.$children.find( jest.spyOn(vm.service, 'deleteIssuable').mockResolvedValue({
// eslint-disable-next-line no-underscore-dangle
child => child.$options._componentTag === 'recaptcha-modal',
);
recaptchaChild.scriptSrc = '//scriptsrc';
}
let modal;
const promise = new Promise(resolve => {
resolve({
data: { data: {
recaptcha_html: '<div class="g-recaptcha">recaptcha_html</div>', web_url: '/test',
}, },
}); });
});
spyOn(vm.service, 'updateIssuable').and.returnValue(promise);
vm.canUpdate = true;
vm.showForm = true;
vm.$nextTick()
.then(() => mockScriptSrc())
.then(() => vm.updateIssuable())
.then(promise)
.then(() => setTimeoutPromise())
.then(() => {
modal = vm.$el.querySelector('.js-recaptcha-modal');
expect(modal.style.display).not.toEqual('none'); return vm.deleteIssuable().then(() => {
expect(modal.querySelector('.g-recaptcha').textContent).toEqual('recaptcha_html');
expect(document.body.querySelector('.js-recaptcha-script').src).toMatch('//scriptsrc');
})
.then(() => modal.querySelector('.close').click())
.then(() => vm.$nextTick())
.then(() => {
expect(modal.style.display).toEqual('none');
expect(document.body.querySelector('.js-recaptcha-script')).toBeNull();
})
.then(done)
.catch(done.fail);
});
describe('deleteIssuable', () => {
it('changes URL when deleted', done => {
const visitUrl = spyOnDependency(issuableApp, 'visitUrl');
spyOn(vm.service, 'deleteIssuable').and.returnValue(
Promise.resolve({
data: {
web_url: '/test',
},
}),
);
vm.deleteIssuable();
setTimeout(() => {
expect(visitUrl).toHaveBeenCalledWith('/test'); expect(visitUrl).toHaveBeenCalledWith('/test');
done();
}); });
}); });
it('stops polling when deleting', done => { it('stops polling when deleting', () => {
spyOnDependency(issuableApp, 'visitUrl'); const spy = jest.spyOn(vm.poll, 'stop');
spyOn(vm.poll, 'stop').and.callThrough(); jest.spyOn(vm.service, 'deleteIssuable').mockResolvedValue({
spyOn(vm.service, 'deleteIssuable').and.returnValue( data: {
Promise.resolve({ web_url: '/test',
data: { },
web_url: '/test', });
},
}),
);
vm.deleteIssuable();
setTimeout(() => {
expect(vm.poll.stop).toHaveBeenCalledWith();
done(); return vm.deleteIssuable().then(() => {
expect(spy).toHaveBeenCalledWith();
}); });
}); });
it('closes form on error', done => { it('closes form on error', () => {
spyOn(vm.service, 'deleteIssuable').and.returnValue(Promise.reject()); jest.spyOn(vm.service, 'deleteIssuable').mockRejectedValue();
vm.deleteIssuable(); return vm.deleteIssuable().then(() => {
setTimeout(() => {
expect(eventHub.$emit).not.toHaveBeenCalledWith('close.form'); expect(eventHub.$emit).not.toHaveBeenCalledWith('close.form');
expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe( expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe(
'Error deleting issue', 'Error deleting issue',
); );
done();
}); });
}); });
}); });
describe('updateAndShowForm', () => { describe('updateAndShowForm', () => {
it('shows locked warning if form is open & data is different', done => { it('shows locked warning if form is open & data is different', () => {
vm.$nextTick() return vm
.$nextTick()
.then(() => { .then(() => {
vm.updateAndShowForm(); vm.updateAndShowForm();
...@@ -436,44 +364,38 @@ describe('Issuable output', () => { ...@@ -436,44 +364,38 @@ describe('Issuable output', () => {
expect(vm.formState.lockedWarningVisible).toEqual(true); expect(vm.formState.lockedWarningVisible).toEqual(true);
expect(vm.formState.lock_version).toEqual(1); expect(vm.formState.lock_version).toEqual(1);
expect(vm.$el.querySelector('.alert')).not.toBeNull(); expect(vm.$el.querySelector('.alert')).not.toBeNull();
}) });
.then(done)
.catch(done.fail);
}); });
}); });
describe('requestTemplatesAndShowForm', () => { describe('requestTemplatesAndShowForm', () => {
let formSpy;
beforeEach(() => { beforeEach(() => {
spyOn(vm, 'updateAndShowForm'); formSpy = jest.spyOn(vm, 'updateAndShowForm');
}); });
it('shows the form if template names request is successful', done => { it('shows the form if template names request is successful', () => {
const mockData = [{ name: 'Bug' }]; const mockData = [{ name: 'Bug' }];
mock.onGet('/issuable-templates-path').reply(() => Promise.resolve([200, mockData])); mock.onGet('/issuable-templates-path').reply(() => Promise.resolve([200, mockData]));
vm.requestTemplatesAndShowForm() return vm.requestTemplatesAndShowForm().then(() => {
.then(() => { expect(formSpy).toHaveBeenCalledWith(mockData);
expect(vm.updateAndShowForm).toHaveBeenCalledWith(mockData); });
})
.then(done)
.catch(done.fail);
}); });
it('shows the form if template names request failed', done => { it('shows the form if template names request failed', () => {
mock mock
.onGet('/issuable-templates-path') .onGet('/issuable-templates-path')
.reply(() => Promise.reject(new Error('something went wrong'))); .reply(() => Promise.reject(new Error('something went wrong')));
vm.requestTemplatesAndShowForm() return vm.requestTemplatesAndShowForm().then(() => {
.then(() => { expect(document.querySelector('.flash-container .flash-text').textContent).toContain(
expect(document.querySelector('.flash-container .flash-text').textContent).toContain( 'Error updating issue',
'Error updating issue', );
);
expect(vm.updateAndShowForm).toHaveBeenCalledWith(); expect(formSpy).toHaveBeenCalledWith();
}) });
.then(done)
.catch(done.fail);
}); });
}); });
...@@ -490,32 +412,26 @@ describe('Issuable output', () => { ...@@ -490,32 +412,26 @@ describe('Issuable output', () => {
}); });
describe('updateStoreState', () => { describe('updateStoreState', () => {
it('should make a request and update the state of the store', done => { it('should make a request and update the state of the store', () => {
const data = { foo: 1 }; const data = { foo: 1 };
spyOn(vm.store, 'updateState'); const getDataSpy = jest.spyOn(vm.service, 'getData').mockResolvedValue({ data });
spyOn(vm.service, 'getData').and.returnValue(Promise.resolve({ data })); const updateStateSpy = jest.spyOn(vm.store, 'updateState').mockImplementation(jest.fn);
vm.updateStoreState() return vm.updateStoreState().then(() => {
.then(() => { expect(getDataSpy).toHaveBeenCalled();
expect(vm.service.getData).toHaveBeenCalled(); expect(updateStateSpy).toHaveBeenCalledWith(data);
expect(vm.store.updateState).toHaveBeenCalledWith(data); });
})
.then(done)
.catch(done.fail);
}); });
it('should show error message if store update fails', done => { it('should show error message if store update fails', () => {
spyOn(vm.service, 'getData').and.returnValue(Promise.reject()); jest.spyOn(vm.service, 'getData').mockRejectedValue();
vm.issuableType = 'merge request'; vm.issuableType = 'merge request';
vm.updateStoreState() return vm.updateStoreState().then(() => {
.then(() => { expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe(
expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe( `Error updating ${vm.issuableType}`,
`Error updating ${vm.issuableType}`, );
); });
})
.then(done)
.catch(done.fail);
}); });
}); });
......
import $ from 'jquery'; import $ from 'jquery';
import Vue from 'vue'; import Vue from 'vue';
import '~/behaviors/markdown/render_gfm'; import '~/behaviors/markdown/render_gfm';
import mountComponent from 'spec/helpers/vue_mount_component_helper'; import mountComponent from 'helpers/vue_mount_component_helper';
import { TEST_HOST } from 'helpers/test_constants';
import Description from '~/issue_show/components/description.vue'; import Description from '~/issue_show/components/description.vue';
import TaskList from '~/task_list';
jest.mock('~/task_list');
describe('Description component', () => { describe('Description component', () => {
let vm; let vm;
...@@ -13,7 +17,7 @@ describe('Description component', () => { ...@@ -13,7 +17,7 @@ describe('Description component', () => {
descriptionText: 'test', descriptionText: 'test',
updatedAt: new Date().toString(), updatedAt: new Date().toString(),
taskStatus: '', taskStatus: '',
updateUrl: gl.TEST_HOST, updateUrl: TEST_HOST,
}; };
beforeEach(() => { beforeEach(() => {
...@@ -39,25 +43,26 @@ describe('Description component', () => { ...@@ -39,25 +43,26 @@ describe('Description component', () => {
$('.issuable-meta .flash-container').remove(); $('.issuable-meta .flash-container').remove();
}); });
it('animates description changes', done => { it('animates description changes', () => {
vm.descriptionHtml = 'changed'; vm.descriptionHtml = 'changed';
Vue.nextTick(() => { return vm
expect( .$nextTick()
vm.$el.querySelector('.md').classList.contains('issue-realtime-pre-pulse'), .then(() => {
).toBeTruthy(); expect(
vm.$el.querySelector('.md').classList.contains('issue-realtime-pre-pulse'),
setTimeout(() => { ).toBeTruthy();
jest.runAllTimers();
return vm.$nextTick();
})
.then(() => {
expect( expect(
vm.$el.querySelector('.md').classList.contains('issue-realtime-trigger-pulse'), vm.$el.querySelector('.md').classList.contains('issue-realtime-trigger-pulse'),
).toBeTruthy(); ).toBeTruthy();
done();
}); });
});
}); });
it('opens reCAPTCHA dialog if update rejected as spam', done => { it('opens reCAPTCHA dialog if update rejected as spam', () => {
let modal; let modal;
const recaptchaChild = vm.$children.find( const recaptchaChild = vm.$children.find(
// eslint-disable-next-line no-underscore-dangle // eslint-disable-next-line no-underscore-dangle
...@@ -70,7 +75,8 @@ describe('Description component', () => { ...@@ -70,7 +75,8 @@ describe('Description component', () => {
recaptcha_html: '<div class="g-recaptcha">recaptcha_html</div>', recaptcha_html: '<div class="g-recaptcha">recaptcha_html</div>',
}); });
vm.$nextTick() return vm
.$nextTick()
.then(() => { .then(() => {
modal = vm.$el.querySelector('.js-recaptcha-modal'); modal = vm.$el.querySelector('.js-recaptcha-modal');
...@@ -83,128 +89,105 @@ describe('Description component', () => { ...@@ -83,128 +89,105 @@ describe('Description component', () => {
.then(() => { .then(() => {
expect(modal.style.display).toEqual('none'); expect(modal.style.display).toEqual('none');
expect(document.body.querySelector('.js-recaptcha-script')).toBeNull(); expect(document.body.querySelector('.js-recaptcha-script')).toBeNull();
}) });
.then(done)
.catch(done.fail);
}); });
describe('TaskList', () => { it('applies syntax highlighting and math when description changed', () => {
let TaskList; const vmSpy = jest.spyOn(vm, 'renderGFM');
const prototypeSpy = jest.spyOn($.prototype, 'renderGFM');
vm.descriptionHtml = 'changed';
return vm.$nextTick().then(() => {
expect(vm.$refs['gfm-content']).toBeDefined();
expect(vmSpy).toHaveBeenCalled();
expect(prototypeSpy).toHaveBeenCalled();
expect($.prototype.renderGFM).toHaveBeenCalled();
});
});
it('sets data-update-url', () => {
expect(vm.$el.querySelector('textarea').dataset.updateUrl).toEqual(TEST_HOST);
});
describe('TaskList', () => {
beforeEach(() => { beforeEach(() => {
vm.$destroy(); vm.$destroy();
TaskList.mockClear();
vm = mountComponent( vm = mountComponent(
DescriptionComponent, DescriptionComponent,
Object.assign({}, props, { Object.assign({}, props, {
issuableType: 'issuableType', issuableType: 'issuableType',
}), }),
); );
TaskList = spyOnDependency(Description, 'TaskList');
}); });
it('re-inits the TaskList when description changed', done => { it('re-inits the TaskList when description changed', () => {
vm.descriptionHtml = 'changed'; vm.descriptionHtml = 'changed';
setTimeout(() => { expect(TaskList).toHaveBeenCalled();
expect(TaskList).toHaveBeenCalled();
done();
});
}); });
it('does not re-init the TaskList when canUpdate is false', done => { it('does not re-init the TaskList when canUpdate is false', () => {
vm.canUpdate = false; vm.canUpdate = false;
vm.descriptionHtml = 'changed'; vm.descriptionHtml = 'changed';
setTimeout(() => { expect(TaskList).toHaveBeenCalledTimes(1);
expect(TaskList).not.toHaveBeenCalled();
done();
});
}); });
it('calls with issuableType dataType', done => { it('calls with issuableType dataType', () => {
vm.descriptionHtml = 'changed'; vm.descriptionHtml = 'changed';
setTimeout(() => { expect(TaskList).toHaveBeenCalledWith({
expect(TaskList).toHaveBeenCalledWith({ dataType: 'issuableType',
dataType: 'issuableType', fieldName: 'description',
fieldName: 'description', selector: '.detail-page-description',
selector: '.detail-page-description', onSuccess: expect.any(Function),
onSuccess: jasmine.any(Function), onError: expect.any(Function),
onError: jasmine.any(Function), lockVersion: 0,
lockVersion: 0,
});
done();
}); });
}); });
}); });
describe('taskStatus', () => { describe('taskStatus', () => {
it('adds full taskStatus', done => { it('adds full taskStatus', () => {
vm.taskStatus = '1 of 1'; vm.taskStatus = '1 of 1';
setTimeout(() => { return vm.$nextTick().then(() => {
expect(document.querySelector('.issuable-meta #task_status').textContent.trim()).toBe( expect(document.querySelector('.issuable-meta #task_status').textContent.trim()).toBe(
'1 of 1', '1 of 1',
); );
done();
}); });
}); });
it('adds short taskStatus', done => { it('adds short taskStatus', () => {
vm.taskStatus = '1 of 1'; vm.taskStatus = '1 of 1';
setTimeout(() => { return vm.$nextTick().then(() => {
expect(document.querySelector('.issuable-meta #task_status_short').textContent.trim()).toBe( expect(document.querySelector('.issuable-meta #task_status_short').textContent.trim()).toBe(
'1/1 task', '1/1 task',
); );
done();
}); });
}); });
it('clears task status text when no tasks are present', done => { it('clears task status text when no tasks are present', () => {
vm.taskStatus = '0 of 0'; vm.taskStatus = '0 of 0';
setTimeout(() => { return vm.$nextTick().then(() => {
expect(document.querySelector('.issuable-meta #task_status').textContent.trim()).toBe(''); expect(document.querySelector('.issuable-meta #task_status').textContent.trim()).toBe('');
done();
}); });
}); });
}); });
it('applies syntax highlighting and math when description changed', done => {
spyOn(vm, 'renderGFM').and.callThrough();
spyOn($.prototype, 'renderGFM').and.callThrough();
vm.descriptionHtml = 'changed';
Vue.nextTick(() => {
setTimeout(() => {
expect(vm.$refs['gfm-content']).toBeDefined();
expect(vm.renderGFM).toHaveBeenCalled();
expect($.prototype.renderGFM).toHaveBeenCalled();
done();
});
});
});
it('sets data-update-url', () => {
expect(vm.$el.querySelector('textarea').dataset.updateUrl).toEqual(gl.TEST_HOST);
});
describe('taskListUpdateError', () => { describe('taskListUpdateError', () => {
it('should create flash notification and emit an event to parent', () => { it('should create flash notification and emit an event to parent', () => {
const msg = const msg =
'Someone edited this issue at the same time you did. The description has been updated and you will need to make your changes again.'; 'Someone edited this issue at the same time you did. The description has been updated and you will need to make your changes again.';
spyOn(vm, '$emit'); const spy = jest.spyOn(vm, '$emit');
vm.taskListUpdateError(); vm.taskListUpdateError();
expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe(msg); expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe(msg);
expect(vm.$emit).toHaveBeenCalledWith('taskListUpdateFailed'); expect(spy).toHaveBeenCalledWith('taskListUpdateFailed');
}); });
}); });
}); });
...@@ -5,7 +5,7 @@ describe('Issue description template component', () => { ...@@ -5,7 +5,7 @@ describe('Issue description template component', () => {
let vm; let vm;
let formState; let formState;
beforeEach(done => { beforeEach(() => {
const Component = Vue.extend(descriptionTemplate); const Component = Vue.extend(descriptionTemplate);
formState = { formState = {
description: 'test', description: 'test',
...@@ -19,8 +19,6 @@ describe('Issue description template component', () => { ...@@ -19,8 +19,6 @@ describe('Issue description template component', () => {
projectNamespace: '/', projectNamespace: '/',
}, },
}).$mount(); }).$mount();
Vue.nextTick(done);
}); });
it('renders templates as JSON array in data attribute', () => { it('renders templates as JSON array in data attribute', () => {
......
import Vue from 'vue'; import Vue from 'vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper'; import mountComponent from 'helpers/vue_mount_component_helper';
import formComponent from '~/issue_show/components/form.vue'; import formComponent from '~/issue_show/components/form.vue';
import Autosave from '~/autosave';
import eventHub from '~/issue_show/event_hub'; import eventHub from '~/issue_show/event_hub';
jest.mock('~/autosave');
describe('Inline edit form component', () => { describe('Inline edit form component', () => {
let vm; let vm;
const defaultProps = { const defaultProps = {
...@@ -65,18 +68,16 @@ describe('Inline edit form component', () => { ...@@ -65,18 +68,16 @@ describe('Inline edit form component', () => {
}); });
describe('autosave', () => { describe('autosave', () => {
let autosaveObj; let spy;
let autosave;
beforeEach(() => { beforeEach(() => {
autosaveObj = { reset: jasmine.createSpy() }; spy = jest.spyOn(Autosave.prototype, 'reset');
autosave = spyOnDependency(formComponent, 'Autosave').and.returnValue(autosaveObj);
}); });
it('initialized Autosave on mount', () => { it('initialized Autosave on mount', () => {
createComponent(); createComponent();
expect(autosave).toHaveBeenCalledTimes(2); expect(Autosave).toHaveBeenCalledTimes(2);
}); });
it('calls reset on autosave when eventHub emits appropriate events', () => { it('calls reset on autosave when eventHub emits appropriate events', () => {
...@@ -84,15 +85,15 @@ describe('Inline edit form component', () => { ...@@ -84,15 +85,15 @@ describe('Inline edit form component', () => {
eventHub.$emit('close.form'); eventHub.$emit('close.form');
expect(autosaveObj.reset).toHaveBeenCalledTimes(2); expect(spy).toHaveBeenCalledTimes(2);
eventHub.$emit('delete.issuable'); eventHub.$emit('delete.issuable');
expect(autosaveObj.reset).toHaveBeenCalledTimes(4); expect(spy).toHaveBeenCalledTimes(4);
eventHub.$emit('update.issuable'); eventHub.$emit('update.issuable');
expect(autosaveObj.reset).toHaveBeenCalledTimes(6); expect(spy).toHaveBeenCalledTimes(6);
}); });
}); });
}); });
...@@ -5,8 +5,9 @@ import eventHub from '~/issue_show/event_hub'; ...@@ -5,8 +5,9 @@ import eventHub from '~/issue_show/event_hub';
describe('Title component', () => { describe('Title component', () => {
let vm; let vm;
beforeEach(() => { beforeEach(() => {
setFixtures(`<title />`);
const Component = Vue.extend(titleComponent); const Component = Vue.extend(titleComponent);
const store = new Store({ const store = new Store({
titleHtml: '', titleHtml: '',
...@@ -28,51 +29,39 @@ describe('Title component', () => { ...@@ -28,51 +29,39 @@ describe('Title component', () => {
expect(vm.$el.querySelector('.title').innerHTML.trim()).toBe('Testing <img>'); expect(vm.$el.querySelector('.title').innerHTML.trim()).toBe('Testing <img>');
}); });
it('updates page title when changing titleHtml', done => { it('updates page title when changing titleHtml', () => {
spyOn(vm, 'setPageTitle'); const spy = jest.spyOn(vm, 'setPageTitle');
vm.titleHtml = 'test'; vm.titleHtml = 'test';
Vue.nextTick(() => { return vm.$nextTick().then(() => {
expect(vm.setPageTitle).toHaveBeenCalled(); expect(spy).toHaveBeenCalled();
done();
}); });
}); });
it('animates title changes', done => { it('animates title changes', () => {
vm.titleHtml = 'test'; vm.titleHtml = 'test';
return vm
Vue.nextTick(() => { .$nextTick()
expect( .then(() => {
vm.$el.querySelector('.title').classList.contains('issue-realtime-pre-pulse'), expect(vm.$el.querySelector('.title').classList).toContain('issue-realtime-pre-pulse');
).toBeTruthy(); jest.runAllTimers();
return vm.$nextTick();
setTimeout(() => { })
expect( .then(() => {
vm.$el.querySelector('.title').classList.contains('issue-realtime-trigger-pulse'), expect(vm.$el.querySelector('.title').classList).toContain('issue-realtime-trigger-pulse');
).toBeTruthy();
done();
}); });
});
}); });
it('updates page title after changing title', done => { it('updates page title after changing title', () => {
vm.titleHtml = 'changed'; vm.titleHtml = 'changed';
vm.titleText = 'changed'; vm.titleText = 'changed';
Vue.nextTick(() => { return vm.$nextTick().then(() => {
expect(document.querySelector('title').textContent.trim()).toContain('changed'); expect(document.querySelector('title').textContent.trim()).toContain('changed');
done();
}); });
}); });
describe('inline edit button', () => { describe('inline edit button', () => {
beforeEach(() => {
spyOn(eventHub, '$emit');
});
it('should not show by default', () => { it('should not show by default', () => {
expect(vm.$el.querySelector('.btn-edit')).toBeNull(); expect(vm.$el.querySelector('.btn-edit')).toBeNull();
}); });
...@@ -92,6 +81,7 @@ describe('Title component', () => { ...@@ -92,6 +81,7 @@ describe('Title component', () => {
}); });
it('should trigger open.form event when clicked', () => { it('should trigger open.form event when clicked', () => {
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
vm.showInlineEditButton = true; vm.showInlineEditButton = true;
vm.canUpdate = true; vm.canUpdate = true;
......
export * from '../../frontend/issue_show/helpers.js';
export * from '../../frontend/issue_show/mock_data';
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