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."
......
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