summaryrefslogtreecommitdiff
path: root/spec/frontend/vue_shared
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/vue_shared')
-rw-r--r--spec/frontend/vue_shared/components/ci_badge_link_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/store/modules/filters/actions_spec.js36
-rw-r--r--spec/frontend/vue_shared/components/group_select/group_select_spec.js150
-rw-r--r--spec/frontend/vue_shared/components/header_ci_component_spec.js3
-rw-r--r--spec/frontend/vue_shared/components/listbox_input/listbox_input_spec.js67
-rw-r--r--spec/frontend/vue_shared/components/markdown/editor_mode_dropdown_spec.js58
-rw-r--r--spec/frontend/vue_shared/components/markdown/field_spec.js18
-rw-r--r--spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js77
-rw-r--r--spec/frontend/vue_shared/components/markdown/toolbar_spec.js15
-rw-r--r--spec/frontend/vue_shared/components/runner_aws_deployments/__snapshots__/runner_aws_deployments_modal_spec.js.snap177
-rw-r--r--spec/frontend/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal_spec.js70
-rw-r--r--spec/frontend/vue_shared/components/runner_instructions/instructions/__snapshots__/runner_docker_instructions_spec.js.snap3
-rw-r--r--spec/frontend/vue_shared/components/runner_instructions/instructions/__snapshots__/runner_kubernetes_instructions_spec.js.snap3
-rw-r--r--spec/frontend/vue_shared/components/runner_instructions/instructions/runner_aws_instructions_spec.js117
-rw-r--r--spec/frontend/vue_shared/components/runner_instructions/instructions/runner_cli_instructions_spec.js169
-rw-r--r--spec/frontend/vue_shared/components/runner_instructions/instructions/runner_docker_instructions_spec.js28
-rw-r--r--spec/frontend/vue_shared/components/runner_instructions/instructions/runner_kubernetes_instructions_spec.js28
-rw-r--r--spec/frontend/vue_shared/components/runner_instructions/mock_data.js8
-rw-r--r--spec/frontend/vue_shared/components/runner_instructions/runner_instructions_modal_spec.js201
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js17
-rw-r--r--spec/frontend/vue_shared/components/timezone_dropdown/timezone_dropdown_spec.js10
-rw-r--r--spec/frontend/vue_shared/components/web_ide_link_spec.js15
-rw-r--r--spec/frontend/vue_shared/issuable/show/components/issuable_edit_form_spec.js24
23 files changed, 832 insertions, 466 deletions
diff --git a/spec/frontend/vue_shared/components/ci_badge_link_spec.js b/spec/frontend/vue_shared/components/ci_badge_link_spec.js
index 07cbfe1e79b..4f24ec2d015 100644
--- a/spec/frontend/vue_shared/components/ci_badge_link_spec.js
+++ b/spec/frontend/vue_shared/components/ci_badge_link_spec.js
@@ -1,6 +1,6 @@
import { GlLink } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import CiBadge from '~/vue_shared/components/ci_badge_link.vue';
+import CiBadgeLink from '~/vue_shared/components/ci_badge_link.vue';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
jest.mock('~/lib/utils/url_utility', () => ({
@@ -79,7 +79,7 @@ describe('CI Badge Link Component', () => {
const findIcon = () => wrapper.findComponent(CiIcon);
const createComponent = (propsData) => {
- wrapper = shallowMount(CiBadge, { propsData });
+ wrapper = shallowMount(CiBadgeLink, { propsData });
};
afterEach(() => {
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/store/modules/filters/actions_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/store/modules/filters/actions_spec.js
index 66ef473f368..63c22aff3d5 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/store/modules/filters/actions_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/store/modules/filters/actions_spec.js
@@ -4,7 +4,7 @@ import testAction from 'helpers/vuex_action_helper';
import { mockBranches } from 'jest/vue_shared/components/filtered_search_bar/mock_data';
import Api from '~/api';
import { createAlert } from '~/flash';
-import httpStatusCodes from '~/lib/utils/http_status';
+import { HTTP_STATUS_OK, HTTP_STATUS_SERVICE_UNAVAILABLE } from '~/lib/utils/http_status';
import * as actions from '~/vue_shared/components/filtered_search_bar/store/modules/filters/actions';
import * as types from '~/vue_shared/components/filtered_search_bar/store/modules/filters/mutation_types';
import initialState from '~/vue_shared/components/filtered_search_bar/store/modules/filters/state';
@@ -122,7 +122,7 @@ describe('Filters actions', () => {
':id',
encodeURIComponent(projectEndpoint),
);
- mock.onGet(url).replyOnce(httpStatusCodes.OK, mockBranches);
+ mock.onGet(url).replyOnce(HTTP_STATUS_OK, mockBranches);
});
it('dispatches RECEIVE_BRANCHES_SUCCESS with received data', () => {
@@ -143,7 +143,7 @@ describe('Filters actions', () => {
describe('error', () => {
beforeEach(() => {
- mock.onAny().replyOnce(httpStatusCodes.SERVICE_UNAVAILABLE);
+ mock.onAny().replyOnce(HTTP_STATUS_SERVICE_UNAVAILABLE);
});
it('dispatches RECEIVE_BRANCHES_ERROR', () => {
@@ -155,7 +155,7 @@ describe('Filters actions', () => {
{ type: types.REQUEST_BRANCHES },
{
type: types.RECEIVE_BRANCHES_ERROR,
- payload: httpStatusCodes.SERVICE_UNAVAILABLE,
+ payload: HTTP_STATUS_SERVICE_UNAVAILABLE,
},
],
[],
@@ -177,7 +177,7 @@ describe('Filters actions', () => {
describe('success', () => {
beforeEach(() => {
- mock.onAny().replyOnce(httpStatusCodes.OK, filterUsers);
+ mock.onAny().replyOnce(HTTP_STATUS_OK, filterUsers);
});
it('dispatches RECEIVE_AUTHORS_SUCCESS with received data and groupEndpoint set', () => {
@@ -215,7 +215,7 @@ describe('Filters actions', () => {
describe('error', () => {
beforeEach(() => {
- mock.onAny().replyOnce(httpStatusCodes.SERVICE_UNAVAILABLE);
+ mock.onAny().replyOnce(HTTP_STATUS_SERVICE_UNAVAILABLE);
});
it('dispatches RECEIVE_AUTHORS_ERROR and groupEndpoint set', () => {
@@ -227,7 +227,7 @@ describe('Filters actions', () => {
{ type: types.REQUEST_AUTHORS },
{
type: types.RECEIVE_AUTHORS_ERROR,
- payload: httpStatusCodes.SERVICE_UNAVAILABLE,
+ payload: HTTP_STATUS_SERVICE_UNAVAILABLE,
},
],
[],
@@ -246,7 +246,7 @@ describe('Filters actions', () => {
{ type: types.REQUEST_AUTHORS },
{
type: types.RECEIVE_AUTHORS_ERROR,
- payload: httpStatusCodes.SERVICE_UNAVAILABLE,
+ payload: HTTP_STATUS_SERVICE_UNAVAILABLE,
},
],
[],
@@ -261,7 +261,7 @@ describe('Filters actions', () => {
describe('fetchMilestones', () => {
describe('success', () => {
beforeEach(() => {
- mock.onGet(milestonesEndpoint).replyOnce(httpStatusCodes.OK, filterMilestones);
+ mock.onGet(milestonesEndpoint).replyOnce(HTTP_STATUS_OK, filterMilestones);
});
it('dispatches RECEIVE_MILESTONES_SUCCESS with received data', () => {
@@ -282,7 +282,7 @@ describe('Filters actions', () => {
describe('error', () => {
beforeEach(() => {
- mock.onAny().replyOnce(httpStatusCodes.SERVICE_UNAVAILABLE);
+ mock.onAny().replyOnce(HTTP_STATUS_SERVICE_UNAVAILABLE);
});
it('dispatches RECEIVE_MILESTONES_ERROR', () => {
@@ -294,7 +294,7 @@ describe('Filters actions', () => {
{ type: types.REQUEST_MILESTONES },
{
type: types.RECEIVE_MILESTONES_ERROR,
- payload: httpStatusCodes.SERVICE_UNAVAILABLE,
+ payload: HTTP_STATUS_SERVICE_UNAVAILABLE,
},
],
[],
@@ -307,7 +307,7 @@ describe('Filters actions', () => {
describe('success', () => {
let restoreVersion;
beforeEach(() => {
- mock.onAny().replyOnce(httpStatusCodes.OK, filterUsers);
+ mock.onAny().replyOnce(HTTP_STATUS_OK, filterUsers);
restoreVersion = gon.api_version;
gon.api_version = 'v1';
});
@@ -352,7 +352,7 @@ describe('Filters actions', () => {
describe('error', () => {
let restoreVersion;
beforeEach(() => {
- mock.onAny().replyOnce(httpStatusCodes.SERVICE_UNAVAILABLE);
+ mock.onAny().replyOnce(HTTP_STATUS_SERVICE_UNAVAILABLE);
restoreVersion = gon.api_version;
gon.api_version = 'v1';
});
@@ -370,7 +370,7 @@ describe('Filters actions', () => {
{ type: types.REQUEST_ASSIGNEES },
{
type: types.RECEIVE_ASSIGNEES_ERROR,
- payload: httpStatusCodes.SERVICE_UNAVAILABLE,
+ payload: HTTP_STATUS_SERVICE_UNAVAILABLE,
},
],
[],
@@ -389,7 +389,7 @@ describe('Filters actions', () => {
{ type: types.REQUEST_ASSIGNEES },
{
type: types.RECEIVE_ASSIGNEES_ERROR,
- payload: httpStatusCodes.SERVICE_UNAVAILABLE,
+ payload: HTTP_STATUS_SERVICE_UNAVAILABLE,
},
],
[],
@@ -404,7 +404,7 @@ describe('Filters actions', () => {
describe('fetchLabels', () => {
describe('success', () => {
beforeEach(() => {
- mock.onGet(labelsEndpoint).replyOnce(httpStatusCodes.OK, filterLabels);
+ mock.onGet(labelsEndpoint).replyOnce(HTTP_STATUS_OK, filterLabels);
});
it('dispatches RECEIVE_LABELS_SUCCESS with received data', () => {
@@ -425,7 +425,7 @@ describe('Filters actions', () => {
describe('error', () => {
beforeEach(() => {
- mock.onAny().replyOnce(httpStatusCodes.SERVICE_UNAVAILABLE);
+ mock.onAny().replyOnce(HTTP_STATUS_SERVICE_UNAVAILABLE);
});
it('dispatches RECEIVE_LABELS_ERROR', () => {
@@ -437,7 +437,7 @@ describe('Filters actions', () => {
{ type: types.REQUEST_LABELS },
{
type: types.RECEIVE_LABELS_ERROR,
- payload: httpStatusCodes.SERVICE_UNAVAILABLE,
+ payload: HTTP_STATUS_SERVICE_UNAVAILABLE,
},
],
[],
diff --git a/spec/frontend/vue_shared/components/group_select/group_select_spec.js b/spec/frontend/vue_shared/components/group_select/group_select_spec.js
index c10b32c6acc..87dd7795b98 100644
--- a/spec/frontend/vue_shared/components/group_select/group_select_spec.js
+++ b/spec/frontend/vue_shared/components/group_select/group_select_spec.js
@@ -1,20 +1,18 @@
import { nextTick } from 'vue';
-import { GlCollapsibleListbox } from '@gitlab/ui';
+import { GlFormGroup, GlCollapsibleListbox } from '@gitlab/ui';
import MockAdapter from 'axios-mock-adapter';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import axios from '~/lib/utils/axios_utils';
-import { createAlert } from '~/flash';
import GroupSelect from '~/vue_shared/components/group_select/group_select.vue';
import {
TOGGLE_TEXT,
+ RESET_LABEL,
FETCH_GROUPS_ERROR,
FETCH_GROUP_ERROR,
QUERY_TOO_SHORT_MESSAGE,
} from '~/vue_shared/components/group_select/constants';
import waitForPromises from 'helpers/wait_for_promises';
-jest.mock('~/flash');
-
describe('GroupSelect', () => {
let wrapper;
let mock;
@@ -26,22 +24,34 @@ describe('GroupSelect', () => {
};
const groupEndpoint = `/api/undefined/groups/${groupMock.id}`;
+ // Stubs
+ const GlAlert = {
+ template: '<div><slot /></div>',
+ };
+
// Props
+ const label = 'label';
const inputName = 'inputName';
const inputId = 'inputId';
// Finders
+ const findFormGroup = () => wrapper.findComponent(GlFormGroup);
const findListbox = () => wrapper.findComponent(GlCollapsibleListbox);
const findInput = () => wrapper.findByTestId('input');
+ const findAlert = () => wrapper.findComponent(GlAlert);
// Helpers
const createComponent = ({ props = {} } = {}) => {
wrapper = shallowMountExtended(GroupSelect, {
propsData: {
+ label,
inputName,
inputId,
...props,
},
+ stubs: {
+ GlAlert,
+ },
});
};
const openListbox = () => findListbox().vm.$emit('shown');
@@ -65,6 +75,12 @@ describe('GroupSelect', () => {
mock.restore();
});
+ it('passes the label to GlFormGroup', () => {
+ createComponent();
+
+ expect(findFormGroup().attributes('label')).toBe(label);
+ });
+
describe('on mount', () => {
it('fetches groups when the listbox is opened', async () => {
createComponent();
@@ -94,13 +110,13 @@ describe('GroupSelect', () => {
.reply(200, [{ full_name: 'notTheSelectedGroup', id: '2' }]);
mock.onGet(groupEndpoint).reply(500);
createComponent({ props: { initialSelection: groupMock.id } });
+
+ expect(findAlert().exists()).toBe(false);
+
await waitForPromises();
- expect(createAlert).toHaveBeenCalledWith({
- message: FETCH_GROUP_ERROR,
- error: expect.any(Error),
- parent: wrapper.vm.$el,
- });
+ expect(findAlert().exists()).toBe(true);
+ expect(findAlert().text()).toBe(FETCH_GROUP_ERROR);
});
});
});
@@ -109,13 +125,12 @@ describe('GroupSelect', () => {
mock.onGet('/api/undefined/groups.json').reply(500);
createComponent();
openListbox();
+ expect(findAlert().exists()).toBe(false);
+
await waitForPromises();
- expect(createAlert).toHaveBeenCalledWith({
- message: FETCH_GROUPS_ERROR,
- error: expect.any(Error),
- parent: wrapper.vm.$el,
- });
+ expect(findAlert().exists()).toBe(true);
+ expect(findAlert().text()).toBe(FETCH_GROUPS_ERROR);
});
describe('selection', () => {
@@ -186,7 +201,11 @@ describe('GroupSelect', () => {
await waitForPromises();
expect(mock.history.get).toHaveLength(2);
- expect(mock.history.get[1].params).toStrictEqual({ search: searchString });
+ expect(mock.history.get[1].params).toStrictEqual({
+ page: 1,
+ per_page: 20,
+ search: searchString,
+ });
});
it('shows a notice if the search query is too short', async () => {
@@ -199,4 +218,105 @@ describe('GroupSelect', () => {
expect(findListbox().props('noResultsText')).toBe(QUERY_TOO_SHORT_MESSAGE);
});
});
+
+ describe('pagination', () => {
+ const searchString = 'searchString';
+
+ beforeEach(async () => {
+ let requestCount = 0;
+ mock.onGet('/api/undefined/groups.json').reply(({ params }) => {
+ requestCount += 1;
+ return [
+ 200,
+ [
+ {
+ full_name: `Group [page: ${params.page} - search: ${params.search}]`,
+ id: requestCount,
+ },
+ ],
+ {
+ page: params.page,
+ 'x-total-pages': 3,
+ },
+ ];
+ });
+ createComponent();
+ openListbox();
+ findListbox().vm.$emit('bottom-reached');
+ return waitForPromises();
+ });
+
+ it('fetches the next page when bottom is reached', async () => {
+ expect(mock.history.get).toHaveLength(2);
+ expect(mock.history.get[1].params).toStrictEqual({
+ page: 2,
+ per_page: 20,
+ search: '',
+ });
+ });
+
+ it('fetches the first page when the search query changes', async () => {
+ search(searchString);
+ await waitForPromises();
+
+ expect(mock.history.get).toHaveLength(3);
+ expect(mock.history.get[2].params).toStrictEqual({
+ page: 1,
+ per_page: 20,
+ search: searchString,
+ });
+ });
+
+ it('retains the search query when infinite scrolling', async () => {
+ search(searchString);
+ await waitForPromises();
+ findListbox().vm.$emit('bottom-reached');
+ await waitForPromises();
+
+ expect(mock.history.get).toHaveLength(4);
+ expect(mock.history.get[3].params).toStrictEqual({
+ page: 2,
+ per_page: 20,
+ search: searchString,
+ });
+ });
+
+ it('pauses infinite scroll after fetching the last page', async () => {
+ expect(findListbox().props('infiniteScroll')).toBe(true);
+
+ findListbox().vm.$emit('bottom-reached');
+ await waitForPromises();
+
+ expect(findListbox().props('infiniteScroll')).toBe(false);
+ });
+
+ it('resumes infinite scroll when search query changes', async () => {
+ findListbox().vm.$emit('bottom-reached');
+ await waitForPromises();
+
+ expect(findListbox().props('infiniteScroll')).toBe(false);
+
+ search(searchString);
+ await waitForPromises();
+
+ expect(findListbox().props('infiniteScroll')).toBe(true);
+ });
+ });
+
+ it.each`
+ description | clearable | expectedLabel
+ ${'passes'} | ${true} | ${RESET_LABEL}
+ ${'does not pass'} | ${false} | ${''}
+ `(
+ '$description the reset button label to the listbox when clearable is $clearable',
+ ({ clearable, expectedLabel }) => {
+ createComponent({
+ props: {
+ clearable,
+ },
+ });
+
+ expect(findListbox().props('resetButtonLabel')).toBe(expectedLabel);
+ },
+ );
});
diff --git a/spec/frontend/vue_shared/components/header_ci_component_spec.js b/spec/frontend/vue_shared/components/header_ci_component_spec.js
index aea76f164f0..94e1ece8c6b 100644
--- a/spec/frontend/vue_shared/components/header_ci_component_spec.js
+++ b/spec/frontend/vue_shared/components/header_ci_component_spec.js
@@ -84,11 +84,12 @@ describe('Header CI Component', () => {
expect(findUserLink().text()).toContain(defaultProps.user.username);
});
- it('has the correct data attributes', () => {
+ it('has the correct HTML attributes', () => {
expect(findUserLink().attributes()).toMatchObject({
'data-user-id': defaultProps.user.id.toString(),
'data-username': defaultProps.user.username,
'data-name': defaultProps.user.name,
+ href: defaultProps.user.web_url,
});
});
diff --git a/spec/frontend/vue_shared/components/listbox_input/listbox_input_spec.js b/spec/frontend/vue_shared/components/listbox_input/listbox_input_spec.js
index cb7262b15e3..7ed6a59c844 100644
--- a/spec/frontend/vue_shared/components/listbox_input/listbox_input_spec.js
+++ b/spec/frontend/vue_shared/components/listbox_input/listbox_input_spec.js
@@ -1,11 +1,13 @@
import { shallowMount } from '@vue/test-utils';
-import { GlListbox } from '@gitlab/ui';
+import { GlFormGroup, GlListbox } from '@gitlab/ui';
import ListboxInput from '~/vue_shared/components/listbox_input/listbox_input.vue';
describe('ListboxInput', () => {
let wrapper;
// Props
+ const label = 'label';
+ const decription = 'decription';
const name = 'name';
const defaultToggleText = 'defaultToggleText';
const items = [
@@ -21,30 +23,70 @@ describe('ListboxInput', () => {
options: [{ text: 'Item 3', value: '3' }],
},
];
+ const id = 'id';
// Finders
+ const findGlFormGroup = () => wrapper.findComponent(GlFormGroup);
const findGlListbox = () => wrapper.findComponent(GlListbox);
const findInput = () => wrapper.find('input');
const createComponent = (propsData) => {
wrapper = shallowMount(ListboxInput, {
propsData: {
+ label,
+ decription,
name,
defaultToggleText,
items,
...propsData,
},
+ attrs: {
+ id,
+ },
});
};
- describe('input attributes', () => {
+ describe('wrapper', () => {
+ it.each`
+ description | labelProp | descriptionProp | rendersGlFormGroup
+ ${'does not render'} | ${''} | ${''} | ${false}
+ ${'renders'} | ${'labelProp'} | ${''} | ${true}
+ ${'renders'} | ${''} | ${'descriptionProp'} | ${true}
+ ${'renders'} | ${'labelProp'} | ${'descriptionProp'} | ${true}
+ `(
+ "$description a GlFormGroup when label is '$labelProp' and description is '$descriptionProp'",
+ ({ labelProp, descriptionProp, rendersGlFormGroup }) => {
+ createComponent({ label: labelProp, description: descriptionProp });
+
+ expect(findGlFormGroup().exists()).toBe(rendersGlFormGroup);
+ },
+ );
+ });
+
+ describe('options', () => {
beforeEach(() => {
createComponent();
});
+ it('passes the label to the form group', () => {
+ expect(findGlFormGroup().attributes('label')).toBe(label);
+ });
+
+ it('passes the decription to the form group', () => {
+ expect(findGlFormGroup().attributes('decription')).toBe(decription);
+ });
+
it('sets the input name', () => {
expect(findInput().attributes('name')).toBe(name);
});
+
+ it('is not filterable with few items', () => {
+ expect(findGlListbox().props('searchable')).toBe(false);
+ });
+
+ it('passes attributes to the root element', () => {
+ expect(findGlFormGroup().attributes('id')).toBe(id);
+ });
});
describe('toggle text', () => {
@@ -91,12 +133,29 @@ describe('ListboxInput', () => {
});
describe('search', () => {
- beforeEach(() => {
- createComponent();
+ it('is searchable when there are more than 10 items', () => {
+ createComponent({
+ items: [
+ {
+ text: 'Group 1',
+ options: [...Array(10).keys()].map((index) => ({
+ text: index + 1,
+ value: String(index + 1),
+ })),
+ },
+ {
+ text: 'Group 2',
+ options: [{ text: 'Item 11', value: '11' }],
+ },
+ ],
+ });
+
+ expect(findGlListbox().props('searchable')).toBe(true);
});
it('passes all items to GlListbox by default', () => {
createComponent();
+
expect(findGlListbox().props('items')).toStrictEqual(items);
});
diff --git a/spec/frontend/vue_shared/components/markdown/editor_mode_dropdown_spec.js b/spec/frontend/vue_shared/components/markdown/editor_mode_dropdown_spec.js
new file mode 100644
index 00000000000..34071775b9c
--- /dev/null
+++ b/spec/frontend/vue_shared/components/markdown/editor_mode_dropdown_spec.js
@@ -0,0 +1,58 @@
+import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import EditorModeDropdown from '~/vue_shared/components/markdown/editor_mode_dropdown.vue';
+
+describe('vue_shared/component/markdown/editor_mode_dropdown', () => {
+ let wrapper;
+
+ const createComponent = ({ value, size } = {}) => {
+ wrapper = shallowMount(EditorModeDropdown, {
+ propsData: {
+ value,
+ size,
+ },
+ });
+ };
+
+ const findDropdown = () => wrapper.findComponent(GlDropdown);
+ const findDropdownItem = (text) =>
+ wrapper
+ .findAllComponents(GlDropdownItem)
+ .filter((item) => item.text().startsWith(text))
+ .at(0);
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe.each`
+ modeText | value | dropdownText | otherMode
+ ${'Rich text'} | ${'richText'} | ${'View markdown'} | ${'Markdown'}
+ ${'Markdown'} | ${'markdown'} | ${'View rich text'} | ${'Rich text'}
+ `('$modeText', ({ modeText, value, dropdownText, otherMode }) => {
+ beforeEach(() => {
+ createComponent({ value });
+ });
+
+ it('shows correct dropdown label', () => {
+ expect(findDropdown().props('text')).toEqual(dropdownText);
+ });
+
+ it('checks correct checked dropdown item', () => {
+ expect(findDropdownItem(modeText).props().isChecked).toBe(true);
+ expect(findDropdownItem(otherMode).props().isChecked).toBe(false);
+ });
+
+ it('emits event on click', () => {
+ findDropdownItem(modeText).vm.$emit('click');
+
+ expect(wrapper.emitted().input).toEqual([[value]]);
+ });
+ });
+
+ it('passes size to dropdown', () => {
+ createComponent({ size: 'small', value: 'markdown' });
+
+ expect(findDropdown().props('size')).toEqual('small');
+ });
+});
diff --git a/spec/frontend/vue_shared/components/markdown/field_spec.js b/spec/frontend/vue_shared/components/markdown/field_spec.js
index 285ea10c813..3b8e78bbadd 100644
--- a/spec/frontend/vue_shared/components/markdown/field_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/field_spec.js
@@ -37,7 +37,7 @@ describe('Markdown field component', () => {
axiosMock.restore();
});
- function createSubject({ lines = [], enablePreview = true } = {}) {
+ function createSubject({ lines = [], enablePreview = true, showContentEditorSwitcher } = {}) {
// We actually mount a wrapper component so that we can force Vue to rerender classes in order to test a regression
// caused by mixing Vanilla JS and Vue.
subject = mountExtended(
@@ -68,6 +68,7 @@ describe('Markdown field component', () => {
lines,
enablePreview,
restrictedToolBarItems,
+ showContentEditorSwitcher,
},
},
);
@@ -191,6 +192,7 @@ describe('Markdown field component', () => {
markdownDocsPath,
quickActionsDocsPath: '',
showCommentToolBar: true,
+ showContentEditorSwitcher: false,
});
});
});
@@ -342,4 +344,18 @@ describe('Markdown field component', () => {
restrictedToolBarItems,
);
});
+
+ describe('showContentEditorSwitcher', () => {
+ it('defaults to false', () => {
+ createSubject();
+
+ expect(findMarkdownToolbar().props('showContentEditorSwitcher')).toBe(false);
+ });
+
+ it('passes showContentEditorSwitcher', () => {
+ createSubject({ showContentEditorSwitcher: true });
+
+ expect(findMarkdownToolbar().props('showContentEditorSwitcher')).toBe(true);
+ });
+ });
});
diff --git a/spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js b/spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js
index 5f416db2676..e3df2cde1c1 100644
--- a/spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js
@@ -1,4 +1,3 @@
-import { GlSegmentedControl } from '@gitlab/ui';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
@@ -49,7 +48,6 @@ describe('vue_shared/component/markdown/markdown_editor', () => {
},
});
};
- const findSegmentedControl = () => wrapper.findComponent(GlSegmentedControl);
const findMarkdownField = () => wrapper.findComponent(MarkdownField);
const findTextarea = () => wrapper.find('textarea');
const findLocalStorageSync = () => wrapper.findComponent(LocalStorageSync);
@@ -97,36 +95,28 @@ describe('vue_shared/component/markdown/markdown_editor', () => {
expect(findTextarea().element.value).toBe(value);
});
- it('renders switch segmented control', () => {
+ it(`emits ${EDITING_MODE_CONTENT_EDITOR} event when enableContentEditor emitted from markdown editor`, async () => {
buildWrapper();
- expect(findSegmentedControl().props()).toEqual({
- checked: EDITING_MODE_MARKDOWN_FIELD,
- options: [
- {
- text: expect.any(String),
- value: EDITING_MODE_MARKDOWN_FIELD,
- },
- {
- text: expect.any(String),
- value: EDITING_MODE_CONTENT_EDITOR,
- },
- ],
- });
- });
+ findMarkdownField().vm.$emit('enableContentEditor');
- describe.each`
- editingMode
- ${EDITING_MODE_CONTENT_EDITOR}
- ${EDITING_MODE_MARKDOWN_FIELD}
- `('when segmented control emits change event with $editingMode value', ({ editingMode }) => {
- it(`emits ${editingMode} event`, () => {
- buildWrapper();
+ await nextTick();
- findSegmentedControl().vm.$emit('change', editingMode);
+ expect(wrapper.emitted(EDITING_MODE_CONTENT_EDITOR)).toHaveLength(1);
+ });
- expect(wrapper.emitted(editingMode)).toHaveLength(1);
+ it(`emits ${EDITING_MODE_MARKDOWN_FIELD} event when enableMarkdownEditor emitted from content editor`, async () => {
+ buildWrapper({
+ stubs: { ContentEditor: stubComponent(ContentEditor) },
});
+
+ findMarkdownField().vm.$emit('enableContentEditor');
+
+ await nextTick();
+
+ findContentEditor().vm.$emit('enableMarkdownEditor');
+
+ expect(wrapper.emitted(EDITING_MODE_MARKDOWN_FIELD)).toHaveLength(1);
});
describe(`when editingMode is ${EDITING_MODE_MARKDOWN_FIELD}`, () => {
@@ -159,11 +149,10 @@ describe('vue_shared/component/markdown/markdown_editor', () => {
expect(wrapper.emitted('keydown')).toHaveLength(1);
});
- describe(`when segmented control triggers input event with ${EDITING_MODE_CONTENT_EDITOR} value`, () => {
+ describe(`when markdown field triggers enableContentEditor event`, () => {
beforeEach(() => {
buildWrapper();
- findSegmentedControl().vm.$emit('input', EDITING_MODE_CONTENT_EDITOR);
- findSegmentedControl().vm.$emit('change', EDITING_MODE_CONTENT_EDITOR);
+ findMarkdownField().vm.$emit('enableContentEditor');
});
it('displays the content editor', () => {
@@ -202,7 +191,7 @@ describe('vue_shared/component/markdown/markdown_editor', () => {
describe(`when editingMode is ${EDITING_MODE_CONTENT_EDITOR}`, () => {
beforeEach(() => {
buildWrapper();
- findSegmentedControl().vm.$emit('input', EDITING_MODE_CONTENT_EDITOR);
+ findMarkdownField().vm.$emit('enableContentEditor');
});
describe('when autofocus is true', () => {
@@ -234,9 +223,9 @@ describe('vue_shared/component/markdown/markdown_editor', () => {
expect(wrapper.emitted('keydown')).toEqual([[event]]);
});
- describe(`when segmented control triggers input event with ${EDITING_MODE_MARKDOWN_FIELD} value`, () => {
+ describe(`when richText editor triggers enableMarkdownEditor event`, () => {
beforeEach(() => {
- findSegmentedControl().vm.$emit('input', EDITING_MODE_MARKDOWN_FIELD);
+ findContentEditor().vm.$emit('enableMarkdownEditor');
});
it('hides the content editor', () => {
@@ -251,29 +240,5 @@ describe('vue_shared/component/markdown/markdown_editor', () => {
expect(findLocalStorageSync().props().value).toBe(EDITING_MODE_MARKDOWN_FIELD);
});
});
-
- describe('when content editor emits loading event', () => {
- beforeEach(() => {
- findContentEditor().vm.$emit('loading');
- });
-
- it('disables switch editing mode control', () => {
- // This is the only way that I found to check the segmented control is disabled
- expect(findSegmentedControl().find('input[disabled]').exists()).toBe(true);
- });
-
- describe.each`
- event
- ${'loadingSuccess'}
- ${'loadingError'}
- `('when content editor emits $event event', ({ event }) => {
- beforeEach(() => {
- findContentEditor().vm.$emit(event);
- });
- it('enables the switch editing mode control', () => {
- expect(findSegmentedControl().find('input[disabled]').exists()).toBe(false);
- });
- });
- });
});
});
diff --git a/spec/frontend/vue_shared/components/markdown/toolbar_spec.js b/spec/frontend/vue_shared/components/markdown/toolbar_spec.js
index f698794b951..b1a1dbbeb7a 100644
--- a/spec/frontend/vue_shared/components/markdown/toolbar_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/toolbar_spec.js
@@ -1,5 +1,6 @@
import { mount } from '@vue/test-utils';
import Toolbar from '~/vue_shared/components/markdown/toolbar.vue';
+import EditorModeDropdown from '~/vue_shared/components/markdown/editor_mode_dropdown.vue';
describe('toolbar', () => {
let wrapper;
@@ -47,4 +48,18 @@ describe('toolbar', () => {
expect(wrapper.find('.comment-toolbar').exists()).toBe(true);
});
});
+
+ describe('with content editor switcher', () => {
+ beforeEach(() => {
+ createMountedWrapper({
+ showContentEditorSwitcher: true,
+ });
+ });
+
+ it('re-emits event from switcher', () => {
+ wrapper.findComponent(EditorModeDropdown).vm.$emit('input', 'richText');
+
+ expect(wrapper.emitted('enableContentEditor')).toEqual([[]]);
+ });
+ });
});
diff --git a/spec/frontend/vue_shared/components/runner_aws_deployments/__snapshots__/runner_aws_deployments_modal_spec.js.snap b/spec/frontend/vue_shared/components/runner_aws_deployments/__snapshots__/runner_aws_deployments_modal_spec.js.snap
deleted file mode 100644
index 2ea8985b16a..00000000000
--- a/spec/frontend/vue_shared/components/runner_aws_deployments/__snapshots__/runner_aws_deployments_modal_spec.js.snap
+++ /dev/null
@@ -1,177 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`RunnerAwsDeploymentsModal renders the modal 1`] = `
-<gl-modal-stub
- actionprimary="[object Object]"
- actionsecondary="[object Object]"
- arialabel=""
- dismisslabel="Close"
- modalclass=""
- modalid="runner-aws-deployments-modal"
- size="sm"
- title="Deploy GitLab Runner in AWS"
- titletag="h4"
->
- <p>
- Select your preferred option here. In the next step, you can choose the capacity for your runner in the AWS CloudFormation console.
- </p>
-
- <gl-form-radio-group-stub
- checked="[object Object]"
- disabledfield="disabled"
- htmlfield="html"
- label="Choose your preferred GitLab Runner"
- label-sr-only=""
- options=""
- textfield="text"
- valuefield="value"
- >
- <gl-form-radio-stub
- class="gl-py-5 gl-pl-8 gl-border-b"
- value="[object Object]"
- >
- <div
- class="gl-mt-n1 gl-pl-4 gl-pb-2 gl-font-weight-bold"
- >
-
- Amazon Linux 2 Docker HA with manual scaling and optional scheduling. Non-spot.
-
- <gl-accordion-stub
- class="gl-pt-3"
- headerlevel="3"
- >
- <gl-accordion-item-stub
- class="gl-font-weight-normal"
- headerclass=""
- title="More Details"
- titlevisible="Less Details"
- >
- <p
- class="gl-pt-2"
- >
- No spot. This is the default choice for Linux Docker executor.
- </p>
-
- <p
- class="gl-m-0"
- >
- A capacity of 1 enables warm HA through Auto Scaling group re-spawn. A capacity of 2 enables hot HA because the service is available even when a node is lost. A capacity of 3 or more enables hot HA and manual scaling of runner fleet.
- </p>
- </gl-accordion-item-stub>
- </gl-accordion-stub>
- </div>
- </gl-form-radio-stub>
- <gl-form-radio-stub
- class="gl-py-5 gl-pl-8 gl-border-b"
- value="[object Object]"
- >
- <div
- class="gl-mt-n1 gl-pl-4 gl-pb-2 gl-font-weight-bold"
- >
-
- Amazon Linux 2 Docker HA with manual scaling and optional scheduling. 100% spot.
-
- <gl-accordion-stub
- class="gl-pt-3"
- headerlevel="3"
- >
- <gl-accordion-item-stub
- class="gl-font-weight-normal"
- headerclass=""
- title="More Details"
- titlevisible="Less Details"
- >
- <p
- class="gl-pt-2"
- >
- 100% spot.
- </p>
-
- <p
- class="gl-m-0"
- >
- Capacity of 1 enables warm HA through Auto Scaling group re-spawn. Capacity of 2 enables hot HA because the service is available even when a node is lost. Capacity of 3 or more enables hot HA and manual scaling of runner fleet.
- </p>
- </gl-accordion-item-stub>
- </gl-accordion-stub>
- </div>
- </gl-form-radio-stub>
- <gl-form-radio-stub
- class="gl-py-5 gl-pl-8 gl-border-b"
- value="[object Object]"
- >
- <div
- class="gl-mt-n1 gl-pl-4 gl-pb-2 gl-font-weight-bold"
- >
-
- Windows 2019 Shell with manual scaling and optional scheduling. Non-spot.
-
- <gl-accordion-stub
- class="gl-pt-3"
- headerlevel="3"
- >
- <gl-accordion-item-stub
- class="gl-font-weight-normal"
- headerclass=""
- title="More Details"
- titlevisible="Less Details"
- >
- <p
- class="gl-pt-2"
- >
- No spot. Default choice for Windows Shell executor.
- </p>
-
- <p
- class="gl-m-0"
- >
- Capacity of 1 enables warm HA through Auto Scaling group re-spawn. Capacity of 2 enables hot HA because the service is available even when a node is lost. Capacity of 3 or more enables hot HA and manual scaling of runner fleet.
- </p>
- </gl-accordion-item-stub>
- </gl-accordion-stub>
- </div>
- </gl-form-radio-stub>
- <gl-form-radio-stub
- class="gl-py-5 gl-pl-8"
- value="[object Object]"
- >
- <div
- class="gl-mt-n1 gl-pl-4 gl-pb-2 gl-font-weight-bold"
- >
-
- Windows 2019 Shell with manual scaling and optional scheduling. 100% spot.
-
- <gl-accordion-stub
- class="gl-pt-3"
- headerlevel="3"
- >
- <gl-accordion-item-stub
- class="gl-font-weight-normal"
- headerclass=""
- title="More Details"
- titlevisible="Less Details"
- >
- <p
- class="gl-pt-2"
- >
- 100% spot.
- </p>
-
- <p
- class="gl-m-0"
- >
- Capacity of 1 enables warm HA through Auto Scaling group re-spawn. Capacity of 2 enables hot HA because the service is available even when a node is lost. Capacity of 3 or more enables hot HA and manual scaling of runner fleet.
- </p>
- </gl-accordion-item-stub>
- </gl-accordion-stub>
- </div>
- </gl-form-radio-stub>
- </gl-form-radio-group-stub>
-
- <p>
- <gl-sprintf-stub
- message="Don't see what you are looking for? See the full list of options, including a fully customizable option %{linkStart}here%{linkEnd}."
- />
- </p>
-</gl-modal-stub>
-`;
diff --git a/spec/frontend/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal_spec.js b/spec/frontend/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal_spec.js
index a9ba4946358..c8ca75787f1 100644
--- a/spec/frontend/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal_spec.js
+++ b/spec/frontend/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal_spec.js
@@ -1,30 +1,28 @@
-import { GlModal, GlFormRadio } from '@gitlab/ui';
+import { GlModal } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import { getBaseURL, visitUrl } from '~/lib/utils/url_utility';
-import { mockTracking } from 'helpers/tracking_helper';
-import {
- CF_BASE_URL,
- TEMPLATES_BASE_URL,
- EASY_BUTTONS,
-} from '~/vue_shared/components/runner_aws_deployments/constants';
+import { s__ } from '~/locale';
import RunnerAwsDeploymentsModal from '~/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal.vue';
+import RunnerAwsInstructions from '~/vue_shared/components/runner_instructions/instructions/runner_aws_instructions.vue';
jest.mock('~/lib/utils/url_utility', () => ({
...jest.requireActual('~/lib/utils/url_utility'),
visitUrl: jest.fn(),
}));
+const mockModalId = 'runner-aws-deployments-modal';
+
describe('RunnerAwsDeploymentsModal', () => {
let wrapper;
const findModal = () => wrapper.findComponent(GlModal);
- const findEasyButtons = () => wrapper.findAllComponents(GlFormRadio);
+ const findRunnerAwsInstructions = () => wrapper.findComponent(RunnerAwsInstructions);
- const createComponent = () => {
+ const createComponent = (options) => {
wrapper = shallowMount(RunnerAwsDeploymentsModal, {
propsData: {
- modalId: 'runner-aws-deployments-modal',
+ modalId: mockModalId,
},
+ ...options,
});
};
@@ -36,39 +34,39 @@ describe('RunnerAwsDeploymentsModal', () => {
wrapper.destroy();
});
- it('renders the modal', () => {
- expect(wrapper.element).toMatchSnapshot();
+ it('renders modal', () => {
+ expect(findModal().props()).toMatchObject({
+ size: 'sm',
+ modalId: mockModalId,
+ title: s__('Runners|Deploy GitLab Runner in AWS'),
+ });
+ expect(findModal().attributes()).toMatchObject({
+ 'hide-footer': '',
+ });
});
- it('should contain all easy buttons', () => {
- expect(findEasyButtons()).toHaveLength(EASY_BUTTONS.length);
+ it('renders modal contents', () => {
+ expect(findRunnerAwsInstructions().exists()).toBe(true);
});
- describe('first easy button', () => {
- it('should contain the correct description', () => {
- expect(findEasyButtons().at(0).text()).toContain(EASY_BUTTONS[0].description);
- });
-
- it('should contain the correct link', () => {
- const templateUrl = encodeURIComponent(TEMPLATES_BASE_URL + EASY_BUTTONS[0].templateName);
- const { stackName } = EASY_BUTTONS[0];
- const instanceUrl = encodeURIComponent(getBaseURL());
- const url = `${CF_BASE_URL}templateURL=${templateUrl}&stackName=${stackName}&param_3GITLABRunnerInstanceURL=${instanceUrl}`;
-
- findModal().vm.$emit('primary');
+ it('when contents trigger closing, modal closes', () => {
+ const mockClose = jest.fn();
- expect(visitUrl).toHaveBeenCalledWith(url, true);
+ createComponent({
+ stubs: {
+ GlModal: {
+ template: '<div><slot/></div>',
+ methods: {
+ close: mockClose,
+ },
+ },
+ },
});
- it('should track an event when clicked', () => {
- const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
+ expect(mockClose).toHaveBeenCalledTimes(0);
- findModal().vm.$emit('primary');
+ findRunnerAwsInstructions().vm.$emit('close');
- expect(trackingSpy).toHaveBeenCalledTimes(1);
- expect(trackingSpy).toHaveBeenCalledWith(undefined, 'template_clicked', {
- label: EASY_BUTTONS[0].stackName,
- });
- });
+ expect(mockClose).toHaveBeenCalledTimes(1);
});
});
diff --git a/spec/frontend/vue_shared/components/runner_instructions/instructions/__snapshots__/runner_docker_instructions_spec.js.snap b/spec/frontend/vue_shared/components/runner_instructions/instructions/__snapshots__/runner_docker_instructions_spec.js.snap
new file mode 100644
index 00000000000..d14f66df8a1
--- /dev/null
+++ b/spec/frontend/vue_shared/components/runner_instructions/instructions/__snapshots__/runner_docker_instructions_spec.js.snap
@@ -0,0 +1,3 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`RunnerDockerInstructions renders contents 1`] = `"To install Runner in a container follow the instructions described in the GitLab documentation View installation instructions Close"`;
diff --git a/spec/frontend/vue_shared/components/runner_instructions/instructions/__snapshots__/runner_kubernetes_instructions_spec.js.snap b/spec/frontend/vue_shared/components/runner_instructions/instructions/__snapshots__/runner_kubernetes_instructions_spec.js.snap
new file mode 100644
index 00000000000..1172bf07dff
--- /dev/null
+++ b/spec/frontend/vue_shared/components/runner_instructions/instructions/__snapshots__/runner_kubernetes_instructions_spec.js.snap
@@ -0,0 +1,3 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`RunnerKubernetesInstructions renders contents 1`] = `"To install Runner in Kubernetes follow the instructions described in the GitLab documentation. View installation instructions Close"`;
diff --git a/spec/frontend/vue_shared/components/runner_instructions/instructions/runner_aws_instructions_spec.js b/spec/frontend/vue_shared/components/runner_instructions/instructions/runner_aws_instructions_spec.js
new file mode 100644
index 00000000000..4d566dbec0c
--- /dev/null
+++ b/spec/frontend/vue_shared/components/runner_instructions/instructions/runner_aws_instructions_spec.js
@@ -0,0 +1,117 @@
+import {
+ GlAccordion,
+ GlAccordionItem,
+ GlButton,
+ GlFormRadio,
+ GlFormRadioGroup,
+ GlLink,
+ GlSprintf,
+} from '@gitlab/ui';
+import { getBaseURL, visitUrl } from '~/lib/utils/url_utility';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { mockTracking } from 'helpers/tracking_helper';
+import {
+ AWS_README_URL,
+ AWS_CF_BASE_URL,
+ AWS_TEMPLATES_BASE_URL,
+ AWS_EASY_BUTTONS,
+} from '~/vue_shared/components/runner_instructions/constants';
+import RunnerAwsInstructions from '~/vue_shared/components/runner_instructions/instructions/runner_aws_instructions.vue';
+import { __ } from '~/locale';
+
+jest.mock('~/lib/utils/url_utility', () => ({
+ ...jest.requireActual('~/lib/utils/url_utility'),
+ visitUrl: jest.fn(),
+}));
+
+describe('RunnerAwsInstructions', () => {
+ let wrapper;
+
+ const findEasyButtonsRadioGroup = () => wrapper.findComponent(GlFormRadioGroup);
+ const findEasyButtons = () => wrapper.findAllComponents(GlFormRadio);
+ const findEasyButtonAt = (i) => findEasyButtons().at(i);
+ const findLink = () => wrapper.findComponent(GlLink);
+ const findOkButton = () =>
+ wrapper
+ .findAllComponents(GlButton)
+ .filter((w) => w.props('variant') === 'confirm')
+ .at(0);
+ const findCloseButton = () => wrapper.findByText(__('Close'));
+
+ const createComponent = () => {
+ wrapper = shallowMountExtended(RunnerAwsInstructions, {
+ stubs: {
+ GlSprintf,
+ },
+ });
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('should contain every button', () => {
+ expect(findEasyButtons()).toHaveLength(AWS_EASY_BUTTONS.length);
+ });
+
+ const AWS_EASY_BUTTONS_PARAMS = AWS_EASY_BUTTONS.map((val, idx) => ({ ...val, idx }));
+
+ describe.each(AWS_EASY_BUTTONS_PARAMS)(
+ 'easy button %#',
+ ({ idx, description, moreDetails1, moreDetails2, templateName, stackName }) => {
+ it('should contain button description', () => {
+ const text = findEasyButtonAt(idx).text();
+
+ expect(text).toContain(description);
+ expect(text).toContain(moreDetails1);
+ expect(text).toContain(moreDetails2);
+ });
+
+ it('should show more details', () => {
+ const accordion = findEasyButtonAt(idx).findComponent(GlAccordion);
+ const accordionItem = accordion.findComponent(GlAccordionItem);
+
+ expect(accordion.props('headerLevel')).toBe(3);
+ expect(accordionItem.props('title')).toBe(__('More Details'));
+ expect(accordionItem.props('titleVisible')).toBe(__('Less Details'));
+ });
+
+ describe('when clicked', () => {
+ let trackingSpy;
+
+ beforeEach(() => {
+ trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
+
+ findEasyButtonsRadioGroup().vm.$emit('input', idx);
+ findOkButton().vm.$emit('click');
+ });
+
+ it('should contain the correct link', () => {
+ const templateUrl = encodeURIComponent(AWS_TEMPLATES_BASE_URL + templateName);
+ const instanceUrl = encodeURIComponent(getBaseURL());
+ const url = `${AWS_CF_BASE_URL}templateURL=${templateUrl}&stackName=${stackName}&param_3GITLABRunnerInstanceURL=${instanceUrl}`;
+
+ expect(visitUrl).toHaveBeenCalledTimes(1);
+ expect(visitUrl).toHaveBeenCalledWith(url, true);
+ });
+
+ it('should track an event when clicked', () => {
+ expect(trackingSpy).toHaveBeenCalledTimes(1);
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, 'template_clicked', {
+ label: stackName,
+ });
+ });
+ });
+ },
+ );
+
+ it('displays link with more information', () => {
+ expect(findLink().attributes('href')).toBe(AWS_README_URL);
+ });
+
+ it('triggers the modal to close', () => {
+ findCloseButton().vm.$emit('click');
+
+ expect(wrapper.emitted('close')).toHaveLength(1);
+ });
+});
diff --git a/spec/frontend/vue_shared/components/runner_instructions/instructions/runner_cli_instructions_spec.js b/spec/frontend/vue_shared/components/runner_instructions/instructions/runner_cli_instructions_spec.js
new file mode 100644
index 00000000000..f9d700fe67f
--- /dev/null
+++ b/spec/frontend/vue_shared/components/runner_instructions/instructions/runner_cli_instructions_spec.js
@@ -0,0 +1,169 @@
+import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import getRunnerSetupInstructionsQuery from '~/vue_shared/components/runner_instructions/graphql/get_runner_setup.query.graphql';
+import RunnerCliInstructions from '~/vue_shared/components/runner_instructions/instructions/runner_cli_instructions.vue';
+
+import { mockRunnerPlatforms, mockInstructions, mockInstructionsWindows } from '../mock_data';
+
+Vue.use(VueApollo);
+
+jest.mock('@gitlab/ui/dist/utils');
+
+const mockPlatforms = mockRunnerPlatforms.data.runnerPlatforms.nodes.map(
+ ({ name, humanReadableName, architectures }) => ({
+ name,
+ humanReadableName,
+ architectures: architectures?.nodes || [],
+ }),
+);
+
+const [mockPlatform, mockPlatform2] = mockPlatforms;
+const mockArchitectures = mockPlatform.architectures;
+
+describe('RunnerCliInstructions component', () => {
+ let wrapper;
+ let fakeApollo;
+ let runnerSetupInstructionsHandler;
+
+ const findGlLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
+ const findAlert = () => wrapper.findComponent(GlAlert);
+ 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 requestHandlers = [[getRunnerSetupInstructionsQuery, runnerSetupInstructionsHandler]];
+
+ fakeApollo = createMockApollo(requestHandlers);
+
+ wrapper = extendedWrapper(
+ shallowMount(RunnerCliInstructions, {
+ propsData: {
+ platform: mockPlatform,
+ registrationToken: 'MY_TOKEN',
+ ...props,
+ },
+ apolloProvider: fakeApollo,
+ ...options,
+ }),
+ );
+ };
+
+ beforeEach(() => {
+ runnerSetupInstructionsHandler = jest.fn().mockResolvedValue(mockInstructions);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('when the instructions are shown', () => {
+ beforeEach(async () => {
+ createComponent();
+ await waitForPromises();
+ });
+
+ it('should not show alert', async () => {
+ expect(findAlert().exists()).toBe(false);
+ });
+
+ it('should contain a number of dropdown items for the architecture options', () => {
+ expect(findArchitectureDropdownItems()).toHaveLength(
+ mockRunnerPlatforms.data.runnerPlatforms.nodes[0].architectures.nodes.length,
+ );
+ });
+
+ describe('should display instructions', () => {
+ const { installInstructions } = mockInstructions.data.runnerSetup;
+
+ it('runner instructions are requested', () => {
+ expect(runnerSetupInstructionsHandler).toHaveBeenCalledWith({
+ platform: 'linux',
+ architecture: 'amd64',
+ });
+ });
+
+ it('binary instructions are shown', async () => {
+ const instructions = findBinaryInstructions().text();
+
+ expect(instructions).toBe(installInstructions.trim());
+ });
+
+ it('register command is shown with a replaced token', async () => {
+ const command = findRegisterCommand().text();
+
+ expect(command).toBe(
+ 'sudo gitlab-runner register --url http://localhost/ --registration-token MY_TOKEN',
+ );
+ });
+
+ it('architecture download link is shown', () => {
+ expect(findBinaryDownloadButton().attributes('href')).toBe(
+ mockArchitectures[0].downloadLocation,
+ );
+ });
+ });
+
+ describe('after another platform and architecture are selected', () => {
+ beforeEach(async () => {
+ runnerSetupInstructionsHandler.mockResolvedValue(mockInstructionsWindows);
+
+ findArchitectureDropdownItems().at(1).vm.$emit('click');
+
+ wrapper.setProps({ platform: mockPlatform2 });
+ await waitForPromises();
+ });
+
+ it('runner instructions are requested', () => {
+ expect(runnerSetupInstructionsHandler).toHaveBeenLastCalledWith({
+ platform: mockPlatform2.name,
+ architecture: mockPlatform2.architectures[0].name,
+ });
+ });
+ });
+ });
+
+ describe('when a register token is not known', () => {
+ 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(mockInstructions.data.runnerSetup.registerInstructions);
+ });
+ });
+
+ describe('when apollo is loading', () => {
+ it('should show a loading icon', async () => {
+ createComponent();
+
+ expect(findGlLoadingIcon().exists()).toBe(true);
+
+ await waitForPromises();
+
+ expect(findGlLoadingIcon().exists()).toBe(false);
+ });
+ });
+
+ describe('when instructions cannot be loaded', () => {
+ beforeEach(async () => {
+ runnerSetupInstructionsHandler.mockRejectedValue();
+
+ createComponent();
+ await waitForPromises();
+ });
+
+ it('should show alert', () => {
+ expect(wrapper.emitted()).toEqual({ error: [[]] });
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/runner_instructions/instructions/runner_docker_instructions_spec.js b/spec/frontend/vue_shared/components/runner_instructions/instructions/runner_docker_instructions_spec.js
new file mode 100644
index 00000000000..2922d261b24
--- /dev/null
+++ b/spec/frontend/vue_shared/components/runner_instructions/instructions/runner_docker_instructions_spec.js
@@ -0,0 +1,28 @@
+import { shallowMount } from '@vue/test-utils';
+
+import { GlButton } from '@gitlab/ui';
+import RunnerDockerInstructions from '~/vue_shared/components/runner_instructions/instructions/runner_docker_instructions.vue';
+
+describe('RunnerDockerInstructions', () => {
+ let wrapper;
+
+ const createComponent = () => {
+ wrapper = shallowMount(RunnerDockerInstructions, {});
+ };
+
+ const findButton = () => wrapper.findComponent(GlButton);
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders contents', () => {
+ expect(wrapper.text().replace(/\s+/g, ' ')).toMatchSnapshot();
+ });
+
+ it('renders link', () => {
+ expect(findButton().attributes('href')).toBe(
+ 'https://docs.gitlab.com/runner/install/docker.html',
+ );
+ });
+});
diff --git a/spec/frontend/vue_shared/components/runner_instructions/instructions/runner_kubernetes_instructions_spec.js b/spec/frontend/vue_shared/components/runner_instructions/instructions/runner_kubernetes_instructions_spec.js
new file mode 100644
index 00000000000..0bfcc0e3d86
--- /dev/null
+++ b/spec/frontend/vue_shared/components/runner_instructions/instructions/runner_kubernetes_instructions_spec.js
@@ -0,0 +1,28 @@
+import { shallowMount } from '@vue/test-utils';
+
+import { GlButton } from '@gitlab/ui';
+import RunnerKubernetesInstructions from '~/vue_shared/components/runner_instructions/instructions/runner_kubernetes_instructions.vue';
+
+describe('RunnerKubernetesInstructions', () => {
+ let wrapper;
+
+ const createComponent = () => {
+ wrapper = shallowMount(RunnerKubernetesInstructions, {});
+ };
+
+ const findButton = () => wrapper.findComponent(GlButton);
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders contents', () => {
+ expect(wrapper.text().replace(/\s+/g, ' ')).toMatchSnapshot();
+ });
+
+ it('renders link', () => {
+ expect(findButton().attributes('href')).toBe(
+ 'https://docs.gitlab.com/runner/install/kubernetes.html',
+ );
+ });
+});
diff --git a/spec/frontend/vue_shared/components/runner_instructions/mock_data.js b/spec/frontend/vue_shared/components/runner_instructions/mock_data.js
index 79cacadd6af..add334f166c 100644
--- a/spec/frontend/vue_shared/components/runner_instructions/mock_data.js
+++ b/spec/frontend/vue_shared/components/runner_instructions/mock_data.js
@@ -1,5 +1,5 @@
-import mockGraphqlRunnerPlatforms from 'test_fixtures/graphql/runner_instructions/get_runner_platforms.query.graphql.json';
-import mockGraphqlInstructions from 'test_fixtures/graphql/runner_instructions/get_runner_setup.query.graphql.json';
-import mockGraphqlInstructionsWindows from 'test_fixtures/graphql/runner_instructions/get_runner_setup.query.graphql.windows.json';
+import mockRunnerPlatforms from 'test_fixtures/graphql/runner_instructions/get_runner_platforms.query.graphql.json';
+import mockInstructions from 'test_fixtures/graphql/runner_instructions/get_runner_setup.query.graphql.json';
+import mockInstructionsWindows from 'test_fixtures/graphql/runner_instructions/get_runner_setup.query.graphql.windows.json';
-export { mockGraphqlRunnerPlatforms, mockGraphqlInstructions, mockGraphqlInstructionsWindows };
+export { mockRunnerPlatforms, mockInstructions, mockInstructionsWindows };
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 ae9157591c5..19f2dd137ff 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
@@ -1,4 +1,4 @@
-import { GlAlert, GlModal, GlButton, GlLoadingIcon, GlSkeletonLoader } from '@gitlab/ui';
+import { GlAlert, GlModal, GlButton, GlSkeletonLoader } from '@gitlab/ui';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import { shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
@@ -6,15 +6,13 @@ import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import getRunnerPlatformsQuery from '~/vue_shared/components/runner_instructions/graphql/queries/get_runner_platforms.query.graphql';
-import getRunnerSetupInstructionsQuery from '~/vue_shared/components/runner_instructions/graphql/queries/get_runner_setup.query.graphql';
+import getRunnerPlatformsQuery from '~/vue_shared/components/runner_instructions/graphql/get_runner_platforms.query.graphql';
import RunnerInstructionsModal from '~/vue_shared/components/runner_instructions/runner_instructions_modal.vue';
+import RunnerCliInstructions from '~/vue_shared/components/runner_instructions/instructions/runner_cli_instructions.vue';
+import RunnerDockerInstructions from '~/vue_shared/components/runner_instructions/instructions/runner_docker_instructions.vue';
+import RunnerKubernetesInstructions from '~/vue_shared/components/runner_instructions/instructions/runner_kubernetes_instructions.vue';
-import {
- mockGraphqlRunnerPlatforms,
- mockGraphqlInstructions,
- mockGraphqlInstructionsWindows,
-} from './mock_data';
+import { mockRunnerPlatforms } from './mock_data';
Vue.use(VueApollo);
@@ -40,24 +38,16 @@ describe('RunnerInstructionsModal component', () => {
let wrapper;
let fakeApollo;
let runnerPlatformsHandler;
- let runnerSetupInstructionsHandler;
const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader);
- const findGlLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findAlert = () => wrapper.findComponent(GlAlert);
const findModal = () => wrapper.findComponent(GlModal);
const findPlatformButtonGroup = () => wrapper.findByTestId('platform-buttons');
const findPlatformButtons = () => findPlatformButtonGroup().findAllComponents(GlButton);
- 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 findRunnerCliInstructions = () => wrapper.findComponent(RunnerCliInstructions);
const createComponent = ({ props, shown = true, ...options } = {}) => {
- const requestHandlers = [
- [getRunnerPlatformsQuery, runnerPlatformsHandler],
- [getRunnerSetupInstructionsQuery, runnerSetupInstructionsHandler],
- ];
+ const requestHandlers = [[getRunnerPlatformsQuery, runnerPlatformsHandler]];
fakeApollo = createMockApollo(requestHandlers);
@@ -80,8 +70,7 @@ describe('RunnerInstructionsModal component', () => {
};
beforeEach(() => {
- runnerPlatformsHandler = jest.fn().mockResolvedValue(mockGraphqlRunnerPlatforms);
- runnerSetupInstructionsHandler = jest.fn().mockResolvedValue(mockGraphqlInstructions);
+ runnerPlatformsHandler = jest.fn().mockResolvedValue(mockRunnerPlatforms);
});
afterEach(() => {
@@ -103,90 +92,15 @@ describe('RunnerInstructionsModal component', () => {
const buttons = findPlatformButtons();
- expect(buttons).toHaveLength(mockGraphqlRunnerPlatforms.data.runnerPlatforms.nodes.length);
+ expect(buttons).toHaveLength(mockRunnerPlatforms.data.runnerPlatforms.nodes.length);
});
- it('should contain a number of dropdown items for the architecture options', () => {
- expect(findArchitectureDropdownItems()).toHaveLength(
- mockGraphqlRunnerPlatforms.data.runnerPlatforms.nodes[0].architectures.nodes.length,
- );
- });
-
- describe('should display default instructions', () => {
- const { installInstructions } = mockGraphqlInstructions.data.runnerSetup;
-
- it('runner instructions are requested', () => {
- expect(runnerSetupInstructionsHandler).toHaveBeenCalledWith({
- platform: 'linux',
- architecture: 'amd64',
- });
- });
-
- it('binary instructions are shown', async () => {
- const instructions = findBinaryInstructions().text();
-
- expect(instructions).toBe(installInstructions.trim());
- });
-
- it('register command is shown with a replaced token', async () => {
- const command = findRegisterCommand().text();
-
- expect(command).toBe(
- 'sudo gitlab-runner register --url http://localhost/ --registration-token MY_TOKEN',
- );
- });
- });
-
- describe('after a platform and architecture are selected', () => {
- const windowsIndex = 2;
- const { installInstructions } = mockGraphqlInstructionsWindows.data.runnerSetup;
-
- beforeEach(async () => {
- runnerSetupInstructionsHandler.mockResolvedValue(mockGraphqlInstructionsWindows);
-
- findPlatformButtons().at(windowsIndex).vm.$emit('click');
- await waitForPromises();
- });
+ it('should display architecture options', () => {
+ const { architectures } = findRunnerCliInstructions().props('platform');
- it('runner instructions are requested', () => {
- expect(runnerSetupInstructionsHandler).toHaveBeenLastCalledWith({
- platform: 'windows',
- architecture: 'amd64',
- });
- });
-
- it('architecture download link is updated', () => {
- const architectures =
- mockGraphqlRunnerPlatforms.data.runnerPlatforms.nodes[windowsIndex].architectures.nodes;
-
- expect(findBinaryDownloadButton().attributes('href')).toBe(
- architectures[0].downloadLocation,
- );
- });
-
- it('other binary instructions are shown', () => {
- const instructions = findBinaryInstructions().text();
-
- expect(instructions).toBe(installInstructions.trim());
- });
-
- it('register command is shown', () => {
- const command = findRegisterCommand().text();
-
- expect(command).toBe(
- './gitlab-runner.exe register --url http://localhost/ --registration-token MY_TOKEN',
- );
- });
-
- it('runner instructions are requested with another architecture', async () => {
- findArchitectureDropdownItems().at(1).vm.$emit('click');
- await waitForPromises();
-
- expect(runnerSetupInstructionsHandler).toHaveBeenLastCalledWith({
- platform: 'windows',
- architecture: '386',
- });
- });
+ expect(architectures).toEqual(
+ mockRunnerPlatforms.data.runnerPlatforms.nodes[0].architectures.nodes,
+ );
});
describe('when the modal resizes', () => {
@@ -206,16 +120,14 @@ describe('RunnerInstructionsModal component', () => {
});
});
- describe('when a register token is not known', () => {
+ describe.each([null, 'DEFINED'])('when registration token is %p', (token) => {
beforeEach(async () => {
- createComponent({ props: { registrationToken: undefined } });
+ createComponent({ props: { registrationToken: token } });
await waitForPromises();
});
it('register command is shown without a defined registration token', () => {
- const instructions = findRegisterCommand().text();
-
- expect(instructions).toBe(mockGraphqlInstructions.data.runnerSetup.registerInstructions);
+ expect(findRunnerCliInstructions().props('registrationToken')).toBe(token);
});
});
@@ -225,21 +137,33 @@ describe('RunnerInstructionsModal component', () => {
await waitForPromises();
});
- it('runner instructions for the default selected platform are requested', () => {
- expect(runnerSetupInstructionsHandler).toHaveBeenLastCalledWith({
- platform: 'osx',
- architecture: 'amd64',
- });
+ it('should preselect', () => {
+ const selected = findPlatformButtons()
+ .filter((btn) => btn.props('selected'))
+ .at(0);
+
+ expect(selected.text()).toBe('macOS');
});
- it('sets the focus on the default selected platform', () => {
- const findOsxPlatformButton = () => wrapper.findComponent({ ref: 'osx' });
+ it('runner instructions for the default selected platform are requested', () => {
+ const { name } = findRunnerCliInstructions().props('platform');
- findOsxPlatformButton().element.focus = jest.fn();
+ expect(name).toBe('osx');
+ });
+ });
- findModal().vm.$emit('shown');
+ describe.each`
+ platform | component
+ ${'docker'} | ${RunnerDockerInstructions}
+ ${'kubernetes'} | ${RunnerKubernetesInstructions}
+ `('with platform "$platform"', ({ platform, component }) => {
+ beforeEach(async () => {
+ createComponent({ props: { defaultPlatformName: platform } });
+ await waitForPromises();
+ });
- expect(findOsxPlatformButton().element.focus).toHaveBeenCalled();
+ it(`runner instructions for ${platform} are shown`, () => {
+ expect(wrapper.findComponent(component).exists()).toBe(true);
});
});
@@ -251,7 +175,6 @@ describe('RunnerInstructionsModal component', () => {
it('does not fetch instructions', () => {
expect(runnerPlatformsHandler).not.toHaveBeenCalled();
- expect(runnerSetupInstructionsHandler).not.toHaveBeenCalled();
});
});
@@ -259,43 +182,41 @@ describe('RunnerInstructionsModal component', () => {
it('should show a skeleton loader', async () => {
createComponent();
await nextTick();
- await nextTick();
expect(findSkeletonLoader().exists()).toBe(true);
- expect(findGlLoadingIcon().exists()).toBe(false);
-
- // wait on fetch of both `platforms` and `instructions`
- await nextTick();
- await nextTick();
-
- expect(findGlLoadingIcon().exists()).toBe(true);
});
it('once loaded, should not show a loading state', async () => {
createComponent();
-
await waitForPromises();
expect(findSkeletonLoader().exists()).toBe(false);
- expect(findGlLoadingIcon().exists()).toBe(false);
});
});
- describe('when instructions cannot be loaded', () => {
- beforeEach(async () => {
- runnerSetupInstructionsHandler.mockRejectedValue();
+ describe('errors', () => {
+ it('should show an alert when platforms cannot be loaded', async () => {
+ runnerPlatformsHandler.mockRejectedValue();
createComponent();
await waitForPromises();
- });
- it('should show alert', () => {
expect(findAlert().exists()).toBe(true);
});
- it('should not show instructions', () => {
- expect(findBinaryInstructions().exists()).toBe(false);
- expect(findRegisterCommand().exists()).toBe(false);
+ it('should show alert when instructions cannot be loaded', async () => {
+ createComponent();
+ await waitForPromises();
+
+ findRunnerCliInstructions().vm.$emit('error');
+ await waitForPromises();
+
+ expect(findAlert().exists()).toBe(true);
+
+ findAlert().vm.$emit('dismiss');
+ await nextTick();
+
+ expect(findAlert().exists()).toBe(false);
});
});
@@ -312,14 +233,16 @@ describe('RunnerInstructionsModal component', () => {
describe('show()', () => {
let mockShow;
+ let mockClose;
beforeEach(() => {
mockShow = jest.fn();
+ mockClose = jest.fn();
createComponent({
shown: false,
stubs: {
- GlModal: getGlModalStub({ show: mockShow }),
+ GlModal: getGlModalStub({ show: mockShow, close: mockClose }),
},
});
});
@@ -329,6 +252,12 @@ describe('RunnerInstructionsModal component', () => {
expect(mockShow).toHaveBeenCalledTimes(1);
});
+
+ it('delegates close()', () => {
+ wrapper.vm.close();
+
+ expect(mockClose).toHaveBeenCalledTimes(1);
+ });
});
});
});
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 33f370efdfa..5461d38599d 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
@@ -90,6 +90,17 @@ describe('Source Viewer component', () => {
});
});
+ describe('legacy fallbacks', () => {
+ it('tracks a fallback event and emits an error when viewing python files', () => {
+ const fallbackLanguage = 'python';
+ const eventData = { label: EVENT_LABEL_FALLBACK, property: fallbackLanguage };
+ createComponent({ language: fallbackLanguage });
+
+ expect(Tracking.event).toHaveBeenCalledWith(undefined, EVENT_ACTION, eventData);
+ expect(wrapper.emitted('error')).toHaveLength(1);
+ });
+ });
+
describe('highlight.js', () => {
beforeEach(() => createComponent({ language: mappedLanguage }));
@@ -114,10 +125,10 @@ describe('Source Viewer component', () => {
});
it('correctly maps languages starting with uppercase', async () => {
- await createComponent({ language: 'Python3' });
- const languageDefinition = await import(`highlight.js/lib/languages/python`);
+ await createComponent({ language: 'Ruby' });
+ const languageDefinition = await import(`highlight.js/lib/languages/ruby`);
- expect(hljs.registerLanguage).toHaveBeenCalledWith('python', languageDefinition.default);
+ expect(hljs.registerLanguage).toHaveBeenCalledWith('ruby', languageDefinition.default);
});
it('highlights the first chunk', () => {
diff --git a/spec/frontend/vue_shared/components/timezone_dropdown/timezone_dropdown_spec.js b/spec/frontend/vue_shared/components/timezone_dropdown/timezone_dropdown_spec.js
index e5f56c63031..c8351ed61d7 100644
--- a/spec/frontend/vue_shared/components/timezone_dropdown/timezone_dropdown_spec.js
+++ b/spec/frontend/vue_shared/components/timezone_dropdown/timezone_dropdown_spec.js
@@ -1,4 +1,5 @@
import { GlDropdownItem, GlDropdown } from '@gitlab/ui';
+import { nextTick } from 'vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import TimezoneDropdown from '~/vue_shared/components/timezone_dropdown/timezone_dropdown.vue';
import { formatTimezone } from '~/lib/utils/datetime_utility';
@@ -105,7 +106,14 @@ describe('Deploy freeze timezone dropdown', () => {
});
it('renders selected time zone as dropdown label', () => {
- expect(wrapper.findComponent(GlDropdown).props().text).toBe('[UTC + 2] Berlin');
+ expect(wrapper.findComponent(GlDropdown).props().text).toBe('[UTC+2] Berlin');
+ });
+
+ it('adds a checkmark to the selected option', async () => {
+ const selectedTZOption = findAllDropdownItems().at(0);
+ selectedTZOption.vm.$emit('click');
+ await nextTick();
+ expect(selectedTZOption.attributes('ischecked')).toBe('true');
});
});
});
diff --git a/spec/frontend/vue_shared/components/web_ide_link_spec.js b/spec/frontend/vue_shared/components/web_ide_link_spec.js
index 3b0f0fe6e73..2a0d2089fe3 100644
--- a/spec/frontend/vue_shared/components/web_ide_link_spec.js
+++ b/spec/frontend/vue_shared/components/web_ide_link_spec.js
@@ -7,15 +7,19 @@ import WebIdeLink, {
i18n,
PREFERRED_EDITOR_RESET_KEY,
PREFERRED_EDITOR_KEY,
- KEY_WEB_IDE,
} from '~/vue_shared/components/web_ide_link.vue';
import ConfirmForkModal from '~/vue_shared/components/confirm_fork_modal.vue';
import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue';
+import { KEY_WEB_IDE } from '~/vue_shared/components/constants';
import { stubComponent } from 'helpers/stub_component';
import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_helper';
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
+import { visitUrl } from '~/lib/utils/url_utility';
+
+jest.mock('~/lib/utils/url_utility');
+
const TEST_EDIT_URL = '/gitlab-test/test/-/edit/main/';
const TEST_WEB_IDE_URL = '/-/ide/project/gitlab-test/test/edit/main/-/';
const TEST_GITPOD_URL = 'https://gitpod.test/';
@@ -52,6 +56,7 @@ const ACTION_WEB_IDE = {
'data-track-action': 'click_consolidated_edit_ide',
'data-track-label': 'web_ide',
},
+ handle: expect.any(Function),
};
const ACTION_WEB_IDE_CONFIRM_FORK = {
...ACTION_WEB_IDE,
@@ -258,6 +263,14 @@ describe('Web IDE link component', () => {
selectedKey: ACTION_PIPELINE_EDITOR.key,
});
});
+
+ it('when web ide button is clicked it opens in a new tab', async () => {
+ findActionsButton().props('actions')[1].handle({
+ preventDefault: jest.fn(),
+ });
+ await nextTick();
+ expect(visitUrl).toHaveBeenCalledWith(ACTION_WEB_IDE.href, true);
+ });
});
describe('with multiple actions', () => {
diff --git a/spec/frontend/vue_shared/issuable/show/components/issuable_edit_form_spec.js b/spec/frontend/vue_shared/issuable/show/components/issuable_edit_form_spec.js
index e5594b6d37e..159be4cd1ef 100644
--- a/spec/frontend/vue_shared/issuable/show/components/issuable_edit_form_spec.js
+++ b/spec/frontend/vue_shared/issuable/show/components/issuable_edit_form_spec.js
@@ -5,9 +5,12 @@ import { nextTick } from 'vue';
import IssuableEditForm from '~/vue_shared/issuable/show/components/issuable_edit_form.vue';
import IssuableEventHub from '~/vue_shared/issuable/show/event_hub';
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
+import Autosave from '~/autosave';
import { mockIssuableShowProps, mockIssuable } from '../mock_data';
+jest.mock('~/autosave');
+
const issuableEditFormProps = {
issuable: mockIssuable,
...mockIssuableShowProps,
@@ -36,10 +39,12 @@ describe('IssuableEditForm', () => {
beforeEach(() => {
wrapper = createComponent();
+ jest.spyOn(Autosave.prototype, 'reset');
});
afterEach(() => {
wrapper.destroy();
+ jest.resetAllMocks();
});
describe('watch', () => {
@@ -100,21 +105,18 @@ describe('IssuableEditForm', () => {
describe('methods', () => {
describe('initAutosave', () => {
- it('initializes `autosaveTitle` and `autosaveDescription` props', () => {
- expect(wrapper.vm.autosaveTitle).toBeDefined();
- expect(wrapper.vm.autosaveDescription).toBeDefined();
+ it('initializes autosave', () => {
+ expect(Autosave.mock.calls).toEqual([
+ [expect.any(Element), ['/', '', 'title']],
+ [expect.any(Element), ['/', '', 'description']],
+ ]);
});
});
describe('resetAutosave', () => {
- it('calls `reset` on `autosaveTitle` and `autosaveDescription` props', () => {
- jest.spyOn(wrapper.vm.autosaveTitle, 'reset').mockImplementation(jest.fn);
- jest.spyOn(wrapper.vm.autosaveDescription, 'reset').mockImplementation(jest.fn);
-
- wrapper.vm.resetAutosave();
-
- expect(wrapper.vm.autosaveTitle.reset).toHaveBeenCalled();
- expect(wrapper.vm.autosaveDescription.reset).toHaveBeenCalled();
+ it('resets title and description on "update.issuable event"', () => {
+ IssuableEventHub.$emit('update.issuable');
+ expect(Autosave.prototype.reset.mock.calls).toEqual([[], []]);
});
});
});