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')
-rw-r--r--spec/frontend/vue_shared/alert_details/alert_details_spec.js22
-rw-r--r--spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap120
-rw-r--r--spec/frontend/vue_shared/components/ci_icon_spec.js48
-rw-r--r--spec/frontend/vue_shared/components/color_select_dropdown/color_item_spec.js35
-rw-r--r--spec/frontend/vue_shared/components/color_select_dropdown/color_select_root_spec.js192
-rw-r--r--spec/frontend/vue_shared/components/color_select_dropdown/dropdown_contents_color_view_spec.js43
-rw-r--r--spec/frontend/vue_shared/components/color_select_dropdown/dropdown_contents_spec.js113
-rw-r--r--spec/frontend/vue_shared/components/color_select_dropdown/dropdown_header_spec.js40
-rw-r--r--spec/frontend/vue_shared/components/color_select_dropdown/dropdown_value_spec.js46
-rw-r--r--spec/frontend/vue_shared/components/color_select_dropdown/mock_data.js30
-rw-r--r--spec/frontend/vue_shared/components/confidentiality_badge_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/content_viewer/viewers/markdown_viewer_spec.js5
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/label_token_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/form/input_copy_toggle_visibility_spec.js69
-rw-r--r--spec/frontend/vue_shared/components/notes/__snapshots__/noteable_warning_spec.js.snap4
-rw-r--r--spec/frontend/vue_shared/components/notes/system_note_spec.js10
-rw-r--r--spec/frontend/vue_shared/components/papa_parse_alert_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/registry/registry_search_spec.js6
-rw-r--r--spec/frontend/vue_shared/components/runner_instructions/runner_instructions_modal_spec.js234
-rw-r--r--spec/frontend/vue_shared/components/runner_instructions/runner_instructions_spec.js24
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js7
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_vue/label_item_spec.js34
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js65
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/getters_spec.js45
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/mutations_spec.js26
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/plugins/index_spec.js14
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/plugins/wrap_comments_spec.js29
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js6
-rw-r--r--spec/frontend/vue_shared/components/upload_dropzone/__snapshots__/upload_dropzone_spec.js.snap30
-rw-r--r--spec/frontend/vue_shared/issuable/list/components/issuable_item_spec.js35
-rw-r--r--spec/frontend/vue_shared/issuable/list/components/issuable_list_root_spec.js9
-rw-r--r--spec/frontend/vue_shared/issuable/show/components/issuable_body_spec.js1
-rw-r--r--spec/frontend/vue_shared/issuable/show/components/issuable_header_spec.js4
-rw-r--r--spec/frontend/vue_shared/issuable/show/components/issuable_show_root_spec.js3
-rw-r--r--spec/frontend/vue_shared/issuable/show/components/issuable_title_spec.js15
-rw-r--r--spec/frontend/vue_shared/issuable/show/mock_data.js1
36 files changed, 1015 insertions, 360 deletions
diff --git a/spec/frontend/vue_shared/alert_details/alert_details_spec.js b/spec/frontend/vue_shared/alert_details/alert_details_spec.js
index 7aa54a1c55a..ce51af31a70 100644
--- a/spec/frontend/vue_shared/alert_details/alert_details_spec.js
+++ b/spec/frontend/vue_shared/alert_details/alert_details_spec.js
@@ -201,28 +201,6 @@ describe('AlertDetails', () => {
});
});
- describe('Threat Monitoring details', () => {
- it('should not render the metrics tab', () => {
- mountComponent({
- data: { alert: mockAlert },
- provide: { isThreatMonitoringPage: true },
- });
- expect(findMetricsTab().exists()).toBe(false);
- });
-
- it('should display "View incident" button that links the issues page when incident exists', () => {
- const iid = '3';
- mountComponent({
- data: { alert: { ...mockAlert, issue: { iid } }, sidebarStatus: false },
- provide: { isThreatMonitoringPage: true },
- });
-
- expect(findViewIncidentBtn().exists()).toBe(true);
- expect(findViewIncidentBtn().attributes('href')).toBe(joinPaths(projectIssuesPath, iid));
- expect(findCreateIncidentBtn().exists()).toBe(false);
- });
- });
-
describe('Create incident from alert', () => {
it('should display "View incident" button that links the incident page when incident exists', () => {
const iid = '3';
diff --git a/spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap b/spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap
index 44b4c0398cd..30e15595193 100644
--- a/spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap
+++ b/spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap
@@ -12,7 +12,7 @@ exports[`Clone Dropdown Button rendering matches the snapshot 1`] = `
right="true"
size="medium"
text="Clone"
- variant="info"
+ variant="confirm"
>
<div
class="pb-2 mx-1"
@@ -24,41 +24,38 @@ exports[`Clone Dropdown Button rendering matches the snapshot 1`] = `
<div
class="mx-3"
>
- <div
- readonly="readonly"
+ <b-input-group-stub
+ readonly=""
+ tag="div"
>
- <b-input-group-stub
+ <!---->
+
+ <b-form-input-stub
+ class="gl-form-input"
+ debounce="0"
+ formatter="[Function]"
+ readonly="true"
+ type="text"
+ value="ssh://foo.bar"
+ />
+
+ <b-input-group-append-stub
tag="div"
>
- <!---->
-
- <b-form-input-stub
- class="gl-form-input"
- debounce="0"
- formatter="[Function]"
- readonly="true"
- type="text"
- value="ssh://foo.bar"
+ <gl-button-stub
+ aria-label="Copy URL"
+ buttontextclasses=""
+ category="primary"
+ class="d-inline-flex"
+ data-clipboard-text="ssh://foo.bar"
+ data-qa-selector="copy_ssh_url_button"
+ icon="copy-to-clipboard"
+ size="medium"
+ title="Copy URL"
+ variant="default"
/>
-
- <b-input-group-append-stub
- tag="div"
- >
- <gl-button-stub
- aria-label="Copy URL"
- buttontextclasses=""
- category="primary"
- class="d-inline-flex"
- data-clipboard-text="ssh://foo.bar"
- data-qa-selector="copy_ssh_url_button"
- icon="copy-to-clipboard"
- size="medium"
- title="Copy URL"
- variant="default"
- />
- </b-input-group-append-stub>
- </b-input-group-stub>
- </div>
+ </b-input-group-append-stub>
+ </b-input-group-stub>
</div>
<gl-dropdown-section-header-stub>
@@ -68,41 +65,38 @@ exports[`Clone Dropdown Button rendering matches the snapshot 1`] = `
<div
class="mx-3"
>
- <div
- readonly="readonly"
+ <b-input-group-stub
+ readonly=""
+ tag="div"
>
- <b-input-group-stub
+ <!---->
+
+ <b-form-input-stub
+ class="gl-form-input"
+ debounce="0"
+ formatter="[Function]"
+ readonly="true"
+ type="text"
+ value="http://foo.bar"
+ />
+
+ <b-input-group-append-stub
tag="div"
>
- <!---->
-
- <b-form-input-stub
- class="gl-form-input"
- debounce="0"
- formatter="[Function]"
- readonly="true"
- type="text"
- value="http://foo.bar"
+ <gl-button-stub
+ aria-label="Copy URL"
+ buttontextclasses=""
+ category="primary"
+ class="d-inline-flex"
+ data-clipboard-text="http://foo.bar"
+ data-qa-selector="copy_http_url_button"
+ icon="copy-to-clipboard"
+ size="medium"
+ title="Copy URL"
+ variant="default"
/>
-
- <b-input-group-append-stub
- tag="div"
- >
- <gl-button-stub
- aria-label="Copy URL"
- buttontextclasses=""
- category="primary"
- class="d-inline-flex"
- data-clipboard-text="http://foo.bar"
- data-qa-selector="copy_http_url_button"
- icon="copy-to-clipboard"
- size="medium"
- title="Copy URL"
- variant="default"
- />
- </b-input-group-append-stub>
- </b-input-group-stub>
- </div>
+ </b-input-group-append-stub>
+ </b-input-group-stub>
</div>
</div>
</gl-dropdown-stub>
diff --git a/spec/frontend/vue_shared/components/ci_icon_spec.js b/spec/frontend/vue_shared/components/ci_icon_spec.js
index 6d52db7ae65..1b502f9587c 100644
--- a/spec/frontend/vue_shared/components/ci_icon_spec.js
+++ b/spec/frontend/vue_shared/components/ci_icon_spec.js
@@ -5,6 +5,8 @@ import ciIcon from '~/vue_shared/components/ci_icon.vue';
describe('CI Icon component', () => {
let wrapper;
+ const findIconWrapper = () => wrapper.find('[data-testid="ci-icon-wrapper"]');
+
afterEach(() => {
wrapper.destroy();
wrapper = null;
@@ -23,6 +25,52 @@ describe('CI Icon component', () => {
expect(wrapper.find(GlIcon).exists()).toBe(true);
});
+ describe('active icons', () => {
+ it.each`
+ isActive | cssClass
+ ${true} | ${'active'}
+ ${false} | ${'active'}
+ `('active should be $isActive', ({ isActive, cssClass }) => {
+ wrapper = shallowMount(ciIcon, {
+ propsData: {
+ status: {
+ icon: 'status_success',
+ },
+ isActive,
+ },
+ });
+
+ if (isActive) {
+ expect(findIconWrapper().classes()).toContain(cssClass);
+ } else {
+ expect(findIconWrapper().classes()).not.toContain(cssClass);
+ }
+ });
+ });
+
+ describe('interactive icons', () => {
+ it.each`
+ isInteractive | cssClass
+ ${true} | ${'interactive'}
+ ${false} | ${'interactive'}
+ `('interactive should be $isInteractive', ({ isInteractive, cssClass }) => {
+ wrapper = shallowMount(ciIcon, {
+ propsData: {
+ status: {
+ icon: 'status_success',
+ },
+ isInteractive,
+ },
+ });
+
+ if (isInteractive) {
+ expect(findIconWrapper().classes()).toContain(cssClass);
+ } else {
+ expect(findIconWrapper().classes()).not.toContain(cssClass);
+ }
+ });
+ });
+
describe('rendering a status', () => {
it.each`
icon | group | cssClass
diff --git a/spec/frontend/vue_shared/components/color_select_dropdown/color_item_spec.js b/spec/frontend/vue_shared/components/color_select_dropdown/color_item_spec.js
new file mode 100644
index 00000000000..fe614f03119
--- /dev/null
+++ b/spec/frontend/vue_shared/components/color_select_dropdown/color_item_spec.js
@@ -0,0 +1,35 @@
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { hexToRgb } from '~/lib/utils/color_utils';
+import ColorItem from '~/vue_shared/components/color_select_dropdown/color_item.vue';
+import { color } from './mock_data';
+
+describe('ColorItem', () => {
+ let wrapper;
+
+ const propsData = color;
+
+ const createComponent = () => {
+ wrapper = shallowMountExtended(ColorItem, {
+ propsData,
+ });
+ };
+
+ const findColorItem = () => wrapper.findByTestId('color-item');
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders the correct title', () => {
+ expect(wrapper.text()).toBe(propsData.title);
+ });
+
+ it('renders the correct background color for the color item', () => {
+ const convertedColor = hexToRgb(propsData.color).join(', ');
+ expect(findColorItem().attributes('style')).toBe(`background-color: rgb(${convertedColor});`);
+ });
+});
diff --git a/spec/frontend/vue_shared/components/color_select_dropdown/color_select_root_spec.js b/spec/frontend/vue_shared/components/color_select_dropdown/color_select_root_spec.js
new file mode 100644
index 00000000000..93b59800c27
--- /dev/null
+++ b/spec/frontend/vue_shared/components/color_select_dropdown/color_select_root_spec.js
@@ -0,0 +1,192 @@
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import createFlash from '~/flash';
+import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue';
+import DropdownContents from '~/vue_shared/components/color_select_dropdown/dropdown_contents.vue';
+import DropdownValue from '~/vue_shared/components/color_select_dropdown/dropdown_value.vue';
+import epicColorQuery from '~/vue_shared/components/color_select_dropdown/graphql/epic_color.query.graphql';
+import updateEpicColorMutation from '~/vue_shared/components/color_select_dropdown/graphql/epic_update_color.mutation.graphql';
+import ColorSelectRoot from '~/vue_shared/components/color_select_dropdown/color_select_root.vue';
+import { DROPDOWN_VARIANT } from '~/vue_shared/components/color_select_dropdown/constants';
+import { colorQueryResponse, updateColorMutationResponse, color } from './mock_data';
+
+jest.mock('~/flash');
+
+Vue.use(VueApollo);
+
+const successfulQueryHandler = jest.fn().mockResolvedValue(colorQueryResponse);
+const successfulMutationHandler = jest.fn().mockResolvedValue(updateColorMutationResponse);
+const errorQueryHandler = jest.fn().mockRejectedValue('Error fetching epic color.');
+const errorMutationHandler = jest.fn().mockRejectedValue('An error occurred while updating color.');
+
+const defaultProps = {
+ allowEdit: true,
+ iid: '1',
+ fullPath: 'workspace-1',
+};
+
+describe('LabelsSelectRoot', () => {
+ let wrapper;
+
+ const findSidebarEditableItem = () => wrapper.findComponent(SidebarEditableItem);
+ const findDropdownValue = () => wrapper.findComponent(DropdownValue);
+ const findDropdownContents = () => wrapper.findComponent(DropdownContents);
+
+ const createComponent = ({
+ queryHandler = successfulQueryHandler,
+ mutationHandler = successfulMutationHandler,
+ propsData,
+ } = {}) => {
+ const mockApollo = createMockApollo([
+ [epicColorQuery, queryHandler],
+ [updateEpicColorMutation, mutationHandler],
+ ]);
+
+ wrapper = shallowMount(ColorSelectRoot, {
+ apolloProvider: mockApollo,
+ propsData: {
+ ...defaultProps,
+ ...propsData,
+ },
+ provide: {
+ canUpdate: true,
+ },
+ stubs: {
+ SidebarEditableItem,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('template', () => {
+ const defaultClasses = ['labels-select-wrapper', 'gl-relative'];
+
+ it.each`
+ variant | cssClass
+ ${'sidebar'} | ${defaultClasses}
+ ${'embedded'} | ${[...defaultClasses, 'is-embedded']}
+ `(
+ 'renders component root element with CSS class `$cssClass` when variant is "$variant"',
+ async ({ variant, cssClass }) => {
+ createComponent({
+ propsData: { variant },
+ });
+
+ expect(wrapper.classes()).toEqual(cssClass);
+ },
+ );
+ });
+
+ describe('if the variant is `sidebar`', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders SidebarEditableItem component', () => {
+ expect(findSidebarEditableItem().exists()).toBe(true);
+ });
+
+ it('renders correct props for the SidebarEditableItem component', () => {
+ expect(findSidebarEditableItem().props()).toMatchObject({
+ title: wrapper.vm.$options.i18n.widgetTitle,
+ canEdit: defaultProps.allowEdit,
+ loading: true,
+ });
+ });
+
+ describe('when colors are loaded', () => {
+ beforeEach(async () => {
+ createComponent();
+ await waitForPromises();
+ });
+
+ it('passes false `loading` prop to sidebar editable item', () => {
+ expect(findSidebarEditableItem().props('loading')).toBe(false);
+ });
+
+ it('renders dropdown value component when query colors is resolved', () => {
+ expect(findDropdownValue().props('selectedColor')).toMatchObject(color);
+ });
+ });
+ });
+
+ describe('if the variant is `embedded`', () => {
+ beforeEach(() => {
+ createComponent({ propsData: { iid: undefined, variant: DROPDOWN_VARIANT.Embedded } });
+ });
+
+ it('renders DropdownContents component', () => {
+ expect(findDropdownContents().exists()).toBe(true);
+ });
+
+ it('renders correct props for the DropdownContents component', () => {
+ expect(findDropdownContents().props()).toMatchObject({
+ variant: DROPDOWN_VARIANT.Embedded,
+ dropdownTitle: wrapper.vm.$options.i18n.assignColor,
+ dropdownButtonText: wrapper.vm.$options.i18n.dropdownButtonText,
+ });
+ });
+
+ it('handles DropdownContents setColor', () => {
+ findDropdownContents().vm.$emit('setColor', color);
+ expect(wrapper.emitted('updateSelectedColor')).toEqual([[color]]);
+ });
+ });
+
+ describe('when epicColorQuery errored', () => {
+ beforeEach(async () => {
+ createComponent({ queryHandler: errorQueryHandler });
+ await waitForPromises();
+ });
+
+ it('creates flash with error message', () => {
+ expect(createFlash).toHaveBeenCalledWith({
+ captureError: true,
+ message: 'Error fetching epic color.',
+ });
+ });
+ });
+
+ it('emits `updateSelectedColor` event on dropdown contents `setColor` event if iid is not set', () => {
+ createComponent({ propsData: { iid: undefined } });
+
+ findDropdownContents().vm.$emit('setColor', color);
+ expect(wrapper.emitted('updateSelectedColor')).toEqual([[color]]);
+ });
+
+ describe('when updating color for epic', () => {
+ beforeEach(() => {
+ createComponent();
+ findDropdownContents().vm.$emit('setColor', color);
+ });
+
+ it('sets the loading state', () => {
+ expect(findSidebarEditableItem().props('loading')).toBe(true);
+ });
+
+ it('updates color correctly after successful mutation', async () => {
+ await waitForPromises();
+ expect(findDropdownValue().props('selectedColor').color).toEqual(
+ updateColorMutationResponse.data.updateIssuableColor.issuable.color,
+ );
+ });
+
+ it('displays an error if mutation was rejected', async () => {
+ createComponent({ mutationHandler: errorMutationHandler });
+ findDropdownContents().vm.$emit('setColor', color);
+ await waitForPromises();
+
+ expect(createFlash).toHaveBeenCalledWith({
+ captureError: true,
+ error: expect.anything(),
+ message: 'An error occurred while updating color.',
+ });
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/color_select_dropdown/dropdown_contents_color_view_spec.js b/spec/frontend/vue_shared/components/color_select_dropdown/dropdown_contents_color_view_spec.js
new file mode 100644
index 00000000000..303824c77b3
--- /dev/null
+++ b/spec/frontend/vue_shared/components/color_select_dropdown/dropdown_contents_color_view_spec.js
@@ -0,0 +1,43 @@
+import { GlDropdownForm } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import DropdownContentsColorView from '~/vue_shared/components/color_select_dropdown/dropdown_contents_color_view.vue';
+import ColorItem from '~/vue_shared/components/color_select_dropdown/color_item.vue';
+import { ISSUABLE_COLORS } from '~/vue_shared/components/color_select_dropdown/constants';
+import { color as defaultColor } from './mock_data';
+
+const propsData = {
+ selectedColor: defaultColor,
+};
+
+describe('DropdownContentsColorView', () => {
+ let wrapper;
+
+ const createComponent = () => {
+ wrapper = shallowMount(DropdownContentsColorView, {
+ propsData,
+ });
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ const findColors = () => wrapper.findAllComponents(ColorItem);
+ const findColorList = () => wrapper.findComponent(GlDropdownForm);
+
+ it('renders color list', async () => {
+ expect(findColorList().exists()).toBe(true);
+ expect(findColors()).toHaveLength(ISSUABLE_COLORS.length);
+ });
+
+ it.each(ISSUABLE_COLORS)('emits an `input` event with %o on click on the option %#', (color) => {
+ const colorIndex = ISSUABLE_COLORS.indexOf(color);
+ findColors().at(colorIndex).trigger('click');
+
+ expect(wrapper.emitted('input')[0][0]).toMatchObject(color);
+ });
+});
diff --git a/spec/frontend/vue_shared/components/color_select_dropdown/dropdown_contents_spec.js b/spec/frontend/vue_shared/components/color_select_dropdown/dropdown_contents_spec.js
new file mode 100644
index 00000000000..74f50b878e2
--- /dev/null
+++ b/spec/frontend/vue_shared/components/color_select_dropdown/dropdown_contents_spec.js
@@ -0,0 +1,113 @@
+import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
+import { DROPDOWN_VARIANT } from '~/vue_shared/components/color_select_dropdown/constants';
+import DropdownContents from '~/vue_shared/components/color_select_dropdown/dropdown_contents.vue';
+import DropdownContentsColorView from '~/vue_shared/components/color_select_dropdown/dropdown_contents_color_view.vue';
+
+import { color } from './mock_data';
+
+const showDropdown = jest.fn();
+const focusInput = jest.fn();
+
+const defaultProps = {
+ dropdownTitle: '',
+ selectedColor: color,
+ dropdownButtonText: '',
+ variant: '',
+ isVisible: false,
+};
+
+const GlDropdownStub = {
+ template: `
+ <div>
+ <slot name="header"></slot>
+ <slot></slot>
+ </div>
+ `,
+ methods: {
+ show: showDropdown,
+ hide: jest.fn(),
+ },
+};
+
+const DropdownHeaderStub = {
+ template: `
+ <div>Hello, I am a header</div>
+ `,
+ methods: {
+ focusInput,
+ },
+};
+
+describe('DropdownContent', () => {
+ let wrapper;
+
+ const createComponent = ({ propsData = {} } = {}) => {
+ wrapper = shallowMount(DropdownContents, {
+ propsData: {
+ ...defaultProps,
+ ...propsData,
+ },
+ stubs: {
+ GlDropdown: GlDropdownStub,
+ DropdownHeader: DropdownHeaderStub,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ const findColorView = () => wrapper.findComponent(DropdownContentsColorView);
+ const findDropdownHeader = () => wrapper.findComponent(DropdownHeaderStub);
+ const findDropdown = () => wrapper.findComponent(GlDropdownStub);
+
+ it('calls dropdown `show` method on `isVisible` prop change', async () => {
+ createComponent();
+ await wrapper.setProps({
+ isVisible: true,
+ });
+
+ expect(showDropdown).toHaveBeenCalledTimes(1);
+ });
+
+ it('does not emit `setColor` event on dropdown hide if color did not change', () => {
+ createComponent();
+ findDropdown().vm.$emit('hide');
+
+ expect(wrapper.emitted('setColor')).toBeUndefined();
+ });
+
+ it('emits `setColor` event on dropdown hide if color changed on non-sidebar widget', async () => {
+ createComponent({ propsData: { variant: DROPDOWN_VARIANT.Embedded } });
+ const updatedColor = {
+ title: 'Blue-gray',
+ color: '#6699cc',
+ };
+ findColorView().vm.$emit('input', updatedColor);
+ await nextTick();
+ findDropdown().vm.$emit('hide');
+
+ expect(wrapper.emitted('setColor')).toEqual([[updatedColor]]);
+ });
+
+ it('emits `setColor` event on visibility change if color changed on sidebar widget', async () => {
+ createComponent({ propsData: { variant: DROPDOWN_VARIANT.Sidebar, isVisible: true } });
+ const updatedColor = {
+ title: 'Blue-gray',
+ color: '#6699cc',
+ };
+ findColorView().vm.$emit('input', updatedColor);
+ wrapper.setProps({ isVisible: false });
+ await nextTick();
+
+ expect(wrapper.emitted('setColor')).toEqual([[updatedColor]]);
+ });
+
+ it('renders header', () => {
+ createComponent();
+
+ expect(findDropdownHeader().exists()).toBe(true);
+ });
+});
diff --git a/spec/frontend/vue_shared/components/color_select_dropdown/dropdown_header_spec.js b/spec/frontend/vue_shared/components/color_select_dropdown/dropdown_header_spec.js
new file mode 100644
index 00000000000..d203d78477f
--- /dev/null
+++ b/spec/frontend/vue_shared/components/color_select_dropdown/dropdown_header_spec.js
@@ -0,0 +1,40 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlButton } from '@gitlab/ui';
+import DropdownHeader from '~/vue_shared/components/color_select_dropdown/dropdown_header.vue';
+
+const propsData = {
+ dropdownTitle: 'Epic color',
+};
+
+describe('DropdownHeader', () => {
+ let wrapper;
+
+ const createComponent = () => {
+ wrapper = shallowMount(DropdownHeader, { propsData });
+ };
+
+ const findButton = () => wrapper.findComponent(GlButton);
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders the correct title', () => {
+ expect(wrapper.text()).toBe(propsData.dropdownTitle);
+ });
+
+ it('renders a close button', () => {
+ expect(findButton().attributes('aria-label')).toBe('Close');
+ });
+
+ it('emits `closeDropdown` event on button click', () => {
+ expect(wrapper.emitted('closeDropdown')).toBeUndefined();
+ findButton().vm.$emit('click');
+
+ expect(wrapper.emitted('closeDropdown')).toEqual([[]]);
+ });
+});
diff --git a/spec/frontend/vue_shared/components/color_select_dropdown/dropdown_value_spec.js b/spec/frontend/vue_shared/components/color_select_dropdown/dropdown_value_spec.js
new file mode 100644
index 00000000000..f22592dd604
--- /dev/null
+++ b/spec/frontend/vue_shared/components/color_select_dropdown/dropdown_value_spec.js
@@ -0,0 +1,46 @@
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+
+import ColorItem from '~/vue_shared/components/color_select_dropdown/color_item.vue';
+import DropdownValue from '~/vue_shared/components/color_select_dropdown/dropdown_value.vue';
+
+import { color } from './mock_data';
+
+const propsData = {
+ selectedColor: color,
+};
+
+describe('DropdownValue', () => {
+ let wrapper;
+
+ const findColorItems = () => wrapper.findAllComponents(ColorItem);
+
+ const createComponent = () => {
+ wrapper = shallowMountExtended(DropdownValue, { propsData });
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('when there is a color set', () => {
+ it('renders the color', () => {
+ expect(findColorItems()).toHaveLength(2);
+ });
+
+ it.each`
+ index | cssClass
+ ${0} | ${['gl-font-base', 'gl-line-height-24']}
+ ${1} | ${['hide-collapsed']}
+ `(
+ 'passes correct props to the ColorItem with CSS class `$cssClass`',
+ async ({ index, cssClass }) => {
+ expect(findColorItems().at(index).props()).toMatchObject(propsData.selectedColor);
+ expect(findColorItems().at(index).classes()).toEqual(cssClass);
+ },
+ );
+ });
+});
diff --git a/spec/frontend/vue_shared/components/color_select_dropdown/mock_data.js b/spec/frontend/vue_shared/components/color_select_dropdown/mock_data.js
new file mode 100644
index 00000000000..097f47cc731
--- /dev/null
+++ b/spec/frontend/vue_shared/components/color_select_dropdown/mock_data.js
@@ -0,0 +1,30 @@
+export const color = {
+ color: '#217645',
+ title: 'Green',
+};
+
+export const colorQueryResponse = {
+ data: {
+ workspace: {
+ id: 'gid://gitlab/Workspace/1',
+ issuable: {
+ __typename: 'Epic',
+ id: 'gid://gitlab/Epic/1',
+ color: '#217645',
+ },
+ },
+ },
+};
+
+export const updateColorMutationResponse = {
+ data: {
+ updateIssuableColor: {
+ issuable: {
+ __typename: 'Epic',
+ id: 'gid://gitlab/Epic/1',
+ color: '#217645',
+ },
+ errors: [],
+ },
+ },
+};
diff --git a/spec/frontend/vue_shared/components/confidentiality_badge_spec.js b/spec/frontend/vue_shared/components/confidentiality_badge_spec.js
index 9d11fbbaf55..e1860d3399b 100644
--- a/spec/frontend/vue_shared/components/confidentiality_badge_spec.js
+++ b/spec/frontend/vue_shared/components/confidentiality_badge_spec.js
@@ -29,8 +29,8 @@ describe('ConfidentialityBadge', () => {
it.each`
workspaceType | issuableType | expectedTooltip
- ${WorkspaceType.project} | ${IssuableType.Issue} | ${'Only project members with at least Reporter role can view or be notified about this issue.'}
- ${WorkspaceType.group} | ${IssuableType.Epic} | ${'Only group members with at least Reporter role can view or be notified about this epic.'}
+ ${WorkspaceType.project} | ${IssuableType.Issue} | ${'Only project members with at least the Reporter role, the author, and assignees can view or be notified about this issue.'}
+ ${WorkspaceType.group} | ${IssuableType.Epic} | ${'Only group members with at least the Reporter role can view or be notified about this epic.'}
`(
'should render gl-badge with correct tooltip when workspaceType is $workspaceType and issuableType is $issuableType',
({ workspaceType, issuableType, expectedTooltip }) => {
diff --git a/spec/frontend/vue_shared/components/content_viewer/viewers/markdown_viewer_spec.js b/spec/frontend/vue_shared/components/content_viewer/viewers/markdown_viewer_spec.js
index 1397fb0405e..01ef52c6af9 100644
--- a/spec/frontend/vue_shared/components/content_viewer/viewers/markdown_viewer_spec.js
+++ b/spec/frontend/vue_shared/components/content_viewer/viewers/markdown_viewer_spec.js
@@ -1,3 +1,4 @@
+import { GlSkeletonLoader } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import $ from 'jquery';
@@ -39,10 +40,10 @@ describe('MarkdownViewer', () => {
});
});
- it('renders an animation container while the markdown is loading', () => {
+ it('renders a skeleton loader while the markdown is loading', () => {
createComponent();
- expect(wrapper.find('.animation-container').exists()).toBe(true);
+ expect(wrapper.findComponent(GlSkeletonLoader).exists()).toBe(true);
});
it('renders markdown preview preview renders and loads rendered markdown from server', () => {
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 f03a2e7934f..51161a1a0ef 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
@@ -77,7 +77,7 @@ describe('LabelToken', () => {
describe('getActiveLabel', () => {
it('returns label object from labels array based on provided `currentValue` param', () => {
- expect(wrapper.vm.getActiveLabel(mockLabels, 'foo label')).toEqual(mockRegularLabel);
+ expect(wrapper.vm.getActiveLabel(mockLabels, 'Foo Label')).toEqual(mockRegularLabel);
});
});
diff --git a/spec/frontend/vue_shared/components/form/input_copy_toggle_visibility_spec.js b/spec/frontend/vue_shared/components/form/input_copy_toggle_visibility_spec.js
index e636f58d868..e1da8b690af 100644
--- a/spec/frontend/vue_shared/components/form/input_copy_toggle_visibility_spec.js
+++ b/spec/frontend/vue_shared/components/form/input_copy_toggle_visibility_spec.js
@@ -66,7 +66,7 @@ describe('InputCopyToggleVisibility', () => {
});
it('displays value as hidden', () => {
- expect(findFormInputGroup().props('value')).toBe('********************');
+ expect(findFormInput().element.value).toBe('********************');
});
it('saves actual value to clipboard when manually copied', () => {
@@ -77,6 +77,16 @@ describe('InputCopyToggleVisibility', () => {
expect(event.preventDefault).toHaveBeenCalled();
});
+ it('emits `copy` event when manually copied the token', () => {
+ expect(wrapper.emitted('copy')).toBeUndefined();
+
+ findFormInput().element.dispatchEvent(createCopyEvent());
+
+ expect(wrapper.emitted()).toHaveProperty('copy');
+ expect(wrapper.emitted('copy')).toHaveLength(1);
+ expect(wrapper.emitted('copy')[0]).toEqual([]);
+ });
+
describe('visibility toggle button', () => {
it('renders a reveal button', () => {
const revealButton = findRevealButton();
@@ -97,7 +107,7 @@ describe('InputCopyToggleVisibility', () => {
});
it('displays value', () => {
- expect(findFormInputGroup().props('value')).toBe(valueProp);
+ expect(findFormInput().element.value).toBe(valueProp);
});
it('renders a hide button', () => {
@@ -135,6 +145,8 @@ describe('InputCopyToggleVisibility', () => {
});
it('emits `copy` event', () => {
+ expect(wrapper.emitted()).toHaveProperty('copy');
+ expect(wrapper.emitted('copy')).toHaveLength(1);
expect(wrapper.emitted('copy')[0]).toEqual([]);
});
});
@@ -147,25 +159,52 @@ describe('InputCopyToggleVisibility', () => {
});
it('displays value as hidden with 20 asterisks', () => {
- expect(findFormInputGroup().props('value')).toBe('********************');
+ expect(findFormInput().element.value).toBe('********************');
});
});
describe('when `initialVisibility` prop is `true`', () => {
+ const label = 'My label';
+
beforeEach(() => {
createComponent({
propsData: {
value: valueProp,
initialVisibility: true,
+ label,
+ 'label-for': 'my-input',
+ formInputGroupProps: {
+ id: 'my-input',
+ },
},
});
});
it('displays value', () => {
- expect(findFormInputGroup().props('value')).toBe(valueProp);
+ expect(findFormInput().element.value).toBe(valueProp);
});
itDoesNotModifyCopyEvent();
+
+ describe('when input is clicked', () => {
+ it('selects input value', async () => {
+ const mockSelect = jest.fn();
+ wrapper.vm.$refs.input.$el.select = mockSelect;
+ await wrapper.findByLabelText(label).trigger('click');
+
+ expect(mockSelect).toHaveBeenCalled();
+ });
+ });
+
+ describe('when label is clicked', () => {
+ it('selects input value', async () => {
+ const mockSelect = jest.fn();
+ wrapper.vm.$refs.input.$el.select = mockSelect;
+ await wrapper.find('label').trigger('click');
+
+ expect(mockSelect).toHaveBeenCalled();
+ });
+ });
});
describe('when `showToggleVisibilityButton` is `false`', () => {
@@ -184,7 +223,7 @@ describe('InputCopyToggleVisibility', () => {
});
it('displays value', () => {
- expect(findFormInputGroup().props('value')).toBe(valueProp);
+ expect(findFormInput().element.value).toBe(valueProp);
});
itDoesNotModifyCopyEvent();
@@ -204,16 +243,30 @@ describe('InputCopyToggleVisibility', () => {
});
});
- it('passes `formInputGroupProps` prop to `GlFormInputGroup`', () => {
+ it('passes `formInputGroupProps` prop only to the input', () => {
createComponent({
propsData: {
formInputGroupProps: {
- label: 'Foo bar',
+ name: 'Foo bar',
+ 'data-qa-selector': 'Foo bar',
+ class: 'Foo bar',
+ id: 'Foo bar',
},
},
});
- expect(findFormInputGroup().props('label')).toBe('Foo bar');
+ expect(findFormInput().attributes()).toMatchObject({
+ name: 'Foo bar',
+ 'data-qa-selector': 'Foo bar',
+ class: expect.stringContaining('Foo bar'),
+ id: 'Foo bar',
+ });
+
+ const attributesInputGroup = findFormInputGroup().attributes();
+ expect(attributesInputGroup.name).toBeUndefined();
+ expect(attributesInputGroup['data-qa-selector']).toBeUndefined();
+ expect(attributesInputGroup.class).not.toContain('Foo bar');
+ expect(attributesInputGroup.id).toBeUndefined();
});
it('passes `copyButtonTitle` prop to `ClipboardButton`', () => {
diff --git a/spec/frontend/vue_shared/components/notes/__snapshots__/noteable_warning_spec.js.snap b/spec/frontend/vue_shared/components/notes/__snapshots__/noteable_warning_spec.js.snap
index f878d685b6d..8a187f3cb1f 100644
--- a/spec/frontend/vue_shared/components/notes/__snapshots__/noteable_warning_spec.js.snap
+++ b/spec/frontend/vue_shared/components/notes/__snapshots__/noteable_warning_spec.js.snap
@@ -10,7 +10,7 @@ exports[`Issue Warning Component when issue is locked but not confidential rende
href="locked-path"
target="_blank"
>
- Learn more
+ Learn more.
</gl-link-stub>
</span>
`;
@@ -25,7 +25,7 @@ exports[`Issue Warning Component when noteable is confidential but not locked re
href="confidential-path"
target="_blank"
>
- Learn more
+ Learn more.
</gl-link-stub>
</span>
`;
diff --git a/spec/frontend/vue_shared/components/notes/system_note_spec.js b/spec/frontend/vue_shared/components/notes/system_note_spec.js
index 65f79bab005..98b04ede943 100644
--- a/spec/frontend/vue_shared/components/notes/system_note_spec.js
+++ b/spec/frontend/vue_shared/components/notes/system_note_spec.js
@@ -1,13 +1,11 @@
import MockAdapter from 'axios-mock-adapter';
import { mount } from '@vue/test-utils';
+import $ from 'jquery';
import waitForPromises from 'helpers/wait_for_promises';
-import initMRPopovers from '~/mr_popover/index';
import createStore from '~/notes/stores';
import IssueSystemNote from '~/vue_shared/components/notes/system_note.vue';
import axios from '~/lib/utils/axios_utils';
-jest.mock('~/mr_popover/index', () => jest.fn());
-
describe('system note component', () => {
let vm;
let props;
@@ -76,10 +74,12 @@ describe('system note component', () => {
expect(vm.find('.system-note-message').html()).toContain('<span>closed</span>');
});
- it('should initMRPopovers onMount', () => {
+ it('should renderGFM onMount', () => {
+ const renderGFMSpy = jest.spyOn($.fn, 'renderGFM');
+
createComponent(props);
- expect(initMRPopovers).toHaveBeenCalled();
+ expect(renderGFMSpy).toHaveBeenCalled();
});
it('renders outdated code lines', async () => {
diff --git a/spec/frontend/vue_shared/components/papa_parse_alert_spec.js b/spec/frontend/vue_shared/components/papa_parse_alert_spec.js
index 9be2de17d01..ff4febd647e 100644
--- a/spec/frontend/vue_shared/components/papa_parse_alert_spec.js
+++ b/spec/frontend/vue_shared/components/papa_parse_alert_spec.js
@@ -22,7 +22,7 @@ describe('app/assets/javascripts/vue_shared/components/papa_parse_alert.vue', ()
it('should render alert with correct props', async () => {
createComponent({ errorMessages: [{ code: 'MissingQuotes' }] });
- await nextTick;
+ await nextTick();
expect(findAlert().props()).toMatchObject({
variant: 'danger',
@@ -37,7 +37,7 @@ describe('app/assets/javascripts/vue_shared/components/papa_parse_alert.vue', ()
createComponent({
errorMessages: [{ code: 'NotDefined', message: 'Error code is undefined' }],
});
- await nextTick;
+ await nextTick();
expect(findAlert().text()).toContain('Error code is undefined');
});
diff --git a/spec/frontend/vue_shared/components/registry/registry_search_spec.js b/spec/frontend/vue_shared/components/registry/registry_search_spec.js
index f5ef5b3d443..20716e79a04 100644
--- a/spec/frontend/vue_shared/components/registry/registry_search_spec.js
+++ b/spec/frontend/vue_shared/components/registry/registry_search_spec.js
@@ -11,7 +11,7 @@ describe('Registry Search', () => {
const findFilteredSearch = () => wrapper.find(GlFilteredSearch);
const defaultProps = {
- filter: [],
+ filters: [],
sorting: { sort: 'asc', orderBy: 'name' },
tokens: [{ type: 'foo' }],
sortableFields: [
@@ -123,7 +123,7 @@ describe('Registry Search', () => {
});
describe('query string calculation', () => {
- const filter = [
+ const filters = [
{ type: FILTERED_SEARCH_TERM, value: { data: 'one' } },
{ type: FILTERED_SEARCH_TERM, value: { data: 'two' } },
{ type: 'typeOne', value: { data: 'value_one' } },
@@ -131,7 +131,7 @@ describe('Registry Search', () => {
];
it('aggregates the filter in the correct object', () => {
- mountComponent({ ...defaultProps, filter });
+ mountComponent({ ...defaultProps, filters });
findFilteredSearch().vm.$emit('submit');
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 001b6ee4a6f..7173abe1316 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
@@ -48,12 +48,12 @@ describe('RunnerInstructionsModal component', () => {
const findModal = () => wrapper.findComponent(GlModal);
const findPlatformButtonGroup = () => wrapper.findByTestId('platform-buttons');
const findPlatformButtons = () => findPlatformButtonGroup().findAllComponents(GlButton);
- const findOsxPlatformButton = () => wrapper.find({ ref: 'osx' });
const findArchitectureDropdownItems = () => wrapper.findAllByTestId('architecture-dropdown-item');
+ const findBinaryDownloadButton = () => wrapper.findByTestId('binary-download-button');
const findBinaryInstructions = () => wrapper.findByTestId('binary-instructions');
const findRegisterCommand = () => wrapper.findByTestId('register-command');
- const createComponent = ({ props, ...options } = {}) => {
+ const createComponent = ({ props, shown = true, ...options } = {}) => {
const requestHandlers = [
[getRunnerPlatformsQuery, runnerPlatformsHandler],
[getRunnerSetupInstructionsQuery, runnerSetupInstructionsHandler],
@@ -72,169 +72,202 @@ describe('RunnerInstructionsModal component', () => {
...options,
}),
);
+
+ // trigger open modal
+ if (shown) {
+ findModal().vm.$emit('shown');
+ }
};
beforeEach(async () => {
runnerPlatformsHandler = jest.fn().mockResolvedValue(mockGraphqlRunnerPlatforms);
runnerSetupInstructionsHandler = jest.fn().mockResolvedValue(mockGraphqlInstructions);
-
- createComponent();
- await waitForPromises();
});
afterEach(() => {
wrapper.destroy();
});
- it('should not show alert', () => {
- expect(findAlert().exists()).toBe(false);
- });
-
- it('should contain a number of platforms buttons', () => {
- expect(runnerPlatformsHandler).toHaveBeenCalledWith({});
+ describe('when the modal is shown', () => {
+ beforeEach(async () => {
+ createComponent();
+ await waitForPromises();
+ });
- const buttons = findPlatformButtons();
+ it('should not show alert', async () => {
+ expect(findAlert().exists()).toBe(false);
+ });
- expect(buttons).toHaveLength(mockGraphqlRunnerPlatforms.data.runnerPlatforms.nodes.length);
- });
+ it('should contain a number of platforms buttons', () => {
+ expect(runnerPlatformsHandler).toHaveBeenCalledWith({});
- it('should contain a number of dropdown items for the architecture options', () => {
- expect(findArchitectureDropdownItems()).toHaveLength(
- mockGraphqlRunnerPlatforms.data.runnerPlatforms.nodes[0].architectures.nodes.length,
- );
- });
+ const buttons = findPlatformButtons();
- describe('should display default instructions', () => {
- const { installInstructions, registerInstructions } = mockGraphqlInstructions.data.runnerSetup;
+ expect(buttons).toHaveLength(mockGraphqlRunnerPlatforms.data.runnerPlatforms.nodes.length);
+ });
- it('runner instructions are requested', () => {
- expect(runnerSetupInstructionsHandler).toHaveBeenCalledWith({
- platform: 'linux',
- architecture: 'amd64',
- });
+ it('should contain a number of dropdown items for the architecture options', () => {
+ expect(findArchitectureDropdownItems()).toHaveLength(
+ mockGraphqlRunnerPlatforms.data.runnerPlatforms.nodes[0].architectures.nodes.length,
+ );
});
- it('binary instructions are shown', async () => {
- await waitForPromises();
- const instructions = findBinaryInstructions().text();
+ describe('should display default instructions', () => {
+ const {
+ installInstructions,
+ registerInstructions,
+ } = mockGraphqlInstructions.data.runnerSetup;
- expect(instructions).toBe(installInstructions);
- });
+ it('runner instructions are requested', () => {
+ expect(runnerSetupInstructionsHandler).toHaveBeenCalledWith({
+ platform: 'linux',
+ architecture: 'amd64',
+ });
+ });
- it('register command is shown with a replaced token', async () => {
- await waitForPromises();
- const instructions = findRegisterCommand().text();
+ it('binary instructions are shown', async () => {
+ const instructions = findBinaryInstructions().text();
- expect(instructions).toBe(
- 'sudo gitlab-runner register --url http://gdk.test:3000/ --registration-token MY_TOKEN',
- );
- });
+ expect(instructions).toBe(installInstructions);
+ });
- describe('when a register token is not shown', () => {
- beforeEach(async () => {
- createComponent({ props: { registrationToken: undefined } });
- await waitForPromises();
+ it('register command is shown with a replaced token', async () => {
+ const command = findRegisterCommand().text();
+
+ expect(command).toBe(
+ 'sudo gitlab-runner register --url http://gdk.test:3000/ --registration-token MY_TOKEN',
+ );
});
- it('register command is shown without a defined registration token', () => {
- const instructions = findRegisterCommand().text();
+ describe('when a register token is not shown', () => {
+ beforeEach(async () => {
+ createComponent({ props: { registrationToken: undefined } });
+ await waitForPromises();
+ });
+
+ it('register command is shown without a defined registration token', () => {
+ const instructions = findRegisterCommand().text();
- expect(instructions).toBe(registerInstructions);
+ expect(instructions).toBe(registerInstructions);
+ });
});
- });
- describe('when the modal is shown', () => {
- it('sets the focus on the selected platform', () => {
- findPlatformButtons().at(0).element.focus = jest.fn();
+ describe('when providing a defaultPlatformName', () => {
+ beforeEach(async () => {
+ createComponent({ props: { defaultPlatformName: 'osx' } });
+ await waitForPromises();
+ });
+
+ it('runner instructions for the default selected platform are requested', () => {
+ expect(runnerSetupInstructionsHandler).toHaveBeenCalledWith({
+ platform: 'osx',
+ architecture: 'amd64',
+ });
+ });
+
+ it('sets the focus on the default selected platform', () => {
+ const findOsxPlatformButton = () => wrapper.find({ ref: 'osx' });
+
+ findOsxPlatformButton().element.focus = jest.fn();
- findModal().vm.$emit('shown');
+ findModal().vm.$emit('shown');
- expect(findPlatformButtons().at(0).element.focus).toHaveBeenCalled();
+ expect(findOsxPlatformButton().element.focus).toHaveBeenCalled();
+ });
});
});
- describe('when providing a defaultPlatformName', () => {
+ describe('after a platform and architecture are selected', () => {
+ const windowsIndex = 2;
+ const { installInstructions } = mockGraphqlInstructionsWindows.data.runnerSetup;
+
beforeEach(async () => {
- createComponent({ props: { defaultPlatformName: 'osx' } });
+ runnerSetupInstructionsHandler.mockResolvedValue(mockGraphqlInstructionsWindows);
+
+ findPlatformButtons().at(windowsIndex).vm.$emit('click');
await waitForPromises();
});
- it('runner instructions for the default selected platform are requested', () => {
- expect(runnerSetupInstructionsHandler).toHaveBeenCalledWith({
- platform: 'osx',
+ it('runner instructions are requested', () => {
+ expect(runnerSetupInstructionsHandler).toHaveBeenLastCalledWith({
+ platform: 'windows',
architecture: 'amd64',
});
});
- it('sets the focus on the default selected platform', () => {
- findOsxPlatformButton().element.focus = jest.fn();
-
- findModal().vm.$emit('shown');
+ it('architecture download link is updated', () => {
+ const architectures =
+ mockGraphqlRunnerPlatforms.data.runnerPlatforms.nodes[windowsIndex].architectures.nodes;
- expect(findOsxPlatformButton().element.focus).toHaveBeenCalled();
+ expect(findBinaryDownloadButton().attributes('href')).toBe(
+ architectures[0].downloadLocation,
+ );
});
- });
- });
- describe('after a platform and architecture are selected', () => {
- const { installInstructions } = mockGraphqlInstructionsWindows.data.runnerSetup;
+ it('other binary instructions are shown', () => {
+ const instructions = findBinaryInstructions().text();
- beforeEach(async () => {
- runnerSetupInstructionsHandler.mockResolvedValue(mockGraphqlInstructionsWindows);
+ expect(instructions).toBe(installInstructions);
+ });
- findPlatformButtons().at(2).vm.$emit('click'); // another option, happens to be windows
- await nextTick();
+ it('register command is shown', () => {
+ const command = findRegisterCommand().text();
- findArchitectureDropdownItems().at(1).vm.$emit('click'); // another option
- await nextTick();
- });
+ expect(command).toBe(
+ './gitlab-runner.exe register --url http://gdk.test:3000/ --registration-token MY_TOKEN',
+ );
+ });
+
+ it('runner instructions are requested with another architecture', async () => {
+ findArchitectureDropdownItems().at(1).vm.$emit('click');
+ await waitForPromises();
- it('runner instructions are requested', () => {
- expect(runnerSetupInstructionsHandler).toHaveBeenCalledWith({
- platform: 'windows',
- architecture: '386',
+ expect(runnerSetupInstructionsHandler).toHaveBeenLastCalledWith({
+ platform: 'windows',
+ architecture: '386',
+ });
});
});
- it('other binary instructions are shown', () => {
- const instructions = findBinaryInstructions().text();
+ describe('when the modal resizes', () => {
+ it('to an xs viewport', async () => {
+ MockResizeObserver.mockResize('xs');
+ await nextTick();
- expect(instructions).toBe(installInstructions);
- });
+ expect(findPlatformButtonGroup().attributes('vertical')).toBeTruthy();
+ });
- it('register command is shown', () => {
- const command = findRegisterCommand().text();
+ it('to a non-xs viewport', async () => {
+ MockResizeObserver.mockResize('sm');
+ await nextTick();
- expect(command).toBe(
- './gitlab-runner.exe register --url http://gdk.test:3000/ --registration-token MY_TOKEN',
- );
+ expect(findPlatformButtonGroup().props('vertical')).toBeFalsy();
+ });
});
});
- describe('when the modal resizes', () => {
- it('to an xs viewport', async () => {
- MockResizeObserver.mockResize('xs');
- await nextTick();
-
- expect(findPlatformButtonGroup().attributes('vertical')).toBeTruthy();
+ describe('when the modal is not shown', () => {
+ beforeEach(async () => {
+ createComponent({ shown: false });
+ await waitForPromises();
});
- it('to a non-xs viewport', async () => {
- MockResizeObserver.mockResize('sm');
- await nextTick();
-
- expect(findPlatformButtonGroup().props('vertical')).toBeFalsy();
+ it('does not fetch instructions', () => {
+ expect(runnerPlatformsHandler).not.toHaveBeenCalled();
+ expect(runnerSetupInstructionsHandler).not.toHaveBeenCalled();
});
});
describe('when apollo is loading', () => {
- it('should show a skeleton loader', async () => {
+ beforeEach(() => {
createComponent();
+ });
+
+ it('should show a skeleton loader', async () => {
expect(findSkeletonLoader().exists()).toBe(true);
expect(findGlLoadingIcon().exists()).toBe(false);
- await nextTick();
- jest.runOnlyPendingTimers();
+ // wait on fetch of both `platforms` and `instructions`
await nextTick();
await nextTick();
@@ -242,7 +275,6 @@ describe('RunnerInstructionsModal component', () => {
});
it('once loaded, should not show a loading state', async () => {
- createComponent();
await waitForPromises();
expect(findSkeletonLoader().exists()).toBe(false);
@@ -255,7 +287,6 @@ describe('RunnerInstructionsModal component', () => {
runnerSetupInstructionsHandler.mockRejectedValue();
createComponent();
-
await waitForPromises();
});
@@ -287,6 +318,7 @@ describe('RunnerInstructionsModal component', () => {
mockShow = jest.fn();
createComponent({
+ shown: false,
stubs: {
GlModal: getGlModalStub({ show: mockShow }),
},
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 9a95a838291..986d76d2b95 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
@@ -1,6 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
-import { nextTick } from 'vue';
-import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import RunnerInstructions from '~/vue_shared/components/runner_instructions/runner_instructions.vue';
import RunnerInstructionsModal from '~/vue_shared/components/runner_instructions/runner_instructions_modal.vue';
@@ -11,7 +10,11 @@ describe('RunnerInstructions component', () => {
const findModal = () => wrapper.findComponent(RunnerInstructionsModal);
const createComponent = () => {
- wrapper = extendedWrapper(shallowMount(RunnerInstructions));
+ wrapper = shallowMountExtended(RunnerInstructions, {
+ directives: {
+ GlModal: createMockDirective(),
+ },
+ });
};
beforeEach(() => {
@@ -23,19 +26,12 @@ describe('RunnerInstructions component', () => {
});
it('should show the "Show runner installation instructions" button', () => {
- expect(findModalButton().exists()).toBe(true);
expect(findModalButton().text()).toBe('Show runner installation instructions');
});
- it('should not render the modal once mounted', () => {
- expect(findModal().exists()).toBe(false);
- });
-
- it('should render the modal once clicked', async () => {
- findModalButton().vm.$emit('click');
-
- await nextTick();
+ it('should render the modal', () => {
+ const modalId = getBinding(findModal().element, 'gl-modal');
- expect(findModal().exists()).toBe(true);
+ expect(findModalButton().attributes('modal-id')).toBe(modalId);
});
});
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 42202db4935..00c8e3a814a 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
@@ -226,12 +226,7 @@ describe('DropdownContentsLabelsView', () => {
preventDefault: fakePreventDefault,
});
- expect(wrapper.vm.updateSelectedLabels).toHaveBeenCalledWith([
- {
- ...mockLabels[2],
- set: true,
- },
- ]);
+ expect(wrapper.vm.updateSelectedLabels).toHaveBeenCalledWith([mockLabels[2]]);
});
it('calls action `toggleDropdownContents` when Esc key is pressed', () => {
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/label_item_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/label_item_spec.js
index bd1705e7693..bedb6204088 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/label_item_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/label_item_spec.js
@@ -1,4 +1,4 @@
-import { GlIcon, GlLink } from '@gitlab/ui';
+import { GlLink } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import LabelItem from '~/vue_shared/components/sidebar/labels_select_vue/label_item.vue';
@@ -45,18 +45,26 @@ describe('LabelItem', () => {
wrapperTemp.destroy();
});
- it('renders visible gl-icon component when `isLabelSet` prop is true', () => {
- const wrapperTemp = createComponent({
- isLabelSet: true,
- });
-
- const iconEl = wrapperTemp.find(GlIcon);
-
- expect(iconEl.isVisible()).toBe(true);
- expect(iconEl.props('name')).toBe('mobile-issue-close');
-
- wrapperTemp.destroy();
- });
+ it.each`
+ isLabelSet | isLabelIndeterminate | testId | iconName
+ ${true} | ${false} | ${'checked-icon'} | ${'mobile-issue-close'}
+ ${false} | ${true} | ${'indeterminate-icon'} | ${'dash'}
+ `(
+ 'renders visible gl-icon component when `isLabelSet` prop is $isLabelSet and `isLabelIndeterminate` is $isLabelIndeterminate',
+ ({ isLabelSet, isLabelIndeterminate, testId, iconName }) => {
+ const wrapperTemp = createComponent({
+ isLabelSet,
+ isLabelIndeterminate,
+ });
+
+ const iconEl = wrapperTemp.find(`[data-testid="${testId}"]`);
+
+ expect(iconEl.isVisible()).toBe(true);
+ expect(iconEl.props('name')).toBe(iconName);
+
+ wrapperTemp.destroy();
+ },
+ );
it('renders visible span element as placeholder instead of gl-icon when `isLabelSet` prop is false', () => {
const wrapperTemp = createComponent({
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 31819d0e2f7..c150410ff8e 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
@@ -46,9 +46,15 @@ describe('LabelsSelectRoot', () => {
describe('methods', () => {
describe('handleVuexActionDispatch', () => {
+ const touchedLabels = [
+ {
+ id: 2,
+ touched: true,
+ },
+ ];
+
it('calls `handleDropdownClose` when params `action.type` is `toggleDropdownContents` and state has `showDropdownButton` & `showDropdownContents` props `false`', () => {
createComponent();
- jest.spyOn(wrapper.vm, 'handleDropdownClose').mockImplementation();
wrapper.vm.handleVuexActionDispatch(
{ type: 'toggleDropdownContents' },
@@ -59,14 +65,12 @@ describe('LabelsSelectRoot', () => {
},
);
- expect(wrapper.vm.handleDropdownClose).toHaveBeenCalledWith(
- expect.arrayContaining([
- {
- id: 2,
- touched: true,
- },
- ]),
- );
+ // We're utilizing `onDropdownClose` event emitted from the component to always include `touchedLabels`
+ // while the first param of the method is the labels list which were added/removed.
+ expect(wrapper.emitted('updateSelectedLabels')).toBeTruthy();
+ expect(wrapper.emitted('updateSelectedLabels')[0]).toEqual([touchedLabels]);
+ expect(wrapper.emitted('onDropdownClose')).toBeTruthy();
+ expect(wrapper.emitted('onDropdownClose')[0]).toEqual([touchedLabels]);
});
it('calls `handleDropdownClose` with state.labels filterd using `set` prop when dropdown variant is `embedded`', () => {
@@ -75,8 +79,6 @@ describe('LabelsSelectRoot', () => {
variant: 'embedded',
});
- jest.spyOn(wrapper.vm, 'handleDropdownClose').mockImplementation();
-
wrapper.vm.handleVuexActionDispatch(
{ type: 'toggleDropdownContents' },
{
@@ -86,34 +88,17 @@ describe('LabelsSelectRoot', () => {
},
);
- expect(wrapper.vm.handleDropdownClose).toHaveBeenCalledWith(
- expect.arrayContaining([
+ expect(wrapper.emitted('updateSelectedLabels')).toBeTruthy();
+ expect(wrapper.emitted('updateSelectedLabels')[0]).toEqual([
+ [
{
id: 2,
set: true,
},
- ]),
- );
- });
- });
-
- describe('handleDropdownClose', () => {
- beforeEach(() => {
- createComponent();
- });
-
- it('emits `updateSelectedLabels` & `onDropdownClose` events on component when provided `labels` param is not empty', () => {
- wrapper.vm.handleDropdownClose([{ id: 1 }, { id: 2 }]);
-
- expect(wrapper.emitted().updateSelectedLabels).toBeTruthy();
- expect(wrapper.emitted().onDropdownClose).toBeTruthy();
- });
-
- it('emits only `onDropdownClose` event on component when provided `labels` param is empty', () => {
- wrapper.vm.handleDropdownClose([]);
-
- expect(wrapper.emitted().updateSelectedLabels).toBeFalsy();
- expect(wrapper.emitted().onDropdownClose).toBeTruthy();
+ ],
+ ]);
+ expect(wrapper.emitted('onDropdownClose')).toBeTruthy();
+ expect(wrapper.emitted('onDropdownClose')[0]).toEqual([[]]);
});
});
@@ -152,13 +137,13 @@ describe('LabelsSelectRoot', () => {
it('renders `dropdown-value-collapsed` component when `allowLabelCreate` prop is `true`', async () => {
createComponent();
- await nextTick;
+ await nextTick();
expect(wrapper.find(DropdownValueCollapsed).exists()).toBe(true);
});
it('renders `dropdown-title` component', async () => {
createComponent();
- await nextTick;
+ await nextTick();
expect(wrapper.find(DropdownTitle).exists()).toBe(true);
});
@@ -166,7 +151,7 @@ describe('LabelsSelectRoot', () => {
createComponent(mockConfig, {
default: 'None',
});
- await nextTick;
+ await nextTick();
const valueComp = wrapper.find(DropdownValue);
@@ -177,14 +162,14 @@ describe('LabelsSelectRoot', () => {
it('renders `dropdown-button` component when `showDropdownButton` prop is `true`', async () => {
createComponent();
wrapper.vm.$store.dispatch('toggleDropdownButton');
- await 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 nextTick;
+ await nextTick();
expect(wrapper.find(DropdownContents).exists()).toBe(true);
});
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/getters_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/getters_spec.js
index 1f899e84897..6ad46dbe898 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/getters_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/getters_spec.js
@@ -17,24 +17,39 @@ describe('LabelsSelect Getters', () => {
},
);
- it('returns label title when state.labels has only 1 label', () => {
- const labels = [{ id: 1, title: 'Foobar', set: true }];
+ describe.each`
+ dropdownVariant | isDropdownVariantSidebar | isDropdownVariantEmbedded
+ ${'sidebar'} | ${true} | ${false}
+ ${'embedded'} | ${false} | ${true}
+ `(
+ 'when dropdown variant is $dropdownVariant',
+ ({ isDropdownVariantSidebar, isDropdownVariantEmbedded }) => {
+ it('returns label title when state.labels has only 1 label', () => {
+ const labels = [{ id: 1, title: 'Foobar', set: true }];
- expect(getters.dropdownButtonText({ labels }, { isDropdownVariantSidebar: true })).toBe(
- 'Foobar',
- );
- });
+ expect(
+ getters.dropdownButtonText(
+ { labels },
+ { isDropdownVariantSidebar, isDropdownVariantEmbedded },
+ ),
+ ).toBe('Foobar');
+ });
- it('returns first label title and remaining labels count when state.labels has more than 1 label', () => {
- const labels = [
- { id: 1, title: 'Foo', set: true },
- { id: 2, title: 'Bar', set: true },
- ];
+ it('returns first label title and remaining labels count when state.labels has more than 1 label', () => {
+ const labels = [
+ { id: 1, title: 'Foo', set: true },
+ { id: 2, title: 'Bar', set: true },
+ ];
- expect(getters.dropdownButtonText({ labels }, { isDropdownVariantSidebar: true })).toBe(
- 'Foo +1 more',
- );
- });
+ expect(
+ getters.dropdownButtonText(
+ { labels },
+ { isDropdownVariantSidebar, isDropdownVariantEmbedded },
+ ),
+ ).toBe('Foo +1 more');
+ });
+ },
+ );
});
describe('selectedLabelsList', () => {
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/mutations_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/mutations_spec.js
index a60e6f52862..1819e750324 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/mutations_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/mutations_spec.js
@@ -80,7 +80,10 @@ describe('LabelsSelect Mutations', () => {
});
describe(`${types.RECEIVE_SET_LABELS_SUCCESS}`, () => {
- const selectedLabels = [{ id: 2 }, { id: 4 }];
+ const selectedLabels = [
+ { id: 2, set: true },
+ { id: 4, set: true },
+ ];
const labels = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }];
it('sets value of `state.labelsFetchInProgress` to false', () => {
@@ -196,20 +199,23 @@ describe('LabelsSelect Mutations', () => {
it('updates labels `set` state to match selected labels', () => {
const state = {
labels: [
- { id: 1, title: 'scoped::test', set: false },
- { id: 2, set: true, title: 'scoped::one', touched: true },
- { id: 3, title: '' },
- { id: 4, title: '' },
+ { id: 1, title: 'scoped::test', set: false, indeterminate: false },
+ { id: 2, title: 'scoped::one', set: true, indeterminate: false, touched: true },
+ { id: 3, title: '', set: false, indeterminate: false },
+ { id: 4, title: '', set: false, indeterminate: false },
+ ],
+ selectedLabels: [
+ { id: 1, set: true },
+ { id: 3, set: true },
],
- selectedLabels: [{ id: 1 }, { id: 3 }],
};
mutations[types.UPDATE_LABELS_SET_STATE](state);
expect(state.labels).toEqual([
- { id: 1, title: 'scoped::test', set: true },
- { id: 2, set: false, title: 'scoped::one', touched: true },
- { id: 3, title: '', set: true },
- { id: 4, title: '', set: false },
+ { id: 1, title: 'scoped::test', set: true, indeterminate: false },
+ { id: 2, title: 'scoped::one', set: false, indeterminate: false, touched: true },
+ { id: 3, title: '', set: true, indeterminate: false },
+ { id: 4, title: '', set: false, indeterminate: false },
]);
});
});
diff --git a/spec/frontend/vue_shared/components/source_viewer/plugins/index_spec.js b/spec/frontend/vue_shared/components/source_viewer/plugins/index_spec.js
new file mode 100644
index 00000000000..83fdc5d669d
--- /dev/null
+++ b/spec/frontend/vue_shared/components/source_viewer/plugins/index_spec.js
@@ -0,0 +1,14 @@
+import { registerPlugins } from '~/vue_shared/components/source_viewer/plugins/index';
+import { HLJS_ON_AFTER_HIGHLIGHT } from '~/vue_shared/components/source_viewer/constants';
+import wrapComments from '~/vue_shared/components/source_viewer/plugins/wrap_comments';
+
+jest.mock('~/vue_shared/components/source_viewer/plugins/wrap_comments');
+const hljsMock = { addPlugin: jest.fn() };
+
+describe('Highlight.js plugin registration', () => {
+ beforeEach(() => registerPlugins(hljsMock));
+
+ it('registers our plugins', () => {
+ expect(hljsMock.addPlugin).toHaveBeenCalledWith({ [HLJS_ON_AFTER_HIGHLIGHT]: wrapComments });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/source_viewer/plugins/wrap_comments_spec.js b/spec/frontend/vue_shared/components/source_viewer/plugins/wrap_comments_spec.js
new file mode 100644
index 00000000000..5fd4182da29
--- /dev/null
+++ b/spec/frontend/vue_shared/components/source_viewer/plugins/wrap_comments_spec.js
@@ -0,0 +1,29 @@
+import { HLJS_COMMENT_SELECTOR } from '~/vue_shared/components/source_viewer/constants';
+import wrapComments from '~/vue_shared/components/source_viewer/plugins/wrap_comments';
+
+describe('Highlight.js plugin for wrapping comments', () => {
+ it('mutates the input value by wrapping each line in a span tag', () => {
+ const inputValue = `<span class="${HLJS_COMMENT_SELECTOR}">/* Line 1 \n* Line 2 \n*/</span>`;
+ const outputValue = `<span class="${HLJS_COMMENT_SELECTOR}">/* Line 1 \n<span class="${HLJS_COMMENT_SELECTOR}">* Line 2 </span>\n<span class="${HLJS_COMMENT_SELECTOR}">*/</span>`;
+ const hljsResultMock = { value: inputValue };
+
+ wrapComments(hljsResultMock);
+ expect(hljsResultMock.value).toBe(outputValue);
+ });
+
+ it('does not mutate the input value if the hljs comment selector is not present', () => {
+ const inputValue = '<span class="hljs-keyword">const</span>';
+ const hljsResultMock = { value: inputValue };
+
+ wrapComments(hljsResultMock);
+ expect(hljsResultMock.value).toBe(inputValue);
+ });
+
+ it('does not mutate the input value if the hljs comment line includes a closing tag', () => {
+ const inputValue = `<span class="${HLJS_COMMENT_SELECTOR}">/* Line 1 </span> \n* Line 2 \n*/`;
+ const hljsResultMock = { value: inputValue };
+
+ wrapComments(hljsResultMock);
+ expect(hljsResultMock.value).toBe(inputValue);
+ });
+});
diff --git a/spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js b/spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js
index 6a9ea75127d..bb0945a1f3e 100644
--- a/spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js
+++ b/spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js
@@ -3,6 +3,7 @@ import Vue from 'vue';
import VueRouter from 'vue-router';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import SourceViewer from '~/vue_shared/components/source_viewer/source_viewer.vue';
+import { registerPlugins } from '~/vue_shared/components/source_viewer/plugins/index';
import Chunk from '~/vue_shared/components/source_viewer/components/chunk.vue';
import { ROUGE_TO_HLJS_LANGUAGE_MAP } from '~/vue_shared/components/source_viewer/constants';
import waitForPromises from 'helpers/wait_for_promises';
@@ -11,6 +12,7 @@ import eventHub from '~/notes/event_hub';
jest.mock('~/blob/line_highlighter');
jest.mock('highlight.js/lib/core');
+jest.mock('~/vue_shared/components/source_viewer/plugins/index');
Vue.use(VueRouter);
const router = new VueRouter();
@@ -59,6 +61,10 @@ describe('Source Viewer component', () => {
describe('highlight.js', () => {
beforeEach(() => createComponent({ language: mappedLanguage }));
+ it('registers our plugins for Highlight.js', () => {
+ expect(registerPlugins).toHaveBeenCalledWith(hljs);
+ });
+
it('registers the language definition', async () => {
const languageDefinition = await import(`highlight.js/lib/languages/${mappedLanguage}`);
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 a613b325462..1798ca5ccde 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
@@ -5,7 +5,7 @@ exports[`Upload dropzone component correctly overrides description and drop mess
class="gl-w-full gl-relative"
>
<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"
+ class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-4"
type="button"
>
<div
@@ -41,7 +41,7 @@ exports[`Upload dropzone component correctly overrides description and drop mess
name="upload-dropzone-fade"
>
<div
- class="card upload-dropzone-border upload-dropzone-overlay gl-w-full gl-h-full gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-3 gl-bg-white"
+ class="card upload-dropzone-border upload-dropzone-overlay gl-w-full gl-h-full gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-4"
style="display: none;"
>
<div
@@ -86,7 +86,7 @@ exports[`Upload dropzone component when dragging renders correct template when d
class="gl-w-full gl-relative"
>
<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"
+ class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-4"
type="button"
>
<div
@@ -126,7 +126,7 @@ exports[`Upload dropzone component when dragging renders correct template when d
name="upload-dropzone-fade"
>
<div
- class="card upload-dropzone-border upload-dropzone-overlay gl-w-full gl-h-full gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-3 gl-bg-white"
+ class="card upload-dropzone-border upload-dropzone-overlay gl-w-full gl-h-full gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-4"
style=""
>
<div
@@ -171,7 +171,7 @@ exports[`Upload dropzone component when dragging renders correct template when d
class="gl-w-full gl-relative"
>
<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"
+ class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-4"
type="button"
>
<div
@@ -211,7 +211,7 @@ exports[`Upload dropzone component when dragging renders correct template when d
name="upload-dropzone-fade"
>
<div
- class="card upload-dropzone-border upload-dropzone-overlay gl-w-full gl-h-full gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-3 gl-bg-white"
+ class="card upload-dropzone-border upload-dropzone-overlay gl-w-full gl-h-full gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-4"
style=""
>
<div
@@ -256,7 +256,7 @@ exports[`Upload dropzone component when dragging renders correct template when d
class="gl-w-full gl-relative"
>
<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"
+ class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-4"
type="button"
>
<div
@@ -296,7 +296,7 @@ exports[`Upload dropzone component when dragging renders correct template when d
name="upload-dropzone-fade"
>
<div
- class="card upload-dropzone-border upload-dropzone-overlay gl-w-full gl-h-full gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-3 gl-bg-white"
+ class="card upload-dropzone-border upload-dropzone-overlay gl-w-full gl-h-full gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-4"
style=""
>
<div
@@ -342,7 +342,7 @@ exports[`Upload dropzone component when dragging renders correct template when d
class="gl-w-full gl-relative"
>
<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"
+ class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-4"
type="button"
>
<div
@@ -382,7 +382,7 @@ exports[`Upload dropzone component when dragging renders correct template when d
name="upload-dropzone-fade"
>
<div
- class="card upload-dropzone-border upload-dropzone-overlay gl-w-full gl-h-full gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-3 gl-bg-white"
+ class="card upload-dropzone-border upload-dropzone-overlay gl-w-full gl-h-full gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-4"
style=""
>
<div
@@ -428,7 +428,7 @@ exports[`Upload dropzone component when dragging renders correct template when d
class="gl-w-full gl-relative"
>
<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"
+ class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-4"
type="button"
>
<div
@@ -468,7 +468,7 @@ exports[`Upload dropzone component when dragging renders correct template when d
name="upload-dropzone-fade"
>
<div
- class="card upload-dropzone-border upload-dropzone-overlay gl-w-full gl-h-full gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-3 gl-bg-white"
+ class="card upload-dropzone-border upload-dropzone-overlay gl-w-full gl-h-full gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-4"
style="display: none;"
>
<div
@@ -514,7 +514,7 @@ exports[`Upload dropzone component when no slot provided renders default dropzon
class="gl-w-full gl-relative"
>
<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"
+ class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-4"
type="button"
>
<div
@@ -554,7 +554,7 @@ exports[`Upload dropzone component when no slot provided renders default dropzon
name="upload-dropzone-fade"
>
<div
- class="card upload-dropzone-border upload-dropzone-overlay gl-w-full gl-h-full gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-3 gl-bg-white"
+ class="card upload-dropzone-border upload-dropzone-overlay gl-w-full gl-h-full gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-4"
style="display: none;"
>
<div
@@ -606,7 +606,7 @@ exports[`Upload dropzone component when slot provided renders dropzone with slot
name="upload-dropzone-fade"
>
<div
- class="card upload-dropzone-border upload-dropzone-overlay gl-w-full gl-h-full gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-3 gl-bg-white"
+ class="card upload-dropzone-border upload-dropzone-overlay gl-w-full gl-h-full gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-4"
style="display: none;"
>
<div
diff --git a/spec/frontend/vue_shared/issuable/list/components/issuable_item_spec.js b/spec/frontend/vue_shared/issuable/list/components/issuable_item_spec.js
index 65eb42ef053..70017903079 100644
--- a/spec/frontend/vue_shared/issuable/list/components/issuable_item_spec.js
+++ b/spec/frontend/vue_shared/issuable/list/components/issuable_item_spec.js
@@ -5,9 +5,10 @@ import { shallowMountExtended as shallowMount } from 'helpers/vue_test_utils_hel
import IssuableItem from '~/vue_shared/issuable/list/components/issuable_item.vue';
import IssuableAssignees from '~/issuable/components/issue_assignees.vue';
-import { mockIssuable, mockRegularLabel, mockScopedLabel } from '../mock_data';
+import { mockIssuable, mockRegularLabel } from '../mock_data';
const createComponent = ({
+ hasScopedLabelsFeature = false,
issuableSymbol = '#',
issuable = mockIssuable,
showCheckbox = true,
@@ -15,6 +16,7 @@ const createComponent = ({
} = {}) =>
shallowMount(IssuableItem, {
propsData: {
+ hasScopedLabelsFeature,
issuableSymbol,
issuable,
showDiscussions: true,
@@ -182,21 +184,6 @@ describe('IssuableItem', () => {
});
describe('methods', () => {
- describe('scopedLabel', () => {
- it.each`
- label | labelType | returnValue
- ${mockRegularLabel} | ${'regular'} | ${false}
- ${mockScopedLabel} | ${'scoped'} | ${true}
- `(
- 'return $returnValue when provided label param is a $labelType label',
- ({ label, returnValue }) => {
- wrapper = createComponent();
-
- expect(wrapper.vm.scopedLabel(label)).toBe(returnValue);
- },
- );
- });
-
describe('labelTitle', () => {
it.each`
label | propWithTitle | returnValue
@@ -500,5 +487,21 @@ describe('IssuableItem', () => {
expect(wrapper.classes()).not.toContain('today');
});
});
+
+ describe('scoped labels', () => {
+ describe.each`
+ description | labelPosition | hasScopedLabelsFeature | scoped
+ ${'when label is not scoped and there is no scoped_labels feature'} | ${0} | ${false} | ${false}
+ ${'when label is scoped and there is no scoped_labels feature'} | ${1} | ${false} | ${false}
+ ${'when label is not scoped and there is scoped_labels feature'} | ${0} | ${true} | ${false}
+ ${'when label is scoped and there is scoped_labels feature'} | ${1} | ${true} | ${true}
+ `('$description', ({ hasScopedLabelsFeature, labelPosition, scoped }) => {
+ it(`${scoped ? 'renders' : 'does not render'} as scoped label`, () => {
+ wrapper = createComponent({ hasScopedLabelsFeature });
+
+ expect(wrapper.findAllComponents(GlLabel).at(labelPosition).props('scoped')).toBe(scoped);
+ });
+ });
+ });
});
});
diff --git a/spec/frontend/vue_shared/issuable/list/components/issuable_list_root_spec.js b/spec/frontend/vue_shared/issuable/list/components/issuable_list_root_spec.js
index 058cb30c1d5..66f71c0b028 100644
--- a/spec/frontend/vue_shared/issuable/list/components/issuable_list_root_spec.js
+++ b/spec/frontend/vue_shared/issuable/list/components/issuable_list_root_spec.js
@@ -1,9 +1,4 @@
-import {
- GlAlert,
- GlKeysetPagination,
- GlDeprecatedSkeletonLoading as GlSkeletonLoading,
- GlPagination,
-} from '@gitlab/ui';
+import { GlAlert, GlKeysetPagination, GlSkeletonLoader, GlPagination } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import VueDraggable from 'vuedraggable';
@@ -263,7 +258,7 @@ describe('IssuableListRoot', () => {
it('renders gl-loading-icon when `issuablesLoading` prop is true', () => {
wrapper = createComponent({ props: { issuablesLoading: true } });
- expect(wrapper.findAllComponents(GlSkeletonLoading)).toHaveLength(
+ expect(wrapper.findAllComponents(GlSkeletonLoader)).toHaveLength(
wrapper.vm.skeletonItemCount,
);
});
diff --git a/spec/frontend/vue_shared/issuable/show/components/issuable_body_spec.js b/spec/frontend/vue_shared/issuable/show/components/issuable_body_spec.js
index 1a93838b03f..7c582360637 100644
--- a/spec/frontend/vue_shared/issuable/show/components/issuable_body_spec.js
+++ b/spec/frontend/vue_shared/issuable/show/components/issuable_body_spec.js
@@ -159,7 +159,6 @@ describe('IssuableBody', () => {
expect(titleEl.exists()).toBe(true);
expect(titleEl.props()).toMatchObject({
issuable: issuableBodyProps.issuable,
- statusBadgeClass: issuableBodyProps.statusBadgeClass,
statusIcon: issuableBodyProps.statusIcon,
enableEdit: issuableBodyProps.enableEdit,
});
diff --git a/spec/frontend/vue_shared/issuable/show/components/issuable_header_spec.js b/spec/frontend/vue_shared/issuable/show/components/issuable_header_spec.js
index 544db891a13..e00bb184535 100644
--- a/spec/frontend/vue_shared/issuable/show/components/issuable_header_spec.js
+++ b/spec/frontend/vue_shared/issuable/show/components/issuable_header_spec.js
@@ -1,4 +1,4 @@
-import { GlIcon, GlAvatarLabeled } from '@gitlab/ui';
+import { GlBadge, GlIcon, GlAvatarLabeled } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import IssuableHeader from '~/vue_shared/issuable/show/components/issuable_header.vue';
@@ -69,7 +69,7 @@ describe('IssuableHeader', () => {
describe('template', () => {
it('renders issuable status icon and text', () => {
createComponent();
- const statusBoxEl = wrapper.findByTestId('status');
+ const statusBoxEl = wrapper.findComponent(GlBadge);
const statusIconEl = statusBoxEl.findComponent(GlIcon);
expect(statusBoxEl.exists()).toBe(true);
diff --git a/spec/frontend/vue_shared/issuable/show/components/issuable_show_root_spec.js b/spec/frontend/vue_shared/issuable/show/components/issuable_show_root_spec.js
index 8b027f990a2..f56064ed8e1 100644
--- a/spec/frontend/vue_shared/issuable/show/components/issuable_show_root_spec.js
+++ b/spec/frontend/vue_shared/issuable/show/components/issuable_show_root_spec.js
@@ -47,7 +47,6 @@ describe('IssuableShowRoot', () => {
describe('template', () => {
const {
- statusBadgeClass,
statusIcon,
statusIconClass,
enableEdit,
@@ -69,7 +68,6 @@ describe('IssuableShowRoot', () => {
expect(issuableHeader.exists()).toBe(true);
expect(issuableHeader.props()).toMatchObject({
issuableState: state,
- statusBadgeClass,
statusIcon,
statusIconClass,
blocked,
@@ -91,7 +89,6 @@ describe('IssuableShowRoot', () => {
expect(issuableBody.exists()).toBe(true);
expect(issuableBody.props()).toMatchObject({
issuable: mockIssuable,
- statusBadgeClass,
statusIcon,
enableEdit,
enableAutocomplete,
diff --git a/spec/frontend/vue_shared/issuable/show/components/issuable_title_spec.js b/spec/frontend/vue_shared/issuable/show/components/issuable_title_spec.js
index 11e3302d409..5aa67667033 100644
--- a/spec/frontend/vue_shared/issuable/show/components/issuable_title_spec.js
+++ b/spec/frontend/vue_shared/issuable/show/components/issuable_title_spec.js
@@ -1,4 +1,4 @@
-import { GlIcon, GlButton, GlIntersectionObserver } from '@gitlab/ui';
+import { GlIcon, GlBadge, GlButton, GlIntersectionObserver } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
@@ -40,7 +40,7 @@ describe('IssuableTitle', () => {
describe('methods', () => {
describe('handleTitleAppear', () => {
it('sets value of `stickyTitleVisible` prop to false', () => {
- wrapper.find(GlIntersectionObserver).vm.$emit('appear');
+ wrapper.findComponent(GlIntersectionObserver).vm.$emit('appear');
expect(wrapper.vm.stickyTitleVisible).toBe(false);
});
@@ -48,7 +48,7 @@ describe('IssuableTitle', () => {
describe('handleTitleDisappear', () => {
it('sets value of `stickyTitleVisible` prop to true', () => {
- wrapper.find(GlIntersectionObserver).vm.$emit('disappear');
+ wrapper.findComponent(GlIntersectionObserver).vm.$emit('disappear');
expect(wrapper.vm.stickyTitleVisible).toBe(true);
});
@@ -70,14 +70,14 @@ describe('IssuableTitle', () => {
expect(titleEl.exists()).toBe(true);
expect(titleEl.html()).toBe(
- '<h1 dir="auto" data-testid="title" class="title qa-title"><b>Sample</b> title</h1>',
+ '<h1 dir="auto" data-testid="title" class="title qa-title gl-font-size-h-display"><b>Sample</b> title</h1>',
);
wrapperWithTitle.destroy();
});
it('renders edit button', () => {
- const editButtonEl = wrapper.find(GlButton);
+ const editButtonEl = wrapper.findComponent(GlButton);
const tooltip = getBinding(editButtonEl.element, 'gl-tooltip');
expect(editButtonEl.exists()).toBe(true);
@@ -97,7 +97,10 @@ describe('IssuableTitle', () => {
const stickyHeaderEl = wrapper.find('[data-testid="header"]');
expect(stickyHeaderEl.exists()).toBe(true);
- expect(stickyHeaderEl.find(GlIcon).props('name')).toBe(issuableTitleProps.statusIcon);
+ expect(stickyHeaderEl.findComponent(GlBadge).props('variant')).toBe('success');
+ expect(stickyHeaderEl.findComponent(GlIcon).props('name')).toBe(
+ issuableTitleProps.statusIcon,
+ );
expect(stickyHeaderEl.text()).toContain('Open');
expect(stickyHeaderEl.text()).toContain(issuableTitleProps.issuable.title);
});
diff --git a/spec/frontend/vue_shared/issuable/show/mock_data.js b/spec/frontend/vue_shared/issuable/show/mock_data.js
index 32bb9edfe08..5ec205a2d5c 100644
--- a/spec/frontend/vue_shared/issuable/show/mock_data.js
+++ b/spec/frontend/vue_shared/issuable/show/mock_data.js
@@ -36,7 +36,6 @@ export const mockIssuableShowProps = {
enableTaskList: true,
enableEdit: true,
showFieldTitle: false,
- statusBadgeClass: 'issuable-status-badge-open',
statusIcon: 'issues',
statusIconClass: 'gl-sm-display-none',
taskCompletionStatus: {