Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/vue_shared/components')
-rw-r--r--spec/frontend/vue_shared/components/blob_viewers/__snapshots__/simple_viewer_spec.js.snap4
-rw-r--r--spec/frontend/vue_shared/components/blob_viewers/simple_viewer_spec.js32
-rw-r--r--spec/frontend/vue_shared/components/chronic_duration_input_spec.js25
-rw-r--r--spec/frontend/vue_shared/components/confirm_fork_modal_spec.js80
-rw-r--r--spec/frontend/vue_shared/components/date_time_picker/date_time_picker_spec.js258
-rw-r--r--spec/frontend/vue_shared/components/deployment_instance/deployment_instance_spec.js29
-rw-r--r--spec/frontend/vue_shared/components/design_management/design_note_pin_spec.js68
-rw-r--r--spec/frontend/vue_shared/components/diff_viewer/diff_viewer_spec.js44
-rw-r--r--spec/frontend/vue_shared/components/diff_viewer/viewers/image_diff_viewer_spec.js120
-rw-r--r--spec/frontend/vue_shared/components/diff_viewer/viewers/renamed_spec.js26
-rw-r--r--spec/frontend/vue_shared/components/dismissible_feedback_alert_spec.js3
-rw-r--r--spec/frontend/vue_shared/components/dropdown/dropdown_search_input_spec.js8
-rw-r--r--spec/frontend/vue_shared/components/dropdown/dropdown_widget_spec.js5
-rw-r--r--spec/frontend/vue_shared/components/dropdown_keyboard_navigation_spec.js3
-rw-r--r--spec/frontend/vue_shared/components/expand_button_spec.js39
-rw-r--r--spec/frontend/vue_shared/components/file_finder/index_spec.js152
-rw-r--r--spec/frontend/vue_shared/components/file_finder/item_spec.js53
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js33
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js26
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js141
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/branch_token_spec.js5
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/emoji_token_spec.js9
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/label_token_spec.js7
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/milestone_token_spec.js35
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/release_token_spec.js3
-rw-r--r--spec/frontend/vue_shared/components/gfm_autocomplete/__snapshots__/utils_spec.js.snap54
-rw-r--r--spec/frontend/vue_shared/components/gfm_autocomplete/gfm_autocomplete_spec.js34
-rw-r--r--spec/frontend/vue_shared/components/gfm_autocomplete/utils_spec.js427
-rw-r--r--spec/frontend/vue_shared/components/gl_countdown_spec.js20
-rw-r--r--spec/frontend/vue_shared/components/gl_modal_vuex_spec.js24
-rw-r--r--spec/frontend/vue_shared/components/help_popover_spec.js110
-rw-r--r--spec/frontend/vue_shared/components/local_storage_sync_spec.js27
-rw-r--r--spec/frontend/vue_shared/components/markdown/field_spec.js111
-rw-r--r--spec/frontend/vue_shared/components/markdown/header_spec.js49
-rw-r--r--spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js10
-rw-r--r--spec/frontend/vue_shared/components/markdown/suggestions_spec.js6
-rw-r--r--spec/frontend/vue_shared/components/modal_copy_button_spec.js21
-rw-r--r--spec/frontend/vue_shared/components/multiselect_dropdown_spec.js35
-rw-r--r--spec/frontend/vue_shared/components/namespace_select/mock_data.js9
-rw-r--r--spec/frontend/vue_shared/components/namespace_select/namespace_select_spec.js151
-rw-r--r--spec/frontend/vue_shared/components/notes/noteable_warning_spec.js15
-rw-r--r--spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs_spec.js15
-rw-r--r--spec/frontend/vue_shared/components/pikaday_spec.js41
-rw-r--r--spec/frontend/vue_shared/components/project_avatar/default_spec.js30
-rw-r--r--spec/frontend/vue_shared/components/project_selector/project_selector_spec.js46
-rw-r--r--spec/frontend/vue_shared/components/registry/list_item_spec.js7
-rw-r--r--spec/frontend/vue_shared/components/resizable_chart/resizable_chart_container_spec.js15
-rw-r--r--spec/frontend/vue_shared/components/runner_instructions/runner_instructions_modal_spec.js20
-rw-r--r--spec/frontend/vue_shared/components/runner_instructions/runner_instructions_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/sidebar/collapsed_calendar_icon_spec.js68
-rw-r--r--spec/frontend/vue_shared/components/sidebar/date_picker_spec.js125
-rw-r--r--spec/frontend/vue_shared/components/sidebar/issuable_move_dropdown_spec.js23
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_button_spec.js9
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view_spec.js65
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js68
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_title_spec.js9
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_value_collapsed_spec.js3
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js33
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_widget/labels_select_root_spec.js12
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_widget/mock_data.js3
-rw-r--r--spec/frontend/vue_shared/components/sidebar/toggle_sidebar_spec.js3
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js (renamed from spec/frontend/vue_shared/components/source_viewer_spec.js)43
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/utils_spec.js13
-rw-r--r--spec/frontend/vue_shared/components/split_button_spec.js9
-rw-r--r--spec/frontend/vue_shared/components/upload_dropzone/__snapshots__/upload_dropzone_spec.js.snap7
-rw-r--r--spec/frontend/vue_shared/components/upload_dropzone/upload_dropzone_spec.js99
-rw-r--r--spec/frontend/vue_shared/components/user_avatar/user_avatar_list_spec.js10
-rw-r--r--spec/frontend/vue_shared/components/user_select_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/web_ide_link_spec.js51
69 files changed, 1309 insertions, 1837 deletions
diff --git a/spec/frontend/vue_shared/components/blob_viewers/__snapshots__/simple_viewer_spec.js.snap b/spec/frontend/vue_shared/components/blob_viewers/__snapshots__/simple_viewer_spec.js.snap
index 06753044e93..fbf3d17fd64 100644
--- a/spec/frontend/vue_shared/components/blob_viewers/__snapshots__/simple_viewer_spec.js.snap
+++ b/spec/frontend/vue_shared/components/blob_viewers/__snapshots__/simple_viewer_spec.js.snap
@@ -6,7 +6,7 @@ exports[`Blob Simple Viewer component rendering matches the snapshot 1`] = `
class="file-content code js-syntax-highlight"
>
<div
- class="line-numbers"
+ class="line-numbers gl-pt-0!"
>
<a
class="diff-line-num js-line-number"
@@ -56,7 +56,7 @@ exports[`Blob Simple Viewer component rendering matches the snapshot 1`] = `
class="blob-content"
>
<pre
- class="code highlight"
+ class="code highlight gl-p-0! gl-display-flex"
>
<code
data-blob-hash="foo-bar"
diff --git a/spec/frontend/vue_shared/components/blob_viewers/simple_viewer_spec.js b/spec/frontend/vue_shared/components/blob_viewers/simple_viewer_spec.js
index 3277aab43f0..663ebd3e12f 100644
--- a/spec/frontend/vue_shared/components/blob_viewers/simple_viewer_spec.js
+++ b/spec/frontend/vue_shared/components/blob_viewers/simple_viewer_spec.js
@@ -1,16 +1,21 @@
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { HIGHLIGHT_CLASS_NAME } from '~/vue_shared/components/blob_viewers/constants';
import SimpleViewer from '~/vue_shared/components/blob_viewers/simple_viewer.vue';
+import LineHighlighter from '~/blob/line_highlighter';
+
+jest.mock('~/blob/line_highlighter');
describe('Blob Simple Viewer component', () => {
let wrapper;
const contentMock = `<span id="LC1">First</span>\n<span id="LC2">Second</span>\n<span id="LC3">Third</span>`;
const blobHash = 'foo-bar';
- function createComponent(content = contentMock, isRawContent = false) {
+ function createComponent(content = contentMock, isRawContent = false, glFeatures = {}) {
wrapper = shallowMount(SimpleViewer, {
provide: {
blobHash,
+ glFeatures,
},
propsData: {
content,
@@ -25,6 +30,20 @@ describe('Blob Simple Viewer component', () => {
wrapper.destroy();
});
+ describe('refactorBlobViewer feature flag', () => {
+ it('loads the LineHighlighter if refactorBlobViewer is enabled', () => {
+ createComponent('', false, { refactorBlobViewer: true });
+
+ expect(LineHighlighter).toHaveBeenCalled();
+ });
+
+ it('does not load the LineHighlighter if refactorBlobViewer is disabled', () => {
+ createComponent('', false, { refactorBlobViewer: false });
+
+ expect(LineHighlighter).not.toHaveBeenCalled();
+ });
+ });
+
it('does not fail if content is empty', () => {
const spy = jest.spyOn(window.console, 'error');
createComponent('');
@@ -69,7 +88,7 @@ describe('Blob Simple Viewer component', () => {
expect(linetoBeHighlighted.classes()).toContain(HIGHLIGHT_CLASS_NAME);
});
- it('switches highlighting when another line is selected', () => {
+ it('switches highlighting when another line is selected', async () => {
const currentlyHighlighted = wrapper.find('#LC2');
const hash = '#LC3';
const linetoBeHighlighted = wrapper.find(hash);
@@ -78,11 +97,10 @@ describe('Blob Simple Viewer component', () => {
wrapper.vm.scrollToLine(hash);
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.vm.highlightedLine).toBe(linetoBeHighlighted.element);
- expect(currentlyHighlighted.classes()).not.toContain(HIGHLIGHT_CLASS_NAME);
- expect(linetoBeHighlighted.classes()).toContain(HIGHLIGHT_CLASS_NAME);
- });
+ await nextTick();
+ expect(wrapper.vm.highlightedLine).toBe(linetoBeHighlighted.element);
+ expect(currentlyHighlighted.classes()).not.toContain(HIGHLIGHT_CLASS_NAME);
+ expect(linetoBeHighlighted.classes()).toContain(HIGHLIGHT_CLASS_NAME);
});
});
});
diff --git a/spec/frontend/vue_shared/components/chronic_duration_input_spec.js b/spec/frontend/vue_shared/components/chronic_duration_input_spec.js
index 083a5f60d1d..6932a812287 100644
--- a/spec/frontend/vue_shared/components/chronic_duration_input_spec.js
+++ b/spec/frontend/vue_shared/components/chronic_duration_input_spec.js
@@ -1,4 +1,5 @@
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import ChronicDurationInput from '~/vue_shared/components/chronic_duration_input.vue';
const MOCK_VALUE = 2 * 3600 + 20 * 60;
@@ -48,7 +49,7 @@ describe('vue_shared/components/chronic_duration_input', () => {
describe('change', () => {
const createAndDispatch = async (initialValue, humanReadableInput) => {
createComponent({ value: initialValue });
- await wrapper.vm.$nextTick();
+ await nextTick();
textElement.value = humanReadableInput;
textElement.dispatchEvent(new Event('input'));
};
@@ -118,7 +119,7 @@ describe('vue_shared/components/chronic_duration_input', () => {
it('emits valid with user input', async () => {
textElement.value = '1m10s';
textElement.dispatchEvent(new Event('input'));
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.emitted('valid')).toEqual([
[{ valid: true, feedback: '' }],
@@ -133,7 +134,7 @@ describe('vue_shared/components/chronic_duration_input', () => {
textElement.value = '';
textElement.dispatchEvent(new Event('input'));
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.emitted('valid')).toEqual([
[{ valid: true, feedback: '' }],
@@ -151,7 +152,7 @@ describe('vue_shared/components/chronic_duration_input', () => {
it('emits invalid with user input', async () => {
textElement.value = 'gobbledygook';
textElement.dispatchEvent(new Event('input'));
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.emitted('valid')).toEqual([
[{ valid: true, feedback: '' }],
@@ -186,7 +187,7 @@ describe('vue_shared/components/chronic_duration_input', () => {
it('emits valid with updated value', async () => {
wrapper.setProps({ value: MOCK_VALUE });
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.emitted('valid')).toEqual([
[{ valid: null, feedback: '' }],
@@ -210,7 +211,7 @@ describe('vue_shared/components/chronic_duration_input', () => {
it('emits valid when input is integer', async () => {
textElement.value = '2hr20min';
textElement.dispatchEvent(new Event('input'));
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.emitted('change')).toEqual([[MOCK_VALUE]]);
expect(wrapper.emitted('valid')).toEqual([
@@ -228,7 +229,7 @@ describe('vue_shared/components/chronic_duration_input', () => {
it('emits valid when input is decimal', async () => {
textElement.value = '1.5s';
textElement.dispatchEvent(new Event('input'));
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.emitted('change')).toEqual([[1.5]]);
expect(wrapper.emitted('valid')).toEqual([
@@ -252,7 +253,7 @@ describe('vue_shared/components/chronic_duration_input', () => {
it('emits valid when input is integer', async () => {
textElement.value = '2hr20min';
textElement.dispatchEvent(new Event('input'));
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.emitted('change')).toEqual([[MOCK_VALUE]]);
expect(wrapper.emitted('valid')).toEqual([
@@ -270,7 +271,7 @@ describe('vue_shared/components/chronic_duration_input', () => {
it('emits invalid when input is decimal', async () => {
textElement.value = '1.5s';
textElement.dispatchEvent(new Event('input'));
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.emitted('change')).toBeUndefined();
expect(wrapper.emitted('valid')).toEqual([
@@ -318,7 +319,7 @@ describe('vue_shared/components/chronic_duration_input', () => {
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({ value: MOCK_VALUE });
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(textElement.value).toBe('2 hrs 20 mins');
expect(hiddenElement.value).toBe(MOCK_VALUE.toString());
@@ -329,7 +330,7 @@ describe('vue_shared/components/chronic_duration_input', () => {
it('passes user input to parent via v-model', async () => {
textElement.value = '2hr20min';
textElement.dispatchEvent(new Event('input'));
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.findComponent(ChronicDurationInput).props('value')).toBe(MOCK_VALUE);
expect(textElement.value).toBe('2hr20min');
@@ -377,7 +378,7 @@ describe('vue_shared/components/chronic_duration_input', () => {
it('creates form data with user-specified value', async () => {
textElement.value = '1m10s';
textElement.dispatchEvent(new Event('input'));
- await wrapper.vm.$nextTick();
+ await nextTick();
const formData = new FormData(wrapper.find('[data-testid=myForm]').element);
const iter = formData.entries();
diff --git a/spec/frontend/vue_shared/components/confirm_fork_modal_spec.js b/spec/frontend/vue_shared/components/confirm_fork_modal_spec.js
new file mode 100644
index 00000000000..1cde92cf522
--- /dev/null
+++ b/spec/frontend/vue_shared/components/confirm_fork_modal_spec.js
@@ -0,0 +1,80 @@
+import { GlModal } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import ConfirmForkModal, { i18n } from '~/vue_shared/components/confirm_fork_modal.vue';
+
+describe('vue_shared/components/confirm_fork_modal', () => {
+ let wrapper = null;
+
+ const forkPath = '/fake/fork/path';
+ const modalId = 'confirm-fork-modal';
+ const defaultProps = { modalId, forkPath };
+
+ const findModal = () => wrapper.findComponent(GlModal);
+ const findModalProp = (prop) => findModal().props(prop);
+ const findModalActionProps = () => findModalProp('actionPrimary');
+
+ const createComponent = (props = {}) =>
+ shallowMountExtended(ConfirmForkModal, {
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('visible = false', () => {
+ beforeEach(() => {
+ wrapper = createComponent();
+ });
+
+ it('sets the visible prop to `false`', () => {
+ expect(findModalProp('visible')).toBe(false);
+ });
+
+ it('sets the modal title', () => {
+ const title = findModalProp('title');
+ expect(title).toBe(i18n.title);
+ });
+
+ it('sets the modal id', () => {
+ const fakeModalId = findModalProp('modalId');
+ expect(fakeModalId).toBe(modalId);
+ });
+
+ it('has the fork path button', () => {
+ const modalProps = findModalActionProps();
+ expect(modalProps.text).toBe(i18n.btnText);
+ expect(modalProps.attributes.variant).toBe('confirm');
+ });
+
+ it('sets the correct fork path', () => {
+ const modalProps = findModalActionProps();
+ expect(modalProps.attributes.href).toBe(forkPath);
+ });
+
+ it('has the fork message', () => {
+ expect(findModal().text()).toContain(i18n.message);
+ });
+ });
+
+ describe('visible = true', () => {
+ beforeEach(() => {
+ wrapper = createComponent({ visible: true });
+ });
+
+ it('sets the visible prop to `true`', () => {
+ expect(findModalProp('visible')).toBe(true);
+ });
+
+ it('emits the `change` event if the modal is hidden', () => {
+ expect(wrapper.emitted('change')).toBeUndefined();
+
+ findModal().vm.$emit('change', false);
+
+ expect(wrapper.emitted('change')).toEqual([[false]]);
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/date_time_picker/date_time_picker_spec.js b/spec/frontend/vue_shared/components/date_time_picker/date_time_picker_spec.js
index 33667a1bb71..d4b6b987c69 100644
--- a/spec/frontend/vue_shared/components/date_time_picker/date_time_picker_spec.js
+++ b/spec/frontend/vue_shared/components/date_time_picker/date_time_picker_spec.js
@@ -1,5 +1,6 @@
import { mount } from '@vue/test-utils';
import timezoneMock from 'timezone-mock';
+import { nextTick } from 'vue';
import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue';
import {
defaultTimeRanges,
@@ -29,26 +30,23 @@ describe('DateTimePicker', () => {
wrapper.destroy();
});
- it('renders dropdown toggle button with selected text', () => {
+ it('renders dropdown toggle button with selected text', async () => {
createComponent();
- return wrapper.vm.$nextTick(() => {
- expect(dropdownToggle().text()).toBe(defaultTimeRange.label);
- });
+ await nextTick();
+ expect(dropdownToggle().text()).toBe(defaultTimeRange.label);
});
- it('renders dropdown toggle button with selected text and utc label', () => {
+ it('renders dropdown toggle button with selected text and utc label', async () => {
createComponent({ utc: true });
- return wrapper.vm.$nextTick(() => {
- expect(dropdownToggle().text()).toContain(defaultTimeRange.label);
- expect(dropdownToggle().text()).toContain('UTC');
- });
+ await nextTick();
+ expect(dropdownToggle().text()).toContain(defaultTimeRange.label);
+ expect(dropdownToggle().text()).toContain('UTC');
});
- it('renders dropdown with 2 custom time range inputs', () => {
+ it('renders dropdown with 2 custom time range inputs', async () => {
createComponent();
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.findAll('input').length).toBe(2);
- });
+ await nextTick();
+ expect(wrapper.findAll('input').length).toBe(2);
});
describe('renders label with h/m/s truncated if possible', () => {
@@ -80,33 +78,30 @@ describe('DateTimePicker', () => {
label: '2019-10-10 00:00:01 to 2019-10-10 00:00:01 UTC',
},
].forEach(({ start, end, utc, label }) => {
- it(`for start ${start}, end ${end}, and utc ${utc}, label is ${label}`, () => {
+ it(`for start ${start}, end ${end}, and utc ${utc}, label is ${label}`, async () => {
createComponent({
value: { start, end },
utc,
});
- return wrapper.vm.$nextTick(() => {
- expect(dropdownToggle().text()).toBe(label);
- });
+ await nextTick();
+ expect(dropdownToggle().text()).toBe(label);
});
});
});
- it(`renders dropdown with ${optionsCount} (default) items in quick range`, () => {
+ it(`renders dropdown with ${optionsCount} (default) items in quick range`, async () => {
createComponent();
dropdownToggle().trigger('click');
- return wrapper.vm.$nextTick(() => {
- expect(findQuickRangeItems().length).toBe(optionsCount);
- });
+ await nextTick();
+ expect(findQuickRangeItems().length).toBe(optionsCount);
});
- it('renders dropdown with a default quick range item selected', () => {
+ it('renders dropdown with a default quick range item selected', async () => {
createComponent();
dropdownToggle().trigger('click');
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.find('.dropdown-item.active').exists()).toBe(true);
- expect(wrapper.find('.dropdown-item.active').text()).toBe(defaultTimeRange.label);
- });
+ await nextTick();
+ expect(wrapper.find('.dropdown-item.active').exists()).toBe(true);
+ expect(wrapper.find('.dropdown-item.active').text()).toBe(defaultTimeRange.label);
});
it('renders a disabled apply button on wrong input', () => {
@@ -118,74 +113,63 @@ describe('DateTimePicker', () => {
});
describe('user input', () => {
- const fillInputAndBlur = (input, val) => {
+ const fillInputAndBlur = async (input, val) => {
wrapper.find(input).setValue(val);
- return wrapper.vm.$nextTick().then(() => {
- wrapper.find(input).trigger('blur');
- return wrapper.vm.$nextTick();
- });
+ await nextTick();
+ wrapper.find(input).trigger('blur');
+ await nextTick();
};
- beforeEach(() => {
+ beforeEach(async () => {
createComponent();
- return wrapper.vm.$nextTick();
+ await nextTick();
});
- it('displays inline error message if custom time range inputs are invalid', () => {
- return fillInputAndBlur('#custom-time-from', '2019-10-01abc')
- .then(() => fillInputAndBlur('#custom-time-to', '2019-10-10abc'))
- .then(() => {
- expect(wrapper.findAll('.invalid-feedback').length).toBe(2);
- });
+ it('displays inline error message if custom time range inputs are invalid', async () => {
+ await fillInputAndBlur('#custom-time-from', '2019-10-01abc');
+ await fillInputAndBlur('#custom-time-to', '2019-10-10abc');
+ expect(wrapper.findAll('.invalid-feedback').length).toBe(2);
});
- it('keeps apply button disabled with invalid custom time range inputs', () => {
- return fillInputAndBlur('#custom-time-from', '2019-10-01abc')
- .then(() => fillInputAndBlur('#custom-time-to', '2019-09-19'))
- .then(() => {
- expect(applyButtonElement().getAttribute('disabled')).toBe('disabled');
- });
+ it('keeps apply button disabled with invalid custom time range inputs', async () => {
+ await fillInputAndBlur('#custom-time-from', '2019-10-01abc');
+ await fillInputAndBlur('#custom-time-to', '2019-09-19');
+ expect(applyButtonElement().getAttribute('disabled')).toBe('disabled');
});
- it('enables apply button with valid custom time range inputs', () => {
- return fillInputAndBlur('#custom-time-from', '2019-10-01')
- .then(() => fillInputAndBlur('#custom-time-to', '2019-10-19'))
- .then(() => {
- expect(applyButtonElement().getAttribute('disabled')).toBeNull();
- });
+ it('enables apply button with valid custom time range inputs', async () => {
+ await fillInputAndBlur('#custom-time-from', '2019-10-01');
+ await fillInputAndBlur('#custom-time-to', '2019-10-19');
+ expect(applyButtonElement().getAttribute('disabled')).toBeNull();
});
describe('when "apply" is clicked', () => {
- it('emits iso dates', () => {
- return fillInputAndBlur('#custom-time-from', '2019-10-01 00:00:00')
- .then(() => fillInputAndBlur('#custom-time-to', '2019-10-19 00:00:00'))
- .then(() => {
- applyButtonElement().click();
-
- expect(wrapper.emitted().input).toHaveLength(1);
- expect(wrapper.emitted().input[0]).toEqual([
- {
- end: '2019-10-19T00:00:00Z',
- start: '2019-10-01T00:00:00Z',
- },
- ]);
- });
+ it('emits iso dates', async () => {
+ await fillInputAndBlur('#custom-time-from', '2019-10-01 00:00:00');
+ await fillInputAndBlur('#custom-time-to', '2019-10-19 00:00:00');
+ applyButtonElement().click();
+
+ expect(wrapper.emitted().input).toHaveLength(1);
+ expect(wrapper.emitted().input[0]).toEqual([
+ {
+ end: '2019-10-19T00:00:00Z',
+ start: '2019-10-01T00:00:00Z',
+ },
+ ]);
});
- it('emits iso dates, for dates without time of day', () => {
- return fillInputAndBlur('#custom-time-from', '2019-10-01')
- .then(() => fillInputAndBlur('#custom-time-to', '2019-10-19'))
- .then(() => {
- applyButtonElement().click();
-
- expect(wrapper.emitted().input).toHaveLength(1);
- expect(wrapper.emitted().input[0]).toEqual([
- {
- end: '2019-10-19T00:00:00Z',
- start: '2019-10-01T00:00:00Z',
- },
- ]);
- });
+ it('emits iso dates, for dates without time of day', async () => {
+ await fillInputAndBlur('#custom-time-from', '2019-10-01');
+ await fillInputAndBlur('#custom-time-to', '2019-10-19');
+ applyButtonElement().click();
+
+ expect(wrapper.emitted().input).toHaveLength(1);
+ expect(wrapper.emitted().input[0]).toEqual([
+ {
+ end: '2019-10-19T00:00:00Z',
+ start: '2019-10-01T00:00:00Z',
+ },
+ ]);
});
describe('when timezone is different', () => {
@@ -196,52 +180,46 @@ describe('DateTimePicker', () => {
timezoneMock.unregister();
});
- it('emits iso dates', () => {
- return fillInputAndBlur('#custom-time-from', '2019-10-01 00:00:00')
- .then(() => fillInputAndBlur('#custom-time-to', '2019-10-19 12:00:00'))
- .then(() => {
- applyButtonElement().click();
-
- expect(wrapper.emitted().input).toHaveLength(1);
- expect(wrapper.emitted().input[0]).toEqual([
- {
- start: '2019-10-01T07:00:00Z',
- end: '2019-10-19T19:00:00Z',
- },
- ]);
- });
+ it('emits iso dates', async () => {
+ await fillInputAndBlur('#custom-time-from', '2019-10-01 00:00:00');
+ await fillInputAndBlur('#custom-time-to', '2019-10-19 12:00:00');
+ applyButtonElement().click();
+
+ expect(wrapper.emitted().input).toHaveLength(1);
+ expect(wrapper.emitted().input[0]).toEqual([
+ {
+ start: '2019-10-01T07:00:00Z',
+ end: '2019-10-19T19:00:00Z',
+ },
+ ]);
});
- it('emits iso dates with utc format', () => {
+ it('emits iso dates with utc format', async () => {
wrapper.setProps({ utc: true });
- return wrapper.vm
- .$nextTick()
- .then(() => fillInputAndBlur('#custom-time-from', '2019-10-01 00:00:00'))
- .then(() => fillInputAndBlur('#custom-time-to', '2019-10-19 12:00:00'))
- .then(() => {
- applyButtonElement().click();
-
- expect(wrapper.emitted().input).toHaveLength(1);
- expect(wrapper.emitted().input[0]).toEqual([
- {
- start: '2019-10-01T00:00:00Z',
- end: '2019-10-19T12:00:00Z',
- },
- ]);
- });
+ await nextTick();
+ await fillInputAndBlur('#custom-time-from', '2019-10-01 00:00:00');
+ await fillInputAndBlur('#custom-time-to', '2019-10-19 12:00:00');
+ applyButtonElement().click();
+
+ expect(wrapper.emitted().input).toHaveLength(1);
+ expect(wrapper.emitted().input[0]).toEqual([
+ {
+ start: '2019-10-01T00:00:00Z',
+ end: '2019-10-19T12:00:00Z',
+ },
+ ]);
});
});
});
- it('unchecks quick range when text is input is clicked', () => {
+ it('unchecks quick range when text is input is clicked', async () => {
const findActiveItems = () =>
findQuickRangeItems().filter((w) => w.classes().includes('active'));
expect(findActiveItems().length).toBe(1);
- return fillInputAndBlur('#custom-time-from', '2019-10-01').then(() => {
- expect(findActiveItems().length).toBe(0);
- });
+ await fillInputAndBlur('#custom-time-from', '2019-10-01');
+ expect(findActiveItems().length).toBe(0);
});
it('emits dates in an object when a is clicked', () => {
@@ -257,16 +235,14 @@ describe('DateTimePicker', () => {
});
});
- it('hides the popover with cancel button', () => {
+ it('hides the popover with cancel button', async () => {
dropdownToggle().trigger('click');
- return wrapper.vm.$nextTick(() => {
- cancelButton().trigger('click');
+ await nextTick();
+ cancelButton().trigger('click');
- return wrapper.vm.$nextTick(() => {
- expect(dropdownMenu().classes('show')).toBe(false);
- });
- });
+ await nextTick();
+ expect(dropdownMenu().classes('show')).toBe(false);
});
});
@@ -293,7 +269,7 @@ describe('DateTimePicker', () => {
jest.spyOn(Date, 'now').mockImplementation(() => MOCK_NOW);
});
- it('renders dropdown with a label in the quick range', () => {
+ it('renders dropdown with a label in the quick range', async () => {
createComponent({
value: {
duration: { seconds: 60 * 5 },
@@ -301,12 +277,11 @@ describe('DateTimePicker', () => {
options: otherTimeRanges,
});
dropdownToggle().trigger('click');
- return wrapper.vm.$nextTick(() => {
- expect(dropdownToggle().text()).toBe('5 minutes');
- });
+ await nextTick();
+ expect(dropdownToggle().text()).toBe('5 minutes');
});
- it('renders dropdown with a label in the quick range and utc label', () => {
+ it('renders dropdown with a label in the quick range and utc label', async () => {
createComponent({
value: {
duration: { seconds: 60 * 5 },
@@ -315,12 +290,11 @@ describe('DateTimePicker', () => {
options: otherTimeRanges,
});
dropdownToggle().trigger('click');
- return wrapper.vm.$nextTick(() => {
- expect(dropdownToggle().text()).toBe('5 minutes UTC');
- });
+ await nextTick();
+ expect(dropdownToggle().text()).toBe('5 minutes UTC');
});
- it('renders dropdown with quick range items', () => {
+ it('renders dropdown with quick range items', async () => {
createComponent({
value: {
duration: { seconds: 60 * 2 },
@@ -328,31 +302,29 @@ describe('DateTimePicker', () => {
options: otherTimeRanges,
});
dropdownToggle().trigger('click');
- return wrapper.vm.$nextTick(() => {
- const items = findQuickRangeItems();
+ await nextTick();
+ const items = findQuickRangeItems();
- expect(items.length).toBe(Object.keys(otherTimeRanges).length);
- expect(items.at(0).text()).toBe('1 minute');
- expect(items.at(0).classes()).not.toContain('active');
+ expect(items.length).toBe(Object.keys(otherTimeRanges).length);
+ expect(items.at(0).text()).toBe('1 minute');
+ expect(items.at(0).classes()).not.toContain('active');
- expect(items.at(1).text()).toBe('2 minutes');
- expect(items.at(1).classes()).toContain('active');
+ expect(items.at(1).text()).toBe('2 minutes');
+ expect(items.at(1).classes()).toContain('active');
- expect(items.at(2).text()).toBe('5 minutes');
- expect(items.at(2).classes()).not.toContain('active');
- });
+ expect(items.at(2).text()).toBe('5 minutes');
+ expect(items.at(2).classes()).not.toContain('active');
});
- it('renders dropdown with a label not in the quick range', () => {
+ it('renders dropdown with a label not in the quick range', async () => {
createComponent({
value: {
duration: { seconds: 60 * 4 },
},
});
dropdownToggle().trigger('click');
- return wrapper.vm.$nextTick(() => {
- expect(dropdownToggle().text()).toBe('2020-01-23 19:56:00 to 2020-01-23 20:00:00');
- });
+ await nextTick();
+ expect(dropdownToggle().text()).toBe('2020-01-23 19:56:00 to 2020-01-23 20:00:00');
});
});
});
diff --git a/spec/frontend/vue_shared/components/deployment_instance/deployment_instance_spec.js b/spec/frontend/vue_shared/components/deployment_instance/deployment_instance_spec.js
index b812ced72c9..59653a0ec13 100644
--- a/spec/frontend/vue_shared/components/deployment_instance/deployment_instance_spec.js
+++ b/spec/frontend/vue_shared/components/deployment_instance/deployment_instance_spec.js
@@ -1,4 +1,5 @@
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import DeployBoardInstance from '~/vue_shared/components/deployment_instance.vue';
import { folder } from './mock_data';
@@ -28,17 +29,15 @@ describe('Deploy Board Instance', () => {
expect(wrapper.attributes('title')).toEqual('This is a pod');
});
- it('should render a div without tooltip data', (done) => {
+ it('should render a div without tooltip data', async () => {
wrapper = createComponent({
status: 'deploying',
tooltipText: '',
});
- wrapper.vm.$nextTick(() => {
- expect(wrapper.classes('deployment-instance-deploying')).toBe(true);
- expect(wrapper.attributes('title')).toEqual('');
- done();
- });
+ await nextTick();
+ expect(wrapper.classes('deployment-instance-deploying')).toBe(true);
+ expect(wrapper.attributes('title')).toEqual('');
});
it('should have a log path computed with a pod name as a parameter', () => {
@@ -58,15 +57,13 @@ describe('Deploy Board Instance', () => {
wrapper.destroy();
});
- it('should render a div with canary class when stable prop is provided as false', (done) => {
+ it('should render a div with canary class when stable prop is provided as false', async () => {
wrapper = createComponent({
stable: false,
});
- wrapper.vm.$nextTick(() => {
- expect(wrapper.classes('deployment-instance-canary')).toBe(true);
- done();
- });
+ await nextTick();
+ expect(wrapper.classes('deployment-instance-canary')).toBe(true);
});
});
@@ -75,17 +72,15 @@ describe('Deploy Board Instance', () => {
wrapper.destroy();
});
- it('should not be a link without a logsPath prop', (done) => {
+ it('should not be a link without a logsPath prop', async () => {
wrapper = createComponent({
stable: false,
logsPath: '',
});
- wrapper.vm.$nextTick(() => {
- expect(wrapper.vm.computedLogPath).toBeNull();
- expect(wrapper.vm.isLink).toBeFalsy();
- done();
- });
+ await nextTick();
+ expect(wrapper.vm.computedLogPath).toBeNull();
+ expect(wrapper.vm.isLink).toBeFalsy();
});
it('should render a link without href if path is not passed', () => {
diff --git a/spec/frontend/vue_shared/components/design_management/design_note_pin_spec.js b/spec/frontend/vue_shared/components/design_management/design_note_pin_spec.js
index 984a28c93d6..353d493add9 100644
--- a/spec/frontend/vue_shared/components/design_management/design_note_pin_spec.js
+++ b/spec/frontend/vue_shared/components/design_management/design_note_pin_spec.js
@@ -39,4 +39,72 @@ describe('Design note pin component', () => {
createComponent({ position: null });
expect(wrapper.element).toMatchSnapshot();
});
+
+ it('applies `on-image` class when isOnImage is true', () => {
+ createComponent({ isOnImage: true });
+
+ expect(wrapper.find('.on-image').exists()).toBe(true);
+ });
+
+ it('applies `draft` class when isDraft is true', () => {
+ createComponent({ isDraft: true });
+
+ expect(wrapper.find('.draft').exists()).toBe(true);
+ });
+
+ describe('size', () => {
+ it('is `sm` it applies `small` class', () => {
+ createComponent({ size: 'sm' });
+ expect(wrapper.find('.small').exists()).toBe(true);
+ });
+
+ it('is `md` it applies no size class', () => {
+ createComponent({ size: 'md' });
+ expect(wrapper.find('.small').exists()).toBe(false);
+ expect(wrapper.find('.medium').exists()).toBe(false);
+ });
+
+ it('throws when passed any other value except `sm` or `md`', () => {
+ jest.spyOn(console, 'error').mockImplementation(() => {});
+
+ createComponent({ size: 'lg' });
+
+ // eslint-disable-next-line no-console
+ expect(console.error).toHaveBeenCalled();
+ });
+ });
+
+ describe('ariaLabel', () => {
+ describe('when value is passed', () => {
+ it('overrides default aria-label', () => {
+ const ariaLabel = 'Aria Label';
+
+ createComponent({ ariaLabel });
+
+ const button = wrapper.find('button');
+
+ expect(button.attributes('aria-label')).toBe(ariaLabel);
+ });
+ });
+
+ describe('when no value is passed', () => {
+ it('shows new note label as aria-label when label is absent', () => {
+ createComponent({ label: null });
+
+ const button = wrapper.find('button');
+
+ expect(button.attributes('aria-label')).toBe('Comment form position');
+ });
+
+ it('shows label position as aria-label when label is present', () => {
+ const label = 1;
+
+ createComponent({ label, isNewNote: false });
+
+ const button = wrapper.find('button');
+
+ expect(button.attributes('aria-label')).toBe(`Comment '${label}' position`);
+ });
+ });
+ });
});
diff --git a/spec/frontend/vue_shared/components/diff_viewer/diff_viewer_spec.js b/spec/frontend/vue_shared/components/diff_viewer/diff_viewer_spec.js
index 68e3ee11a0d..69964b2687d 100644
--- a/spec/frontend/vue_shared/components/diff_viewer/diff_viewer_spec.js
+++ b/spec/frontend/vue_shared/components/diff_viewer/diff_viewer_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import mountComponent from 'helpers/vue_mount_component_helper';
import { GREEN_BOX_IMAGE_URL, RED_BOX_IMAGE_URL } from 'spec/test_constants';
@@ -26,27 +26,25 @@ describe('DiffViewer', () => {
vm.$destroy();
});
- it('renders image diff', (done) => {
+ it('renders image diff', async () => {
window.gon = {
relative_url_root: '',
};
createComponent({ ...requiredProps, projectPath: '' });
- setImmediate(() => {
- expect(vm.$el.querySelector('.deleted img').getAttribute('src')).toBe(
- `//-/raw/DEF/${RED_BOX_IMAGE_URL}`,
- );
+ await nextTick();
- expect(vm.$el.querySelector('.added img').getAttribute('src')).toBe(
- `//-/raw/ABC/${GREEN_BOX_IMAGE_URL}`,
- );
+ expect(vm.$el.querySelector('.deleted img').getAttribute('src')).toBe(
+ `//-/raw/DEF/${RED_BOX_IMAGE_URL}`,
+ );
- done();
- });
+ expect(vm.$el.querySelector('.added img').getAttribute('src')).toBe(
+ `//-/raw/ABC/${GREEN_BOX_IMAGE_URL}`,
+ );
});
- it('renders fallback download diff display', (done) => {
+ it('renders fallback download diff display', async () => {
createComponent({
...requiredProps,
diffViewerMode: 'added',
@@ -54,22 +52,18 @@ describe('DiffViewer', () => {
oldPath: 'testold.abc',
});
- setImmediate(() => {
- expect(vm.$el.querySelector('.deleted .file-info').textContent.trim()).toContain(
- 'testold.abc',
- );
+ await nextTick();
- expect(vm.$el.querySelector('.deleted .btn.btn-default').textContent.trim()).toContain(
- 'Download',
- );
+ expect(vm.$el.querySelector('.deleted .file-info').textContent.trim()).toContain('testold.abc');
- expect(vm.$el.querySelector('.added .file-info').textContent.trim()).toContain('test.abc');
- expect(vm.$el.querySelector('.added .btn.btn-default').textContent.trim()).toContain(
- 'Download',
- );
+ expect(vm.$el.querySelector('.deleted .btn.btn-default').textContent.trim()).toContain(
+ 'Download',
+ );
- done();
- });
+ expect(vm.$el.querySelector('.added .file-info').textContent.trim()).toContain('test.abc');
+ expect(vm.$el.querySelector('.added .btn.btn-default').textContent.trim()).toContain(
+ 'Download',
+ );
});
describe('renamed file', () => {
diff --git a/spec/frontend/vue_shared/components/diff_viewer/viewers/image_diff_viewer_spec.js b/spec/frontend/vue_shared/components/diff_viewer/viewers/image_diff_viewer_spec.js
index 8deb466b33c..d0fa8b8dacb 100644
--- a/spec/frontend/vue_shared/components/diff_viewer/viewers/image_diff_viewer_spec.js
+++ b/spec/frontend/vue_shared/components/diff_viewer/viewers/image_diff_viewer_spec.js
@@ -1,5 +1,5 @@
import { mount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import { compileToFunctions } from 'vue-template-compiler';
import { GREEN_BOX_IMAGE_URL, RED_BOX_IMAGE_URL } from 'spec/test_constants';
@@ -51,60 +51,53 @@ describe('ImageDiffViewer', () => {
wrapper.destroy();
});
- it('renders image diff for replaced', (done) => {
+ it('renders image diff for replaced', async () => {
createComponent({ ...allProps });
- vm.$nextTick(() => {
- const metaInfoElements = vm.$el.querySelectorAll('.image-info');
+ await nextTick();
+ const metaInfoElements = vm.$el.querySelectorAll('.image-info');
- expect(vm.$el.querySelector('.added img').getAttribute('src')).toBe(GREEN_BOX_IMAGE_URL);
+ expect(vm.$el.querySelector('.added img').getAttribute('src')).toBe(GREEN_BOX_IMAGE_URL);
- expect(vm.$el.querySelector('.deleted img').getAttribute('src')).toBe(RED_BOX_IMAGE_URL);
+ expect(vm.$el.querySelector('.deleted img').getAttribute('src')).toBe(RED_BOX_IMAGE_URL);
- expect(vm.$el.querySelector('.view-modes-menu li.active').textContent.trim()).toBe('2-up');
- expect(vm.$el.querySelector('.view-modes-menu li:nth-child(2)').textContent.trim()).toBe(
- 'Swipe',
- );
+ expect(vm.$el.querySelector('.view-modes-menu li.active').textContent.trim()).toBe('2-up');
+ expect(vm.$el.querySelector('.view-modes-menu li:nth-child(2)').textContent.trim()).toBe(
+ 'Swipe',
+ );
- expect(vm.$el.querySelector('.view-modes-menu li:nth-child(3)').textContent.trim()).toBe(
- 'Onion skin',
- );
+ expect(vm.$el.querySelector('.view-modes-menu li:nth-child(3)').textContent.trim()).toBe(
+ 'Onion skin',
+ );
- expect(metaInfoElements.length).toBe(2);
- expect(metaInfoElements[0]).toHaveText('2.00 KiB');
- expect(metaInfoElements[1]).toHaveText('1.00 KiB');
-
- done();
- });
+ expect(metaInfoElements.length).toBe(2);
+ expect(metaInfoElements[0]).toHaveText('2.00 KiB');
+ expect(metaInfoElements[1]).toHaveText('1.00 KiB');
});
- it('renders image diff for new', (done) => {
+ it('renders image diff for new', async () => {
createComponent({ ...allProps, diffMode: 'new', oldPath: '' });
- setImmediate(() => {
- const metaInfoElement = vm.$el.querySelector('.image-info');
+ await nextTick();
- expect(vm.$el.querySelector('.added img').getAttribute('src')).toBe(GREEN_BOX_IMAGE_URL);
- expect(metaInfoElement).toHaveText('1.00 KiB');
+ const metaInfoElement = vm.$el.querySelector('.image-info');
- done();
- });
+ expect(vm.$el.querySelector('.added img').getAttribute('src')).toBe(GREEN_BOX_IMAGE_URL);
+ expect(metaInfoElement).toHaveText('1.00 KiB');
});
- it('renders image diff for deleted', (done) => {
+ it('renders image diff for deleted', async () => {
createComponent({ ...allProps, diffMode: 'deleted', newPath: '' });
- setImmediate(() => {
- const metaInfoElement = vm.$el.querySelector('.image-info');
+ await nextTick();
- expect(vm.$el.querySelector('.deleted img').getAttribute('src')).toBe(RED_BOX_IMAGE_URL);
- expect(metaInfoElement).toHaveText('2.00 KiB');
+ const metaInfoElement = vm.$el.querySelector('.image-info');
- done();
- });
+ expect(vm.$el.querySelector('.deleted img').getAttribute('src')).toBe(RED_BOX_IMAGE_URL);
+ expect(metaInfoElement).toHaveText('2.00 KiB');
});
- it('renders image diff for renamed', (done) => {
+ it('renders image diff for renamed', async () => {
vm = new Vue({
components: {
imageDiffViewer,
@@ -130,69 +123,56 @@ describe('ImageDiffViewer', () => {
`),
}).$mount();
- setImmediate(() => {
- const metaInfoElement = vm.$el.querySelector('.image-info');
+ await nextTick();
- expect(vm.$el.querySelector('img').getAttribute('src')).toBe(GREEN_BOX_IMAGE_URL);
- expect(vm.$el.querySelector('.overlay')).not.toBe(null);
+ const metaInfoElement = vm.$el.querySelector('.image-info');
- expect(metaInfoElement).toHaveText('2.00 KiB');
+ expect(vm.$el.querySelector('img').getAttribute('src')).toBe(GREEN_BOX_IMAGE_URL);
+ expect(vm.$el.querySelector('.overlay')).not.toBe(null);
- done();
- });
+ expect(metaInfoElement).toHaveText('2.00 KiB');
});
describe('swipeMode', () => {
- beforeEach((done) => {
+ beforeEach(() => {
createComponent({ ...requiredProps });
- setImmediate(() => {
- done();
- });
+ return nextTick();
});
- it('switches to Swipe Mode', (done) => {
+ it('switches to Swipe Mode', async () => {
vm.$el.querySelector('.view-modes-menu li:nth-child(2)').click();
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.view-modes-menu li.active').textContent.trim()).toBe('Swipe');
- done();
- });
+ await nextTick();
+ expect(vm.$el.querySelector('.view-modes-menu li.active').textContent.trim()).toBe('Swipe');
});
});
describe('onionSkin', () => {
- beforeEach((done) => {
+ beforeEach(() => {
createComponent({ ...requiredProps });
- setImmediate(() => {
- done();
- });
+ return nextTick();
});
- it('switches to Onion Skin Mode', (done) => {
+ it('switches to Onion Skin Mode', async () => {
vm.$el.querySelector('.view-modes-menu li:nth-child(3)').click();
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.view-modes-menu li.active').textContent.trim()).toBe(
- 'Onion skin',
- );
- done();
- });
+ await nextTick();
+ expect(vm.$el.querySelector('.view-modes-menu li.active').textContent.trim()).toBe(
+ 'Onion skin',
+ );
});
- it('has working drag handler', (done) => {
+ it('has working drag handler', async () => {
vm.$el.querySelector('.view-modes-menu li:nth-child(3)').click();
- vm.$nextTick(() => {
- dragSlider(vm.$el.querySelector('.dragger'), document, 20);
+ await nextTick();
+ dragSlider(vm.$el.querySelector('.dragger'), document, 20);
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.dragger').style.left).toBe('20px');
- expect(vm.$el.querySelector('.added.frame').style.opacity).toBe('0.2');
- done();
- });
- });
+ await nextTick();
+ expect(vm.$el.querySelector('.dragger').style.left).toBe('20px');
+ expect(vm.$el.querySelector('.added.frame').style.opacity).toBe('0.2');
});
});
});
diff --git a/spec/frontend/vue_shared/components/diff_viewer/viewers/renamed_spec.js b/spec/frontend/vue_shared/components/diff_viewer/viewers/renamed_spec.js
index b8d3cbebe16..549388c1a5c 100644
--- a/spec/frontend/vue_shared/components/diff_viewer/viewers/renamed_spec.js
+++ b/spec/frontend/vue_shared/components/diff_viewer/viewers/renamed_spec.js
@@ -1,5 +1,5 @@
import { shallowMount, mount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import {
TRANSITION_LOAD_START,
@@ -126,15 +126,14 @@ describe('Renamed Diff Viewer', () => {
store = null;
});
- it('calls the switchToFullDiffFromRenamedFile action when the method is triggered', () => {
+ it('calls the switchToFullDiffFromRenamedFile action when the method is triggered', async () => {
store.dispatch.mockResolvedValue();
wrapper.vm.switchToFull();
- return wrapper.vm.$nextTick().then(() => {
- expect(store.dispatch).toHaveBeenCalledWith('diffs/switchToFullDiffFromRenamedFile', {
- diffFile,
- });
+ await nextTick();
+ expect(store.dispatch).toHaveBeenCalledWith('diffs/switchToFullDiffFromRenamedFile', {
+ diffFile,
});
});
@@ -144,7 +143,7 @@ describe('Renamed Diff Viewer', () => {
${STATE_ERRORED} | ${'mockRejectedValue'} | ${'rejected'}
`(
'moves through the correct states during a $resolution request',
- ({ after, resolvePromise }) => {
+ async ({ after, resolvePromise }) => {
store.dispatch[resolvePromise]();
expect(wrapper.vm.state).toEqual(STATE_IDLING);
@@ -153,16 +152,9 @@ describe('Renamed Diff Viewer', () => {
expect(wrapper.vm.state).toEqual(STATE_LOADING);
- return (
- wrapper.vm
- // This tick is needed for when the action (promise) finishes
- .$nextTick()
- // This tick waits for the state change in the promise .then/.catch to bubble into the component
- .then(() => wrapper.vm.$nextTick())
- .then(() => {
- expect(wrapper.vm.state).toEqual(after);
- })
- );
+ await nextTick(); // This tick is needed for when the action (promise) finishes
+ await nextTick(); // This tick waits for the state change in the promise .then/.catch to bubble into the component
+ expect(wrapper.vm.state).toEqual(after);
},
);
});
diff --git a/spec/frontend/vue_shared/components/dismissible_feedback_alert_spec.js b/spec/frontend/vue_shared/components/dismissible_feedback_alert_spec.js
index 194681a6138..4b32fbffebe 100644
--- a/spec/frontend/vue_shared/components/dismissible_feedback_alert_spec.js
+++ b/spec/frontend/vue_shared/components/dismissible_feedback_alert_spec.js
@@ -1,5 +1,6 @@
import { GlAlert, GlSprintf } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import Component from '~/vue_shared/components/dismissible_feedback_alert.vue';
@@ -64,7 +65,7 @@ describe('Dismissible Feedback Alert', () => {
it('should not show the alert once dismissed', async () => {
localStorage.setItem(STORAGE_DISMISSAL_KEY, 'true');
createFullComponent();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findAlert().exists()).toBe(false);
});
diff --git a/spec/frontend/vue_shared/components/dropdown/dropdown_search_input_spec.js b/spec/frontend/vue_shared/components/dropdown/dropdown_search_input_spec.js
index ec553c52236..b32dbeb8852 100644
--- a/spec/frontend/vue_shared/components/dropdown/dropdown_search_input_spec.js
+++ b/spec/frontend/vue_shared/components/dropdown/dropdown_search_input_spec.js
@@ -1,4 +1,5 @@
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import DropdownSearchInputComponent from '~/vue_shared/components/dropdown/dropdown_search_input.vue';
describe('DropdownSearchInputComponent', () => {
@@ -36,16 +37,15 @@ describe('DropdownSearchInputComponent', () => {
expect(findInputEl().attributes('placeholder')).toBe(defaultProps.placeholderText);
});
- it('focuses input element when focused property equals true', () => {
+ it('focuses input element when focused property equals true', async () => {
const inputEl = findInputEl().element;
jest.spyOn(inputEl, 'focus');
wrapper.setProps({ focused: true });
- return wrapper.vm.$nextTick().then(() => {
- expect(inputEl.focus).toHaveBeenCalled();
- });
+ await nextTick();
+ expect(inputEl.focus).toHaveBeenCalled();
});
});
});
diff --git a/spec/frontend/vue_shared/components/dropdown/dropdown_widget_spec.js b/spec/frontend/vue_shared/components/dropdown/dropdown_widget_spec.js
index b3af5fd3feb..084d0559665 100644
--- a/spec/frontend/vue_shared/components/dropdown/dropdown_widget_spec.js
+++ b/spec/frontend/vue_shared/components/dropdown/dropdown_widget_spec.js
@@ -1,6 +1,7 @@
import { GlDropdown, GlSearchBoxByType, GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import DropdownWidget from '~/vue_shared/components/dropdown/dropdown_widget/dropdown_widget.vue';
describe('DropdownWidget component', () => {
@@ -53,7 +54,7 @@ describe('DropdownWidget component', () => {
describe('when dropdown is open', () => {
beforeEach(async () => {
findDropdown().vm.$emit('show');
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('emits search event when typing in search box', () => {
@@ -69,7 +70,7 @@ describe('DropdownWidget component', () => {
it('emits set-option event when clicking on an option', async () => {
wrapper.findAll('[data-testid="unselected-option"]').at(1).trigger('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.emitted('set-option')).toEqual([[wrapper.props().options[1]]]);
});
diff --git a/spec/frontend/vue_shared/components/dropdown_keyboard_navigation_spec.js b/spec/frontend/vue_shared/components/dropdown_keyboard_navigation_spec.js
index 996df34f2ff..c34041f9305 100644
--- a/spec/frontend/vue_shared/components/dropdown_keyboard_navigation_spec.js
+++ b/spec/frontend/vue_shared/components/dropdown_keyboard_navigation_spec.js
@@ -1,4 +1,5 @@
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import DropdownKeyboardNavigation from '~/vue_shared/components/dropdown_keyboard_navigation.vue';
import { UP_KEY_CODE, DOWN_KEY_CODE, TAB_KEY_CODE } from '~/lib/utils/keycodes';
@@ -53,7 +54,7 @@ describe('DropdownKeyboardNavigation', () => {
it('should $emit @change with the default index when max changes', async () => {
wrapper.setProps({ max: 20 });
- await wrapper.vm.$nextTick();
+ await nextTick();
// The first @change`call happens on created() so we test for the second [1]
expect(wrapper.emitted('change')[1]).toStrictEqual([MOCK_DEFAULT_INDEX]);
});
diff --git a/spec/frontend/vue_shared/components/expand_button_spec.js b/spec/frontend/vue_shared/components/expand_button_spec.js
index 7874658cc0f..87d6ed6b21f 100644
--- a/spec/frontend/vue_shared/components/expand_button_spec.js
+++ b/spec/frontend/vue_shared/components/expand_button_spec.js
@@ -1,5 +1,5 @@
import { mount } from '@vue/test-utils';
-import Vue from 'vue';
+import { nextTick } from 'vue';
import ExpandButton from '~/vue_shared/components/expand_button.vue';
const text = {
@@ -66,9 +66,9 @@ describe('Expand button', () => {
});
describe('on click', () => {
- beforeEach((done) => {
+ beforeEach(async () => {
expanderPrependEl().trigger('click');
- Vue.nextTick(done);
+ await nextTick();
});
afterEach(() => {
@@ -85,7 +85,7 @@ describe('Expand button', () => {
});
describe('when short text is provided', () => {
- beforeEach((done) => {
+ beforeEach(async () => {
factory({
slots: {
expanded: `<p>${text.expanded}</p>`,
@@ -94,7 +94,7 @@ describe('Expand button', () => {
});
expanderPrependEl().trigger('click');
- Vue.nextTick(done);
+ await nextTick();
});
it('only renders expanded text', () => {
@@ -110,31 +110,29 @@ describe('Expand button', () => {
});
describe('append button', () => {
- beforeEach((done) => {
+ beforeEach(async () => {
expanderPrependEl().trigger('click');
- Vue.nextTick(done);
+ await nextTick();
});
- it('clicking hides itself and shows prepend', () => {
+ it('clicking hides itself and shows prepend', async () => {
expect(expanderAppendEl().isVisible()).toBe(true);
expanderAppendEl().trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(expanderPrependEl().isVisible()).toBe(true);
- });
+ await nextTick();
+ expect(expanderPrependEl().isVisible()).toBe(true);
});
- it('clicking hides expanded text', () => {
+ it('clicking hides expanded text', async () => {
expect(wrapper.find(ExpandButton).text().trim()).toBe(text.expanded);
expanderAppendEl().trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(ExpandButton).text().trim()).not.toBe(text.expanded);
- });
+ await nextTick();
+ expect(wrapper.find(ExpandButton).text().trim()).not.toBe(text.expanded);
});
describe('when short text is provided', () => {
- beforeEach((done) => {
+ beforeEach(async () => {
factory({
slots: {
expanded: `<p>${text.expanded}</p>`,
@@ -143,16 +141,15 @@ describe('Expand button', () => {
});
expanderPrependEl().trigger('click');
- Vue.nextTick(done);
+ await nextTick();
});
- it('clicking reveals short text', () => {
+ it('clicking reveals short text', async () => {
expect(wrapper.find(ExpandButton).text().trim()).toBe(text.expanded);
expanderAppendEl().trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(ExpandButton).text().trim()).toBe(text.short);
- });
+ await nextTick();
+ expect(wrapper.find(ExpandButton).text().trim()).toBe(text.short);
});
});
});
diff --git a/spec/frontend/vue_shared/components/file_finder/index_spec.js b/spec/frontend/vue_shared/components/file_finder/index_spec.js
index 181fc4017a3..921091c5b84 100644
--- a/spec/frontend/vue_shared/components/file_finder/index_spec.js
+++ b/spec/frontend/vue_shared/components/file_finder/index_spec.js
@@ -1,6 +1,5 @@
import Mousetrap from 'mousetrap';
-import Vue from 'vue';
-import waitForPromises from 'helpers/wait_for_promises';
+import Vue, { nextTick } from 'vue';
import { file } from 'jest/ide/helpers';
import { UP_KEY_CODE, DOWN_KEY_CODE, ENTER_KEY_CODE, ESC_KEY_CODE } from '~/lib/utils/keycodes';
import FindFileComponent from '~/vue_shared/components/file_finder/index.vue';
@@ -31,7 +30,7 @@ describe('File finder item spec', () => {
});
describe('with entries', () => {
- beforeEach((done) => {
+ beforeEach(() => {
createComponent({
files: [
{
@@ -48,7 +47,7 @@ describe('File finder item spec', () => {
],
});
- setImmediate(done);
+ return nextTick();
});
it('renders list of blobs', () => {
@@ -57,68 +56,48 @@ describe('File finder item spec', () => {
expect(vm.$el.textContent).not.toContain('folder');
});
- it('filters entries', (done) => {
+ it('filters entries', async () => {
vm.searchText = 'index';
- setImmediate(() => {
- expect(vm.$el.textContent).toContain('index.js');
- expect(vm.$el.textContent).not.toContain('component.js');
+ await nextTick();
- done();
- });
+ expect(vm.$el.textContent).toContain('index.js');
+ expect(vm.$el.textContent).not.toContain('component.js');
});
- it('shows clear button when searchText is not empty', (done) => {
+ it('shows clear button when searchText is not empty', async () => {
vm.searchText = 'index';
- setImmediate(() => {
- expect(vm.$el.querySelector('.dropdown-input').classList).toContain('has-value');
- expect(vm.$el.querySelector('.dropdown-input-search').classList).toContain('hidden');
+ await nextTick();
- done();
- });
+ expect(vm.$el.querySelector('.dropdown-input').classList).toContain('has-value');
+ expect(vm.$el.querySelector('.dropdown-input-search').classList).toContain('hidden');
});
- it('clear button resets searchText', (done) => {
+ it('clear button resets searchText', async () => {
vm.searchText = 'index';
- waitForPromises()
- .then(() => {
- vm.clearSearchInput();
- })
- .then(waitForPromises)
- .then(() => {
- expect(vm.searchText).toBe('');
- })
- .then(done)
- .catch(done.fail);
+ vm.clearSearchInput();
+
+ expect(vm.searchText).toBe('');
});
- it('clear button focuses search input', (done) => {
+ it('clear button focuses search input', async () => {
jest.spyOn(vm.$refs.searchInput, 'focus').mockImplementation(() => {});
vm.searchText = 'index';
- waitForPromises()
- .then(() => {
- vm.clearSearchInput();
- })
- .then(waitForPromises)
- .then(() => {
- expect(vm.$refs.searchInput.focus).toHaveBeenCalled();
- })
- .then(done)
- .catch(done.fail);
+ vm.clearSearchInput();
+
+ await nextTick();
+
+ expect(vm.$refs.searchInput.focus).toHaveBeenCalled();
});
describe('listShowCount', () => {
- it('returns 1 when no filtered entries exist', (done) => {
+ it('returns 1 when no filtered entries exist', () => {
vm.searchText = 'testing 123';
- setImmediate(() => {
- expect(vm.listShowCount).toBe(1);
-
- done();
- });
+ expect(vm.listShowCount).toBe(1);
});
it('returns entries length when not filtered', () => {
@@ -131,26 +110,18 @@ describe('File finder item spec', () => {
expect(vm.listHeight).toBe(55);
});
- it('returns 33 when entries dont exist', (done) => {
+ it('returns 33 when entries dont exist', () => {
vm.searchText = 'testing 123';
- setImmediate(() => {
- expect(vm.listHeight).toBe(33);
-
- done();
- });
+ expect(vm.listHeight).toBe(33);
});
});
describe('filteredBlobsLength', () => {
- it('returns length of filtered blobs', (done) => {
+ it('returns length of filtered blobs', () => {
vm.searchText = 'index';
- setImmediate(() => {
- expect(vm.filteredBlobsLength).toBe(1);
-
- done();
- });
+ expect(vm.filteredBlobsLength).toBe(1);
});
});
@@ -158,7 +129,7 @@ describe('File finder item spec', () => {
it('renders less DOM nodes if not visible by utilizing v-if', async () => {
vm.visible = false;
- await waitForPromises();
+ await nextTick();
expect(vm.$el).toBeInstanceOf(Comment);
});
@@ -166,33 +137,24 @@ describe('File finder item spec', () => {
describe('watches', () => {
describe('searchText', () => {
- it('resets focusedIndex when updated', (done) => {
+ it('resets focusedIndex when updated', async () => {
vm.focusedIndex = 1;
vm.searchText = 'test';
- setImmediate(() => {
- expect(vm.focusedIndex).toBe(0);
+ await nextTick();
- done();
- });
+ expect(vm.focusedIndex).toBe(0);
});
});
describe('visible', () => {
- it('resets searchText when changed to false', (done) => {
+ it('resets searchText when changed to false', async () => {
vm.searchText = 'test';
- vm.visible = true;
-
- waitForPromises()
- .then(() => {
- vm.visible = false;
- })
- .then(waitForPromises)
- .then(() => {
- expect(vm.searchText).toBe('');
- })
- .then(done)
- .catch(done.fail);
+ vm.visible = false;
+
+ await nextTick();
+
+ expect(vm.searchText).toBe('');
});
});
});
@@ -216,7 +178,7 @@ describe('File finder item spec', () => {
});
describe('onKeyup', () => {
- it('opens file on enter key', (done) => {
+ it('opens file on enter key', async () => {
const event = new CustomEvent('keyup');
event.keyCode = ENTER_KEY_CODE;
@@ -224,14 +186,12 @@ describe('File finder item spec', () => {
vm.$refs.searchInput.dispatchEvent(event);
- setImmediate(() => {
- expect(vm.openFile).toHaveBeenCalledWith(vm.files[0]);
+ await nextTick();
- done();
- });
+ expect(vm.openFile).toHaveBeenCalledWith(vm.files[0]);
});
- it('closes file finder on esc key', (done) => {
+ it('closes file finder on esc key', async () => {
const event = new CustomEvent('keyup');
event.keyCode = ESC_KEY_CODE;
@@ -239,11 +199,9 @@ describe('File finder item spec', () => {
vm.$refs.searchInput.dispatchEvent(event);
- setImmediate(() => {
- expect(vm.$emit).toHaveBeenCalledWith('toggle', false);
+ await nextTick();
- done();
- });
+ expect(vm.$emit).toHaveBeenCalledWith('toggle', false);
});
});
@@ -310,34 +268,26 @@ describe('File finder item spec', () => {
});
describe('keyboard shortcuts', () => {
- beforeEach((done) => {
+ beforeEach(async () => {
createComponent();
jest.spyOn(vm, 'toggle').mockImplementation(() => {});
- vm.$nextTick(done);
+ await nextTick();
});
- it('calls toggle on `t` key press', (done) => {
+ it('calls toggle on `t` key press', async () => {
Mousetrap.trigger('t');
- vm.$nextTick()
- .then(() => {
- expect(vm.toggle).toHaveBeenCalled();
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ expect(vm.toggle).toHaveBeenCalled();
});
- it('calls toggle on `mod+p` key press', (done) => {
+ it('calls toggle on `mod+p` key press', async () => {
Mousetrap.trigger('mod+p');
- vm.$nextTick()
- .then(() => {
- expect(vm.toggle).toHaveBeenCalled();
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ expect(vm.toggle).toHaveBeenCalled();
});
it('always allows `mod+p` to trigger toggle', () => {
diff --git a/spec/frontend/vue_shared/components/file_finder/item_spec.js b/spec/frontend/vue_shared/components/file_finder/item_spec.js
index 1a4a97efb95..b69c33055c1 100644
--- a/spec/frontend/vue_shared/components/file_finder/item_spec.js
+++ b/spec/frontend/vue_shared/components/file_finder/item_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import createComponent from 'helpers/vue_mount_component_helper';
import { file } from 'jest/ide/helpers';
import ItemComponent from '~/vue_shared/components/file_finder/item.vue';
@@ -37,14 +37,11 @@ describe('File finder item spec', () => {
expect(vm.$el.classList).toContain('is-focused');
});
- it('does not have is-focused class when not focused', (done) => {
+ it('does not have is-focused class when not focused', async () => {
vm.focused = false;
- vm.$nextTick(() => {
- expect(vm.$el.classList).not.toContain('is-focused');
-
- done();
- });
+ await nextTick();
+ expect(vm.$el.classList).not.toContain('is-focused');
});
});
@@ -53,24 +50,18 @@ describe('File finder item spec', () => {
expect(vm.$el.querySelector('.diff-changed-stats')).toBe(null);
});
- it('renders when a changed file', (done) => {
+ it('renders when a changed file', async () => {
vm.file.changed = true;
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.diff-changed-stats')).not.toBe(null);
-
- done();
- });
+ await nextTick();
+ expect(vm.$el.querySelector('.diff-changed-stats')).not.toBe(null);
});
- it('renders when a temp file', (done) => {
+ it('renders when a temp file', async () => {
vm.file.tempFile = true;
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.diff-changed-stats')).not.toBe(null);
-
- done();
- });
+ await nextTick();
+ expect(vm.$el.querySelector('.diff-changed-stats')).not.toBe(null);
});
});
@@ -85,56 +76,52 @@ describe('File finder item spec', () => {
describe('path', () => {
let el;
- beforeEach((done) => {
+ beforeEach(async () => {
vm.searchText = 'file';
el = vm.$el.querySelector('.diff-changed-file-path');
- vm.$nextTick(done);
+ nextTick();
});
it('highlights text', () => {
expect(el.querySelectorAll('.highlighted').length).toBe(4);
});
- it('adds ellipsis to long text', (done) => {
+ it('adds ellipsis to long text', async () => {
vm.file.path = new Array(70)
.fill()
.map((_, i) => `${i}-`)
.join('');
- vm.$nextTick(() => {
- expect(el.textContent).toBe(`...${vm.file.path.substr(vm.file.path.length - 60)}`);
- done();
- });
+ await nextTick();
+ expect(el.textContent).toBe(`...${vm.file.path.substr(vm.file.path.length - 60)}`);
});
});
describe('name', () => {
let el;
- beforeEach((done) => {
+ beforeEach(async () => {
vm.searchText = 'file';
el = vm.$el.querySelector('.diff-changed-file-name');
- vm.$nextTick(done);
+ await nextTick();
});
it('highlights text', () => {
expect(el.querySelectorAll('.highlighted').length).toBe(4);
});
- it('does not add ellipsis to long text', (done) => {
+ it('does not add ellipsis to long text', async () => {
vm.file.name = new Array(70)
.fill()
.map((_, i) => `${i}-`)
.join('');
- vm.$nextTick(() => {
- expect(el.textContent).not.toBe(`...${vm.file.name.substr(vm.file.name.length - 60)}`);
- done();
- });
+ await nextTick();
+ expect(el.textContent).not.toBe(`...${vm.file.name.substr(vm.file.name.length - 60)}`);
});
});
});
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js
index 4e9eac2dde2..575e8a73050 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js
@@ -8,6 +8,7 @@ import {
} from '@gitlab/ui';
import { shallowMount, mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import RecentSearchesService from '~/filtered_search/services/recent_searches_service';
import RecentSearchesStore from '~/filtered_search/stores/recent_searches_store';
import { SortDirection } from '~/vue_shared/components/filtered_search_bar/constants';
@@ -172,7 +173,7 @@ describe('FilteredSearchBarRoot', () => {
recentSearches: [{ foo: 'bar' }, 'foo'],
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.filteredRecentSearches).toHaveLength(1);
expect(wrapper.vm.filteredRecentSearches[0]).toEqual({ foo: 'bar' });
@@ -188,7 +189,7 @@ describe('FilteredSearchBarRoot', () => {
],
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.filteredRecentSearches).toHaveLength(2);
expect(uniqueTokens).toHaveBeenCalled();
@@ -199,7 +200,7 @@ describe('FilteredSearchBarRoot', () => {
recentSearchesStorageKey: '',
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.filteredRecentSearches).not.toBeDefined();
});
@@ -208,7 +209,7 @@ describe('FilteredSearchBarRoot', () => {
describe('watchers', () => {
describe('filterValue', () => {
- it('emits component event `onFilter` with empty array and false when filter was never selected', () => {
+ it('emits component event `onFilter` with empty array and false when filter was never selected', async () => {
wrapper = createComponent({ initialFilterValue: [tokenValueEmpty] });
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
@@ -217,12 +218,11 @@ describe('FilteredSearchBarRoot', () => {
filterValue: [tokenValueEmpty],
});
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.emitted('onFilter')[0]).toEqual([[], false]);
- });
+ await nextTick();
+ expect(wrapper.emitted('onFilter')[0]).toEqual([[], false]);
});
- it('emits component event `onFilter` with empty array and true when initially selected filter value was cleared', () => {
+ it('emits component event `onFilter` with empty array and true when initially selected filter value was cleared', async () => {
wrapper = createComponent({ initialFilterValue: [tokenValueLabel] });
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
@@ -231,9 +231,8 @@ describe('FilteredSearchBarRoot', () => {
filterValue: [tokenValueEmpty],
});
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.emitted('onFilter')[0]).toEqual([[], true]);
- });
+ await nextTick();
+ expect(wrapper.emitted('onFilter')[0]).toEqual([[], true]);
});
});
});
@@ -336,7 +335,7 @@ describe('FilteredSearchBarRoot', () => {
filterValue: mockFilters,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('calls `uniqueTokens` on `filterValue` prop to remove duplicates', () => {
@@ -395,7 +394,7 @@ describe('FilteredSearchBarRoot', () => {
});
describe('template', () => {
- beforeEach(() => {
+ beforeEach(async () => {
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({
@@ -404,7 +403,7 @@ describe('FilteredSearchBarRoot', () => {
recentSearches: mockHistoryItems,
});
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('renders gl-filtered-search component', () => {
@@ -439,7 +438,7 @@ describe('FilteredSearchBarRoot', () => {
const wrapperFullMount = createComponent({ sortOptions: mockSortOptions, shallow: false });
wrapperFullMount.vm.recentSearchesStore.addRecentSearch(mockHistoryItems[0]);
- await wrapperFullMount.vm.$nextTick();
+ await nextTick();
const searchHistoryItemsEl = wrapperFullMount.findAll(
'.gl-search-box-by-click-menu .gl-search-box-by-click-history-item',
@@ -462,7 +461,7 @@ describe('FilteredSearchBarRoot', () => {
wrapperFullMount.vm.recentSearchesStore.addRecentSearch([tokenValueMembership]);
- await wrapperFullMount.vm.$nextTick();
+ await nextTick();
expect(wrapperFullMount.find(GlDropdownItem).text()).toBe('Membership := Direct');
@@ -480,7 +479,7 @@ describe('FilteredSearchBarRoot', () => {
wrapperFullMount.vm.recentSearchesStore.addRecentSearch([tokenValueMembership]);
- await wrapperFullMount.vm.$nextTick();
+ await nextTick();
expect(wrapperFullMount.find(GlDropdownItem).text()).toBe('Membership := exclude');
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js
index 5865c6a41b8..87066b70023 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js
@@ -6,6 +6,7 @@ import {
} from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
+import { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
@@ -167,7 +168,7 @@ describe('AuthorToken', () => {
const tokenSegments = wrapper.findAllComponents(GlFilteredSearchTokenSegment);
const suggestionsSegment = tokenSegments.at(2);
suggestionsSegment.vm.$emit('activate');
- await wrapper.vm.$nextTick();
+ await nextTick();
};
it('renders base-token component', () => {
@@ -185,23 +186,22 @@ describe('AuthorToken', () => {
});
});
- it('renders token item when value is selected', () => {
+ it('renders token item when value is selected', async () => {
wrapper = createComponent({
value: { data: mockAuthors[0].username },
data: { authors: mockAuthors },
stubs: { Portal: true },
});
- return wrapper.vm.$nextTick(() => {
- const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
+ await nextTick();
+ const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
- expect(tokenSegments).toHaveLength(3); // Author, =, "Administrator"
+ expect(tokenSegments).toHaveLength(3); // Author, =, "Administrator"
- const tokenValue = tokenSegments.at(2);
+ const tokenValue = tokenSegments.at(2);
- expect(tokenValue.findComponent(GlAvatar).props('src')).toBe(mockAuthors[0].avatar_url);
- expect(tokenValue.text()).toBe(mockAuthors[0].name); // "Administrator"
- });
+ expect(tokenValue.findComponent(GlAvatar).props('src')).toBe(mockAuthors[0].avatar_url);
+ expect(tokenValue.text()).toBe(mockAuthors[0].name); // "Administrator"
});
it('renders token value with correct avatarUrl from author object', async () => {
@@ -220,7 +220,7 @@ describe('AuthorToken', () => {
stubs: { Portal: true },
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(getAvatarEl().props('src')).toBe(mockAuthors[0].avatar_url);
@@ -236,7 +236,7 @@ describe('AuthorToken', () => {
],
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(getAvatarEl().props('src')).toBe(mockAuthors[0].avatar_url);
});
@@ -268,7 +268,7 @@ describe('AuthorToken', () => {
const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
const suggestionsSegment = tokenSegments.at(2);
suggestionsSegment.vm.$emit('activate');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find(GlDropdownDivider).exists()).toBe(false);
});
@@ -323,7 +323,7 @@ describe('AuthorToken', () => {
it('does not show current user while searching', async () => {
wrapper.findComponent(BaseToken).vm.handleInput({ data: 'foo' });
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.findComponent(GlFilteredSearchSuggestion).exists()).toBe(false);
});
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js
index 84f0151d9db..dd9bf2ff598 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js
@@ -1,5 +1,6 @@
-import { GlFilteredSearchToken } from '@gitlab/ui';
+import { GlFilteredSearchToken, GlLoadingIcon } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import {
mockRegularLabel,
mockLabels,
@@ -61,13 +62,10 @@ const mockProps = {
getActiveTokenValue: (labels, data) => labels.find((label) => label.title === data),
};
-function createComponent({
- props = { ...mockProps },
- stubs = defaultStubs,
- slots = defaultSlots,
-} = {}) {
+function createComponent({ props = {}, stubs = defaultStubs, slots = defaultSlots } = {}) {
return mount(BaseToken, {
propsData: {
+ ...mockProps,
...props,
},
provide: {
@@ -83,15 +81,7 @@ function createComponent({
describe('BaseToken', () => {
let wrapper;
- beforeEach(() => {
- wrapper = createComponent({
- props: {
- ...mockProps,
- value: { data: `"${mockRegularLabel.title}"` },
- suggestions: mockLabels,
- },
- });
- });
+ const findGlFilteredSearchToken = () => wrapper.findComponent(GlFilteredSearchToken);
afterEach(() => {
wrapper.destroy();
@@ -99,21 +89,25 @@ describe('BaseToken', () => {
describe('data', () => {
it('calls `getRecentlyUsedSuggestions` to populate `recentSuggestions` when `recentSuggestionsStorageKey` is defined', () => {
+ wrapper = createComponent();
+
expect(getRecentlyUsedSuggestions).toHaveBeenCalledWith(mockStorageKey);
});
});
describe('computed', () => {
describe('activeTokenValue', () => {
- it('calls `getActiveTokenValue` when it is provided', async () => {
+ it('calls `getActiveTokenValue` when it is provided', () => {
const mockGetActiveTokenValue = jest.fn();
- wrapper.setProps({
- getActiveTokenValue: mockGetActiveTokenValue,
+ wrapper = createComponent({
+ props: {
+ value: { data: `"${mockRegularLabel.title}"` },
+ suggestions: mockLabels,
+ getActiveTokenValue: mockGetActiveTokenValue,
+ },
});
- await wrapper.vm.$nextTick();
-
expect(mockGetActiveTokenValue).toHaveBeenCalledTimes(1);
expect(mockGetActiveTokenValue).toHaveBeenCalledWith(
mockLabels,
@@ -125,33 +119,19 @@ describe('BaseToken', () => {
describe('watch', () => {
describe('active', () => {
- let wrapperWithTokenActive;
-
beforeEach(() => {
- wrapperWithTokenActive = createComponent({
+ wrapper = createComponent({
props: {
- ...mockProps,
value: { data: `"${mockRegularLabel.title}"` },
active: true,
},
});
});
- afterEach(() => {
- wrapperWithTokenActive.destroy();
- });
-
it('emits `fetch-suggestions` event on the component when value of this prop is changed to false and `suggestions` array is empty', async () => {
- wrapperWithTokenActive.setProps({
- active: false,
- });
-
- await wrapperWithTokenActive.vm.$nextTick();
+ await wrapper.setProps({ active: false });
- expect(wrapperWithTokenActive.emitted('fetch-suggestions')).toBeTruthy();
- expect(wrapperWithTokenActive.emitted('fetch-suggestions')).toEqual([
- [`"${mockRegularLabel.title}"`],
- ]);
+ expect(wrapper.emitted('fetch-suggestions')).toEqual([[`"${mockRegularLabel.title}"`]]);
});
});
});
@@ -161,17 +141,15 @@ describe('BaseToken', () => {
const mockTokenValue = mockLabels[0];
it('calls `setTokenValueToRecentlyUsed` when `recentSuggestionsStorageKey` is defined', () => {
+ wrapper = createComponent({ props: { suggestions: mockLabels } });
+
wrapper.vm.handleTokenValueSelected(mockTokenValue.title);
expect(setTokenValueToRecentlyUsed).toHaveBeenCalledWith(mockStorageKey, mockTokenValue);
});
- it('does not add token from preloadedSuggestions', async () => {
- wrapper.setProps({
- preloadedSuggestions: [mockTokenValue],
- });
-
- await wrapper.vm.$nextTick();
+ it('does not add token from preloadedSuggestions', () => {
+ wrapper = createComponent({ props: { preloadedSuggestions: [mockTokenValue] } });
wrapper.vm.handleTokenValueSelected(mockTokenValue.title);
@@ -182,58 +160,60 @@ describe('BaseToken', () => {
describe('template', () => {
it('renders gl-filtered-search-token component', () => {
- const wrapperWithNoStubs = createComponent({
- stubs: {},
- });
- const glFilteredSearchToken = wrapperWithNoStubs.find(GlFilteredSearchToken);
-
- expect(glFilteredSearchToken.exists()).toBe(true);
- expect(glFilteredSearchToken.props('config')).toEqual(mockProps.config);
+ wrapper = createComponent({ stubs: {} });
- wrapperWithNoStubs.destroy();
+ expect(findGlFilteredSearchToken().props('config')).toEqual(mockProps.config);
});
it('renders `view-token` slot when present', () => {
+ wrapper = createComponent();
+
expect(wrapper.find('.js-view-token').exists()).toBe(true);
});
it('renders `view` slot when present', () => {
+ wrapper = createComponent();
+
expect(wrapper.find('.js-view').exists()).toBe(true);
});
- describe('events', () => {
- let wrapperWithNoStubs;
-
- afterEach(() => {
- wrapperWithNoStubs.destroy();
+ it('renders loading spinner when loading', () => {
+ wrapper = createComponent({
+ props: {
+ active: true,
+ config: mockLabelToken,
+ suggestionsLoading: true,
+ },
+ stubs: { Portal: true },
});
+ expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
+ });
+
+ describe('events', () => {
describe('when activeToken has been selected', () => {
beforeEach(() => {
- wrapperWithNoStubs = createComponent({
- props: {
- ...mockProps,
- getActiveTokenValue: () => ({ title: '' }),
- suggestionsLoading: true,
- },
+ wrapper = createComponent({
+ props: { getActiveTokenValue: () => ({ title: '' }) },
stubs: { Portal: true },
});
});
+
it('does not emit `fetch-suggestions` event on component after a delay when component emits `input` event', async () => {
jest.useFakeTimers();
- wrapperWithNoStubs.find(GlFilteredSearchToken).vm.$emit('input', { data: 'foo' });
- await wrapperWithNoStubs.vm.$nextTick();
+ findGlFilteredSearchToken().vm.$emit('input', { data: 'foo' });
+ await nextTick();
jest.runAllTimers();
- expect(wrapperWithNoStubs.emitted('fetch-suggestions')).toEqual([['']]);
+ expect(wrapper.emitted('fetch-suggestions')).toEqual([['']]);
});
});
describe('when activeToken has not been selected', () => {
beforeEach(() => {
- wrapperWithNoStubs = createComponent({
+ wrapper = createComponent({
stubs: { Portal: true },
});
});
@@ -241,38 +221,27 @@ describe('BaseToken', () => {
it('emits `fetch-suggestions` event on component after a delay when component emits `input` event', async () => {
jest.useFakeTimers();
- wrapperWithNoStubs.find(GlFilteredSearchToken).vm.$emit('input', { data: 'foo' });
- await wrapperWithNoStubs.vm.$nextTick();
+ findGlFilteredSearchToken().vm.$emit('input', { data: 'foo' });
+ await nextTick();
jest.runAllTimers();
- expect(wrapperWithNoStubs.emitted('fetch-suggestions')).toBeTruthy();
- expect(wrapperWithNoStubs.emitted('fetch-suggestions')[2]).toEqual(['foo']);
+ expect(wrapper.emitted('fetch-suggestions')[2]).toEqual(['foo']);
});
describe('when search is started with a quote', () => {
- it('emits `fetch-suggestions` with filtered value', async () => {
- jest.useFakeTimers();
-
- wrapperWithNoStubs.find(GlFilteredSearchToken).vm.$emit('input', { data: '"foo' });
- await wrapperWithNoStubs.vm.$nextTick();
+ it('emits `fetch-suggestions` with filtered value', () => {
+ findGlFilteredSearchToken().vm.$emit('input', { data: '"foo' });
- jest.runAllTimers();
-
- expect(wrapperWithNoStubs.emitted('fetch-suggestions')[2]).toEqual(['foo']);
+ expect(wrapper.emitted('fetch-suggestions')[2]).toEqual(['foo']);
});
});
describe('when search starts and ends with a quote', () => {
- it('emits `fetch-suggestions` with filtered value', async () => {
- jest.useFakeTimers();
-
- wrapperWithNoStubs.find(GlFilteredSearchToken).vm.$emit('input', { data: '"foo"' });
- await wrapperWithNoStubs.vm.$nextTick();
-
- jest.runAllTimers();
+ it('emits `fetch-suggestions` with filtered value', () => {
+ findGlFilteredSearchToken().vm.$emit('input', { data: '"foo"' });
- expect(wrapperWithNoStubs.emitted('fetch-suggestions')[2]).toEqual(['foo']);
+ expect(wrapper.emitted('fetch-suggestions')[2]).toEqual(['foo']);
});
});
});
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/branch_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/branch_token_spec.js
index cd8be765fb5..7a7db434052 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/branch_token_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/branch_token_spec.js
@@ -6,6 +6,7 @@ import {
} from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
+import { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
import createFlash from '~/flash';
@@ -115,7 +116,7 @@ describe('BranchToken', () => {
const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
const suggestionsSegment = tokenSegments.at(2);
suggestionsSegment.vm.$emit('activate');
- await wrapper.vm.$nextTick();
+ await nextTick();
}
beforeEach(async () => {
@@ -127,7 +128,7 @@ describe('BranchToken', () => {
branches: mockBranches,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('renders gl-filtered-search-token component', () => {
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/emoji_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/emoji_token_spec.js
index ed9ac7c271e..b163563cea4 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/emoji_token_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/emoji_token_spec.js
@@ -6,6 +6,7 @@ import {
} from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
+import { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
@@ -129,7 +130,7 @@ describe('EmojiToken', () => {
emojis: mockEmojis,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('renders gl-filtered-search-token component', () => {
@@ -152,7 +153,7 @@ describe('EmojiToken', () => {
const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
const suggestionsSegment = tokenSegments.at(2);
suggestionsSegment.vm.$emit('activate');
- await wrapper.vm.$nextTick();
+ await nextTick();
const suggestions = wrapper.findAll(GlFilteredSearchSuggestion);
@@ -171,7 +172,7 @@ describe('EmojiToken', () => {
const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
const suggestionsSegment = tokenSegments.at(2);
suggestionsSegment.vm.$emit('activate');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find(GlFilteredSearchSuggestion).exists()).toBe(false);
expect(wrapper.find(GlDropdownDivider).exists()).toBe(false);
@@ -186,7 +187,7 @@ describe('EmojiToken', () => {
const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
const suggestionsSegment = tokenSegments.at(2);
suggestionsSegment.vm.$emit('activate');
- await wrapper.vm.$nextTick();
+ await nextTick();
const suggestions = wrapper.findAll(GlFilteredSearchSuggestion);
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/label_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/label_token_spec.js
index b9af71ad8a7..52df27c2d00 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/label_token_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/label_token_spec.js
@@ -5,6 +5,7 @@ import {
} from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
+import { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
import {
mockRegularLabel,
@@ -150,7 +151,7 @@ describe('LabelToken', () => {
labels: mockLabels,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('renders base-token component', () => {
@@ -182,7 +183,7 @@ describe('LabelToken', () => {
const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
const suggestionsSegment = tokenSegments.at(2);
suggestionsSegment.vm.$emit('activate');
- await wrapper.vm.$nextTick();
+ await nextTick();
const suggestions = wrapper.findAll(GlFilteredSearchSuggestion);
@@ -201,7 +202,7 @@ describe('LabelToken', () => {
const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
const suggestionsSegment = tokenSegments.at(2);
suggestionsSegment.vm.$emit('activate');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find(GlFilteredSearchSuggestion).exists()).toBe(false);
expect(wrapper.find(GlDropdownDivider).exists()).toBe(false);
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/milestone_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/milestone_token_spec.js
index c0d8b5fd139..de9ec863dd5 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/milestone_token_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/milestone_token_spec.js
@@ -6,6 +6,7 @@ import {
} from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
+import { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
@@ -31,7 +32,7 @@ const defaultStubs = {
function createComponent(options = {}) {
const {
- config = mockMilestoneToken,
+ config = { ...mockMilestoneToken, shouldSkipSort: true },
value = { data: '' },
active = false,
stubs = defaultStubs,
@@ -67,6 +68,27 @@ describe('MilestoneToken', () => {
describe('methods', () => {
describe('fetchMilestones', () => {
+ describe('when config.shouldSkipSort is true', () => {
+ beforeEach(() => {
+ wrapper.vm.config.shouldSkipSort = true;
+ });
+
+ afterEach(() => {
+ wrapper.vm.config.shouldSkipSort = false;
+ });
+ it('does not call sortMilestonesByDueDate', async () => {
+ jest.spyOn(wrapper.vm.config, 'fetchMilestones').mockResolvedValue({
+ data: mockMilestones,
+ });
+
+ wrapper.vm.fetchMilestones();
+
+ await waitForPromises();
+
+ expect(sortMilestonesByDueDate).toHaveBeenCalledTimes(0);
+ });
+ });
+
it('calls `config.fetchMilestones` with provided searchTerm param', () => {
jest.spyOn(wrapper.vm.config, 'fetchMilestones');
@@ -76,10 +98,11 @@ describe('MilestoneToken', () => {
});
it('sets response to `milestones` when request is successful', () => {
+ wrapper.vm.config.shouldSkipSort = false;
+
jest.spyOn(wrapper.vm.config, 'fetchMilestones').mockResolvedValue({
data: mockMilestones,
});
-
wrapper.vm.fetchMilestones();
return waitForPromises().then(() => {
@@ -127,7 +150,7 @@ describe('MilestoneToken', () => {
milestones: mockMilestones,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('renders gl-filtered-search-token component', () => {
@@ -150,7 +173,7 @@ describe('MilestoneToken', () => {
const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
const suggestionsSegment = tokenSegments.at(2);
suggestionsSegment.vm.$emit('activate');
- await wrapper.vm.$nextTick();
+ await nextTick();
const suggestions = wrapper.findAll(GlFilteredSearchSuggestion);
@@ -169,7 +192,7 @@ describe('MilestoneToken', () => {
const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
const suggestionsSegment = tokenSegments.at(2);
suggestionsSegment.vm.$emit('activate');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find(GlFilteredSearchSuggestion).exists()).toBe(false);
expect(wrapper.find(GlDropdownDivider).exists()).toBe(false);
@@ -184,7 +207,7 @@ describe('MilestoneToken', () => {
const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
const suggestionsSegment = tokenSegments.at(2);
suggestionsSegment.vm.$emit('activate');
- await wrapper.vm.$nextTick();
+ await nextTick();
const suggestions = wrapper.findAll(GlFilteredSearchSuggestion);
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/release_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/release_token_spec.js
index b2f246a5985..8be21b35414 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/release_token_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/release_token_spec.js
@@ -1,5 +1,6 @@
import { GlFilteredSearchToken, GlFilteredSearchTokenSegment } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
import createFlash from '~/flash';
import ReleaseToken from '~/vue_shared/components/filtered_search_bar/tokens/release_token.vue';
@@ -31,7 +32,7 @@ describe('ReleaseToken', () => {
it('renders release value', async () => {
wrapper = createComponent({ value: { data: id } });
- await wrapper.vm.$nextTick();
+ await nextTick();
const tokenSegments = wrapper.findAllComponents(GlFilteredSearchTokenSegment);
diff --git a/spec/frontend/vue_shared/components/gfm_autocomplete/__snapshots__/utils_spec.js.snap b/spec/frontend/vue_shared/components/gfm_autocomplete/__snapshots__/utils_spec.js.snap
deleted file mode 100644
index 370b6eb01bc..00000000000
--- a/spec/frontend/vue_shared/components/gfm_autocomplete/__snapshots__/utils_spec.js.snap
+++ /dev/null
@@ -1,54 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`gfm_autocomplete/utils emojis config shows the emoji name and icon in the menu item 1`] = `"raised_hands <gl-emoji data-name=\\"raised_hands\\"></gl-emoji>"`;
-
-exports[`gfm_autocomplete/utils issues config shows the iid and title in the menu item within a project context 1`] = `"<small>123456</small> Project context issue title &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt;"`;
-
-exports[`gfm_autocomplete/utils issues config shows the reference and title in the menu item within a group context 1`] = `"<small>gitlab#987654</small> Group context issue title &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt;"`;
-
-exports[`gfm_autocomplete/utils labels config shows the title in the menu item 1`] = `
-"
- <span class=\\"dropdown-label-box\\" style=\\"background: #123456;\\"></span>
- bug &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt;"
-`;
-
-exports[`gfm_autocomplete/utils members config shows an avatar character, name, parent name, and count in the menu item for a group 1`] = `
-"
- <div class=\\"gl-display-flex gl-align-items-center\\">
- <div class=\\"gl-avatar gl-avatar-s32 gl-flex-shrink-0 gl-rounded-small
- gl-display-flex gl-align-items-center gl-justify-content-center\\" aria-hidden=\\"true\\">
- G</div>
- <div class=\\"gl-line-height-normal gl-ml-4\\">
- <div>1-1s &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt; (2)</div>
- <div class=\\"gl-text-gray-700\\">GitLab Support Team</div>
- </div>
-
- </div>
- "
-`;
-
-exports[`gfm_autocomplete/utils members config shows the avatar, name and username in the menu item for a user 1`] = `
-"
- <div class=\\"gl-display-flex gl-align-items-center\\">
- <img class=\\"gl-avatar gl-avatar-s32 gl-flex-shrink-0 gl-avatar-circle\\" src=\\"/uploads/-/system/user/avatar/123456/avatar.png\\" alt=\\"\\" />
- <div class=\\"gl-line-height-normal gl-ml-4\\">
- <div>My Name &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt;</div>
- <div class=\\"gl-text-gray-700\\">@myusername</div>
- </div>
-
- </div>
- "
-`;
-
-exports[`gfm_autocomplete/utils merge requests config shows the iid and title in the menu item within a project context 1`] = `"<small>123456</small> Project context merge request title &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt;"`;
-
-exports[`gfm_autocomplete/utils merge requests config shows the reference and title in the menu item within a group context 1`] = `"<small>gitlab!456789</small> Group context merge request title &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt;"`;
-
-exports[`gfm_autocomplete/utils milestones config shows the title in the menu item 1`] = `"13.2 &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt;"`;
-
-exports[`gfm_autocomplete/utils quick actions config shows the name, aliases, params and description in the menu item 1`] = `
-"<div>/unlabel <small>(or /remove_label)</small> <small>~label1 ~\\"label 2\\"</small></div>
- <div><small><em>Remove all or specific label(s)</em></small></div>"
-`;
-
-exports[`gfm_autocomplete/utils snippets config shows the id and title in the menu item 1`] = `"<small>123456</small> Snippet title &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt;"`;
diff --git a/spec/frontend/vue_shared/components/gfm_autocomplete/gfm_autocomplete_spec.js b/spec/frontend/vue_shared/components/gfm_autocomplete/gfm_autocomplete_spec.js
deleted file mode 100644
index b4002fdf4ec..00000000000
--- a/spec/frontend/vue_shared/components/gfm_autocomplete/gfm_autocomplete_spec.js
+++ /dev/null
@@ -1,34 +0,0 @@
-import Tribute from '@gitlab/tributejs';
-import { shallowMount } from '@vue/test-utils';
-import GfmAutocomplete from '~/vue_shared/components/gfm_autocomplete/gfm_autocomplete.vue';
-
-describe('GfmAutocomplete', () => {
- let wrapper;
-
- describe('tribute', () => {
- const mentions = '/gitlab-org/gitlab-test/-/autocomplete_sources/members?type=Issue&type_id=1';
-
- beforeEach(() => {
- wrapper = shallowMount(GfmAutocomplete, {
- propsData: {
- dataSources: {
- mentions,
- },
- },
- slots: {
- default: ['<input/>'],
- },
- });
- });
-
- it('is set to tribute instance variable', () => {
- expect(wrapper.vm.tribute instanceof Tribute).toBe(true);
- });
-
- it('contains the slot input element', () => {
- wrapper.find('input').setValue('@');
-
- expect(wrapper.vm.tribute.current.element).toBe(wrapper.find('input').element);
- });
- });
-});
diff --git a/spec/frontend/vue_shared/components/gfm_autocomplete/utils_spec.js b/spec/frontend/vue_shared/components/gfm_autocomplete/utils_spec.js
deleted file mode 100644
index 7ec3fbd4e3b..00000000000
--- a/spec/frontend/vue_shared/components/gfm_autocomplete/utils_spec.js
+++ /dev/null
@@ -1,427 +0,0 @@
-import { escape, last } from 'lodash';
-import { GfmAutocompleteType, tributeConfig } from '~/vue_shared/components/gfm_autocomplete/utils';
-
-describe('gfm_autocomplete/utils', () => {
- describe('emojis config', () => {
- const emojisConfig = tributeConfig[GfmAutocompleteType.Emojis].config;
- const emoji = 'raised_hands';
-
- it('uses : as the trigger', () => {
- expect(emojisConfig.trigger).toBe(':');
- });
-
- it('searches using the emoji name', () => {
- expect(emojisConfig.lookup(emoji)).toBe(emoji);
- });
-
- it('limits the number of rendered items to 100', () => {
- expect(emojisConfig.menuItemLimit).toBe(100);
- });
-
- it('shows the emoji name and icon in the menu item', () => {
- expect(emojisConfig.menuItemTemplate({ original: emoji })).toMatchSnapshot();
- });
-
- it('inserts the emoji name on autocomplete selection', () => {
- expect(emojisConfig.selectTemplate({ original: emoji })).toBe(`:${emoji}:`);
- });
- });
-
- describe('issues config', () => {
- const issuesConfig = tributeConfig[GfmAutocompleteType.Issues].config;
- const groupContextIssue = {
- iid: 987654,
- reference: 'gitlab#987654',
- title: "Group context issue title <script>alert('hi')</script>",
- };
- const projectContextIssue = {
- id: null,
- iid: 123456,
- time_estimate: 0,
- title: "Project context issue title <script>alert('hi')</script>",
- };
-
- it('uses # as the trigger', () => {
- expect(issuesConfig.trigger).toBe('#');
- });
-
- it('searches using both the iid and title', () => {
- expect(issuesConfig.lookup(projectContextIssue)).toBe(
- `${projectContextIssue.iid}${projectContextIssue.title}`,
- );
- });
-
- it('limits the number of rendered items to 100', () => {
- expect(issuesConfig.menuItemLimit).toBe(100);
- });
-
- it('shows the reference and title in the menu item within a group context', () => {
- expect(issuesConfig.menuItemTemplate({ original: groupContextIssue })).toMatchSnapshot();
- });
-
- it('shows the iid and title in the menu item within a project context', () => {
- expect(issuesConfig.menuItemTemplate({ original: projectContextIssue })).toMatchSnapshot();
- });
-
- it('inserts the reference on autocomplete selection within a group context', () => {
- expect(issuesConfig.selectTemplate({ original: groupContextIssue })).toBe(
- groupContextIssue.reference,
- );
- });
-
- it('inserts the iid on autocomplete selection within a project context', () => {
- expect(issuesConfig.selectTemplate({ original: projectContextIssue })).toBe(
- `#${projectContextIssue.iid}`,
- );
- });
- });
-
- describe('labels config', () => {
- const labelsConfig = tributeConfig[GfmAutocompleteType.Labels].config;
- const labelsFilter = tributeConfig[GfmAutocompleteType.Labels].filterValues;
- const label = {
- color: '#123456',
- textColor: '#FFFFFF',
- title: `bug <script>alert('hi')</script>`,
- type: 'GroupLabel',
- };
- const singleWordLabel = {
- color: '#456789',
- textColor: '#DDD',
- title: `bug`,
- type: 'GroupLabel',
- };
- const numericalLabel = {
- color: '#abcdef',
- textColor: '#AAA',
- title: 123456,
- type: 'ProjectLabel',
- };
-
- it('uses ~ as the trigger', () => {
- expect(labelsConfig.trigger).toBe('~');
- });
-
- it('searches using `title`', () => {
- expect(labelsConfig.lookup).toBe('title');
- });
-
- it('limits the number of rendered items to 100', () => {
- expect(labelsConfig.menuItemLimit).toBe(100);
- });
-
- it('shows the title in the menu item', () => {
- expect(labelsConfig.menuItemTemplate({ original: label })).toMatchSnapshot();
- });
-
- it('inserts the title on autocomplete selection', () => {
- expect(labelsConfig.selectTemplate({ original: singleWordLabel })).toBe(
- `~${escape(singleWordLabel.title)}`,
- );
- });
-
- it('inserts the title enclosed with quotes on autocomplete selection when the title is numerical', () => {
- expect(labelsConfig.selectTemplate({ original: numericalLabel })).toBe(
- `~"${escape(numericalLabel.title)}"`,
- );
- });
-
- it('inserts the title enclosed with quotes on autocomplete selection when the title contains multiple words', () => {
- expect(labelsConfig.selectTemplate({ original: label })).toBe(`~"${escape(label.title)}"`);
- });
-
- describe('filter', () => {
- const collection = [label, singleWordLabel, { ...numericalLabel, set: true }];
-
- describe('/label quick action', () => {
- describe('when the line starts with `/label`', () => {
- it('shows labels that are not currently selected', () => {
- const fullText = '/label ~';
- const selectionStart = 8;
-
- expect(labelsFilter({ collection, fullText, selectionStart })).toEqual([
- collection[0],
- collection[1],
- ]);
- });
- });
-
- describe('when the line does not start with `/label`', () => {
- it('shows all labels', () => {
- const fullText = '~';
- const selectionStart = 1;
-
- expect(labelsFilter({ collection, fullText, selectionStart })).toEqual(collection);
- });
- });
- });
-
- describe('/unlabel quick action', () => {
- describe('when the line starts with `/unlabel`', () => {
- it('shows labels that are currently selected', () => {
- const fullText = '/unlabel ~';
- const selectionStart = 10;
-
- expect(labelsFilter({ collection, fullText, selectionStart })).toEqual([collection[2]]);
- });
- });
-
- describe('when the line does not start with `/unlabel`', () => {
- it('shows all labels', () => {
- const fullText = '~';
- const selectionStart = 1;
-
- expect(labelsFilter({ collection, fullText, selectionStart })).toEqual(collection);
- });
- });
- });
- });
- });
-
- describe('members config', () => {
- const membersConfig = tributeConfig[GfmAutocompleteType.Members].config;
- const membersFilter = tributeConfig[GfmAutocompleteType.Members].filterValues;
- const userMember = {
- type: 'User',
- username: 'myusername',
- name: "My Name <script>alert('hi')</script>",
- avatar_url: '/uploads/-/system/user/avatar/123456/avatar.png',
- availability: null,
- };
- const groupMember = {
- type: 'Group',
- username: 'gitlab-com/support/1-1s',
- name: "GitLab.com / GitLab Support Team / 1-1s <script>alert('hi')</script>",
- avatar_url: null,
- count: 2,
- mentionsDisabled: null,
- };
-
- it('uses @ as the trigger', () => {
- expect(membersConfig.trigger).toBe('@');
- });
-
- it('inserts the username on autocomplete selection', () => {
- expect(membersConfig.fillAttr).toBe('username');
- });
-
- it('searches using both the name and username for a user', () => {
- expect(membersConfig.lookup(userMember)).toBe(`${userMember.name}${userMember.username}`);
- });
-
- it('searches using only its own name and not its ancestors for a group', () => {
- expect(membersConfig.lookup(groupMember)).toBe(last(groupMember.name.split(' / ')));
- });
-
- it('limits the items in the autocomplete menu to 10', () => {
- expect(membersConfig.menuItemLimit).toBe(10);
- });
-
- it('shows the avatar, name and username in the menu item for a user', () => {
- expect(membersConfig.menuItemTemplate({ original: userMember })).toMatchSnapshot();
- });
-
- it('shows an avatar character, name, parent name, and count in the menu item for a group', () => {
- expect(membersConfig.menuItemTemplate({ original: groupMember })).toMatchSnapshot();
- });
-
- describe('filter', () => {
- const assignees = [userMember.username];
- const collection = [userMember, groupMember];
-
- describe('/assign quick action', () => {
- describe('when the line starts with `/assign`', () => {
- it('shows members that are not currently selected', () => {
- const fullText = '/assign @';
- const selectionStart = 9;
-
- expect(membersFilter({ assignees, collection, fullText, selectionStart })).toEqual([
- collection[1],
- ]);
- });
- });
-
- describe('when the line does not start with `/assign`', () => {
- it('shows all labels', () => {
- const fullText = '@';
- const selectionStart = 1;
-
- expect(membersFilter({ assignees, collection, fullText, selectionStart })).toEqual(
- collection,
- );
- });
- });
- });
-
- describe('/unassign quick action', () => {
- describe('when the line starts with `/unassign`', () => {
- it('shows members that are currently selected', () => {
- const fullText = '/unassign @';
- const selectionStart = 11;
-
- expect(membersFilter({ assignees, collection, fullText, selectionStart })).toEqual([
- collection[0],
- ]);
- });
- });
-
- describe('when the line does not start with `/unassign`', () => {
- it('shows all members', () => {
- const fullText = '@';
- const selectionStart = 1;
-
- expect(membersFilter({ assignees, collection, fullText, selectionStart })).toEqual(
- collection,
- );
- });
- });
- });
- });
- });
-
- describe('merge requests config', () => {
- const mergeRequestsConfig = tributeConfig[GfmAutocompleteType.MergeRequests].config;
- const groupContextMergeRequest = {
- iid: 456789,
- reference: 'gitlab!456789',
- title: "Group context merge request title <script>alert('hi')</script>",
- };
- const projectContextMergeRequest = {
- id: null,
- iid: 123456,
- time_estimate: 0,
- title: "Project context merge request title <script>alert('hi')</script>",
- };
-
- it('uses ! as the trigger', () => {
- expect(mergeRequestsConfig.trigger).toBe('!');
- });
-
- it('searches using both the iid and title', () => {
- expect(mergeRequestsConfig.lookup(projectContextMergeRequest)).toBe(
- `${projectContextMergeRequest.iid}${projectContextMergeRequest.title}`,
- );
- });
-
- it('limits the number of rendered items to 100', () => {
- expect(mergeRequestsConfig.menuItemLimit).toBe(100);
- });
-
- it('shows the reference and title in the menu item within a group context', () => {
- expect(
- mergeRequestsConfig.menuItemTemplate({ original: groupContextMergeRequest }),
- ).toMatchSnapshot();
- });
-
- it('shows the iid and title in the menu item within a project context', () => {
- expect(
- mergeRequestsConfig.menuItemTemplate({ original: projectContextMergeRequest }),
- ).toMatchSnapshot();
- });
-
- it('inserts the reference on autocomplete selection within a group context', () => {
- expect(mergeRequestsConfig.selectTemplate({ original: groupContextMergeRequest })).toBe(
- groupContextMergeRequest.reference,
- );
- });
-
- it('inserts the iid on autocomplete selection within a project context', () => {
- expect(mergeRequestsConfig.selectTemplate({ original: projectContextMergeRequest })).toBe(
- `!${projectContextMergeRequest.iid}`,
- );
- });
- });
-
- describe('milestones config', () => {
- const milestonesConfig = tributeConfig[GfmAutocompleteType.Milestones].config;
- const milestone = {
- id: null,
- iid: 49,
- title: "13.2 <script>alert('hi')</script>",
- };
-
- it('uses % as the trigger', () => {
- expect(milestonesConfig.trigger).toBe('%');
- });
-
- it('searches using the title', () => {
- expect(milestonesConfig.lookup).toBe('title');
- });
-
- it('limits the number of rendered items to 100', () => {
- expect(milestonesConfig.menuItemLimit).toBe(100);
- });
-
- it('shows the title in the menu item', () => {
- expect(milestonesConfig.menuItemTemplate({ original: milestone })).toMatchSnapshot();
- });
-
- it('inserts the title on autocomplete selection', () => {
- expect(milestonesConfig.selectTemplate({ original: milestone })).toBe(
- `%"${escape(milestone.title)}"`,
- );
- });
- });
-
- describe('quick actions config', () => {
- const quickActionsConfig = tributeConfig[GfmAutocompleteType.QuickActions].config;
- const quickAction = {
- name: 'unlabel',
- aliases: ['remove_label'],
- description: 'Remove all or specific label(s)',
- warning: '',
- icon: '',
- params: ['~label1 ~"label 2"'],
- };
-
- it('uses / as the trigger', () => {
- expect(quickActionsConfig.trigger).toBe('/');
- });
-
- it('inserts the name on autocomplete selection', () => {
- expect(quickActionsConfig.fillAttr).toBe('name');
- });
-
- it('searches using both the name and aliases', () => {
- expect(quickActionsConfig.lookup(quickAction)).toBe(
- `${quickAction.name}${quickAction.aliases.join(', /')}`,
- );
- });
-
- it('limits the number of rendered items to 100', () => {
- expect(quickActionsConfig.menuItemLimit).toBe(100);
- });
-
- it('shows the name, aliases, params and description in the menu item', () => {
- expect(quickActionsConfig.menuItemTemplate({ original: quickAction })).toMatchSnapshot();
- });
- });
-
- describe('snippets config', () => {
- const snippetsConfig = tributeConfig[GfmAutocompleteType.Snippets].config;
- const snippet = {
- id: 123456,
- title: "Snippet title <script>alert('hi')</script>",
- };
-
- it('uses $ as the trigger', () => {
- expect(snippetsConfig.trigger).toBe('$');
- });
-
- it('inserts the id on autocomplete selection', () => {
- expect(snippetsConfig.fillAttr).toBe('id');
- });
-
- it('searches using both the id and title', () => {
- expect(snippetsConfig.lookup(snippet)).toBe(`${snippet.id}${snippet.title}`);
- });
-
- it('limits the number of rendered items to 100', () => {
- expect(snippetsConfig.menuItemLimit).toBe(100);
- });
-
- it('shows the id and title in the menu item', () => {
- expect(snippetsConfig.menuItemTemplate({ original: snippet })).toMatchSnapshot();
- });
- });
-});
diff --git a/spec/frontend/vue_shared/components/gl_countdown_spec.js b/spec/frontend/vue_shared/components/gl_countdown_spec.js
index 82d18c7fd3f..0d1d42082ab 100644
--- a/spec/frontend/vue_shared/components/gl_countdown_spec.js
+++ b/spec/frontend/vue_shared/components/gl_countdown_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import mountComponent from 'helpers/vue_mount_component_helper';
import GlCountdown from '~/vue_shared/components/gl_countdown.vue';
@@ -17,38 +17,34 @@ describe('GlCountdown', () => {
});
describe('when there is time remaining', () => {
- beforeEach((done) => {
+ beforeEach(async () => {
vm = mountComponent(Component, {
endDateString: '2000-01-01T01:02:03Z',
});
- Vue.nextTick().then(done).catch(done.fail);
+ await nextTick();
});
it('displays remaining time', () => {
expect(vm.$el.textContent).toContain('01:02:03');
});
- it('updates remaining time', (done) => {
+ it('updates remaining time', async () => {
now = '2000-01-01T00:00:01Z';
jest.advanceTimersByTime(1000);
- Vue.nextTick()
- .then(() => {
- expect(vm.$el.textContent).toContain('01:02:02');
- done();
- })
- .catch(done.fail);
+ await nextTick();
+ expect(vm.$el.textContent).toContain('01:02:02');
});
});
describe('when there is no time remaining', () => {
- beforeEach((done) => {
+ beforeEach(async () => {
vm = mountComponent(Component, {
endDateString: '1900-01-01T00:00:00Z',
});
- Vue.nextTick().then(done).catch(done.fail);
+ await nextTick();
});
it('displays 00:00:00', () => {
diff --git a/spec/frontend/vue_shared/components/gl_modal_vuex_spec.js b/spec/frontend/vue_shared/components/gl_modal_vuex_spec.js
index b837a998cd6..c0a6588833e 100644
--- a/spec/frontend/vue_shared/components/gl_modal_vuex_spec.js
+++ b/spec/frontend/vue_shared/components/gl_modal_vuex_spec.js
@@ -1,6 +1,6 @@
import { GlModal } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import { BV_SHOW_MODAL, BV_HIDE_MODAL } from '~/lib/utils/constants';
import GlModalVuex from '~/vue_shared/components/gl_modal_vuex.vue';
@@ -118,7 +118,7 @@ describe('GlModalVuex', () => {
expect(actions.hide).toHaveBeenCalledTimes(1);
});
- it('calls bootstrap show when isVisible changes', (done) => {
+ it('calls bootstrap show when isVisible changes', async () => {
state.isVisible = false;
factory();
@@ -126,16 +126,11 @@ describe('GlModalVuex', () => {
state.isVisible = true;
- wrapper.vm
- .$nextTick()
- .then(() => {
- expect(rootEmit).toHaveBeenCalledWith(BV_SHOW_MODAL, TEST_MODAL_ID);
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ expect(rootEmit).toHaveBeenCalledWith(BV_SHOW_MODAL, TEST_MODAL_ID);
});
- it('calls bootstrap hide when isVisible changes', (done) => {
+ it('calls bootstrap hide when isVisible changes', async () => {
state.isVisible = true;
factory();
@@ -143,13 +138,8 @@ describe('GlModalVuex', () => {
state.isVisible = false;
- wrapper.vm
- .$nextTick()
- .then(() => {
- expect(rootEmit).toHaveBeenCalledWith(BV_HIDE_MODAL, TEST_MODAL_ID);
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ expect(rootEmit).toHaveBeenCalledWith(BV_HIDE_MODAL, TEST_MODAL_ID);
});
it.each(['ok', 'cancel'])(
diff --git a/spec/frontend/vue_shared/components/help_popover_spec.js b/spec/frontend/vue_shared/components/help_popover_spec.js
index 30c6fa04032..597fb63d95c 100644
--- a/spec/frontend/vue_shared/components/help_popover_spec.js
+++ b/spec/frontend/vue_shared/components/help_popover_spec.js
@@ -9,59 +9,117 @@ describe('HelpPopover', () => {
const findQuestionButton = () => wrapper.find(GlButton);
const findPopover = () => wrapper.find(GlPopover);
- const buildWrapper = (options = {}) => {
+
+ const createComponent = ({ props, ...opts } = {}) => {
wrapper = mount(HelpPopover, {
propsData: {
options: {
title,
content,
- ...options,
},
+ ...props,
},
+ ...opts,
});
};
- beforeEach(() => {
- buildWrapper();
- });
-
afterEach(() => {
wrapper.destroy();
});
- it('renders a link button with an icon question', () => {
- expect(findQuestionButton().props()).toMatchObject({
- icon: 'question',
- variant: 'link',
+ describe('with title and content', () => {
+ beforeEach(() => {
+ createComponent();
});
- });
- it('renders popover that uses the question button as target', () => {
- expect(findPopover().props().target()).toBe(findQuestionButton().vm.$el);
- });
+ it('renders a link button with an icon question', () => {
+ expect(findQuestionButton().props()).toMatchObject({
+ icon: 'question',
+ variant: 'link',
+ });
+ });
- it('allows rendering title with HTML tags', () => {
- expect(findPopover().find('strong').exists()).toBe(true);
- });
+ it('renders popover that uses the question button as target', () => {
+ expect(findPopover().props().target()).toBe(findQuestionButton().vm.$el);
+ });
- it('allows rendering content with HTML tags', () => {
- expect(findPopover().find('b').exists()).toBe(true);
+ it('shows title and content', () => {
+ expect(findPopover().html()).toContain(title);
+ expect(findPopover().html()).toContain(content);
+ });
+
+ it('allows rendering title with HTML tags', () => {
+ expect(findPopover().find('strong').exists()).toBe(true);
+ });
+
+ it('allows rendering content with HTML tags', () => {
+ expect(findPopover().find('b').exists()).toBe(true);
+ });
});
describe('without title', () => {
- it('does not render title', () => {
- buildWrapper({ title: null });
+ beforeEach(() => {
+ createComponent({
+ props: {
+ options: {
+ title: null,
+ content,
+ },
+ },
+ });
+ });
+
+ it('does not show title', () => {
+ expect(findPopover().html()).not.toContain(title);
+ });
- expect(findPopover().find('span').exists()).toBe(false);
+ it('shows content', () => {
+ expect(findPopover().html()).toContain(content);
});
});
- it('binds other popover options to the popover instance', () => {
+ describe('with other options', () => {
const placement = 'bottom';
- wrapper.destroy();
- buildWrapper({ placement });
+ beforeEach(() => {
+ createComponent({
+ props: {
+ options: {
+ placement,
+ },
+ },
+ });
+ });
+
+ it('options bind to the popover', () => {
+ expect(findPopover().props().placement).toBe(placement);
+ });
+ });
+
+ describe('with custom slots', () => {
+ const titleSlot = '<h1>title</h1>';
+ const defaultSlot = '<strong>content</strong>';
- expect(findPopover().props().placement).toBe(placement);
+ beforeEach(() => {
+ createComponent({
+ slots: {
+ title: titleSlot,
+ default: defaultSlot,
+ },
+ });
+ });
+
+ it('shows title slot', () => {
+ expect(findPopover().html()).toContain(titleSlot);
+ });
+
+ it('shows default content slot', () => {
+ expect(findPopover().html()).toContain(defaultSlot);
+ });
+
+ it('overrides title and content from options', () => {
+ expect(findPopover().html()).not.toContain(title);
+ expect(findPopover().html()).toContain(content);
+ });
});
});
diff --git a/spec/frontend/vue_shared/components/local_storage_sync_spec.js b/spec/frontend/vue_shared/components/local_storage_sync_spec.js
index 4c5a0c1e601..dac633fe6c8 100644
--- a/spec/frontend/vue_shared/components/local_storage_sync_spec.js
+++ b/spec/frontend/vue_shared/components/local_storage_sync_spec.js
@@ -1,4 +1,5 @@
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
describe('Local Storage Sync', () => {
@@ -49,7 +50,7 @@ describe('Local Storage Sync', () => {
it.each('foo', 3, true, ['foo', 'bar'], { foo: 'bar' })(
'saves updated value to localStorage',
- (newValue) => {
+ async (newValue) => {
createComponent({
props: {
storageKey,
@@ -59,9 +60,8 @@ describe('Local Storage Sync', () => {
wrapper.setProps({ value: newValue });
- return wrapper.vm.$nextTick().then(() => {
- expect(localStorage.getItem(storageKey)).toBe(String(newValue));
- });
+ await nextTick();
+ expect(localStorage.getItem(storageKey)).toBe(String(newValue));
},
);
@@ -109,7 +109,7 @@ describe('Local Storage Sync', () => {
expect(localStorage.getItem(storageKey)).toBe(savedValue);
});
- it('updating the value updates localStorage', () => {
+ it('updating the value updates localStorage', async () => {
createComponent({
props: {
storageKey,
@@ -122,9 +122,8 @@ describe('Local Storage Sync', () => {
value: newValue,
});
- return wrapper.vm.$nextTick().then(() => {
- expect(localStorage.getItem(storageKey)).toBe(newValue);
- });
+ await nextTick();
+ expect(localStorage.getItem(storageKey)).toBe(newValue);
});
it('persists the value by default', async () => {
@@ -137,7 +136,7 @@ describe('Local Storage Sync', () => {
});
wrapper.setProps({ value: persistedValue });
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(localStorage.getItem(storageKey)).toBe(persistedValue);
});
@@ -151,7 +150,7 @@ describe('Local Storage Sync', () => {
});
wrapper.setProps({ persist: false, value: notPersistedValue });
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(localStorage.getItem(storageKey)).not.toBe(notPersistedValue);
});
});
@@ -172,7 +171,7 @@ describe('Local Storage Sync', () => {
${{ foo: 'bar' }} | ${'{"foo":"bar"}'}
`('given $value', ({ value, serializedValue }) => {
describe('is a new value', () => {
- beforeEach(() => {
+ beforeEach(async () => {
createComponent({
props: {
storageKey,
@@ -183,7 +182,7 @@ describe('Local Storage Sync', () => {
wrapper.setProps({ value });
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('serializes the value correctly to localStorage', () => {
@@ -253,7 +252,7 @@ describe('Local Storage Sync', () => {
value,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(localStorage.getItem(storageKey)).toBe(value);
@@ -261,7 +260,7 @@ describe('Local Storage Sync', () => {
clear: true,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(localStorage.getItem(storageKey)).toBe(null);
});
diff --git a/spec/frontend/vue_shared/components/markdown/field_spec.js b/spec/frontend/vue_shared/components/markdown/field_spec.js
index 0d90ca7f1f6..c7ad47b6ef7 100644
--- a/spec/frontend/vue_shared/components/markdown/field_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/field_spec.js
@@ -1,10 +1,10 @@
-import { mount } from '@vue/test-utils';
import { nextTick } from 'vue';
import AxiosMockAdapter from 'axios-mock-adapter';
import $ from 'jquery';
import { TEST_HOST, FIXTURES_PATH } from 'spec/test_constants';
import axios from '~/lib/utils/axios_utils';
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
const markdownPreviewPath = `${TEST_HOST}/preview`;
const markdownDocsPath = `${TEST_HOST}/docs`;
@@ -12,8 +12,8 @@ const textareaValue = 'testing\n123';
const uploadsPath = 'test/uploads';
function assertMarkdownTabs(isWrite, writeLink, previewLink, wrapper) {
- expect(writeLink.element.parentNode.classList.contains('active')).toBe(isWrite);
- expect(previewLink.element.parentNode.classList.contains('active')).toBe(!isWrite);
+ expect(writeLink.element.children[0].classList.contains('active')).toBe(isWrite);
+ expect(previewLink.element.children[0].classList.contains('active')).toBe(!isWrite);
expect(wrapper.find('.md-preview-holder').element.style.display).toBe(isWrite ? 'none' : '');
}
@@ -29,14 +29,13 @@ describe('Markdown field component', () => {
afterEach(() => {
subject.destroy();
- subject = null;
axiosMock.restore();
});
function createSubject(lines = []) {
// We actually mount a wrapper component so that we can force Vue to rerender classes in order to test a regression
// caused by mixing Vanilla JS and Vue.
- subject = mount(
+ subject = mountExtended(
{
components: {
MarkdownField,
@@ -63,12 +62,17 @@ describe('Markdown field component', () => {
textareaValue,
lines,
},
+ provide: {
+ glFeatures: {
+ contactsAutocomplete: true,
+ },
+ },
},
);
}
- const getPreviewLink = () => subject.find('.nav-links .js-preview-link');
- const getWriteLink = () => subject.find('.nav-links .js-write-link');
+ const getPreviewLink = () => subject.findByTestId('preview-tab');
+ const getWriteLink = () => subject.findByTestId('write-tab');
const getMarkdownButton = () => subject.find('.js-md');
const getAllMarkdownButtons = () => subject.findAll('.js-md');
const getVideo = () => subject.find('video');
@@ -100,115 +104,100 @@ describe('Markdown field component', () => {
axiosMock.onPost(markdownPreviewPath).reply(200, { body: previewHTML });
});
- it('sets preview link as active', () => {
+ it('sets preview link as active', async () => {
previewLink = getPreviewLink();
- previewLink.trigger('click');
+ previewLink.vm.$emit('click', { target: {} });
- return subject.vm.$nextTick().then(() => {
- expect(previewLink.element.parentNode.classList.contains('active')).toBeTruthy();
- });
+ await nextTick();
+ expect(previewLink.element.children[0].classList.contains('active')).toBe(true);
});
- it('shows preview loading text', () => {
+ it('shows preview loading text', async () => {
previewLink = getPreviewLink();
- previewLink.trigger('click');
+ previewLink.vm.$emit('click', { target: {} });
- return subject.vm.$nextTick(() => {
- expect(subject.find('.md-preview-holder').element.textContent.trim()).toContain(
- 'Loading…',
- );
- });
+ await nextTick();
+ expect(subject.find('.md-preview-holder').element.textContent.trim()).toContain('Loading…');
});
- it('renders markdown preview and GFM', () => {
+ it('renders markdown preview and GFM', async () => {
const renderGFMSpy = jest.spyOn($.fn, 'renderGFM');
previewLink = getPreviewLink();
- previewLink.trigger('click');
+ previewLink.vm.$emit('click', { target: {} });
- return axios.waitFor(markdownPreviewPath).then(() => {
- expect(subject.find('.md-preview-holder').element.innerHTML).toContain(previewHTML);
- expect(renderGFMSpy).toHaveBeenCalled();
- });
+ await axios.waitFor(markdownPreviewPath);
+ expect(subject.find('.md-preview-holder').element.innerHTML).toContain(previewHTML);
+ expect(renderGFMSpy).toHaveBeenCalled();
});
- it('calls video.pause() on comment input when isSubmitting is changed to true', () => {
+ it('calls video.pause() on comment input when isSubmitting is changed to true', async () => {
previewLink = getPreviewLink();
- previewLink.trigger('click');
+ previewLink.vm.$emit('click', { target: {} });
- let callPause;
+ await axios.waitFor(markdownPreviewPath);
+ const video = getVideo();
+ const callPause = jest.spyOn(video.element, 'pause').mockImplementation(() => true);
- return axios
- .waitFor(markdownPreviewPath)
- .then(() => {
- const video = getVideo();
- callPause = jest.spyOn(video.element, 'pause').mockImplementation(() => true);
+ subject.setProps({ isSubmitting: true });
- subject.setProps({ isSubmitting: true });
-
- return subject.vm.$nextTick();
- })
- .then(() => {
- expect(callPause).toHaveBeenCalled();
- });
+ await nextTick();
+ expect(callPause).toHaveBeenCalled();
});
it('clicking already active write or preview link does nothing', async () => {
writeLink = getWriteLink();
previewLink = getPreviewLink();
- writeLink.trigger('click');
- await subject.vm.$nextTick();
+ writeLink.vm.$emit('click', { target: {} });
+ await nextTick();
assertMarkdownTabs(true, writeLink, previewLink, subject);
- writeLink.trigger('click');
- await subject.vm.$nextTick();
+ writeLink.vm.$emit('click', { target: {} });
+ await nextTick();
assertMarkdownTabs(true, writeLink, previewLink, subject);
- previewLink.trigger('click');
- await subject.vm.$nextTick();
+ previewLink.vm.$emit('click', { target: {} });
+ await nextTick();
assertMarkdownTabs(false, writeLink, previewLink, subject);
- previewLink.trigger('click');
- await subject.vm.$nextTick();
+ previewLink.vm.$emit('click', { target: {} });
+ await nextTick();
assertMarkdownTabs(false, writeLink, previewLink, subject);
});
});
describe('markdown buttons', () => {
- it('converts single words', () => {
+ it('converts single words', async () => {
const textarea = subject.find('textarea').element;
textarea.setSelectionRange(0, 7);
const markdownButton = getMarkdownButton();
markdownButton.trigger('click');
- return subject.vm.$nextTick(() => {
- expect(textarea.value).toContain('**testing**');
- });
+ await nextTick();
+ expect(textarea.value).toContain('**testing**');
});
- it('converts a line', () => {
+ it('converts a line', async () => {
const textarea = subject.find('textarea').element;
textarea.setSelectionRange(0, 0);
const markdownButton = getAllMarkdownButtons().wrappers[5];
markdownButton.trigger('click');
- return subject.vm.$nextTick(() => {
- expect(textarea.value).toContain('- testing');
- });
+ await nextTick();
+ expect(textarea.value).toContain('- testing');
});
- it('converts multiple lines', () => {
+ it('converts multiple lines', async () => {
const textarea = subject.find('textarea').element;
textarea.setSelectionRange(0, 50);
const markdownButton = getAllMarkdownButtons().wrappers[5];
markdownButton.trigger('click');
- return subject.vm.$nextTick(() => {
- expect(textarea.value).toContain('- testing\n- 123');
- });
+ await nextTick();
+ expect(textarea.value).toContain('- testing\n- 123');
});
});
@@ -229,7 +218,7 @@ describe('Markdown field component', () => {
// Do something to trigger rerendering the class
subject.setProps({ wrapperClasses: 'foo' });
- await subject.vm.$nextTick();
+ await nextTick();
});
it('should have rerendered classes and kept gfm-form', () => {
diff --git a/spec/frontend/vue_shared/components/markdown/header_spec.js b/spec/frontend/vue_shared/components/markdown/header_spec.js
index fec6abc9639..93ce3935fab 100644
--- a/spec/frontend/vue_shared/components/markdown/header_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/header_spec.js
@@ -1,20 +1,25 @@
-import { shallowMount } from '@vue/test-utils';
import $ from 'jquery';
+import { nextTick } from 'vue';
+import { GlTabs } from '@gitlab/ui';
import HeaderComponent from '~/vue_shared/components/markdown/header.vue';
import ToolbarButton from '~/vue_shared/components/markdown/toolbar_button.vue';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
describe('Markdown field header component', () => {
let wrapper;
const createWrapper = (props) => {
- wrapper = shallowMount(HeaderComponent, {
+ wrapper = shallowMountExtended(HeaderComponent, {
propsData: {
previewMarkdown: false,
...props,
},
+ stubs: { GlTabs },
});
};
+ const findWriteTab = () => wrapper.findByTestId('write-tab');
+ const findPreviewTab = () => wrapper.findByTestId('preview-tab');
const findToolbarButtons = () => wrapper.findAll(ToolbarButton);
const findToolbarButtonByProp = (prop, value) =>
findToolbarButtons()
@@ -33,7 +38,6 @@ describe('Markdown field header component', () => {
afterEach(() => {
wrapper.destroy();
- wrapper = null;
});
describe('markdown header buttons', () => {
@@ -74,30 +78,29 @@ describe('Markdown field header component', () => {
});
});
- it('renders `write` link as active when previewMarkdown is false', () => {
- expect(wrapper.find('li:nth-child(1)').classes()).toContain('active');
+ it('activates `write` tab when previewMarkdown is false', () => {
+ expect(findWriteTab().attributes('active')).toBe('true');
+ expect(findPreviewTab().attributes('active')).toBeUndefined();
});
- it('renders `preview` link as active when previewMarkdown is true', () => {
+ it('activates `preview` tab when previewMarkdown is true', () => {
createWrapper({ previewMarkdown: true });
- expect(wrapper.find('li:nth-child(2)').classes()).toContain('active');
+ expect(findWriteTab().attributes('active')).toBeUndefined();
+ expect(findPreviewTab().attributes('active')).toBe('true');
});
- it('emits toggle markdown event when clicking preview', () => {
- wrapper.find('.js-preview-link').trigger('click');
+ it('emits toggle markdown event when clicking preview tab', async () => {
+ const eventData = { target: {} };
+ findPreviewTab().vm.$emit('click', eventData);
- return wrapper.vm
- .$nextTick()
- .then(() => {
- expect(wrapper.emitted('preview-markdown').length).toEqual(1);
+ await nextTick();
+ expect(wrapper.emitted('preview-markdown').length).toEqual(1);
- wrapper.find('.js-write-link').trigger('click');
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(wrapper.emitted('write-markdown').length).toEqual(1);
- });
+ findWriteTab().vm.$emit('click', eventData);
+
+ await nextTick();
+ expect(wrapper.emitted('write-markdown').length).toEqual(1);
});
it('does not emit toggle markdown event when triggered from another form', () => {
@@ -112,12 +115,10 @@ describe('Markdown field header component', () => {
});
it('blurs preview link after click', () => {
- const link = wrapper.find('li:nth-child(2) button');
- jest.spyOn(HTMLElement.prototype, 'blur').mockImplementation();
-
- link.trigger('click');
+ const target = { blur: jest.fn() };
+ findPreviewTab().vm.$emit('click', { target });
- expect(link.element.blur).toHaveBeenCalled();
+ expect(target.blur).toHaveBeenCalled();
});
it('renders markdown table template', () => {
diff --git a/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js b/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js
index 9bc2aad1895..9944267cf24 100644
--- a/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js
@@ -1,5 +1,6 @@
import { GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import ApplySuggestion from '~/vue_shared/components/markdown/apply_suggestion.vue';
import SuggestionDiffHeader from '~/vue_shared/components/markdown/suggestion_diff_header.vue';
@@ -103,15 +104,14 @@ describe('Suggestion Diff component', () => {
expect(wrapper.text()).toContain('Applying suggestion...');
});
- it('when callback of apply is called, hides loading', () => {
+ it('when callback of apply is called, hides loading', async () => {
const [callback] = wrapper.emitted().apply[0];
callback();
- return wrapper.vm.$nextTick().then(() => {
- expect(findApplyButton().exists()).toBe(true);
- expect(findLoading().exists()).toBe(false);
- });
+ await nextTick();
+ expect(findApplyButton().exists()).toBe(true);
+ expect(findLoading().exists()).toBe(false);
});
});
diff --git a/spec/frontend/vue_shared/components/markdown/suggestions_spec.js b/spec/frontend/vue_shared/components/markdown/suggestions_spec.js
index 6fcac2df0b6..8f4235cfe41 100644
--- a/spec/frontend/vue_shared/components/markdown/suggestions_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/suggestions_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import SuggestionsComponent from '~/vue_shared/components/markdown/suggestions.vue';
const MOCK_DATA = {
@@ -51,7 +51,7 @@ describe('Suggestion component', () => {
let vm;
let diffTable;
- beforeEach((done) => {
+ beforeEach(async () => {
const Component = Vue.extend(SuggestionsComponent);
vm = new Component({
@@ -62,7 +62,7 @@ describe('Suggestion component', () => {
jest.spyOn(vm, 'renderSuggestions').mockImplementation(() => {});
vm.renderSuggestions();
- Vue.nextTick(done);
+ await nextTick();
});
describe('mounted', () => {
diff --git a/spec/frontend/vue_shared/components/modal_copy_button_spec.js b/spec/frontend/vue_shared/components/modal_copy_button_spec.js
index adb72c3ef85..b57efc88d57 100644
--- a/spec/frontend/vue_shared/components/modal_copy_button_spec.js
+++ b/spec/frontend/vue_shared/components/modal_copy_button_spec.js
@@ -1,4 +1,5 @@
import { shallowMount, createWrapper } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue';
@@ -20,7 +21,7 @@ describe('modal copy button', () => {
});
describe('clipboard', () => {
- it('should fire a `success` event on click', () => {
+ it('should fire a `success` event on click', async () => {
const root = createWrapper(wrapper.vm.$root);
document.execCommand = jest.fn(() => true);
window.getSelection = jest.fn(() => ({
@@ -29,20 +30,18 @@ describe('modal copy button', () => {
}));
wrapper.trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted().success).not.toBeEmpty();
- expect(document.execCommand).toHaveBeenCalledWith('copy');
- expect(root.emitted(BV_HIDE_TOOLTIP)).toEqual([['test-id']]);
- });
+ await nextTick();
+ expect(wrapper.emitted().success).not.toBeEmpty();
+ expect(document.execCommand).toHaveBeenCalledWith('copy');
+ expect(root.emitted(BV_HIDE_TOOLTIP)).toEqual([['test-id']]);
});
- it("should propagate the clipboard error event if execCommand doesn't work", () => {
+ it("should propagate the clipboard error event if execCommand doesn't work", async () => {
document.execCommand = jest.fn(() => false);
wrapper.trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted().error).not.toBeEmpty();
- expect(document.execCommand).toHaveBeenCalledWith('copy');
- });
+ await nextTick();
+ expect(wrapper.emitted().error).not.toBeEmpty();
+ expect(document.execCommand).toHaveBeenCalledWith('copy');
});
});
});
diff --git a/spec/frontend/vue_shared/components/multiselect_dropdown_spec.js b/spec/frontend/vue_shared/components/multiselect_dropdown_spec.js
deleted file mode 100644
index 566ca1817f2..00000000000
--- a/spec/frontend/vue_shared/components/multiselect_dropdown_spec.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import { GlDropdown } from '@gitlab/ui';
-import { getByText } from '@testing-library/dom';
-import { shallowMount } from '@vue/test-utils';
-import MultiSelectDropdown from '~/vue_shared/components/sidebar/multiselect_dropdown.vue';
-
-describe('MultiSelectDropdown Component', () => {
- it('renders items slot', () => {
- const wrapper = shallowMount(MultiSelectDropdown, {
- propsData: {
- text: '',
- headerText: '',
- },
- slots: {
- items: '<p>Test</p>',
- },
- });
- expect(getByText(wrapper.element, 'Test')).toBeDefined();
- });
-
- it('renders search slot', () => {
- const wrapper = shallowMount(MultiSelectDropdown, {
- propsData: {
- text: '',
- headerText: '',
- },
- slots: {
- search: '<p>Search</p>',
- },
- stubs: {
- GlDropdown,
- },
- });
- expect(getByText(wrapper.element, 'Search')).toBeDefined();
- });
-});
diff --git a/spec/frontend/vue_shared/components/namespace_select/mock_data.js b/spec/frontend/vue_shared/components/namespace_select/mock_data.js
index c9d96672e85..cfd521c67cb 100644
--- a/spec/frontend/vue_shared/components/namespace_select/mock_data.js
+++ b/spec/frontend/vue_shared/components/namespace_select/mock_data.js
@@ -1,11 +1,6 @@
-export const group = [
+export const groupNamespaces = [
{ id: 1, name: 'Group 1', humanName: 'Group 1' },
{ id: 2, name: 'Subgroup 1', humanName: 'Group 1 / Subgroup 1' },
];
-export const user = [{ id: 3, name: 'User namespace 1', humanName: 'User namespace 1' }];
-
-export const namespaces = {
- group,
- user,
-};
+export const userNamespaces = [{ id: 3, name: 'User namespace 1', humanName: 'User namespace 1' }];
diff --git a/spec/frontend/vue_shared/components/namespace_select/namespace_select_spec.js b/spec/frontend/vue_shared/components/namespace_select/namespace_select_spec.js
index 8f07f63993d..c11b20a692e 100644
--- a/spec/frontend/vue_shared/components/namespace_select/namespace_select_spec.js
+++ b/spec/frontend/vue_shared/components/namespace_select/namespace_select_spec.js
@@ -1,9 +1,15 @@
-import { GlDropdown, GlDropdownItem, GlDropdownSectionHeader } from '@gitlab/ui';
+import { nextTick } from 'vue';
+import { GlDropdown, GlDropdownItem, GlDropdownSectionHeader, GlSearchBoxByType } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import NamespaceSelect, {
i18n,
+ EMPTY_NAMESPACE_ID,
} from '~/vue_shared/components/namespace_select/namespace_select.vue';
-import { user, group, namespaces } from './mock_data';
+import { userNamespaces, groupNamespaces } from './mock_data';
+
+const FLAT_NAMESPACES = [...groupNamespaces, ...userNamespaces];
+const EMPTY_NAMESPACE_TITLE = 'Empty namespace TEST';
+const EMPTY_NAMESPACE_ITEM = { id: EMPTY_NAMESPACE_ID, humanName: EMPTY_NAMESPACE_TITLE };
describe('Namespace Select', () => {
let wrapper;
@@ -11,71 +17,115 @@ describe('Namespace Select', () => {
const createComponent = (props = {}) =>
shallowMountExtended(NamespaceSelect, {
propsData: {
- data: namespaces,
+ userNamespaces,
+ groupNamespaces,
...props,
},
+ stubs: {
+ // We have to "full" mount GlDropdown so that slot children will render
+ GlDropdown,
+ },
});
const wrappersText = (arr) => arr.wrappers.map((w) => w.text());
- const flatNamespaces = () => [...group, ...user];
const findDropdown = () => wrapper.findComponent(GlDropdown);
- const findDropdownAttributes = (attr) => findDropdown().attributes(attr);
- const selectedDropdownItemText = () => findDropdownAttributes('text');
+ const findDropdownText = () => findDropdown().props('text');
const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
+ const findDropdownItemsTexts = () => findDropdownItems().wrappers.map((x) => x.text());
const findSectionHeaders = () => wrapper.findAllComponents(GlDropdownSectionHeader);
-
- beforeEach(() => {
- wrapper = createComponent();
- });
+ const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType);
+ const search = (term) => findSearchBox().vm.$emit('input', term);
afterEach(() => {
wrapper.destroy();
});
- it('renders the dropdown', () => {
- expect(findDropdown().exists()).toBe(true);
- });
+ describe('default', () => {
+ beforeEach(() => {
+ wrapper = createComponent();
+ });
- it('renders each dropdown item', () => {
- const items = findDropdownItems().wrappers;
- expect(items).toHaveLength(flatNamespaces().length);
- });
+ it('renders the dropdown', () => {
+ expect(findDropdown().exists()).toBe(true);
+ });
- it('renders the human name for each item', () => {
- const dropdownItems = wrappersText(findDropdownItems());
- const flatNames = flatNamespaces().map(({ humanName }) => humanName);
- expect(dropdownItems).toEqual(flatNames);
- });
+ it('renders each dropdown item', () => {
+ expect(findDropdownItemsTexts()).toEqual(FLAT_NAMESPACES.map((x) => x.humanName));
+ });
+
+ it('renders default dropdown text', () => {
+ expect(findDropdownText()).toBe(i18n.DEFAULT_TEXT);
+ });
+
+ it('splits group and user namespaces', () => {
+ const headers = findSectionHeaders();
+ expect(wrappersText(headers)).toEqual([i18n.GROUPS, i18n.USERS]);
+ });
- it('sets the initial dropdown text', () => {
- expect(selectedDropdownItemText()).toBe(i18n.DEFAULT_TEXT);
+ it('does not render wrapper as full width', () => {
+ expect(findDropdown().attributes('block')).toBeUndefined();
+ });
});
- it('splits group and user namespaces', () => {
- const headers = findSectionHeaders();
- expect(headers).toHaveLength(2);
- expect(wrappersText(headers)).toEqual([i18n.GROUPS, i18n.USERS]);
+ it('with defaultText, it overrides dropdown text', () => {
+ const textOverride = 'Select an option';
+
+ wrapper = createComponent({ defaultText: textOverride });
+
+ expect(findDropdownText()).toBe(textOverride);
});
- it('sets the dropdown to full width', () => {
- expect(findDropdownAttributes('block')).toBeUndefined();
+ it('with includeHeaders=false, hides group/user headers', () => {
+ wrapper = createComponent({ includeHeaders: false });
+
+ expect(findSectionHeaders()).toHaveLength(0);
+ });
+ it('with fullWidth=true, sets the dropdown to full width', () => {
wrapper = createComponent({ fullWidth: true });
- expect(findDropdownAttributes('block')).not.toBeUndefined();
- expect(findDropdownAttributes('block')).toBe('true');
+ expect(findDropdown().attributes('block')).toBe('true');
+ });
+
+ describe('with search', () => {
+ it.each`
+ term | includeEmptyNamespace | expectedItems
+ ${''} | ${false} | ${[...groupNamespaces, ...userNamespaces]}
+ ${'sub'} | ${false} | ${[groupNamespaces[1]]}
+ ${'User'} | ${false} | ${[...userNamespaces]}
+ ${'User'} | ${true} | ${[...userNamespaces]}
+ ${'namespace'} | ${true} | ${[EMPTY_NAMESPACE_ITEM, ...userNamespaces]}
+ `(
+ 'with term=$term and includeEmptyNamespace=$includeEmptyNamespace, should show $expectedItems.length',
+ async ({ term, includeEmptyNamespace, expectedItems }) => {
+ wrapper = createComponent({
+ includeEmptyNamespace,
+ emptyNamespaceTitle: EMPTY_NAMESPACE_TITLE,
+ });
+
+ search(term);
+
+ await nextTick();
+
+ const expected = expectedItems.map((x) => x.humanName);
+
+ expect(findDropdownItemsTexts()).toEqual(expected);
+ },
+ );
});
describe('with a selected namespace', () => {
const selectedGroupIndex = 1;
- const selectedItem = group[selectedGroupIndex];
+ const selectedItem = groupNamespaces[selectedGroupIndex];
beforeEach(() => {
+ wrapper = createComponent();
+
findDropdownItems().at(selectedGroupIndex).vm.$emit('click');
});
it('sets the dropdown text', () => {
- expect(selectedDropdownItemText()).toBe(selectedItem.humanName);
+ expect(findDropdownText()).toBe(selectedItem.humanName);
});
it('emits the `select` event when a namespace is selected', () => {
@@ -83,4 +133,37 @@ describe('Namespace Select', () => {
expect(wrapper.emitted('select')).toEqual([args]);
});
});
+
+ describe('with an empty namespace option', () => {
+ beforeEach(() => {
+ wrapper = createComponent({
+ includeEmptyNamespace: true,
+ emptyNamespaceTitle: EMPTY_NAMESPACE_TITLE,
+ });
+ });
+
+ it('includes the empty namespace', () => {
+ const first = findDropdownItems().at(0);
+
+ expect(first.text()).toBe(EMPTY_NAMESPACE_TITLE);
+ });
+
+ it('emits the `select` event when a namespace is selected', () => {
+ findDropdownItems().at(0).vm.$emit('click');
+
+ expect(wrapper.emitted('select')).toEqual([[EMPTY_NAMESPACE_ITEM]]);
+ });
+
+ it.each`
+ desc | term | shouldShow
+ ${'should hide empty option'} | ${'group'} | ${false}
+ ${'should show empty option'} | ${'Empty'} | ${true}
+ `('when search for $term, $desc', async ({ term, shouldShow }) => {
+ search(term);
+
+ await nextTick();
+
+ expect(findDropdownItemsTexts().includes(EMPTY_NAMESPACE_TITLE)).toBe(shouldShow);
+ });
+ });
});
diff --git a/spec/frontend/vue_shared/components/notes/noteable_warning_spec.js b/spec/frontend/vue_shared/components/notes/noteable_warning_spec.js
index 835759b1f20..accbf14572d 100644
--- a/spec/frontend/vue_shared/components/notes/noteable_warning_spec.js
+++ b/spec/frontend/vue_shared/components/notes/noteable_warning_spec.js
@@ -1,5 +1,6 @@
import { GlIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import NoteableWarning from '~/vue_shared/components/notes/noteable_warning.vue';
describe('Issue Warning Component', () => {
@@ -64,7 +65,7 @@ describe('Issue Warning Component', () => {
expect(findConfidentialBlock().exists()).toBe(true);
expect(findConfidentialBlock().element).toMatchSnapshot();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findConfidentialBlock(wrapper).text()).toContain('This is a confidential issue.');
});
@@ -154,15 +155,15 @@ describe('Issue Warning Component', () => {
noteableType: 'Epic',
});
- await wrapperLocked.vm.$nextTick();
+ await nextTick();
expect(findLockedBlock(wrapperLocked).text()).toContain('This epic is locked.');
- await wrapperConfidential.vm.$nextTick();
+ await nextTick();
expect(findConfidentialBlock(wrapperConfidential).text()).toContain(
'This is a confidential epic.',
);
- await wrapperLockedAndConfidential.vm.$nextTick();
+ await nextTick();
expect(findLockedAndConfidentialBlock(wrapperLockedAndConfidential).text()).toContain(
'This epic is confidential and locked.',
);
@@ -179,15 +180,15 @@ describe('Issue Warning Component', () => {
noteableType: 'MergeRequest',
});
- await wrapperLocked.vm.$nextTick();
+ await nextTick();
expect(findLockedBlock(wrapperLocked).text()).toContain('This merge request is locked.');
- await wrapperConfidential.vm.$nextTick();
+ await nextTick();
expect(findConfidentialBlock(wrapperConfidential).text()).toContain(
'This is a confidential merge request.',
);
- await wrapperLockedAndConfidential.vm.$nextTick();
+ await nextTick();
expect(findLockedAndConfidentialBlock(wrapperLockedAndConfidential).text()).toContain(
'This merge request is confidential and locked.',
);
diff --git a/spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs_spec.js b/spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs_spec.js
index b330b4f5657..36050a42da7 100644
--- a/spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs_spec.js
+++ b/spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs_spec.js
@@ -1,5 +1,6 @@
import { GlAlert, GlBadge, GlPagination, GlTabs, GlTab } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import Tracking from '~/tracking';
import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants';
import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
@@ -219,21 +220,21 @@ describe('AlertManagementEmptyState', () => {
it('returns prevPage button', async () => {
findPagination().vm.$emit('input', 3);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findPagination().findAll('.page-item').at(0).text()).toBe('Prev');
});
it('returns prevPage number', async () => {
findPagination().vm.$emit('input', 3);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.previousPage).toBe(2);
});
it('returns 0 when it is the first page', async () => {
findPagination().vm.$emit('input', 1);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.previousPage).toBe(0);
});
});
@@ -242,7 +243,7 @@ describe('AlertManagementEmptyState', () => {
it('returns nextPage button', async () => {
findPagination().vm.$emit('input', 3);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findPagination().findAll('.page-item').at(1).text()).toBe('Next');
});
@@ -257,14 +258,14 @@ describe('AlertManagementEmptyState', () => {
});
findPagination().vm.$emit('input', 1);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.nextPage).toBe(2);
});
it('returns `null` when currentPage is already last page', async () => {
findStatusTabs().vm.$emit('input', 1);
findPagination().vm.$emit('input', 1);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.nextPage).toBeNull();
});
});
@@ -319,7 +320,7 @@ describe('AlertManagementEmptyState', () => {
searchTerm,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.filteredSearchValue).toEqual([searchTerm]);
});
diff --git a/spec/frontend/vue_shared/components/pikaday_spec.js b/spec/frontend/vue_shared/components/pikaday_spec.js
deleted file mode 100644
index fed4ce5e696..00000000000
--- a/spec/frontend/vue_shared/components/pikaday_spec.js
+++ /dev/null
@@ -1,41 +0,0 @@
-import { GlDatepicker } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
-import datePicker from '~/vue_shared/components/pikaday.vue';
-
-describe('datePicker', () => {
- let wrapper;
-
- const buildWrapper = (propsData = {}) => {
- wrapper = shallowMount(datePicker, {
- propsData,
- });
- };
-
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
- it('should emit newDateSelected when GlDatePicker emits the input event', () => {
- const minDate = new Date();
- const maxDate = new Date();
- const selectedDate = new Date();
- const theDate = selectedDate.toISOString().slice(0, 10);
-
- buildWrapper({ minDate, maxDate, selectedDate });
-
- expect(wrapper.find(GlDatepicker).props()).toMatchObject({
- minDate,
- maxDate,
- value: selectedDate,
- });
- wrapper.find(GlDatepicker).vm.$emit('input', selectedDate);
- expect(wrapper.emitted('newDateSelected')[0][0]).toBe(theDate);
- });
- it('should emit the hidePicker event when GlDatePicker emits the close event', () => {
- buildWrapper();
-
- wrapper.find(GlDatepicker).vm.$emit('close');
-
- expect(wrapper.emitted('hidePicker')).toHaveLength(1);
- });
-});
diff --git a/spec/frontend/vue_shared/components/project_avatar/default_spec.js b/spec/frontend/vue_shared/components/project_avatar/default_spec.js
index 84dad2374cb..d042db6051c 100644
--- a/spec/frontend/vue_shared/components/project_avatar/default_spec.js
+++ b/spec/frontend/vue_shared/components/project_avatar/default_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import mountComponent from 'helpers/vue_mount_component_helper';
import { projectData } from 'jest/ide/mock_data';
import { TEST_HOST } from 'spec/test_constants';
@@ -19,7 +19,7 @@ describe('ProjectAvatarDefault component', () => {
vm.$destroy();
});
- it('renders identicon if project has no avatar_url', (done) => {
+ it('renders identicon if project has no avatar_url', async () => {
const expectedText = getFirstCharacterCapitalized(projectData.name);
vm.project = {
@@ -27,18 +27,14 @@ describe('ProjectAvatarDefault component', () => {
avatar_url: null,
};
- vm.$nextTick()
- .then(() => {
- const identiconEl = vm.$el.querySelector('.identicon');
+ await nextTick();
+ const identiconEl = vm.$el.querySelector('.identicon');
- expect(identiconEl).not.toBe(null);
- expect(identiconEl.textContent.trim()).toEqual(expectedText);
- })
- .then(done)
- .catch(done.fail);
+ expect(identiconEl).not.toBe(null);
+ expect(identiconEl.textContent.trim()).toEqual(expectedText);
});
- it('renders avatar image if project has avatar_url', (done) => {
+ it('renders avatar image if project has avatar_url', async () => {
const avatarUrl = `${TEST_HOST}/images/home/nasa.svg`;
vm.project = {
@@ -46,13 +42,9 @@ describe('ProjectAvatarDefault component', () => {
avatar_url: avatarUrl,
};
- vm.$nextTick()
- .then(() => {
- expect(vm.$el.querySelector('.avatar')).not.toBeNull();
- expect(vm.$el.querySelector('.identicon')).toBeNull();
- expect(vm.$el.querySelector('img')).toHaveAttr('src', avatarUrl);
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ expect(vm.$el.querySelector('.avatar')).not.toBeNull();
+ expect(vm.$el.querySelector('.identicon')).toBeNull();
+ expect(vm.$el.querySelector('img')).toHaveAttr('src', avatarUrl);
});
});
diff --git a/spec/frontend/vue_shared/components/project_selector/project_selector_spec.js b/spec/frontend/vue_shared/components/project_selector/project_selector_spec.js
index 34cee10392d..379e60c1b2d 100644
--- a/spec/frontend/vue_shared/components/project_selector/project_selector_spec.js
+++ b/spec/frontend/vue_shared/components/project_selector/project_selector_spec.js
@@ -1,7 +1,7 @@
import { GlSearchBoxByType, GlInfiniteScroll } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { head } from 'lodash';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import mockProjects from 'test_fixtures_static/projects.json';
import { trimText } from 'helpers/text_helper';
import ProjectListItem from '~/vue_shared/components/project_selector/project_list_item.vue';
@@ -77,39 +77,36 @@ describe('ProjectSelector component', () => {
expect(vm.$emit).toHaveBeenCalledWith('projectClicked', head(searchResults));
});
- it(`shows a "no results" message if showNoResultsMessage === true`, () => {
+ it(`shows a "no results" message if showNoResultsMessage === true`, async () => {
wrapper.setProps({ showNoResultsMessage: true });
- return vm.$nextTick().then(() => {
- const noResultsEl = wrapper.find('.js-no-results-message');
+ await nextTick();
+ const noResultsEl = wrapper.find('.js-no-results-message');
- expect(noResultsEl.exists()).toBe(true);
- expect(trimText(noResultsEl.text())).toEqual('Sorry, no projects matched your search');
- });
+ expect(noResultsEl.exists()).toBe(true);
+ expect(trimText(noResultsEl.text())).toEqual('Sorry, no projects matched your search');
});
- it(`shows a "minimum search query" message if showMinimumSearchQueryMessage === true`, () => {
+ it(`shows a "minimum search query" message if showMinimumSearchQueryMessage === true`, async () => {
wrapper.setProps({ showMinimumSearchQueryMessage: true });
- return vm.$nextTick().then(() => {
- const minimumSearchEl = wrapper.find('.js-minimum-search-query-message');
+ await nextTick();
+ const minimumSearchEl = wrapper.find('.js-minimum-search-query-message');
- expect(minimumSearchEl.exists()).toBe(true);
- expect(trimText(minimumSearchEl.text())).toEqual('Enter at least three characters to search');
- });
+ expect(minimumSearchEl.exists()).toBe(true);
+ expect(trimText(minimumSearchEl.text())).toEqual('Enter at least three characters to search');
});
- it(`shows a error message if showSearchErrorMessage === true`, () => {
+ it(`shows a error message if showSearchErrorMessage === true`, async () => {
wrapper.setProps({ showSearchErrorMessage: true });
- return vm.$nextTick().then(() => {
- const errorMessageEl = wrapper.find('.js-search-error-message');
+ await nextTick();
+ const errorMessageEl = wrapper.find('.js-search-error-message');
- expect(errorMessageEl.exists()).toBe(true);
- expect(trimText(errorMessageEl.text())).toEqual(
- 'Something went wrong, unable to search projects',
- );
- });
+ expect(errorMessageEl.exists()).toBe(true);
+ expect(trimText(errorMessageEl.text())).toEqual(
+ 'Something went wrong, unable to search projects',
+ );
});
describe('the search results legend', () => {
@@ -121,7 +118,7 @@ describe('ProjectSelector component', () => {
${2} | ${3} | ${'Showing 2 of 3 projects'}
`(
'is "$expected" given $count results are showing out of $total',
- ({ count, total, expected }) => {
+ async ({ count, total, expected }) => {
search('gitlab ui');
wrapper.setProps({
@@ -129,9 +126,8 @@ describe('ProjectSelector component', () => {
totalResults: total,
});
- return wrapper.vm.$nextTick().then(() => {
- expect(findLegendText()).toBe(expected);
- });
+ await nextTick();
+ expect(findLegendText()).toBe(expected);
},
);
diff --git a/spec/frontend/vue_shared/components/registry/list_item_spec.js b/spec/frontend/vue_shared/components/registry/list_item_spec.js
index ca4bf0b0652..1b93292e37b 100644
--- a/spec/frontend/vue_shared/components/registry/list_item_spec.js
+++ b/spec/frontend/vue_shared/components/registry/list_item_spec.js
@@ -1,5 +1,6 @@
import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import component from '~/vue_shared/components/registry/list_item.vue';
describe('list item', () => {
@@ -70,10 +71,10 @@ describe('list item', () => {
it('are visible when details is shown', async () => {
mountComponent({}, slotMocks);
- await wrapper.vm.$nextTick();
+ await nextTick();
findToggleDetailsButton().vm.$emit('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
slotNames.forEach((name) => {
expect(findDetailsSlot(name).exists()).toBe(true);
});
@@ -90,7 +91,7 @@ describe('list item', () => {
describe('details toggle button', () => {
it('is visible when at least one details slot exists', async () => {
mountComponent({}, { 'details-foo': '<span></span>' });
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findToggleDetailsButton().exists()).toBe(true);
});
diff --git a/spec/frontend/vue_shared/components/resizable_chart/resizable_chart_container_spec.js b/spec/frontend/vue_shared/components/resizable_chart/resizable_chart_container_spec.js
index 40f0c0f29f2..7536df24ac6 100644
--- a/spec/frontend/vue_shared/components/resizable_chart/resizable_chart_container_spec.js
+++ b/spec/frontend/vue_shared/components/resizable_chart/resizable_chart_container_spec.js
@@ -1,6 +1,6 @@
import { mount } from '@vue/test-utils';
import $ from 'jquery';
-import Vue from 'vue';
+import { nextTick } from 'vue';
import ResizableChartContainer from '~/vue_shared/components/resizable_chart/resizable_chart_container.vue';
jest.mock('~/lib/utils/common_utils', () => ({
@@ -35,7 +35,7 @@ describe('Resizable Chart Container', () => {
expect(wrapper.element).toMatchSnapshot();
});
- it('updates the slot width and height props', () => {
+ it('updates the slot width and height props', async () => {
const width = 1920;
const height = 1080;
@@ -44,13 +44,12 @@ describe('Resizable Chart Container', () => {
$(document).trigger('content.resize');
- return Vue.nextTick().then(() => {
- const widthNode = wrapper.find('.slot > .width');
- const heightNode = wrapper.find('.slot > .height');
+ await nextTick();
+ const widthNode = wrapper.find('.slot > .width');
+ const heightNode = wrapper.find('.slot > .height');
- expect(parseInt(widthNode.text(), 10)).toEqual(width);
- expect(parseInt(heightNode.text(), 10)).toEqual(height);
- });
+ expect(parseInt(widthNode.text(), 10)).toEqual(width);
+ expect(parseInt(heightNode.text(), 10)).toEqual(height);
});
it('calls onResize on manual resize', () => {
diff --git a/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_modal_spec.js b/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_modal_spec.js
index e74a867ec97..0da9939e97f 100644
--- a/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_modal_spec.js
+++ b/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_modal_spec.js
@@ -77,8 +77,7 @@ describe('RunnerInstructionsModal component', () => {
runnerSetupInstructionsHandler = jest.fn().mockResolvedValue(mockGraphqlInstructions);
createComponent();
-
- await nextTick();
+ await waitForPromises();
});
afterEach(() => {
@@ -113,13 +112,15 @@ describe('RunnerInstructionsModal component', () => {
});
});
- it('binary instructions are shown', () => {
+ it('binary instructions are shown', async () => {
+ await waitForPromises();
const instructions = findBinaryInstructions().text();
expect(instructions).toBe(installInstructions);
});
- it('register command is shown with a replaced token', () => {
+ it('register command is shown with a replaced token', async () => {
+ await waitForPromises();
const instructions = findRegisterCommand().text();
expect(instructions).toBe(
@@ -130,7 +131,7 @@ describe('RunnerInstructionsModal component', () => {
describe('when a register token is not shown', () => {
beforeEach(async () => {
createComponent({ props: { registrationToken: undefined } });
- await nextTick();
+ await waitForPromises();
});
it('register command is shown without a defined registration token', () => {
@@ -198,16 +199,17 @@ describe('RunnerInstructionsModal component', () => {
expect(findSkeletonLoader().exists()).toBe(true);
expect(findGlLoadingIcon().exists()).toBe(false);
- await nextTick(); // wait for platforms
+ await nextTick();
+ jest.runOnlyPendingTimers();
+ await nextTick();
+ await nextTick();
expect(findGlLoadingIcon().exists()).toBe(true);
});
it('once loaded, should not show a loading state', async () => {
createComponent();
-
- await nextTick(); // wait for platforms
- await nextTick(); // wait for architectures
+ await waitForPromises();
expect(findSkeletonLoader().exists()).toBe(false);
expect(findGlLoadingIcon().exists()).toBe(false);
diff --git a/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_spec.js b/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_spec.js
index 23f8d6afcb5..9a95a838291 100644
--- a/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_spec.js
+++ b/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_spec.js
@@ -22,9 +22,9 @@ describe('RunnerInstructions component', () => {
wrapper.destroy();
});
- it('should show the "Show Runner installation instructions" button', () => {
+ it('should show the "Show runner installation instructions" button', () => {
expect(findModalButton().exists()).toBe(true);
- expect(findModalButton().text()).toBe('Show Runner installation instructions');
+ expect(findModalButton().text()).toBe('Show runner installation instructions');
});
it('should not render the modal once mounted', () => {
diff --git a/spec/frontend/vue_shared/components/sidebar/collapsed_calendar_icon_spec.js b/spec/frontend/vue_shared/components/sidebar/collapsed_calendar_icon_spec.js
deleted file mode 100644
index 79e41ed0c9e..00000000000
--- a/spec/frontend/vue_shared/components/sidebar/collapsed_calendar_icon_spec.js
+++ /dev/null
@@ -1,68 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import { GlIcon } from '@gitlab/ui';
-import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
-
-import CollapsedCalendarIcon from '~/vue_shared/components/sidebar/collapsed_calendar_icon.vue';
-
-describe('CollapsedCalendarIcon', () => {
- let wrapper;
-
- const defaultProps = {
- containerClass: 'test-class',
- text: 'text',
- tooltipText: 'tooltip text',
- showIcon: false,
- };
-
- const createComponent = ({ props = {} } = {}) => {
- wrapper = shallowMount(CollapsedCalendarIcon, {
- propsData: { ...defaultProps, ...props },
- directives: {
- GlTooltip: createMockDirective(),
- },
- });
- };
-
- beforeEach(() => {
- createComponent();
- });
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- const findGlIcon = () => wrapper.findComponent(GlIcon);
- const getTooltip = () => getBinding(wrapper.element, 'gl-tooltip');
-
- it('adds class to container', () => {
- expect(wrapper.classes()).toContain(defaultProps.containerClass);
- });
-
- it('does not render calendar icon when showIcon is false', () => {
- expect(findGlIcon().exists()).toBe(false);
- });
-
- it('renders calendar icon when showIcon is true', () => {
- createComponent({
- props: { showIcon: true },
- });
-
- expect(findGlIcon().exists()).toBe(true);
- });
-
- it('renders text', () => {
- expect(wrapper.text()).toBe(defaultProps.text);
- });
-
- it('renders tooltipText as tooltip', () => {
- expect(getTooltip().value).toBe(defaultProps.tooltipText);
- });
-
- it('emits click event when container is clicked', async () => {
- wrapper.trigger('click');
-
- await wrapper.vm.$nextTick();
-
- expect(wrapper.emitted('click')[0]).toBeDefined();
- });
-});
diff --git a/spec/frontend/vue_shared/components/sidebar/date_picker_spec.js b/spec/frontend/vue_shared/components/sidebar/date_picker_spec.js
deleted file mode 100644
index 263d1e9d947..00000000000
--- a/spec/frontend/vue_shared/components/sidebar/date_picker_spec.js
+++ /dev/null
@@ -1,125 +0,0 @@
-import { GlLoadingIcon } from '@gitlab/ui';
-import { mount } from '@vue/test-utils';
-import DatePicker from '~/vue_shared/components/pikaday.vue';
-import SidebarDatePicker from '~/vue_shared/components/sidebar/date_picker.vue';
-
-describe('SidebarDatePicker', () => {
- let wrapper;
-
- const createComponent = (propsData = {}, data = {}) => {
- wrapper = mount(SidebarDatePicker, {
- propsData,
- data: () => data,
- });
- };
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- const findDatePicker = () => wrapper.findComponent(DatePicker);
- const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
- const findEditButton = () => wrapper.find('.title .btn-blank');
- const findRemoveButton = () => wrapper.find('.value-content .btn-blank');
- const findSidebarToggle = () => wrapper.find('.title .gutter-toggle');
- const findValueContent = () => wrapper.find('.value-content');
-
- it('should emit toggleCollapse when collapsed toggle sidebar is clicked', () => {
- createComponent();
-
- wrapper.find('.issuable-sidebar-header .gutter-toggle').trigger('click');
-
- expect(wrapper.emitted('toggleCollapse')).toEqual([[]]);
- });
-
- it('should render collapsed-calendar-icon', () => {
- createComponent();
-
- expect(wrapper.find('.sidebar-collapsed-icon').exists()).toBe(true);
- });
-
- it('should render value when not editing', () => {
- createComponent();
-
- expect(findValueContent().exists()).toBe(true);
- });
-
- it('should render None if there is no selectedDate', () => {
- createComponent();
-
- expect(findValueContent().text()).toBe('None');
- });
-
- it('should render date-picker when editing', () => {
- createComponent({}, { editing: true });
-
- expect(findDatePicker().exists()).toBe(true);
- });
-
- it('should render label', () => {
- const label = 'label';
- createComponent({ label });
- expect(wrapper.find('.title').text()).toBe(label);
- });
-
- it('should render loading-icon when isLoading', () => {
- createComponent({ isLoading: true });
- expect(findLoadingIcon().exists()).toBe(true);
- });
-
- describe('editable', () => {
- beforeEach(() => {
- createComponent({ editable: true });
- });
-
- it('should render edit button', () => {
- expect(findEditButton().text()).toBe('Edit');
- });
-
- it('should enable editing when edit button is clicked', async () => {
- findEditButton().trigger('click');
-
- await wrapper.vm.$nextTick();
-
- expect(wrapper.vm.editing).toBe(true);
- });
- });
-
- it('should render date if selectedDate', () => {
- createComponent({ selectedDate: new Date('07/07/2017') });
-
- expect(wrapper.find('.value-content strong').text()).toBe('Jul 7, 2017');
- });
-
- describe('selectedDate and editable', () => {
- beforeEach(() => {
- createComponent({ selectedDate: new Date('07/07/2017'), editable: true });
- });
-
- it('should render remove button if selectedDate and editable', () => {
- expect(findRemoveButton().text()).toBe('remove');
- });
-
- it('should emit saveDate with null when remove button is clicked', () => {
- findRemoveButton().trigger('click');
-
- expect(wrapper.emitted('saveDate')).toEqual([[null]]);
- });
- });
-
- describe('showToggleSidebar', () => {
- beforeEach(() => {
- createComponent({ showToggleSidebar: true });
- });
-
- it('should render toggle-sidebar when showToggleSidebar', () => {
- expect(findSidebarToggle().exists()).toBe(true);
- });
-
- it('should emit toggleCollapse when toggle sidebar is clicked', () => {
- findSidebarToggle().trigger('click');
-
- expect(wrapper.emitted('toggleCollapse')).toEqual([[]]);
- });
- });
-});
diff --git a/spec/frontend/vue_shared/components/sidebar/issuable_move_dropdown_spec.js b/spec/frontend/vue_shared/components/sidebar/issuable_move_dropdown_spec.js
index 5336ecc614c..f213e37cbc1 100644
--- a/spec/frontend/vue_shared/components/sidebar/issuable_move_dropdown_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/issuable_move_dropdown_spec.js
@@ -10,6 +10,7 @@ import {
import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
+import { nextTick } from 'vue';
import axios from '~/lib/utils/axios_utils';
import IssuableMoveDropdown from '~/vue_shared/components/sidebar/issuable_move_dropdown.vue';
@@ -74,7 +75,7 @@ describe('IssuableMoveDropdown', () => {
searchKey: 'foo',
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.fetchProjects).toHaveBeenCalledWith('foo');
});
@@ -151,7 +152,7 @@ describe('IssuableMoveDropdown', () => {
selectedProject,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.isSelectedProject(project)).toBe(returnValue);
},
@@ -164,7 +165,7 @@ describe('IssuableMoveDropdown', () => {
selectedProject: null,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.isSelectedProject(mockProjects[0])).toBe(false);
});
@@ -218,7 +219,7 @@ describe('IssuableMoveDropdown', () => {
projectsListLoading: true,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findDropdownEl().find(GlLoadingIcon).exists()).toBe(true);
});
@@ -231,7 +232,7 @@ describe('IssuableMoveDropdown', () => {
selectedProject: mockProjects[0],
});
- await wrapper.vm.$nextTick();
+ await nextTick();
const dropdownItems = wrapper.findAll(GlDropdownItem);
@@ -251,7 +252,7 @@ describe('IssuableMoveDropdown', () => {
});
// Wait for `searchKey` watcher to run.
- await wrapper.vm.$nextTick();
+ await nextTick();
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
@@ -260,7 +261,7 @@ describe('IssuableMoveDropdown', () => {
projectsListLoading: false,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
const dropdownContentEl = wrapper.find('[data-testid="content"]');
@@ -276,7 +277,7 @@ describe('IssuableMoveDropdown', () => {
projectsListLoadFailed: true,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
const dropdownContentEl = wrapper.find('[data-testid="content"]');
@@ -295,7 +296,7 @@ describe('IssuableMoveDropdown', () => {
selectedProject: mockProjects[0],
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(
wrapper.find('[data-testid="footer"]').find(GlButton).attributes('disabled'),
@@ -352,7 +353,7 @@ describe('IssuableMoveDropdown', () => {
projects: mockProjects,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
wrapper.findAll(GlDropdownItem).at(0).vm.$emit('click', mockEvent);
@@ -366,7 +367,7 @@ describe('IssuableMoveDropdown', () => {
selectedProject: mockProjects[0],
});
- await wrapper.vm.$nextTick();
+ await nextTick();
wrapper.find('[data-testid="footer"]').find(GlButton).vm.$emit('click');
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_button_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_button_spec.js
index c4ed975e746..c05513a6d5f 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_button_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_button_spec.js
@@ -1,6 +1,6 @@
import { GlIcon, GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import DropdownButton from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_button.vue';
@@ -71,13 +71,12 @@ describe('DropdownButton', () => {
expect(dropdownTextEl.text()).toBe('Label');
});
- it('renders provided button text element', () => {
+ it('renders provided button text element', async () => {
store.state.dropdownButtonText = 'Custom label';
const dropdownTextEl = findDropdownText();
- return wrapper.vm.$nextTick().then(() => {
- expect(dropdownTextEl.text()).toBe('Custom label');
- });
+ await nextTick();
+ expect(dropdownTextEl.text()).toBe('Custom label');
});
it('renders chevron icon element', () => {
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view_spec.js
index 0eff6a1dace..0673ffee22b 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view_spec.js
@@ -1,6 +1,6 @@
import { GlButton, GlFormInput, GlLink, GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import DropdownContentsCreateView from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view.vue';
@@ -42,7 +42,7 @@ describe('DropdownContentsCreateView', () => {
expect(wrapper.vm.disableCreate).toBe(true);
});
- it('returns `true` when `labelCreateInProgress` is true', () => {
+ it('returns `true` when `labelCreateInProgress` is true', async () => {
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({
@@ -51,12 +51,11 @@ describe('DropdownContentsCreateView', () => {
});
wrapper.vm.$store.dispatch('requestCreateLabel');
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.vm.disableCreate).toBe(true);
- });
+ await nextTick();
+ expect(wrapper.vm.disableCreate).toBe(true);
});
- it('returns `false` when label title and color is defined and create request is not already in progress', () => {
+ it('returns `false` when label title and color is defined and create request is not already in progress', async () => {
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({
@@ -64,9 +63,8 @@ describe('DropdownContentsCreateView', () => {
selectedColor: '#ff0000',
});
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.vm.disableCreate).toBe(false);
- });
+ await nextTick();
+ expect(wrapper.vm.disableCreate).toBe(false);
});
});
@@ -101,7 +99,7 @@ describe('DropdownContentsCreateView', () => {
});
describe('handleCreateClick', () => {
- it('calls action `createLabel` with object containing `labelTitle` & `selectedColor`', () => {
+ it('calls action `createLabel` with object containing `labelTitle` & `selectedColor`', async () => {
jest.spyOn(wrapper.vm, 'createLabel').mockImplementation();
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
@@ -112,14 +110,13 @@ describe('DropdownContentsCreateView', () => {
wrapper.vm.handleCreateClick();
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.vm.createLabel).toHaveBeenCalledWith(
- expect.objectContaining({
- title: 'Foo',
- color: '#ff0000',
- }),
- );
- });
+ await nextTick();
+ expect(wrapper.vm.createLabel).toHaveBeenCalledWith(
+ expect.objectContaining({
+ title: 'Foo',
+ color: '#ff0000',
+ }),
+ );
});
});
});
@@ -169,25 +166,22 @@ describe('DropdownContentsCreateView', () => {
});
});
- it('renders color input element', () => {
+ it('renders color input element', async () => {
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({
selectedColor: '#ff0000',
});
- return wrapper.vm.$nextTick(() => {
- const colorPreviewEl = wrapper.find(
- '.color-input-container > .dropdown-label-color-preview',
- );
- const colorInputEl = wrapper.find('.color-input-container').find(GlFormInput);
+ await nextTick();
+ const colorPreviewEl = wrapper.find('.color-input-container > .dropdown-label-color-preview');
+ const colorInputEl = wrapper.find('.color-input-container').find(GlFormInput);
- expect(colorPreviewEl.exists()).toBe(true);
- expect(colorPreviewEl.attributes('style')).toContain('background-color');
- expect(colorInputEl.exists()).toBe(true);
- expect(colorInputEl.attributes('placeholder')).toBe('Use custom color #FF0000');
- expect(colorInputEl.attributes('value')).toBe('#ff0000');
- });
+ expect(colorPreviewEl.exists()).toBe(true);
+ expect(colorPreviewEl.attributes('style')).toContain('background-color');
+ expect(colorInputEl.exists()).toBe(true);
+ expect(colorInputEl.attributes('placeholder')).toBe('Use custom color #FF0000');
+ expect(colorInputEl.attributes('value')).toBe('#ff0000');
});
it('renders create button element', () => {
@@ -197,15 +191,14 @@ describe('DropdownContentsCreateView', () => {
expect(createBtnEl.text()).toContain('Create');
});
- it('shows gl-loading-icon within create button element when `labelCreateInProgress` is `true`', () => {
+ it('shows gl-loading-icon within create button element when `labelCreateInProgress` is `true`', async () => {
wrapper.vm.$store.dispatch('requestCreateLabel');
- return wrapper.vm.$nextTick(() => {
- const loadingIconEl = wrapper.find('.dropdown-actions').find(GlLoadingIcon);
+ await nextTick();
+ const loadingIconEl = wrapper.find('.dropdown-actions').find(GlLoadingIcon);
- expect(loadingIconEl.exists()).toBe(true);
- expect(loadingIconEl.isVisible()).toBe(true);
- });
+ expect(loadingIconEl.exists()).toBe(true);
+ expect(loadingIconEl.isVisible()).toBe(true);
});
it('renders cancel button element', () => {
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js
index 93a0e2f75bb..42202db4935 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js
@@ -6,7 +6,7 @@ import {
GlLink,
} from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import { UP_KEY_CODE, DOWN_KEY_CODE, ENTER_KEY_CODE, ESC_KEY_CODE } from '~/lib/utils/keycodes';
import DropdownContentsLabelsView from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue';
@@ -114,7 +114,7 @@ describe('DropdownContentsLabelsView', () => {
wrapper.vm.$store.dispatch('receiveLabelsSuccess', labels);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.showNoMatchingResultsMessage).toBe(returnValue);
},
@@ -249,7 +249,7 @@ describe('DropdownContentsLabelsView', () => {
expect(wrapper.vm.toggleDropdownContents).toHaveBeenCalled();
});
- it('calls action `scrollIntoViewIfNeeded` in next tick when any key is pressed', () => {
+ it('calls action `scrollIntoViewIfNeeded` in next tick when any key is pressed', async () => {
jest.spyOn(wrapper.vm, 'scrollIntoViewIfNeeded').mockImplementation();
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
@@ -261,9 +261,8 @@ describe('DropdownContentsLabelsView', () => {
keyCode: DOWN_KEY_CODE,
});
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.vm.scrollIntoViewIfNeeded).toHaveBeenCalled();
- });
+ await nextTick();
+ expect(wrapper.vm.scrollIntoViewIfNeeded).toHaveBeenCalled();
});
});
@@ -294,15 +293,14 @@ describe('DropdownContentsLabelsView', () => {
expect(wrapper.find(GlIntersectionObserver).exists()).toBe(true);
});
- it('renders gl-loading-icon component when `labelsFetchInProgress` prop is true', () => {
+ it('renders gl-loading-icon component when `labelsFetchInProgress` prop is true', async () => {
wrapper.vm.$store.dispatch('requestLabels');
- return wrapper.vm.$nextTick(() => {
- const loadingIconEl = findLoadingIcon();
+ await nextTick();
+ const loadingIconEl = findLoadingIcon();
- expect(loadingIconEl.exists()).toBe(true);
- expect(loadingIconEl.attributes('class')).toContain('labels-fetch-loading');
- });
+ expect(loadingIconEl.exists()).toBe(true);
+ expect(loadingIconEl.attributes('class')).toContain('labels-fetch-loading');
});
it('renders dropdown title element', () => {
@@ -339,47 +337,44 @@ describe('DropdownContentsLabelsView', () => {
expect(wrapper.findAll(LabelItem)).toHaveLength(mockLabels.length);
});
- it('renders label element with `highlight` set to true when value of `currentHighlightItem` is more than -1', () => {
+ it('renders label element with `highlight` set to true when value of `currentHighlightItem` is more than -1', async () => {
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({
currentHighlightItem: 0,
});
- return wrapper.vm.$nextTick(() => {
- const labelItemEl = findDropdownContent().find(LabelItem);
+ await nextTick();
+ const labelItemEl = findDropdownContent().find(LabelItem);
- expect(labelItemEl.attributes('highlight')).toBe('true');
- });
+ expect(labelItemEl.attributes('highlight')).toBe('true');
});
- it('renders element containing "No matching results" when `searchKey` does not match with any label', () => {
+ it('renders element containing "No matching results" when `searchKey` does not match with any label', async () => {
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({
searchKey: 'abc',
});
- return wrapper.vm.$nextTick(() => {
- const noMatchEl = findDropdownContent().find('li');
+ await nextTick();
+ const noMatchEl = findDropdownContent().find('li');
- expect(noMatchEl.isVisible()).toBe(true);
- expect(noMatchEl.text()).toContain('No matching results');
- });
+ expect(noMatchEl.isVisible()).toBe(true);
+ expect(noMatchEl.text()).toContain('No matching results');
});
- it('renders empty content while loading', () => {
+ it('renders empty content while loading', async () => {
wrapper.vm.$store.state.labelsFetchInProgress = true;
- return wrapper.vm.$nextTick(() => {
- const dropdownContent = findDropdownContent();
- const loadingIcon = findLoadingIcon();
+ await nextTick();
+ const dropdownContent = findDropdownContent();
+ const loadingIcon = findLoadingIcon();
- expect(dropdownContent.exists()).toBe(true);
- expect(dropdownContent.isVisible()).toBe(true);
- expect(loadingIcon.exists()).toBe(true);
- expect(loadingIcon.isVisible()).toBe(true);
- });
+ expect(dropdownContent.exists()).toBe(true);
+ expect(dropdownContent.isVisible()).toBe(true);
+ expect(loadingIcon.exists()).toBe(true);
+ expect(loadingIcon.isVisible()).toBe(true);
});
it('renders footer list items', () => {
@@ -393,14 +388,13 @@ describe('DropdownContentsLabelsView', () => {
expect(manageLabelsLink.text()).toBe('Manage labels');
});
- it('does not render "Create label" footer link when `state.allowLabelCreate` is `false`', () => {
+ it('does not render "Create label" footer link when `state.allowLabelCreate` is `false`', async () => {
wrapper.vm.$store.state.allowLabelCreate = false;
- return wrapper.vm.$nextTick(() => {
- const createLabelLink = findDropdownFooter().findAll(GlLink).at(0);
+ await nextTick();
+ const createLabelLink = findDropdownFooter().findAll(GlLink).at(0);
- expect(createLabelLink.text()).not.toBe('Create label');
- });
+ expect(createLabelLink.text()).not.toBe('Create label');
});
it('does not render footer list items when `state.variant` is "standalone"', () => {
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_title_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_title_spec.js
index 110c1d1b7eb..84e9f3f41c3 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_title_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_title_spec.js
@@ -1,6 +1,6 @@
import { GlButton, GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import DropdownTitle from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_title.vue';
@@ -47,14 +47,13 @@ describe('DropdownTitle', () => {
expect(editBtnEl.text()).toBe('Edit');
});
- it('renders loading icon element when `labelsSelectInProgress` prop is true', () => {
+ it('renders loading icon element when `labelsSelectInProgress` prop is true', async () => {
wrapper.setProps({
labelsSelectInProgress: true,
});
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.find(GlLoadingIcon).isVisible()).toBe(true);
- });
+ await nextTick();
+ expect(wrapper.find(GlLoadingIcon).isVisible()).toBe(true);
});
});
});
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_value_collapsed_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_value_collapsed_spec.js
index a7f9391cb5f..c6400320dea 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_value_collapsed_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_value_collapsed_spec.js
@@ -1,5 +1,6 @@
import { shallowMount } from '@vue/test-utils';
import { GlIcon } from '@gitlab/ui';
+import { nextTick } from 'vue';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import DropdownValueCollapsedComponent from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_value_collapsed.vue';
@@ -42,7 +43,7 @@ describe('DropdownValueCollapsedComponent', () => {
wrapper.trigger('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.emitted('onValueClick')[0]).toBeDefined();
});
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js
index 4b0ba075eda..31819d0e2f7 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js
@@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import { isInViewport } from '~/lib/utils/common_utils';
@@ -139,27 +139,26 @@ describe('LabelsSelectRoot', () => {
${'embedded'} | ${'is-embedded'}
`(
'renders component root element with CSS class `$cssClass` when `state.variant` is "$variant"',
- ({ variant, cssClass }) => {
+ async ({ variant, cssClass }) => {
createComponent({
...mockConfig,
variant,
});
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.classes()).toContain(cssClass);
- });
+ await nextTick();
+ expect(wrapper.classes()).toContain(cssClass);
},
);
it('renders `dropdown-value-collapsed` component when `allowLabelCreate` prop is `true`', async () => {
createComponent();
- await wrapper.vm.$nextTick;
+ await nextTick;
expect(wrapper.find(DropdownValueCollapsed).exists()).toBe(true);
});
it('renders `dropdown-title` component', async () => {
createComponent();
- await wrapper.vm.$nextTick;
+ await nextTick;
expect(wrapper.find(DropdownTitle).exists()).toBe(true);
});
@@ -167,7 +166,7 @@ describe('LabelsSelectRoot', () => {
createComponent(mockConfig, {
default: 'None',
});
- await wrapper.vm.$nextTick;
+ await nextTick;
const valueComp = wrapper.find(DropdownValue);
@@ -178,14 +177,14 @@ describe('LabelsSelectRoot', () => {
it('renders `dropdown-button` component when `showDropdownButton` prop is `true`', async () => {
createComponent();
wrapper.vm.$store.dispatch('toggleDropdownButton');
- await wrapper.vm.$nextTick;
+ await nextTick;
expect(wrapper.find(DropdownButton).exists()).toBe(true);
});
it('renders `dropdown-contents` component when `showDropdownButton` & `showDropdownContents` prop is `true`', async () => {
createComponent();
wrapper.vm.$store.dispatch('toggleDropdownContents');
- await wrapper.vm.$nextTick;
+ await nextTick;
expect(wrapper.find(DropdownContents).exists()).toBe(true);
});
@@ -198,22 +197,20 @@ describe('LabelsSelectRoot', () => {
wrapper.vm.$store.dispatch('toggleDropdownContents');
});
- it('set direction when out of viewport', () => {
+ it('set direction when out of viewport', async () => {
isInViewport.mockImplementation(() => false);
wrapper.vm.setContentIsOnViewport(wrapper.vm.$store.state);
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(DropdownContents).props('renderOnTop')).toBe(true);
- });
+ await nextTick();
+ expect(wrapper.find(DropdownContents).props('renderOnTop')).toBe(true);
});
- it('does not set direction when inside of viewport', () => {
+ it('does not set direction when inside of viewport', async () => {
isInViewport.mockImplementation(() => true);
wrapper.vm.setContentIsOnViewport(wrapper.vm.$store.state);
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(DropdownContents).props('renderOnTop')).toBe(false);
- });
+ await nextTick();
+ expect(wrapper.find(DropdownContents).props('renderOnTop')).toBe(false);
});
},
);
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/labels_select_root_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/labels_select_root_spec.js
index a4199bb3e27..67e1a3ce932 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/labels_select_root_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/labels_select_root_spec.js
@@ -117,9 +117,15 @@ describe('LabelsSelectRoot', () => {
it('renders dropdown value component when query labels is resolved', () => {
expect(findDropdownValue().exists()).toBe(true);
- expect(findDropdownValue().props('selectedLabels')).toEqual(
- issuableLabelsQueryResponse.data.workspace.issuable.labels.nodes,
- );
+ expect(findDropdownValue().props('selectedLabels')).toEqual([
+ {
+ color: '#330066',
+ description: null,
+ id: 'gid://gitlab/ProjectLabel/1',
+ title: 'Label1',
+ textColor: '#000000',
+ },
+ ]);
});
it('emits `onLabelRemove` event on dropdown value label remove event', () => {
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/mock_data.js b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/mock_data.js
index 6ef54ce37ce..49224fb915c 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/mock_data.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/mock_data.js
@@ -96,6 +96,7 @@ export const workspaceLabelsQueryResponse = {
labels: {
nodes: [
{
+ __typename: 'Label',
color: '#330066',
description: null,
id: 'gid://gitlab/ProjectLabel/1',
@@ -103,6 +104,7 @@ export const workspaceLabelsQueryResponse = {
textColor: '#000000',
},
{
+ __typename: 'Label',
color: '#2f7b2e',
description: null,
id: 'gid://gitlab/ProjectLabel/2',
@@ -125,6 +127,7 @@ export const issuableLabelsQueryResponse = {
labels: {
nodes: [
{
+ __typename: 'Label',
color: '#330066',
description: null,
id: 'gid://gitlab/ProjectLabel/1',
diff --git a/spec/frontend/vue_shared/components/sidebar/toggle_sidebar_spec.js b/spec/frontend/vue_shared/components/sidebar/toggle_sidebar_spec.js
index a6c9bda1aa2..267a467059d 100644
--- a/spec/frontend/vue_shared/components/sidebar/toggle_sidebar_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/toggle_sidebar_spec.js
@@ -1,6 +1,7 @@
import { GlButton } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import ToggleSidebar from '~/vue_shared/components/sidebar/toggle_sidebar.vue';
describe('ToggleSidebar', () => {
@@ -38,7 +39,7 @@ describe('ToggleSidebar', () => {
createComponent({ mountFn: mount });
findGlButton().trigger('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.emitted('toggle')[0]).toBeDefined();
});
diff --git a/spec/frontend/vue_shared/components/source_viewer_spec.js b/spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js
index 094d8d42a47..2010bac7060 100644
--- a/spec/frontend/vue_shared/components/source_viewer_spec.js
+++ b/spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js
@@ -1,8 +1,10 @@
import hljs from 'highlight.js/lib/core';
+import { GlLoadingIcon } from '@gitlab/ui';
import Vue, { nextTick } from 'vue';
import VueRouter from 'vue-router';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import SourceViewer from '~/vue_shared/components/source_viewer.vue';
+import SourceViewer from '~/vue_shared/components/source_viewer/source_viewer.vue';
+import { ROUGE_TO_HLJS_LANGUAGE_MAP } from '~/vue_shared/components/source_viewer/constants';
import LineNumbers from '~/vue_shared/components/line_numbers.vue';
import waitForPromises from 'helpers/wait_for_promises';
@@ -12,42 +14,50 @@ const router = new VueRouter();
describe('Source Viewer component', () => {
let wrapper;
+ const language = 'docker';
+ const mappedLanguage = ROUGE_TO_HLJS_LANGUAGE_MAP[language];
const content = `// Some source code`;
+ const DEFAULT_BLOB_DATA = { language, rawTextBlob: content };
const highlightedContent = `<span data-testid='test-highlighted' id='LC1'>${content}</span><span id='LC2'></span>`;
- const language = 'javascript';
- hljs.highlight.mockImplementation(() => ({ value: highlightedContent }));
- hljs.highlightAuto.mockImplementation(() => ({ value: highlightedContent }));
-
- const createComponent = async (props = {}) => {
+ const createComponent = async (blob = {}) => {
wrapper = shallowMountExtended(SourceViewer, {
router,
- propsData: { content, language, ...props },
+ propsData: { blob: { ...DEFAULT_BLOB_DATA, ...blob } },
});
await waitForPromises();
};
+ const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findLineNumbers = () => wrapper.findComponent(LineNumbers);
const findHighlightedContent = () => wrapper.findByTestId('test-highlighted');
const findFirstLine = () => wrapper.find('#LC1');
- beforeEach(() => createComponent());
+ beforeEach(() => {
+ hljs.highlight.mockImplementation(() => ({ value: highlightedContent }));
+ hljs.highlightAuto.mockImplementation(() => ({ value: highlightedContent }));
+
+ return createComponent();
+ });
afterEach(() => wrapper.destroy());
describe('highlight.js', () => {
it('registers the language definition', async () => {
- const languageDefinition = await import(`highlight.js/lib/languages/${language}`);
+ const languageDefinition = await import(`highlight.js/lib/languages/${mappedLanguage}`);
- expect(hljs.registerLanguage).toHaveBeenCalledWith(language, languageDefinition.default);
+ expect(hljs.registerLanguage).toHaveBeenCalledWith(
+ mappedLanguage,
+ languageDefinition.default,
+ );
});
it('highlights the content', () => {
- expect(hljs.highlight).toHaveBeenCalledWith(content, { language });
+ expect(hljs.highlight).toHaveBeenCalledWith(content, { language: mappedLanguage });
});
- describe('auto-detect enabled', () => {
- beforeEach(() => createComponent({ autoDetect: true }));
+ describe('auto-detects if a language cannot be loaded', () => {
+ beforeEach(() => createComponent({ language: 'some_unknown_language' }));
it('highlights the content with auto-detection', () => {
expect(hljs.highlightAuto).toHaveBeenCalledWith(content);
@@ -56,6 +66,13 @@ describe('Source Viewer component', () => {
});
describe('rendering', () => {
+ it('renders a loading icon if no highlighted content is available yet', async () => {
+ hljs.highlight.mockImplementation(() => ({ value: null }));
+ await createComponent();
+
+ expect(findLoadingIcon().exists()).toBe(true);
+ });
+
it('renders Line Numbers', () => {
expect(findLineNumbers().props('lines')).toBe(1);
});
diff --git a/spec/frontend/vue_shared/components/source_viewer/utils_spec.js b/spec/frontend/vue_shared/components/source_viewer/utils_spec.js
new file mode 100644
index 00000000000..937c3b26c67
--- /dev/null
+++ b/spec/frontend/vue_shared/components/source_viewer/utils_spec.js
@@ -0,0 +1,13 @@
+import { wrapLines } from '~/vue_shared/components/source_viewer/utils';
+
+describe('Wrap lines', () => {
+ it.each`
+ input | output
+ ${'line 1'} | ${'<span id="LC1" class="line">line 1</span>'}
+ ${'line 1\nline 2'} | ${`<span id="LC1" class="line">line 1</span>\n<span id="LC2" class="line">line 2</span>`}
+ ${'<span class="hljs-code">line 1\nline 2</span>'} | ${`<span id="LC1" class="hljs-code">line 1\n<span id="LC2" class="line">line 2</span></span>`}
+ ${'<span class="hljs-code">```bash'} | ${'<span id="LC1" class="hljs-code">```bash'}
+ `('returns lines wrapped in spans containing line numbers', ({ input, output }) => {
+ expect(wrapLines(input)).toBe(output);
+ });
+});
diff --git a/spec/frontend/vue_shared/components/split_button_spec.js b/spec/frontend/vue_shared/components/split_button_spec.js
index ad11e6519c4..4965969bc3e 100644
--- a/spec/frontend/vue_shared/components/split_button_spec.js
+++ b/spec/frontend/vue_shared/components/split_button_spec.js
@@ -1,6 +1,7 @@
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import SplitButton from '~/vue_shared/components/split_button.vue';
const mockActionItems = [
@@ -27,15 +28,15 @@ describe('SplitButton', () => {
const findDropdown = () => wrapper.find(GlDropdown);
const findDropdownItem = (index = 0) => findDropdown().findAll(GlDropdownItem).at(index);
- const selectItem = (index) => {
+ const selectItem = async (index) => {
findDropdownItem(index).vm.$emit('click');
- return wrapper.vm.$nextTick();
+ await nextTick();
};
- const clickToggleButton = () => {
+ const clickToggleButton = async () => {
findDropdown().vm.$emit('click');
- return wrapper.vm.$nextTick();
+ await nextTick();
};
it('fails for empty actionItems', () => {
diff --git a/spec/frontend/vue_shared/components/upload_dropzone/__snapshots__/upload_dropzone_spec.js.snap b/spec/frontend/vue_shared/components/upload_dropzone/__snapshots__/upload_dropzone_spec.js.snap
index 0f1e118d44c..a613b325462 100644
--- a/spec/frontend/vue_shared/components/upload_dropzone/__snapshots__/upload_dropzone_spec.js.snap
+++ b/spec/frontend/vue_shared/components/upload_dropzone/__snapshots__/upload_dropzone_spec.js.snap
@@ -6,6 +6,7 @@ exports[`Upload dropzone component correctly overrides description and drop mess
>
<button
class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-3"
+ type="button"
>
<div
class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center gl-flex-direction-column"
@@ -86,6 +87,7 @@ exports[`Upload dropzone component when dragging renders correct template when d
>
<button
class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-3"
+ type="button"
>
<div
class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center gl-flex-direction-column"
@@ -170,6 +172,7 @@ exports[`Upload dropzone component when dragging renders correct template when d
>
<button
class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-3"
+ type="button"
>
<div
class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center gl-flex-direction-column"
@@ -254,6 +257,7 @@ exports[`Upload dropzone component when dragging renders correct template when d
>
<button
class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-3"
+ type="button"
>
<div
class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center gl-flex-direction-column"
@@ -339,6 +343,7 @@ exports[`Upload dropzone component when dragging renders correct template when d
>
<button
class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-3"
+ type="button"
>
<div
class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center gl-flex-direction-column"
@@ -424,6 +429,7 @@ exports[`Upload dropzone component when dragging renders correct template when d
>
<button
class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-3"
+ type="button"
>
<div
class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center gl-flex-direction-column"
@@ -509,6 +515,7 @@ exports[`Upload dropzone component when no slot provided renders default dropzon
>
<button
class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-3"
+ type="button"
>
<div
class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center gl-flex-direction-column"
diff --git a/spec/frontend/vue_shared/components/upload_dropzone/upload_dropzone_spec.js b/spec/frontend/vue_shared/components/upload_dropzone/upload_dropzone_spec.js
index b3cdbccb271..21e9b401215 100644
--- a/spec/frontend/vue_shared/components/upload_dropzone/upload_dropzone_spec.js
+++ b/spec/frontend/vue_shared/components/upload_dropzone/upload_dropzone_spec.js
@@ -1,5 +1,6 @@
import { GlIcon, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import UploadDropzone from '~/vue_shared/components/upload_dropzone/upload_dropzone.vue';
jest.mock('~/flash');
@@ -15,6 +16,7 @@ describe('Upload dropzone component', () => {
const findDropzoneArea = () => wrapper.find('[data-testid="dropzone-area"]');
const findIcon = () => wrapper.find(GlIcon);
const findUploadText = () => wrapper.find('[data-testid="upload-text"]').text();
+ const findFileInput = () => wrapper.find('input[type="file"]');
function createComponent({ slots = {}, data = {}, props = {} } = {}) {
wrapper = shallowMount(UploadDropzone, {
@@ -84,47 +86,40 @@ describe('Upload dropzone component', () => {
${'contains text'} | ${mockDragEvent({ types: ['text'] })}
${'contains files and text'} | ${mockDragEvent({ types: ['Files', 'text'] })}
${'contains files'} | ${mockDragEvent({ types: ['Files'] })}
- `('renders correct template when drag event $description', ({ eventPayload }) => {
+ `('renders correct template when drag event $description', async ({ eventPayload }) => {
createComponent();
wrapper.trigger('dragenter', eventPayload);
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.element).toMatchSnapshot();
- });
+ await nextTick();
+ expect(wrapper.element).toMatchSnapshot();
});
- it('renders correct template when dragging stops', () => {
+ it('renders correct template when dragging stops', async () => {
createComponent();
wrapper.trigger('dragenter');
- return wrapper.vm
- .$nextTick()
- .then(() => {
- wrapper.trigger('dragleave');
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(wrapper.element).toMatchSnapshot();
- });
+
+ await nextTick();
+ wrapper.trigger('dragleave');
+
+ await nextTick();
+ expect(wrapper.element).toMatchSnapshot();
});
});
describe('when dropping', () => {
- it('emits upload event', () => {
+ it('emits upload event', async () => {
createComponent();
const mockFile = { name: 'test', type: 'image/jpg' };
const mockEvent = mockDragEvent({ files: [mockFile] });
wrapper.trigger('dragenter', mockEvent);
- return wrapper.vm
- .$nextTick()
- .then(() => {
- wrapper.trigger('drop', mockEvent);
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(wrapper.emitted().change[0]).toEqual([[mockFile]]);
- });
+
+ await nextTick();
+ wrapper.trigger('drop', mockEvent);
+
+ await nextTick();
+ expect(wrapper.emitted().change[0]).toEqual([[mockFile]]);
});
});
@@ -203,4 +198,60 @@ describe('Upload dropzone component', () => {
expect(wrapper.element).toMatchSnapshot();
});
+
+ describe('file input form name', () => {
+ it('applies inputFieldName as file input name', () => {
+ createComponent({ props: { inputFieldName: 'test_field_name' } });
+ expect(findFileInput().attributes('name')).toBe('test_field_name');
+ });
+
+ it('uses default file input name if no inputFieldName provided', () => {
+ createComponent();
+ expect(findFileInput().attributes('name')).toBe('upload_file');
+ });
+ });
+
+ describe('updates file input files value', () => {
+ // NOTE: the component assigns dropped files from the drop event to the
+ // input.files property. There's a restriction that nothing but a FileList
+ // can be assigned to this property. While FileList can't be created
+ // manually: it has no constructor. And currently there's no good workaround
+ // for jsdom. So we have to stub the file input in vm.$refs to ensure that
+ // the files property is updated. This enforces following tests to know a
+ // bit too much about the SUT internals See this thread for more details on
+ // FileList in jsdom: https://github.com/jsdom/jsdom/issues/1272
+ function stubFileInputOnWrapper() {
+ const fakeFileInput = { files: [] };
+ wrapper.vm.$refs.fileUpload = fakeFileInput;
+ }
+
+ it('assigns dragged files to the input files property', async () => {
+ const mockFile = { name: 'test', type: 'image/jpg' };
+ const mockEvent = mockDragEvent({ files: [mockFile] });
+ createComponent({ props: { shouldUpdateInputOnFileDrop: true } });
+ stubFileInputOnWrapper();
+
+ wrapper.trigger('dragenter', mockEvent);
+ await nextTick();
+ wrapper.trigger('drop', mockEvent);
+ await nextTick();
+
+ expect(wrapper.vm.$refs.fileUpload.files).toEqual([mockFile]);
+ });
+
+ it('throws an error when multiple files are dropped on a single file input dropzone', async () => {
+ const mockFile = { name: 'test', type: 'image/jpg' };
+ const mockEvent = mockDragEvent({ files: [mockFile, mockFile] });
+ createComponent({ props: { shouldUpdateInputOnFileDrop: true, singleFileSelection: true } });
+ stubFileInputOnWrapper();
+
+ wrapper.trigger('dragenter', mockEvent);
+ await nextTick();
+ wrapper.trigger('drop', mockEvent);
+ await nextTick();
+
+ expect(wrapper.vm.$refs.fileUpload.files).toEqual([]);
+ expect(wrapper.emitted('error')).toHaveLength(1);
+ });
+ });
});
diff --git a/spec/frontend/vue_shared/components/user_avatar/user_avatar_list_spec.js b/spec/frontend/vue_shared/components/user_avatar/user_avatar_list_spec.js
index 1d15da491cd..66bb234aef6 100644
--- a/spec/frontend/vue_shared/components/user_avatar/user_avatar_list_spec.js
+++ b/spec/frontend/vue_shared/components/user_avatar/user_avatar_list_spec.js
@@ -1,5 +1,6 @@
import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { TEST_HOST } from 'spec/test_constants';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import UserAvatarList from '~/vue_shared/components/user_avatar/user_avatar_list.vue';
@@ -142,14 +143,13 @@ describe('UserAvatarList', () => {
expect(links.length).toEqual(props.items.length);
});
- it('with collapse clicked, it renders avatars up to breakpoint', () => {
+ it('with collapse clicked, it renders avatars up to breakpoint', async () => {
clickButton();
- return wrapper.vm.$nextTick(() => {
- const links = wrapper.findAll(UserAvatarLink);
+ await nextTick();
+ const links = wrapper.findAll(UserAvatarLink);
- expect(links.length).toEqual(TEST_BREAKPOINT);
- });
+ expect(links.length).toEqual(TEST_BREAKPOINT);
});
});
});
diff --git a/spec/frontend/vue_shared/components/user_select_spec.js b/spec/frontend/vue_shared/components/user_select_spec.js
index 8994e16e517..411a15e1c74 100644
--- a/spec/frontend/vue_shared/components/user_select_spec.js
+++ b/spec/frontend/vue_shared/components/user_select_spec.js
@@ -104,14 +104,14 @@ describe('User select dropdown', () => {
createComponent({ participantsQueryHandler: mockError });
await waitForPromises();
- expect(wrapper.emitted('error')).toEqual([[], []]);
+ expect(wrapper.emitted('error')).toEqual([[]]);
});
it('emits an `error` event if search query was rejected', async () => {
createComponent({ searchQueryHandler: mockError });
await waitForSearch();
- expect(wrapper.emitted('error')).toEqual([[], []]);
+ expect(wrapper.emitted('error')).toEqual([[]]);
});
it('renders current user if they are not in participants or assignees', async () => {
diff --git a/spec/frontend/vue_shared/components/web_ide_link_spec.js b/spec/frontend/vue_shared/components/web_ide_link_spec.js
index 659d93d6597..5589cbfd08f 100644
--- a/spec/frontend/vue_shared/components/web_ide_link_spec.js
+++ b/spec/frontend/vue_shared/components/web_ide_link_spec.js
@@ -4,6 +4,7 @@ import { nextTick } from 'vue';
import ActionsButton from '~/vue_shared/components/actions_button.vue';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import WebIdeLink from '~/vue_shared/components/web_ide_link.vue';
+import ConfirmForkModal from '~/vue_shared/components/confirm_fork_modal.vue';
import { stubComponent } from 'helpers/stub_component';
import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_helper';
@@ -13,6 +14,7 @@ const TEST_WEB_IDE_URL = '/-/ide/project/gitlab-test/test/edit/main/-/';
const TEST_GITPOD_URL = 'https://gitpod.test/';
const TEST_USER_PREFERENCES_GITPOD_PATH = '/-/profile/preferences#user_gitpod_enabled';
const TEST_USER_PROFILE_ENABLE_GITPOD_PATH = '/-/profile?user%5Bgitpod_enabled%5D=true';
+const forkPath = '/some/fork/path';
const ACTION_EDIT = {
href: TEST_EDIT_URL,
@@ -74,6 +76,7 @@ describe('Web IDE link component', () => {
editUrl: TEST_EDIT_URL,
webIdeUrl: TEST_WEB_IDE_URL,
gitpodUrl: TEST_GITPOD_URL,
+ forkPath,
...props,
},
stubs: {
@@ -96,6 +99,7 @@ describe('Web IDE link component', () => {
const findActionsButton = () => wrapper.find(ActionsButton);
const findLocalStorageSync = () => wrapper.find(LocalStorageSync);
const findModal = () => wrapper.findComponent(GlModal);
+ const findForkConfirmModal = () => wrapper.findComponent(ConfirmForkModal);
it.each([
{
@@ -213,7 +217,7 @@ describe('Web IDE link component', () => {
findLocalStorageSync().vm.$emit('input', ACTION_GITPOD.key);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findActionsButton().props('selectedKey')).toBe(ACTION_GITPOD.key);
});
@@ -223,7 +227,7 @@ describe('Web IDE link component', () => {
findActionsButton().vm.$emit('select', ACTION_GITPOD.key);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findActionsButton().props('selectedKey')).toBe(ACTION_GITPOD.key);
expect(findLocalStorageSync().props('value')).toBe(ACTION_GITPOD.key);
@@ -231,16 +235,28 @@ describe('Web IDE link component', () => {
});
describe('edit actions', () => {
- it.each([
+ const testActions = [
{
- props: { showWebIdeButton: true, showEditButton: false },
+ props: {
+ showWebIdeButton: true,
+ showEditButton: false,
+ forkPath,
+ forkModalId: 'edit-modal',
+ },
expectedEventPayload: 'ide',
},
{
- props: { showWebIdeButton: false, showEditButton: true },
+ props: {
+ showWebIdeButton: false,
+ showEditButton: true,
+ forkPath,
+ forkModalId: 'webide-modal',
+ },
expectedEventPayload: 'simple',
},
- ])(
+ ];
+
+ it.each(testActions)(
'emits the correct event when an action handler is called',
async ({ props, expectedEventPayload }) => {
createComponent({ ...props, needsToFork: true, disableForkModal: true });
@@ -250,6 +266,29 @@ describe('Web IDE link component', () => {
expect(wrapper.emitted('edit')).toEqual([[expectedEventPayload]]);
},
);
+
+ it.each(testActions)('renders the fork confirmation modal', async ({ props }) => {
+ createComponent({ ...props, needsToFork: true });
+
+ expect(findForkConfirmModal().exists()).toBe(true);
+ expect(findForkConfirmModal().props()).toEqual({
+ visible: false,
+ forkPath,
+ modalId: props.forkModalId,
+ });
+ });
+
+ it.each(testActions)('opens the modal when the button is clicked', async ({ props }) => {
+ createComponent({ ...props, needsToFork: true }, mountExtended);
+
+ await findActionsButton().trigger('click');
+
+ expect(findForkConfirmModal().props()).toEqual({
+ visible: true,
+ forkPath,
+ modalId: props.forkModalId,
+ });
+ });
});
describe('when Gitpod is not enabled', () => {