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:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-02-24 18:15:02 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-02-24 18:15:02 +0300
commitc4b4a75c35cb2015c01ef0b60f8ad8baaaf889df (patch)
tree16eabfd63477e1904d7eb5d9f92f3e5a4e4d3e0f /spec/frontend
parente40c68997d44209aed2baf3a8ec6be9ae99fb0b5 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend')
-rw-r--r--spec/frontend/branches/ajax_loading_spinner_spec.js32
-rw-r--r--spec/frontend/content_editor/components/content_editor_alert_spec.js17
-rw-r--r--spec/frontend/content_editor/components/content_editor_spec.js10
-rw-r--r--spec/frontend/content_editor/components/editor_state_observer_spec.js63
-rw-r--r--spec/frontend/content_editor/components/toolbar_button_spec.js2
-rw-r--r--spec/frontend/content_editor/components/toolbar_link_button_spec.js2
-rw-r--r--spec/frontend/content_editor/components/toolbar_text_style_dropdown_spec.js2
-rw-r--r--spec/frontend/content_editor/extensions/attachment_spec.js14
-rw-r--r--spec/frontend/content_editor/services/content_editor_spec.js14
-rw-r--r--spec/frontend/jobs/components/job_log_controllers_spec.js24
-rw-r--r--spec/frontend/jobs/components/job_sidebar_retry_button_spec.js13
-rw-r--r--spec/frontend/jobs/components/sidebar_spec.js43
-rw-r--r--spec/frontend/pipeline_wizard/components/widgets/list_spec.js212
13 files changed, 356 insertions, 92 deletions
diff --git a/spec/frontend/branches/ajax_loading_spinner_spec.js b/spec/frontend/branches/ajax_loading_spinner_spec.js
deleted file mode 100644
index 31cc7b99e42..00000000000
--- a/spec/frontend/branches/ajax_loading_spinner_spec.js
+++ /dev/null
@@ -1,32 +0,0 @@
-import AjaxLoadingSpinner from '~/branches/ajax_loading_spinner';
-
-describe('Ajax Loading Spinner', () => {
- let ajaxLoadingSpinnerElement;
- let fauxEvent;
- beforeEach(() => {
- document.body.innerHTML = `
- <div>
- <a class="js-ajax-loading-spinner"
- data-remote
- href="http://goesnowhere.nothing/whereami">
- Remove me
- </a></div>`;
- AjaxLoadingSpinner.init();
- ajaxLoadingSpinnerElement = document.querySelector('.js-ajax-loading-spinner');
- fauxEvent = { target: ajaxLoadingSpinnerElement };
- });
-
- afterEach(() => {
- document.body.innerHTML = '';
- });
-
- it('`ajaxBeforeSend` event handler sets current icon to spinner and disables link', () => {
- expect(ajaxLoadingSpinnerElement.parentNode.querySelector('.gl-spinner')).toBeNull();
- expect(ajaxLoadingSpinnerElement.classList.contains('hidden')).toBe(false);
-
- AjaxLoadingSpinner.ajaxBeforeSend(fauxEvent);
-
- expect(ajaxLoadingSpinnerElement.parentNode.querySelector('.gl-spinner')).not.toBeNull();
- expect(ajaxLoadingSpinnerElement.classList.contains('hidden')).toBe(true);
- });
-});
diff --git a/spec/frontend/content_editor/components/content_editor_alert_spec.js b/spec/frontend/content_editor/components/content_editor_alert_spec.js
index 2ddcd8f024e..12484cb13c6 100644
--- a/spec/frontend/content_editor/components/content_editor_alert_spec.js
+++ b/spec/frontend/content_editor/components/content_editor_alert_spec.js
@@ -3,20 +3,25 @@ import { nextTick } from 'vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import ContentEditorAlert from '~/content_editor/components/content_editor_alert.vue';
import EditorStateObserver from '~/content_editor/components/editor_state_observer.vue';
-import { createTestEditor, emitEditorEvent } from '../test_utils';
+import eventHubFactory from '~/helpers/event_hub_factory';
+import { ALERT_EVENT } from '~/content_editor/constants';
+import { createTestEditor } from '../test_utils';
describe('content_editor/components/content_editor_alert', () => {
let wrapper;
let tiptapEditor;
+ let eventHub;
const findErrorAlert = () => wrapper.findComponent(GlAlert);
const createWrapper = async () => {
tiptapEditor = createTestEditor();
+ eventHub = eventHubFactory();
wrapper = shallowMountExtended(ContentEditorAlert, {
provide: {
tiptapEditor,
+ eventHub,
},
stubs: {
EditorStateObserver,
@@ -37,7 +42,9 @@ describe('content_editor/components/content_editor_alert', () => {
async ({ message, variant }) => {
createWrapper();
- await emitEditorEvent({ tiptapEditor, event: 'alert', params: { message, variant } });
+ eventHub.$emit(ALERT_EVENT, { message, variant });
+
+ await nextTick();
expect(findErrorAlert().text()).toBe(message);
expect(findErrorAlert().attributes().variant).toBe(variant);
@@ -48,11 +55,9 @@ describe('content_editor/components/content_editor_alert', () => {
const message = 'error message';
createWrapper();
-
- await emitEditorEvent({ tiptapEditor, event: 'alert', params: { message } });
-
+ eventHub.$emit(ALERT_EVENT, { message });
+ await nextTick();
findErrorAlert().vm.$emit('dismiss');
-
await nextTick();
expect(findErrorAlert().exists()).toBe(false);
diff --git a/spec/frontend/content_editor/components/content_editor_spec.js b/spec/frontend/content_editor/components/content_editor_spec.js
index 9a772c41e52..a713211b6f4 100644
--- a/spec/frontend/content_editor/components/content_editor_spec.js
+++ b/spec/frontend/content_editor/components/content_editor_spec.js
@@ -121,7 +121,7 @@ describe('ContentEditor', () => {
beforeEach(async () => {
createWrapper();
- contentEditor.emit(LOADING_CONTENT_EVENT);
+ contentEditor.eventHub.$emit(LOADING_CONTENT_EVENT);
await nextTick();
});
@@ -143,9 +143,9 @@ describe('ContentEditor', () => {
beforeEach(async () => {
createWrapper();
- contentEditor.emit(LOADING_CONTENT_EVENT);
+ contentEditor.eventHub.$emit(LOADING_CONTENT_EVENT);
await nextTick();
- contentEditor.emit(LOADING_SUCCESS_EVENT);
+ contentEditor.eventHub.$emit(LOADING_SUCCESS_EVENT);
await nextTick();
});
@@ -164,9 +164,9 @@ describe('ContentEditor', () => {
beforeEach(async () => {
createWrapper();
- contentEditor.emit(LOADING_CONTENT_EVENT);
+ contentEditor.eventHub.$emit(LOADING_CONTENT_EVENT);
await nextTick();
- contentEditor.emit(LOADING_ERROR_EVENT, error);
+ contentEditor.eventHub.$emit(LOADING_ERROR_EVENT, error);
await nextTick();
});
diff --git a/spec/frontend/content_editor/components/editor_state_observer_spec.js b/spec/frontend/content_editor/components/editor_state_observer_spec.js
index 5e4bb348e1f..51a594a606b 100644
--- a/spec/frontend/content_editor/components/editor_state_observer_spec.js
+++ b/spec/frontend/content_editor/components/editor_state_observer_spec.js
@@ -3,6 +3,13 @@ import { each } from 'lodash';
import EditorStateObserver, {
tiptapToComponentMap,
} from '~/content_editor/components/editor_state_observer.vue';
+import eventHubFactory from '~/helpers/event_hub_factory';
+import {
+ LOADING_CONTENT_EVENT,
+ LOADING_SUCCESS_EVENT,
+ LOADING_ERROR_EVENT,
+ ALERT_EVENT,
+} from '~/content_editor/constants';
import { createTestEditor } from '../test_utils';
describe('content_editor/components/editor_state_observer', () => {
@@ -11,19 +18,29 @@ describe('content_editor/components/editor_state_observer', () => {
let onDocUpdateListener;
let onSelectionUpdateListener;
let onTransactionListener;
+ let onLoadingContentListener;
+ let onLoadingSuccessListener;
+ let onLoadingErrorListener;
+ let onAlertListener;
+ let eventHub;
const buildEditor = () => {
tiptapEditor = createTestEditor();
+ eventHub = eventHubFactory();
jest.spyOn(tiptapEditor, 'on');
};
const buildWrapper = () => {
wrapper = shallowMount(EditorStateObserver, {
- provide: { tiptapEditor },
+ provide: { tiptapEditor, eventHub },
listeners: {
docUpdate: onDocUpdateListener,
selectionUpdate: onSelectionUpdateListener,
transaction: onTransactionListener,
+ [ALERT_EVENT]: onAlertListener,
+ [LOADING_CONTENT_EVENT]: onLoadingContentListener,
+ [LOADING_SUCCESS_EVENT]: onLoadingSuccessListener,
+ [LOADING_ERROR_EVENT]: onLoadingErrorListener,
},
});
};
@@ -32,8 +49,11 @@ describe('content_editor/components/editor_state_observer', () => {
onDocUpdateListener = jest.fn();
onSelectionUpdateListener = jest.fn();
onTransactionListener = jest.fn();
+ onAlertListener = jest.fn();
+ onLoadingSuccessListener = jest.fn();
+ onLoadingContentListener = jest.fn();
+ onLoadingErrorListener = jest.fn();
buildEditor();
- buildWrapper();
});
afterEach(() => {
@@ -44,6 +64,8 @@ describe('content_editor/components/editor_state_observer', () => {
it('emits update, selectionUpdate, and transaction events', () => {
const content = '<p>My paragraph</p>';
+ buildWrapper();
+
tiptapEditor.commands.insertContent(content);
expect(onDocUpdateListener).toHaveBeenCalledWith(
@@ -58,10 +80,27 @@ describe('content_editor/components/editor_state_observer', () => {
});
});
+ it.each`
+ event | listener
+ ${ALERT_EVENT} | ${() => onAlertListener}
+ ${LOADING_CONTENT_EVENT} | ${() => onLoadingContentListener}
+ ${LOADING_SUCCESS_EVENT} | ${() => onLoadingSuccessListener}
+ ${LOADING_ERROR_EVENT} | ${() => onLoadingErrorListener}
+ `('listens to $event event in the eventBus object', ({ event, listener }) => {
+ const args = {};
+
+ buildWrapper();
+
+ eventHub.$emit(event, args);
+ expect(listener()).toHaveBeenCalledWith(args);
+ });
+
describe('when component is destroyed', () => {
it('removes onTiptapDocUpdate and onTiptapSelectionUpdate hooks', () => {
jest.spyOn(tiptapEditor, 'off');
+ buildWrapper();
+
wrapper.destroy();
each(tiptapToComponentMap, (_, tiptapEvent) => {
@@ -71,5 +110,25 @@ describe('content_editor/components/editor_state_observer', () => {
);
});
});
+
+ it.each`
+ event
+ ${ALERT_EVENT}
+ ${LOADING_CONTENT_EVENT}
+ ${LOADING_SUCCESS_EVENT}
+ ${LOADING_ERROR_EVENT}
+ `('removes $event event hook from eventHub', ({ event }) => {
+ jest.spyOn(eventHub, '$off');
+ jest.spyOn(eventHub, '$on');
+
+ buildWrapper();
+
+ wrapper.destroy();
+
+ expect(eventHub.$off).toHaveBeenCalledWith(
+ event,
+ eventHub.$on.mock.calls.find(([eventName]) => eventName === event)[1],
+ );
+ });
});
});
diff --git a/spec/frontend/content_editor/components/toolbar_button_spec.js b/spec/frontend/content_editor/components/toolbar_button_spec.js
index 60263c46bdd..ce50482302d 100644
--- a/spec/frontend/content_editor/components/toolbar_button_spec.js
+++ b/spec/frontend/content_editor/components/toolbar_button_spec.js
@@ -2,6 +2,7 @@ import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import EditorStateObserver from '~/content_editor/components/editor_state_observer.vue';
import ToolbarButton from '~/content_editor/components/toolbar_button.vue';
+import eventHubFactory from '~/helpers/event_hub_factory';
import { createTestEditor, mockChainedCommands, emitEditorEvent } from '../test_utils';
describe('content_editor/components/toolbar_button', () => {
@@ -25,6 +26,7 @@ describe('content_editor/components/toolbar_button', () => {
},
provide: {
tiptapEditor,
+ eventHub: eventHubFactory(),
},
propsData: {
contentType: CONTENT_TYPE,
diff --git a/spec/frontend/content_editor/components/toolbar_link_button_spec.js b/spec/frontend/content_editor/components/toolbar_link_button_spec.js
index 0cf488260bd..fc26a9da471 100644
--- a/spec/frontend/content_editor/components/toolbar_link_button_spec.js
+++ b/spec/frontend/content_editor/components/toolbar_link_button_spec.js
@@ -1,6 +1,7 @@
import { GlDropdown, GlButton, GlFormInputGroup } from '@gitlab/ui';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import ToolbarLinkButton from '~/content_editor/components/toolbar_link_button.vue';
+import eventHubFactory from '~/helpers/event_hub_factory';
import Link from '~/content_editor/extensions/link';
import { hasSelection } from '~/content_editor/services/utils';
import { createTestEditor, mockChainedCommands, emitEditorEvent } from '../test_utils';
@@ -15,6 +16,7 @@ describe('content_editor/components/toolbar_link_button', () => {
wrapper = mountExtended(ToolbarLinkButton, {
provide: {
tiptapEditor: editor,
+ eventHub: eventHubFactory(),
},
});
};
diff --git a/spec/frontend/content_editor/components/toolbar_text_style_dropdown_spec.js b/spec/frontend/content_editor/components/toolbar_text_style_dropdown_spec.js
index 65c1c8c8310..608be1bd693 100644
--- a/spec/frontend/content_editor/components/toolbar_text_style_dropdown_spec.js
+++ b/spec/frontend/content_editor/components/toolbar_text_style_dropdown_spec.js
@@ -4,6 +4,7 @@ import EditorStateObserver from '~/content_editor/components/editor_state_observ
import ToolbarTextStyleDropdown from '~/content_editor/components/toolbar_text_style_dropdown.vue';
import { TEXT_STYLE_DROPDOWN_ITEMS } from '~/content_editor/constants';
import Heading from '~/content_editor/extensions/heading';
+import eventHubFactory from '~/helpers/event_hub_factory';
import { createTestEditor, mockChainedCommands, emitEditorEvent } from '../test_utils';
describe('content_editor/components/toolbar_text_style_dropdown', () => {
@@ -27,6 +28,7 @@ describe('content_editor/components/toolbar_text_style_dropdown', () => {
},
provide: {
tiptapEditor,
+ eventHub: eventHubFactory(),
},
propsData: {
...propsData,
diff --git a/spec/frontend/content_editor/extensions/attachment_spec.js b/spec/frontend/content_editor/extensions/attachment_spec.js
index d2d2cd98a78..e095a3d0b6a 100644
--- a/spec/frontend/content_editor/extensions/attachment_spec.js
+++ b/spec/frontend/content_editor/extensions/attachment_spec.js
@@ -5,6 +5,7 @@ import Image from '~/content_editor/extensions/image';
import Link from '~/content_editor/extensions/link';
import Loading from '~/content_editor/extensions/loading';
import httpStatus from '~/lib/utils/http_status';
+import eventHubFactory from '~/helpers/event_hub_factory';
import { createTestEditor, createDocBuilder } from '../test_utils';
const PROJECT_WIKI_ATTACHMENT_IMAGE_HTML = `<p data-sourcepos="1:1-1:27" dir="auto">
@@ -25,6 +26,7 @@ describe('content_editor/extensions/attachment', () => {
let link;
let renderMarkdown;
let mock;
+ let eventHub;
const uploadsPath = '/uploads/';
const imageFile = new File(['foo'], 'test-file.png', { type: 'image/png' });
@@ -50,9 +52,15 @@ describe('content_editor/extensions/attachment', () => {
beforeEach(() => {
renderMarkdown = jest.fn();
+ eventHub = eventHubFactory();
tiptapEditor = createTestEditor({
- extensions: [Loading, Link, Image, Attachment.configure({ renderMarkdown, uploadsPath })],
+ extensions: [
+ Loading,
+ Link,
+ Image,
+ Attachment.configure({ renderMarkdown, uploadsPath, eventHub }),
+ ],
});
({
@@ -160,7 +168,7 @@ describe('content_editor/extensions/attachment', () => {
it('emits an alert event that includes an error message', (done) => {
tiptapEditor.commands.uploadAttachment({ file: imageFile });
- tiptapEditor.on('alert', ({ message }) => {
+ eventHub.$on('alert', ({ message }) => {
expect(message).toBe('An error occurred while uploading the image. Please try again.');
done();
});
@@ -236,7 +244,7 @@ describe('content_editor/extensions/attachment', () => {
it('emits an alert event that includes an error message', (done) => {
tiptapEditor.commands.uploadAttachment({ file: attachmentFile });
- tiptapEditor.on('alert', ({ message }) => {
+ eventHub.$on('alert', ({ message }) => {
expect(message).toBe('An error occurred while uploading the file. Please try again.');
done();
});
diff --git a/spec/frontend/content_editor/services/content_editor_spec.js b/spec/frontend/content_editor/services/content_editor_spec.js
index e48687f1548..ac4f71a80cb 100644
--- a/spec/frontend/content_editor/services/content_editor_spec.js
+++ b/spec/frontend/content_editor/services/content_editor_spec.js
@@ -4,19 +4,21 @@ import {
LOADING_ERROR_EVENT,
} from '~/content_editor/constants';
import { ContentEditor } from '~/content_editor/services/content_editor';
-
+import eventHubFactory from '~/helpers/event_hub_factory';
import { createTestEditor } from '../test_utils';
describe('content_editor/services/content_editor', () => {
let contentEditor;
let serializer;
+ let eventHub;
beforeEach(() => {
const tiptapEditor = createTestEditor();
jest.spyOn(tiptapEditor, 'destroy');
serializer = { deserialize: jest.fn() };
- contentEditor = new ContentEditor({ tiptapEditor, serializer });
+ eventHub = eventHubFactory();
+ contentEditor = new ContentEditor({ tiptapEditor, serializer, eventHub });
});
describe('.dispose', () => {
@@ -34,13 +36,13 @@ describe('content_editor/services/content_editor', () => {
serializer.deserialize.mockResolvedValueOnce('');
});
- it('emits loadingContent and loadingSuccess event', () => {
+ it('emits loadingContent and loadingSuccess event in the eventHub', () => {
let loadingContentEmitted = false;
- contentEditor.on(LOADING_CONTENT_EVENT, () => {
+ eventHub.$on(LOADING_CONTENT_EVENT, () => {
loadingContentEmitted = true;
});
- contentEditor.on(LOADING_SUCCESS_EVENT, () => {
+ eventHub.$on(LOADING_SUCCESS_EVENT, () => {
expect(loadingContentEmitted).toBe(true);
});
@@ -56,7 +58,7 @@ describe('content_editor/services/content_editor', () => {
});
it('emits loadingError event', async () => {
- contentEditor.on(LOADING_ERROR_EVENT, (e) => {
+ eventHub.$on(LOADING_ERROR_EVENT, (e) => {
expect(e).toBe('error');
});
diff --git a/spec/frontend/jobs/components/job_log_controllers_spec.js b/spec/frontend/jobs/components/job_log_controllers_spec.js
index 226322a2951..cd3ee734466 100644
--- a/spec/frontend/jobs/components/job_log_controllers_spec.js
+++ b/spec/frontend/jobs/components/job_log_controllers_spec.js
@@ -8,7 +8,6 @@ describe('Job log controllers', () => {
afterEach(() => {
if (wrapper?.destroy) {
wrapper.destroy();
- wrapper = null;
}
});
@@ -34,7 +33,6 @@ describe('Job log controllers', () => {
const findTruncatedInfo = () => wrapper.find('[data-testid="log-truncated-info"]');
const findRawLink = () => wrapper.find('[data-testid="raw-link"]');
const findRawLinkController = () => wrapper.find('[data-testid="job-raw-link-controller"]');
- const findEraseLink = () => wrapper.find('[data-testid="job-log-erase-link"]');
const findScrollTop = () => wrapper.find('[data-testid="job-controller-scroll-top"]');
const findScrollBottom = () => wrapper.find('[data-testid="job-controller-scroll-bottom"]');
@@ -76,28 +74,6 @@ describe('Job log controllers', () => {
expect(findRawLinkController().exists()).toBe(false);
});
});
-
- describe('when is erasable', () => {
- beforeEach(() => {
- createWrapper();
- });
-
- it('renders erase job link', () => {
- expect(findEraseLink().exists()).toBe(true);
- });
- });
-
- describe('when it is not erasable', () => {
- beforeEach(() => {
- createWrapper({
- erasePath: null,
- });
- });
-
- it('does not render erase button', () => {
- expect(findEraseLink().exists()).toBe(false);
- });
- });
});
describe('scroll buttons', () => {
diff --git a/spec/frontend/jobs/components/job_sidebar_retry_button_spec.js b/spec/frontend/jobs/components/job_sidebar_retry_button_spec.js
index 6914b8d4fa1..ad72b9be261 100644
--- a/spec/frontend/jobs/components/job_sidebar_retry_button_spec.js
+++ b/spec/frontend/jobs/components/job_sidebar_retry_button_spec.js
@@ -1,5 +1,4 @@
-import { GlButton, GlLink } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import JobsSidebarRetryButton from '~/jobs/components/job_sidebar_retry_button.vue';
import createStore from '~/jobs/store';
import job from '../mock_data';
@@ -9,12 +8,12 @@ describe('Job Sidebar Retry Button', () => {
let wrapper;
const forwardDeploymentFailure = 'forward_deployment_failure';
- const findRetryButton = () => wrapper.find(GlButton);
- const findRetryLink = () => wrapper.find(GlLink);
+ const findRetryButton = () => wrapper.findByTestId('retry-job-button');
+ const findRetryLink = () => wrapper.findByTestId('retry-job-link');
const createWrapper = ({ props = {} } = {}) => {
store = createStore();
- wrapper = shallowMount(JobsSidebarRetryButton, {
+ wrapper = shallowMountExtended(JobsSidebarRetryButton, {
propsData: {
href: job.retry_path,
modalId: 'modal-id',
@@ -27,7 +26,6 @@ describe('Job Sidebar Retry Button', () => {
afterEach(() => {
if (wrapper) {
wrapper.destroy();
- wrapper = null;
}
});
@@ -44,7 +42,6 @@ describe('Job Sidebar Retry Button', () => {
expect(findRetryButton().exists()).toBe(buttonExists);
expect(findRetryLink().exists()).toBe(linkExists);
- expect(wrapper.text()).toMatch('Retry');
},
);
@@ -55,6 +52,7 @@ describe('Job Sidebar Retry Button', () => {
expect(findRetryButton().attributes()).toMatchObject({
category: 'primary',
variant: 'confirm',
+ icon: 'retry',
});
});
});
@@ -64,6 +62,7 @@ describe('Job Sidebar Retry Button', () => {
expect(findRetryLink().attributes()).toMatchObject({
'data-method': 'post',
href: job.retry_path,
+ icon: 'retry',
});
});
});
diff --git a/spec/frontend/jobs/components/sidebar_spec.js b/spec/frontend/jobs/components/sidebar_spec.js
index 6e327725627..39c71986ce4 100644
--- a/spec/frontend/jobs/components/sidebar_spec.js
+++ b/spec/frontend/jobs/components/sidebar_spec.js
@@ -21,25 +21,54 @@ describe('Sidebar details block', () => {
const findNewIssueButton = () => wrapper.findByTestId('job-new-issue');
const findRetryButton = () => wrapper.find(JobRetryButton);
const findTerminalLink = () => wrapper.findByTestId('terminal-link');
+ const findEraseLink = () => wrapper.findByTestId('job-log-erase-link');
- const createWrapper = ({ props = {} } = {}) => {
+ const createWrapper = (props) => {
store = createStore();
store.state.job = job;
wrapper = extendedWrapper(
shallowMount(Sidebar, {
- ...props,
+ propsData: {
+ ...props,
+ },
+
store,
}),
);
};
afterEach(() => {
- if (wrapper) {
- wrapper.destroy();
- wrapper = null;
- }
+ wrapper.destroy();
+ });
+
+ describe('when job log is erasable', () => {
+ const path = '/root/ci-project/-/jobs/1447/erase';
+
+ beforeEach(() => {
+ createWrapper({
+ erasePath: path,
+ });
+ });
+
+ it('renders erase job link', () => {
+ expect(findEraseLink().exists()).toBe(true);
+ });
+
+ it('erase job link has correct path', () => {
+ expect(findEraseLink().attributes('href')).toBe(path);
+ });
+ });
+
+ describe('when job log is not erasable', () => {
+ beforeEach(() => {
+ createWrapper();
+ });
+
+ it('does not render erase button', () => {
+ expect(findEraseLink().exists()).toBe(false);
+ });
});
describe('when there is no retry path retry', () => {
@@ -86,7 +115,7 @@ describe('Sidebar details block', () => {
});
it('should render link to cancel job', () => {
- expect(findCancelButton().text()).toMatch('Cancel');
+ expect(findCancelButton().props('icon')).toBe('cancel');
expect(findCancelButton().attributes('href')).toBe(job.cancel_path);
});
});
diff --git a/spec/frontend/pipeline_wizard/components/widgets/list_spec.js b/spec/frontend/pipeline_wizard/components/widgets/list_spec.js
new file mode 100644
index 00000000000..796356634bc
--- /dev/null
+++ b/spec/frontend/pipeline_wizard/components/widgets/list_spec.js
@@ -0,0 +1,212 @@
+import { GlFormGroup, GlFormInputGroup } from '@gitlab/ui';
+import { nextTick } from 'vue';
+import ListWidget from '~/pipeline_wizard/components/widgets/list.vue';
+import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
+
+describe('Pipeline Wizard - List Widget', () => {
+ const defaultProps = {
+ label: 'This label',
+ description: 'some description',
+ placeholder: 'some placeholder',
+ pattern: '^[a-z]+$',
+ invalidFeedback: 'some feedback',
+ };
+ let wrapper;
+ let addStepBtn;
+
+ const findGlFormGroup = () => wrapper.findComponent(GlFormGroup);
+ const findGlFormGroupInvalidFeedback = () => findGlFormGroup().find('.invalid-feedback').text();
+ const findFirstGlFormInputGroup = () => wrapper.findComponent(GlFormInputGroup);
+ const findAllGlFormInputGroups = () => wrapper.findAllComponents(GlFormInputGroup);
+ const findGlFormInputGroupByIndex = (index) => findAllGlFormInputGroups().at(index);
+ const setValueOnInputField = (value, atIndex = 0) => {
+ return findGlFormInputGroupByIndex(atIndex).vm.$emit('input', value);
+ };
+ const findAddStepButton = () => wrapper.findByTestId('add-step-button');
+ const addStep = () => findAddStepButton().vm.$emit('click');
+
+ const createComponent = (props = {}, mountFn = shallowMountExtended) => {
+ wrapper = mountFn(ListWidget, {
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ });
+ addStepBtn = findAddStepButton();
+ };
+
+ describe('component setup and interface', () => {
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('prints the label inside the legend', () => {
+ createComponent();
+
+ expect(findGlFormGroup().attributes('label')).toBe(defaultProps.label);
+ });
+
+ it('prints the description inside the legend', () => {
+ createComponent();
+
+ expect(findGlFormGroup().attributes('labeldescription')).toBe(defaultProps.description);
+ });
+
+ it('sets the input field type attribute to "text"', async () => {
+ createComponent();
+
+ expect(findFirstGlFormInputGroup().attributes('type')).toBe('text');
+ });
+
+ it('passes the placeholder to the first input field', () => {
+ createComponent();
+
+ expect(findFirstGlFormInputGroup().attributes('placeholder')).toBe(defaultProps.placeholder);
+ });
+
+ it('shows a delete button on all fields if there are more than one', async () => {
+ createComponent({}, mountExtended);
+
+ await addStep();
+ await addStep();
+ const inputGroups = findAllGlFormInputGroups().wrappers;
+
+ expect(inputGroups.length).toBe(3);
+ inputGroups.forEach((inputGroup) => {
+ const button = inputGroup.find('[data-testid="remove-step-button"]');
+ expect(button.find('[data-testid="remove-icon"]').exists()).toBe(true);
+ expect(button.attributes('aria-label')).toBe('remove step');
+ });
+ });
+
+ it('null values do not cause an input event', async () => {
+ createComponent();
+
+ await addStep();
+
+ expect(wrapper.emitted('input')).toBe(undefined);
+ });
+
+ it('hides the delete button if there is only one', () => {
+ createComponent({}, mountExtended);
+
+ const inputGroups = findAllGlFormInputGroups().wrappers;
+
+ expect(inputGroups.length).toBe(1);
+ expect(wrapper.findByTestId('remove-step-button').exists()).toBe(false);
+ });
+
+ it('shows an "add step" button', () => {
+ createComponent();
+
+ expect(addStepBtn.attributes('icon')).toBe('plus');
+ expect(addStepBtn.text()).toBe('add another step');
+ });
+
+ it('the "add step" button increases the number of input fields', async () => {
+ createComponent();
+
+ expect(findAllGlFormInputGroups().wrappers.length).toBe(1);
+ await addStep();
+ expect(findAllGlFormInputGroups().wrappers.length).toBe(2);
+ });
+
+ it('does not pass the placeholder on subsequent input fields', async () => {
+ createComponent();
+
+ await addStep();
+ await addStep();
+ const nullOrUndefined = [null, undefined];
+ expect(nullOrUndefined).toContain(findAllGlFormInputGroups().at(1).attributes('placeholder'));
+ expect(nullOrUndefined).toContain(findAllGlFormInputGroups().at(2).attributes('placeholder'));
+ });
+
+ it('emits an update event on input', async () => {
+ createComponent();
+
+ const localValue = 'somevalue';
+ await setValueOnInputField(localValue);
+ await nextTick();
+
+ expect(wrapper.emitted('input')).toEqual([[[localValue]]]);
+ });
+
+ it('only emits non-null values', async () => {
+ createComponent();
+
+ await addStep();
+ await addStep();
+ await setValueOnInputField('abc', 1);
+ await nextTick();
+
+ const events = wrapper.emitted('input');
+
+ expect(events.length).toBe(1);
+ expect(events[0]).toEqual([['abc']]);
+ });
+ });
+
+ describe('form validation', () => {
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('does not show validation state when untouched', async () => {
+ createComponent({}, mountExtended);
+ expect(findGlFormGroup().classes()).not.toContain('is-valid');
+ expect(findGlFormGroup().classes()).not.toContain('is-invalid');
+ });
+
+ it('shows invalid state on blur', async () => {
+ createComponent({}, mountExtended);
+ expect(findGlFormGroup().classes()).not.toContain('is-invalid');
+ const input = findFirstGlFormInputGroup().find('input');
+ await input.setValue('invalid99');
+ await input.trigger('blur');
+ expect(input.classes()).toContain('is-invalid');
+ expect(findGlFormGroup().classes()).toContain('is-invalid');
+ });
+
+ it('shows invalid state when toggling `validate` prop', async () => {
+ createComponent({ required: true, validate: false }, mountExtended);
+ await setValueOnInputField(null);
+ expect(findGlFormGroup().classes()).not.toContain('is-invalid');
+ await wrapper.setProps({ validate: true });
+ expect(findGlFormGroup().classes()).toContain('is-invalid');
+ });
+
+ it.each`
+ scenario | required | values | inputFieldClasses | inputGroupClass | feedback
+ ${'shows invalid if all inputs are empty'} | ${true} | ${[null, null]} | ${['is-invalid', null]} | ${'is-invalid'} | ${'At least one entry is required'}
+ ${'is valid if at least one field has a valid entry'} | ${true} | ${[null, 'abc']} | ${[null, 'is-valid']} | ${'is-valid'} | ${expect.anything()}
+ ${'is invalid if one field has an invalid entry'} | ${true} | ${['abc', '99']} | ${['is-valid', 'is-invalid']} | ${'is-invalid'} | ${defaultProps.invalidFeedback}
+ ${'is not invalid if its not required but all values are null'} | ${false} | ${[null, null]} | ${[null, null]} | ${'is-valid'} | ${expect.anything()}
+ ${'is invalid if pattern does not match even if its not required'} | ${false} | ${['99', null]} | ${['is-invalid', null]} | ${'is-invalid'} | ${defaultProps.invalidFeedback}
+ `('$scenario', async ({ required, values, inputFieldClasses, inputGroupClass, feedback }) => {
+ createComponent({ required, validate: true }, mountExtended);
+
+ await Promise.all(
+ values.map(async (value, i) => {
+ if (i > 0) {
+ await addStep();
+ }
+ await setValueOnInputField(value, i);
+ }),
+ );
+ await nextTick();
+
+ inputFieldClasses.forEach((expected, i) => {
+ const inputWrapper = findGlFormInputGroupByIndex(i).find('input');
+ if (expected === null) {
+ expect(inputWrapper.classes()).not.toContain('is-valid');
+ expect(inputWrapper.classes()).not.toContain('is-invalid');
+ } else {
+ expect(inputWrapper.classes()).toContain(expected);
+ }
+ });
+
+ expect(findGlFormGroup().classes()).toContain(inputGroupClass);
+ expect(findGlFormGroupInvalidFeedback()).toEqual(feedback);
+ });
+ });
+});