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.js103
-rw-r--r--spec/frontend/vue_shared/components/ci_icon/ci_icon_spec.js (renamed from spec/frontend/vue_shared/components/ci_icon_spec.js)2
-rw-r--r--spec/frontend/vue_shared/components/confirm_danger/confirm_danger_modal_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/diff_stats_dropdown_spec.js3
-rw-r--r--spec/frontend/vue_shared/components/dropdown/dropdown_widget_spec.js11
-rw-r--r--spec/frontend/vue_shared/components/entity_select/entity_select_spec.js10
-rw-r--r--spec/frontend/vue_shared/components/entity_select/organization_select_spec.js155
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js193
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js13
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/date_token_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/milestone_token_spec.js9
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/user_token_spec.js23
-rw-r--r--spec/frontend/vue_shared/components/keep_alive_slots_spec.js118
-rw-r--r--spec/frontend/vue_shared/components/list_selector/deploy_key_item_spec.js61
-rw-r--r--spec/frontend/vue_shared/components/list_selector/index_spec.js51
-rw-r--r--spec/frontend/vue_shared/components/markdown/header_spec.js8
-rw-r--r--spec/frontend/vue_shared/components/metric_images/store/actions_spec.js10
-rw-r--r--spec/frontend/vue_shared/components/new_resource_dropdown/new_resource_dropdown_spec.js18
-rw-r--r--spec/frontend/vue_shared/components/number_to_human_size/number_to_human_size_spec.js47
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/components/chunk_new_spec.js1
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/highlight_util_spec.js35
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/mock_data.js1
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/source_viewer_new_spec.js38
-rw-r--r--spec/frontend/vue_shared/components/time_ago_tooltip_spec.js6
-rw-r--r--spec/frontend/vue_shared/components/vuex_module_provider_spec.js39
-rw-r--r--spec/frontend/vue_shared/directives/track_event_spec.js55
-rw-r--r--spec/frontend/vue_shared/issuable/create/components/issuable_label_selector_spec.js2
-rw-r--r--spec/frontend/vue_shared/issuable/list/components/issuable_item_spec.js31
-rw-r--r--spec/frontend/vue_shared/issuable/list/components/issuable_list_root_spec.js6
-rw-r--r--spec/frontend/vue_shared/issuable/sidebar/components/issuable_sidebar_root_spec.js16
-rw-r--r--spec/frontend/vue_shared/new_namespace/new_namespace_page_spec.js37
-rw-r--r--spec/frontend/vue_shared/security_reports/components/manage_via_mr_spec.js2
32 files changed, 650 insertions, 458 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 6c2b21053f0..d2dfb6ee1bf 100644
--- a/spec/frontend/vue_shared/alert_details/alert_details_spec.js
+++ b/spec/frontend/vue_shared/alert_details/alert_details_spec.js
@@ -1,16 +1,19 @@
+import Vue, { nextTick } from 'vue';
+import VueApollo from 'vue-apollo';
import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
-import { nextTick } from 'vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
+import createMockApollo from 'helpers/mock_apollo_helper';
import { joinPaths } from '~/lib/utils/url_utility';
import Tracking from '~/tracking';
import AlertDetails from '~/vue_shared/alert_details/components/alert_details.vue';
import AlertSummaryRow from '~/vue_shared/alert_details/components/alert_summary_row.vue';
import { PAGE_CONFIG, SEVERITY_LEVELS } from '~/vue_shared/alert_details/constants';
import createIssueMutation from '~/vue_shared/alert_details/graphql/mutations/alert_issue_create.mutation.graphql';
+import alertQuery from '~/vue_shared/alert_details/graphql/queries/alert_sidebar_details.query.graphql';
import AlertDetailsTable from '~/vue_shared/components/alert_details_table.vue';
import MetricImagesTab from '~/vue_shared/components/metric_images/metric_images_tab.vue';
import createStore from '~/vue_shared/components/metric_images/store/';
@@ -27,20 +30,57 @@ describe('AlertDetails', () => {
let environmentData = { name: environmentName, path: environmentPath };
let mock;
let wrapper;
+ let requestHandlers;
const projectPath = 'root/alerts';
const projectIssuesPath = 'root/alerts/-/issues';
const projectId = '1';
const $router = { push: jest.fn() };
+ const defaultHandlers = {
+ createIssueMutationMock: jest.fn().mockResolvedValue({
+ data: {
+ createAlertIssue: {
+ errors: [],
+ issue: {
+ id: 'id',
+ iid: 'iid',
+ webUrl: 'webUrl',
+ },
+ },
+ },
+ }),
+ alertQueryMock: jest.fn().mockResolvedValue({
+ data: {
+ project: {
+ id: '1',
+ alertManagementAlerts: {
+ nodes: [],
+ },
+ },
+ },
+ }),
+ };
+
+ const createMockApolloProvider = (handlers) => {
+ Vue.use(VueApollo);
+ requestHandlers = handlers;
+
+ return createMockApollo([
+ [alertQuery, handlers.alertQueryMock],
+ [createIssueMutation, handlers.createIssueMutationMock],
+ ]);
+ };
+
function mountComponent({
data,
- loading = false,
mountMethod = shallowMount,
provide = {},
stubs = {},
+ handlers = defaultHandlers,
} = {}) {
wrapper = extendedWrapper(
mountMethod(AlertDetails, {
+ apolloProvider: createMockApolloProvider(handlers),
provide: {
alertId: 'alertId',
projectPath,
@@ -59,15 +99,6 @@ describe('AlertDetails', () => {
};
},
mocks: {
- $apollo: {
- mutate: jest.fn(),
- queries: {
- alert: {
- loading,
- },
- sidebarStatus: {},
- },
- },
$router,
$route: { params: {} },
},
@@ -139,7 +170,6 @@ describe('AlertDetails', () => {
describe('Metrics tab', () => {
it('should mount without errors', () => {
mountComponent({
- mountMethod: mount,
provide: {
canUpdate: true,
iid: '1',
@@ -216,7 +246,6 @@ describe('AlertDetails', () => {
it('should display "Create incident" button when incident doesn\'t exist yet', async () => {
const issue = null;
mountComponent({
- mountMethod: mount,
data: { alert: { ...mockAlert, issue } },
});
@@ -226,23 +255,16 @@ describe('AlertDetails', () => {
});
it('calls `$apollo.mutate` with `createIssueQuery`', () => {
- const issueIid = '10';
mountComponent({
mountMethod: mount,
data: { alert: { ...mockAlert } },
});
- jest
- .spyOn(wrapper.vm.$apollo, 'mutate')
- .mockResolvedValue({ data: { createAlertIssue: { issue: { iid: issueIid } } } });
findCreateIncidentBtn().trigger('click');
- expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
- mutation: createIssueMutation,
- variables: {
- iid: mockAlert.iid,
- projectPath,
- },
+ expect(requestHandlers.createIssueMutationMock).toHaveBeenCalledWith({
+ iid: mockAlert.iid,
+ projectPath,
});
});
@@ -251,25 +273,44 @@ describe('AlertDetails', () => {
mountComponent({
mountMethod: mount,
data: { alert: { ...mockAlert, alertIid: 1 } },
+ handlers: {
+ ...defaultHandlers,
+ createIssueMutationMock: jest.fn().mockRejectedValue(new Error(errorMsg)),
+ },
});
- jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValue(errorMsg);
findCreateIncidentBtn().trigger('click');
await waitForPromises();
- expect(findIncidentCreationAlert().text()).toBe(errorMsg);
+ expect(findIncidentCreationAlert().text()).toBe(`Error: ${errorMsg}`);
});
});
describe('View full alert details', () => {
- beforeEach(() => {
- mountComponent({ data: { alert: mockAlert } });
+ beforeEach(async () => {
+ mountComponent({
+ data: { alert: mockAlert },
+ handlers: {
+ ...defaultHandlers,
+ alertQueryMock: jest.fn().mockResolvedValue({
+ data: {
+ project: {
+ id: '1',
+ alertManagementAlerts: {
+ nodes: [{ id: '1' }],
+ },
+ },
+ },
+ }),
+ },
+ });
+ await waitForPromises();
});
it('should display a table of raw alert details data', () => {
- const details = findDetailsTable();
- expect(details.exists()).toBe(true);
- expect(details.props()).toStrictEqual({
+ expect(findDetailsTable().exists()).toBe(true);
+
+ expect(findDetailsTable().props()).toStrictEqual({
alert: mockAlert,
statuses: PAGE_CONFIG.OPERATIONS.STATUSES,
loading: false,
@@ -279,7 +320,7 @@ describe('AlertDetails', () => {
describe('loading state', () => {
beforeEach(() => {
- mountComponent({ loading: true });
+ mountComponent();
});
it('displays a loading state when loading', () => {
diff --git a/spec/frontend/vue_shared/components/ci_icon_spec.js b/spec/frontend/vue_shared/components/ci_icon/ci_icon_spec.js
index cbb725bf9e6..792470c8e89 100644
--- a/spec/frontend/vue_shared/components/ci_icon_spec.js
+++ b/spec/frontend/vue_shared/components/ci_icon/ci_icon_spec.js
@@ -1,6 +1,6 @@
import { GlIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import CiIcon from '~/vue_shared/components/ci_icon.vue';
+import CiIcon from '~/vue_shared/components/ci_icon/ci_icon.vue';
const mockStatus = {
group: 'success',
diff --git a/spec/frontend/vue_shared/components/confirm_danger/confirm_danger_modal_spec.js b/spec/frontend/vue_shared/components/confirm_danger/confirm_danger_modal_spec.js
index 53218d794c7..b825a578cee 100644
--- a/spec/frontend/vue_shared/components/confirm_danger/confirm_danger_modal_spec.js
+++ b/spec/frontend/vue_shared/components/confirm_danger/confirm_danger_modal_spec.js
@@ -19,7 +19,7 @@ describe('Confirm Danger Modal', () => {
const findModal = () => wrapper.findComponent(GlModal);
const findConfirmationPhrase = () => wrapper.findByTestId('confirm-danger-phrase');
- const findConfirmationInput = () => wrapper.findByTestId('confirm-danger-input');
+ const findConfirmationInput = () => wrapper.findByTestId('confirm-danger-field');
const findDefaultWarning = () => wrapper.findByTestId('confirm-danger-warning');
const findAdditionalMessage = () => wrapper.findByTestId('confirm-danger-message');
const findPrimaryAction = () => findModal().props('actionPrimary');
diff --git a/spec/frontend/vue_shared/components/diff_stats_dropdown_spec.js b/spec/frontend/vue_shared/components/diff_stats_dropdown_spec.js
index 810269257b6..e2c3fc89525 100644
--- a/spec/frontend/vue_shared/components/diff_stats_dropdown_spec.js
+++ b/spec/frontend/vue_shared/components/diff_stats_dropdown_spec.js
@@ -152,7 +152,8 @@ describe('Diff Stats Dropdown', () => {
});
it('focuses the first item when pressing the down key within the search box', () => {
- const spy = jest.spyOn(wrapper.vm, 'focusFirstItem');
+ const { element } = wrapper.find('.gl-new-dropdown-item');
+ const spy = jest.spyOn(element, 'focus');
findSearchBox().vm.$emit('keydown', new KeyboardEvent({ key: ARROW_DOWN_KEY }));
expect(spy).toHaveBeenCalled();
diff --git a/spec/frontend/vue_shared/components/dropdown/dropdown_widget_spec.js b/spec/frontend/vue_shared/components/dropdown/dropdown_widget_spec.js
index dd5a05a40c6..1a9a08a9656 100644
--- a/spec/frontend/vue_shared/components/dropdown/dropdown_widget_spec.js
+++ b/spec/frontend/vue_shared/components/dropdown/dropdown_widget_spec.js
@@ -1,8 +1,8 @@
import { GlDropdown, GlSearchBoxByType, GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-
import { nextTick } from 'vue';
import DropdownWidget from '~/vue_shared/components/dropdown/dropdown_widget/dropdown_widget.vue';
+import { stubComponent, RENDER_ALL_SLOTS_TEMPLATE } from 'helpers/stub_component';
describe('DropdownWidget component', () => {
let wrapper;
@@ -27,11 +27,14 @@ describe('DropdownWidget component', () => {
...props,
},
stubs: {
- GlDropdown,
+ GlDropdown: stubComponent(GlDropdown, {
+ methods: {
+ hide: jest.fn(),
+ },
+ template: RENDER_ALL_SLOTS_TEMPLATE,
+ }),
},
});
-
- jest.spyOn(findDropdown().vm, 'hide').mockImplementation();
};
beforeEach(() => {
diff --git a/spec/frontend/vue_shared/components/entity_select/entity_select_spec.js b/spec/frontend/vue_shared/components/entity_select/entity_select_spec.js
index 1376133ec37..02da6079466 100644
--- a/spec/frontend/vue_shared/components/entity_select/entity_select_spec.js
+++ b/spec/frontend/vue_shared/components/entity_select/entity_select_spec.js
@@ -8,7 +8,7 @@ import waitForPromises from 'helpers/wait_for_promises';
describe('EntitySelect', () => {
let wrapper;
let fetchItemsMock;
- let fetchInitialSelectionTextMock;
+ let fetchInitialSelectionMock;
// Mocks
const itemMock = {
@@ -96,16 +96,16 @@ describe('EntitySelect', () => {
});
it("fetches the initially selected value's name", async () => {
- fetchInitialSelectionTextMock = jest.fn().mockImplementation(() => itemMock.text);
+ fetchInitialSelectionMock = jest.fn().mockImplementation(() => itemMock);
createComponent({
props: {
- fetchInitialSelectionText: fetchInitialSelectionTextMock,
+ fetchInitialSelection: fetchInitialSelectionMock,
initialSelection: itemMock.value,
},
});
await nextTick();
- expect(fetchInitialSelectionTextMock).toHaveBeenCalledTimes(1);
+ expect(fetchInitialSelectionMock).toHaveBeenCalledTimes(1);
expect(findListbox().props('toggleText')).toBe(itemMock.text);
});
});
@@ -188,7 +188,7 @@ describe('EntitySelect', () => {
findListbox().vm.$emit('reset');
await nextTick();
- expect(Object.keys(wrapper.emitted('input')[2][0]).length).toBe(0);
+ expect(wrapper.emitted('input')[2][0]).toEqual({});
});
});
});
diff --git a/spec/frontend/vue_shared/components/entity_select/organization_select_spec.js b/spec/frontend/vue_shared/components/entity_select/organization_select_spec.js
index ea029ba4f27..6dc38bbd0c6 100644
--- a/spec/frontend/vue_shared/components/entity_select/organization_select_spec.js
+++ b/spec/frontend/vue_shared/components/entity_select/organization_select_spec.js
@@ -1,35 +1,35 @@
import VueApollo from 'vue-apollo';
-import Vue, { nextTick } from 'vue';
-import { GlCollapsibleListbox } from '@gitlab/ui';
-import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import Vue from 'vue';
+import { GlCollapsibleListbox, GlAlert } from '@gitlab/ui';
+import { chunk } from 'lodash';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
import OrganizationSelect from '~/vue_shared/components/entity_select/organization_select.vue';
import EntitySelect from '~/vue_shared/components/entity_select/entity_select.vue';
+import { DEFAULT_PER_PAGE } from '~/api';
import {
ORGANIZATION_TOGGLE_TEXT,
ORGANIZATION_HEADER_TEXT,
FETCH_ORGANIZATIONS_ERROR,
FETCH_ORGANIZATION_ERROR,
} from '~/vue_shared/components/entity_select/constants';
-import resolvers from '~/organizations/shared/graphql/resolvers';
-import organizationsQuery from '~/organizations/index/graphql/organizations.query.graphql';
-import { organizations as organizationsMock } from '~/organizations/mock_data';
+import getCurrentUserOrganizationsQuery from '~/organizations/shared/graphql/queries/organizations.query.graphql';
+import getOrganizationQuery from '~/organizations/shared/graphql/queries/organization.query.graphql';
+import { organizations as nodes, pageInfo, pageInfoEmpty } from '~/organizations/mock_data';
import waitForPromises from 'helpers/wait_for_promises';
import createMockApollo from 'helpers/mock_apollo_helper';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
Vue.use(VueApollo);
-jest.useFakeTimers();
-
describe('OrganizationSelect', () => {
let wrapper;
let mockApollo;
// Mocks
- const [organizationMock] = organizationsMock;
-
- // Stubs
- const GlAlert = {
- template: '<div><slot /></div>',
+ const [organization] = nodes;
+ const organizations = {
+ nodes,
+ pageInfo,
};
// Props
@@ -44,23 +44,26 @@ describe('OrganizationSelect', () => {
const findEntitySelect = () => wrapper.findComponent(EntitySelect);
const findAlert = () => wrapper.findComponent(GlAlert);
+ // Mock handlers
const handleInput = jest.fn();
+ const getCurrentUserOrganizationsQueryHandler = jest.fn().mockResolvedValue({
+ data: { currentUser: { id: 'gid://gitlab/User/1', __typename: 'CurrentUser', organizations } },
+ });
+ const getOrganizationQueryHandler = jest.fn().mockResolvedValue({
+ data: { organization },
+ });
// Helpers
- const createComponent = ({ props = {}, mockResolvers = resolvers, handlers } = {}) => {
- mockApollo = createMockApollo(
- handlers || [
- [
- organizationsQuery,
- jest.fn().mockResolvedValueOnce({
- data: { currentUser: { id: 1, organizations: { nodes: organizationsMock } } },
- }),
- ],
- ],
- mockResolvers,
- );
-
- wrapper = shallowMountExtended(OrganizationSelect, {
+ const createComponent = ({
+ props = {},
+ handlers = [
+ [getCurrentUserOrganizationsQuery, getCurrentUserOrganizationsQueryHandler],
+ [getOrganizationQuery, getOrganizationQueryHandler],
+ ],
+ } = {}) => {
+ mockApollo = createMockApollo(handlers);
+
+ wrapper = mountExtended(OrganizationSelect, {
apolloProvider: mockApollo,
propsData: {
label,
@@ -70,10 +73,6 @@ describe('OrganizationSelect', () => {
toggleClass,
...props,
},
- stubs: {
- GlAlert,
- EntitySelect,
- },
listeners: {
input: handleInput,
},
@@ -81,10 +80,6 @@ describe('OrganizationSelect', () => {
};
const openListbox = () => findListbox().vm.$emit('shown');
- afterEach(() => {
- mockApollo = null;
- });
-
describe('entity_select props', () => {
beforeEach(() => {
createComponent();
@@ -107,40 +102,31 @@ describe('OrganizationSelect', () => {
describe('on mount', () => {
it('fetches organizations when the listbox is opened', async () => {
createComponent();
- await nextTick();
- jest.runAllTimers();
- await waitForPromises();
-
openListbox();
- jest.runAllTimers();
await waitForPromises();
- expect(findListbox().props('items')).toEqual([
- { text: organizationsMock[0].name, value: 1 },
- { text: organizationsMock[1].name, value: 2 },
- { text: organizationsMock[2].name, value: 3 },
- ]);
+
+ const expectedItems = nodes.map((node) => ({
+ ...node,
+ text: node.name,
+ value: getIdFromGraphQLId(node.id),
+ }));
+
+ expect(findListbox().props('items')).toEqual(expectedItems);
});
describe('with an initial selection', () => {
it("fetches the initially selected value's name", async () => {
- createComponent({ props: { initialSelection: organizationMock.id } });
- await nextTick();
- jest.runAllTimers();
+ createComponent({ props: { initialSelection: organization.id } });
await waitForPromises();
- expect(findListbox().props('toggleText')).toBe(organizationMock.name);
+ expect(findListbox().props('toggleText')).toBe(organization.name);
});
it('show an error if fetching initially selected fails', async () => {
- const mockResolvers = {
- Query: {
- organization: jest.fn().mockRejectedValueOnce(new Error()),
- },
- };
-
- createComponent({ props: { initialSelection: organizationMock.id }, mockResolvers });
- await nextTick();
- jest.runAllTimers();
+ createComponent({
+ props: { initialSelection: organization.id },
+ handlers: [[getOrganizationQuery, jest.fn().mockRejectedValueOnce()]],
+ });
expect(findAlert().exists()).toBe(false);
@@ -152,18 +138,59 @@ describe('OrganizationSelect', () => {
});
});
+ describe('when listbox bottom is reached and there are more organizations to load', () => {
+ const [firstPage, secondPage] = chunk(nodes, Math.ceil(nodes.length / 2));
+ const getCurrentUserOrganizationsQueryMultiplePagesHandler = jest
+ .fn()
+ .mockResolvedValueOnce({
+ data: {
+ currentUser: {
+ id: 'gid://gitlab/User/1',
+ __typename: 'CurrentUser',
+ organizations: { nodes: firstPage, pageInfo },
+ },
+ },
+ })
+ .mockResolvedValueOnce({
+ data: {
+ currentUser: {
+ id: 'gid://gitlab/User/1',
+ __typename: 'CurrentUser',
+ organizations: { nodes: secondPage, pageInfo: pageInfoEmpty },
+ },
+ },
+ });
+
+ beforeEach(async () => {
+ createComponent({
+ handlers: [
+ [getCurrentUserOrganizationsQuery, getCurrentUserOrganizationsQueryMultiplePagesHandler],
+ [getOrganizationQuery, getOrganizationQueryHandler],
+ ],
+ });
+ openListbox();
+ await waitForPromises();
+
+ findListbox().vm.$emit('bottom-reached');
+ await waitForPromises();
+ });
+
+ it('calls graphQL query correct `after` variable', () => {
+ expect(getCurrentUserOrganizationsQueryMultiplePagesHandler).toHaveBeenCalledWith({
+ after: pageInfo.endCursor,
+ first: DEFAULT_PER_PAGE,
+ });
+ expect(findListbox().props('infiniteScroll')).toBe(false);
+ });
+ });
+
it('shows an error when fetching organizations fails', async () => {
createComponent({
- handlers: [[organizationsQuery, jest.fn().mockRejectedValueOnce(new Error())]],
+ handlers: [[getCurrentUserOrganizationsQuery, jest.fn().mockRejectedValueOnce()]],
});
- await nextTick();
- jest.runAllTimers();
- await waitForPromises();
-
openListbox();
expect(findAlert().exists()).toBe(false);
- jest.runAllTimers();
await waitForPromises();
expect(findAlert().exists()).toBe(true);
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js
index bb612a13209..3a5c7d7729f 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js
@@ -1,11 +1,4 @@
-import {
- GlFilteredSearch,
- GlButtonGroup,
- GlButton,
- GlDropdown,
- GlDropdownItem,
- GlFormCheckbox,
-} from '@gitlab/ui';
+import { GlDropdownItem, GlSorting, GlFilteredSearch, GlFormCheckbox } from '@gitlab/ui';
import { shallowMount, mount } from '@vue/test-utils';
import { nextTick } from 'vue';
@@ -13,7 +6,6 @@ import RecentSearchesService from '~/filtered_search/services/recent_searches_se
import RecentSearchesStore from '~/filtered_search/stores/recent_searches_store';
import {
FILTERED_SEARCH_TERM,
- SORT_DIRECTION,
TOKEN_TYPE_AUTHOR,
TOKEN_TYPE_LABEL,
TOKEN_TYPE_MILESTONE,
@@ -48,6 +40,7 @@ const createComponent = ({
recentSearchesStorageKey = 'requirements',
tokens = mockAvailableTokens,
sortOptions,
+ initialSortBy,
initialFilterValue = [],
showCheckbox = false,
checkboxChecked = false,
@@ -61,6 +54,7 @@ const createComponent = ({
recentSearchesStorageKey,
tokens,
sortOptions,
+ initialSortBy,
initialFilterValue,
showCheckbox,
checkboxChecked,
@@ -72,34 +66,38 @@ const createComponent = ({
describe('FilteredSearchBarRoot', () => {
let wrapper;
- const findGlButton = () => wrapper.findComponent(GlButton);
- const findGlDropdown = () => wrapper.findComponent(GlDropdown);
+ const findGlSorting = () => wrapper.findComponent(GlSorting);
const findGlFilteredSearch = () => wrapper.findComponent(GlFilteredSearch);
- beforeEach(() => {
- wrapper = createComponent({ sortOptions: mockSortOptions });
- });
-
describe('data', () => {
- it('initializes `filterValue`, `selectedSortOption` and `selectedSortDirection` data props and displays the sort dropdown', () => {
- expect(wrapper.vm.filterValue).toEqual([]);
- expect(wrapper.vm.selectedSortOption).toBe(mockSortOptions[0]);
- expect(wrapper.vm.selectedSortDirection).toBe(SORT_DIRECTION.descending);
- expect(wrapper.findComponent(GlButtonGroup).exists()).toBe(true);
- expect(wrapper.findComponent(GlButton).exists()).toBe(true);
- expect(wrapper.findComponent(GlDropdown).exists()).toBe(true);
- expect(wrapper.findComponent(GlDropdownItem).exists()).toBe(true);
+ describe('when `sortOptions` are provided', () => {
+ beforeEach(() => {
+ wrapper = createComponent({ sortOptions: mockSortOptions });
+ });
+
+ it('sets a correct initial value for GlFilteredSearch', () => {
+ expect(findGlFilteredSearch().props('value')).toEqual([]);
+ });
+
+ it('emits an event with the selectedSortOption provided by default', async () => {
+ findGlSorting().vm.$emit('sortByChange', mockSortOptions[1].id);
+ await nextTick();
+
+ expect(wrapper.emitted('onSort')[0]).toEqual([mockSortOptions[1].sortDirection.descending]);
+ });
+
+ it('emits an event with the selectedSortDirection provided by default', async () => {
+ findGlSorting().vm.$emit('sortDirectionChange', true);
+ await nextTick();
+
+ expect(wrapper.emitted('onSort')[0]).toEqual([mockSortOptions[0].sortDirection.ascending]);
+ });
});
- it('does not initialize `selectedSortOption` and `selectedSortDirection` when `sortOptions` is not applied and hides the sort dropdown', () => {
- const wrapperNoSort = createComponent();
+ it('does not initialize the sort dropdown when `sortOptions` are not provided', () => {
+ wrapper = createComponent();
- expect(wrapperNoSort.vm.filterValue).toEqual([]);
- expect(wrapperNoSort.vm.selectedSortOption).toBe(undefined);
- expect(wrapperNoSort.findComponent(GlButtonGroup).exists()).toBe(false);
- expect(wrapperNoSort.findComponent(GlButton).exists()).toBe(false);
- expect(wrapperNoSort.findComponent(GlDropdown).exists()).toBe(false);
- expect(wrapperNoSort.findComponent(GlDropdownItem).exists()).toBe(false);
+ expect(findGlSorting().exists()).toBe(false);
});
});
@@ -125,27 +123,27 @@ describe('FilteredSearchBarRoot', () => {
});
describe('sortDirectionIcon', () => {
- it('renders `sort-highest` descending icon by default', () => {
- expect(findGlButton().props('icon')).toBe('sort-highest');
- expect(findGlButton().attributes()).toMatchObject({
- 'aria-label': 'Sort direction: Descending',
- title: 'Sort direction: Descending',
- });
+ beforeEach(() => {
+ wrapper = createComponent({ sortOptions: mockSortOptions });
+ });
+
+ it('passes isAscending=false to GlSorting by default', () => {
+ expect(findGlSorting().props('isAscending')).toBe(false);
});
it('renders `sort-lowest` ascending icon when the sort button is clicked', async () => {
- findGlButton().vm.$emit('click');
+ findGlSorting().vm.$emit('sortDirectionChange', true);
await nextTick();
- expect(findGlButton().props('icon')).toBe('sort-lowest');
- expect(findGlButton().attributes()).toMatchObject({
- 'aria-label': 'Sort direction: Ascending',
- title: 'Sort direction: Ascending',
- });
+ expect(findGlSorting().props('isAscending')).toBe(true);
});
});
describe('filteredRecentSearches', () => {
+ beforeEach(() => {
+ wrapper = createComponent();
+ });
+
it('returns array of recent searches filtering out any string type (unsupported) items', async () => {
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
@@ -227,34 +225,37 @@ describe('FilteredSearchBarRoot', () => {
});
});
- describe('handleSortOptionClick', () => {
- it('emits component event `onSort` with selected sort by value', () => {
- wrapper.vm.handleSortOptionClick(mockSortOptions[1]);
+ describe('handleSortOptionChange', () => {
+ it('emits component event `onSort` with selected sort by value', async () => {
+ wrapper = createComponent({ sortOptions: mockSortOptions });
+
+ findGlSorting().vm.$emit('sortByChange', mockSortOptions[1].id);
+ await nextTick();
expect(wrapper.vm.selectedSortOption).toBe(mockSortOptions[1]);
expect(wrapper.emitted('onSort')[0]).toEqual([mockSortOptions[1].sortDirection.descending]);
});
});
- describe('handleSortDirectionClick', () => {
+ describe('handleSortDirectionChange', () => {
beforeEach(() => {
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({
- selectedSortOption: mockSortOptions[0],
+ wrapper = createComponent({
+ sortOptions: mockSortOptions,
+ initialSortBy: mockSortOptions[0].sortDirection.descending,
});
});
- it('sets `selectedSortDirection` to be opposite of its current value', () => {
- expect(wrapper.vm.selectedSortDirection).toBe(SORT_DIRECTION.descending);
+ it('sets sort direction to be opposite of its current value', async () => {
+ expect(findGlSorting().props('isAscending')).toBe(false);
- wrapper.vm.handleSortDirectionClick();
+ findGlSorting().vm.$emit('sortDirectionChange', true);
+ await nextTick();
- expect(wrapper.vm.selectedSortDirection).toBe(SORT_DIRECTION.ascending);
+ expect(findGlSorting().props('isAscending')).toBe(true);
});
it('emits component event `onSort` with opposite of currently selected sort by value', () => {
- wrapper.vm.handleSortDirectionClick();
+ findGlSorting().vm.$emit('sortDirectionChange', true);
expect(wrapper.emitted('onSort')[0]).toEqual([mockSortOptions[0].sortDirection.ascending]);
});
@@ -288,6 +289,8 @@ describe('FilteredSearchBarRoot', () => {
const mockFilters = [tokenValueAuthor, 'foo'];
beforeEach(async () => {
+ wrapper = createComponent();
+
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({
@@ -358,19 +361,14 @@ describe('FilteredSearchBarRoot', () => {
});
describe('template', () => {
- beforeEach(async () => {
+ it('renders gl-filtered-search component', async () => {
+ wrapper = createComponent();
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
- wrapper.setData({
- selectedSortOption: mockSortOptions[0],
- selectedSortDirection: SORT_DIRECTION.descending,
+ await wrapper.setData({
recentSearches: mockHistoryItems,
});
- await nextTick();
- });
-
- it('renders gl-filtered-search component', () => {
const glFilteredSearchEl = wrapper.findComponent(GlFilteredSearch);
expect(glFilteredSearchEl.props('placeholder')).toBe('Filter requirements');
@@ -454,25 +452,28 @@ describe('FilteredSearchBarRoot', () => {
});
it('renders sort dropdown component', () => {
- expect(wrapper.findComponent(GlButtonGroup).exists()).toBe(true);
- expect(wrapper.findComponent(GlDropdown).exists()).toBe(true);
- expect(wrapper.findComponent(GlDropdown).props('text')).toBe(mockSortOptions[0].title);
- });
-
- it('renders sort dropdown items', () => {
- const dropdownItemsEl = wrapper.findAllComponents(GlDropdownItem);
+ wrapper = createComponent({ sortOptions: mockSortOptions });
- expect(dropdownItemsEl).toHaveLength(mockSortOptions.length);
- expect(dropdownItemsEl.at(0).text()).toBe(mockSortOptions[0].title);
- expect(dropdownItemsEl.at(0).props('isChecked')).toBe(true);
- expect(dropdownItemsEl.at(1).text()).toBe(mockSortOptions[1].title);
+ expect(findGlSorting().exists()).toBe(true);
});
- it('renders sort direction button', () => {
- const sortButtonEl = wrapper.findComponent(GlButton);
-
- expect(sortButtonEl.attributes('title')).toBe('Sort direction: Descending');
- expect(sortButtonEl.props('icon')).toBe('sort-highest');
+ it('renders sort dropdown items', () => {
+ wrapper = createComponent({ sortOptions: mockSortOptions });
+
+ const { sortOptions, sortBy } = findGlSorting().props();
+
+ expect(sortOptions).toEqual([
+ {
+ value: mockSortOptions[0].id,
+ text: mockSortOptions[0].title,
+ },
+ {
+ value: mockSortOptions[1].id,
+ text: mockSortOptions[1].title,
+ },
+ ]);
+
+ expect(sortBy).toBe(mockSortOptions[0].id);
});
});
@@ -483,6 +484,10 @@ describe('FilteredSearchBarRoot', () => {
value: { data: '' },
};
+ beforeEach(() => {
+ wrapper = createComponent({ sortOptions: mockSortOptions });
+ });
+
it('syncs filter value', async () => {
await wrapper.setProps({ initialFilterValue: [tokenValue], syncFilterAndSort: true });
@@ -498,17 +503,33 @@ describe('FilteredSearchBarRoot', () => {
it('syncs sort values', async () => {
await wrapper.setProps({ initialSortBy: 'updated_asc', syncFilterAndSort: true });
- expect(findGlDropdown().props('text')).toBe('Last updated');
- expect(findGlButton().props('icon')).toBe('sort-lowest');
- expect(findGlButton().attributes('aria-label')).toBe('Sort direction: Ascending');
+ expect(findGlSorting().props()).toMatchObject({
+ sortBy: 2,
+ isAscending: true,
+ });
});
it('does not sync sort values when syncFilterAndSort=false', async () => {
await wrapper.setProps({ initialSortBy: 'updated_asc', syncFilterAndSort: false });
- expect(findGlDropdown().props('text')).toBe('Created date');
- expect(findGlButton().props('icon')).toBe('sort-highest');
- expect(findGlButton().attributes('aria-label')).toBe('Sort direction: Descending');
+ expect(findGlSorting().props()).toMatchObject({
+ sortBy: 1,
+ isAscending: false,
+ });
+ });
+
+ it('does not sync sort values when initialSortBy is unset', async () => {
+ // Give initialSort some value which changes the current sort option...
+ await wrapper.setProps({ initialSortBy: 'updated_asc', syncFilterAndSort: true });
+
+ // ... Read the new sort options...
+ const { sortBy, isAscending } = findGlSorting().props();
+
+ // ... Then *unset* initialSortBy...
+ await wrapper.setProps({ initialSortBy: undefined });
+
+ // ... The sort options should not have changed.
+ expect(findGlSorting().props()).toMatchObject({ sortBy, isAscending });
});
});
});
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js
index 88618de6979..1d6834a5604 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js
@@ -156,9 +156,12 @@ describe('BaseToken', () => {
it('uses last item in list when value is an array', () => {
const mockGetActiveTokenValue = jest.fn();
+ const config = { ...mockConfig, multiSelect: true };
+
wrapper = createComponent({
props: {
- value: { data: mockLabels.map((l) => l.title) },
+ config,
+ value: { data: mockLabels.map((l) => l.title), operator: '||' },
suggestions: mockLabels,
getActiveTokenValue: mockGetActiveTokenValue,
},
@@ -409,8 +412,9 @@ describe('BaseToken', () => {
});
it('emits token-selected event when groupMultiSelectTokens: true', () => {
+ const config = { ...mockConfig, multiSelect: true };
wrapper = createComponent({
- props: { suggestions: mockLabels },
+ props: { suggestions: mockLabels, config, value: { operator: '||' } },
groupMultiSelectTokens: true,
});
@@ -419,9 +423,10 @@ describe('BaseToken', () => {
expect(wrapper.emitted('token-selected')).toEqual([[mockTokenValue.title]]);
});
- it('does not emit token-selected event when groupMultiSelectTokens: true', () => {
+ it('does not emit token-selected event when groupMultiSelectTokens: false', () => {
+ const config = { ...mockConfig, multiSelect: true };
wrapper = createComponent({
- props: { suggestions: mockLabels },
+ props: { suggestions: mockLabels, config, value: { operator: '||' } },
groupMultiSelectTokens: false,
});
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/date_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/date_token_spec.js
index 56a59790210..34d0c7f0566 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/date_token_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/date_token_spec.js
@@ -42,7 +42,7 @@ describe('DateToken', () => {
findDatepicker().vm.$emit('close');
expect(findGlFilteredSearchToken().emitted()).toEqual({
- complete: [[]],
+ complete: [['2014-10-13']],
select: [['2014-10-13']],
});
});
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/milestone_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/milestone_token_spec.js
index 36e82b39df4..ee54fb5b941 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/milestone_token_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/milestone_token_spec.js
@@ -5,15 +5,12 @@ import {
GlDropdownDivider,
} from '@gitlab/ui';
import { mount } from '@vue/test-utils';
-import MockAdapter from 'axios-mock-adapter';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { createAlert } from '~/alert';
-import axios from '~/lib/utils/axios_utils';
import { sortMilestonesByDueDate } from '~/milestones/utils';
-
import searchMilestonesQuery from '~/issues/list/queries/search_milestones.query.graphql';
import { DEFAULT_MILESTONES } from '~/vue_shared/components/filtered_search_bar/constants';
import MilestoneToken from '~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue';
@@ -70,7 +67,6 @@ function createComponent(options = {}) {
}
describe('MilestoneToken', () => {
- let mock;
let wrapper;
const findBaseToken = () => wrapper.findComponent(BaseToken);
@@ -80,14 +76,9 @@ describe('MilestoneToken', () => {
};
beforeEach(() => {
- mock = new MockAdapter(axios);
wrapper = createComponent();
});
- afterEach(() => {
- mock.restore();
- });
-
describe('methods', () => {
describe('fetchMilestones', () => {
it('sets loading state', async () => {
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/user_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/user_token_spec.js
index 4462d1bfaf5..decf843091e 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/user_token_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/user_token_spec.js
@@ -313,11 +313,11 @@ describe('UserToken', () => {
describe('multiSelect', () => {
it('renders check icons in suggestions when multiSelect is true', async () => {
wrapper = createComponent({
- value: { data: [mockUsers[0].username, mockUsers[1].username], operator: '=' },
+ value: { data: [mockUsers[0].username, mockUsers[1].username], operator: '||' },
data: {
users: mockUsers,
},
- config: { ...mockAuthorToken, multiSelect: true, initialUsers: mockUsers },
+ config: { ...mockAuthorToken, multiSelect: true },
active: true,
stubs: { Portal: true },
groupMultiSelectTokens: true,
@@ -327,18 +327,17 @@ describe('UserToken', () => {
const suggestions = wrapper.findAllComponents(GlFilteredSearchSuggestion);
- expect(findIconAtSuggestion(1).exists()).toBe(false);
- expect(findIconAtSuggestion(2).props('name')).toBe('check');
- expect(findIconAtSuggestion(3).props('name')).toBe('check');
+ expect(findIconAtSuggestion(0).props('name')).toBe('check');
+ expect(findIconAtSuggestion(1).props('name')).toBe('check');
+ expect(findIconAtSuggestion(2).exists()).toBe(false);
// test for left padding on unchecked items (so alignment is correct)
- expect(findIconAtSuggestion(4).exists()).toBe(false);
- expect(suggestions.at(4).find('.gl-pl-6').exists()).toBe(true);
+ expect(suggestions.at(2).find('.gl-pl-6').exists()).toBe(true);
});
it('renders multiple users when multiSelect is true', async () => {
wrapper = createComponent({
- value: { data: [mockUsers[0].username, mockUsers[1].username], operator: '=' },
+ value: { data: [mockUsers[0].username, mockUsers[1].username], operator: '||' },
data: {
users: mockUsers,
},
@@ -363,7 +362,7 @@ describe('UserToken', () => {
it('adds new user to multi-select-values', () => {
wrapper = createComponent({
- value: { data: [mockUsers[0].username], operator: '=' },
+ value: { data: [mockUsers[0].username], operator: '||' },
data: {
users: mockUsers,
},
@@ -383,7 +382,7 @@ describe('UserToken', () => {
it('removes existing user from array', () => {
const initialUsers = [mockUsers[0].username, mockUsers[1].username];
wrapper = createComponent({
- value: { data: initialUsers, operator: '=' },
+ value: { data: initialUsers, operator: '||' },
data: {
users: mockUsers,
},
@@ -399,7 +398,7 @@ describe('UserToken', () => {
it('clears input field after token selected', () => {
wrapper = createComponent({
- value: { data: [mockUsers[0].username, mockUsers[1].username], operator: '=' },
+ value: { data: [mockUsers[0].username, mockUsers[1].username], operator: '||' },
data: {
users: mockUsers,
},
@@ -410,7 +409,7 @@ describe('UserToken', () => {
findBaseToken().vm.$emit('token-selected', 'test');
- expect(wrapper.emitted('input')).toEqual([[{ operator: '=', data: '' }]]);
+ expect(wrapper.emitted('input')).toEqual([[{ operator: '||', data: '' }]]);
});
});
diff --git a/spec/frontend/vue_shared/components/keep_alive_slots_spec.js b/spec/frontend/vue_shared/components/keep_alive_slots_spec.js
deleted file mode 100644
index f69a883ee4d..00000000000
--- a/spec/frontend/vue_shared/components/keep_alive_slots_spec.js
+++ /dev/null
@@ -1,118 +0,0 @@
-import { nextTick } from 'vue';
-import { mountExtended } from 'helpers/vue_test_utils_helper';
-import KeepAliveSlots from '~/vue_shared/components/keep_alive_slots.vue';
-
-const SLOT_1 = {
- slotKey: 'slot-1',
- title: 'Hello 1',
-};
-const SLOT_2 = {
- slotKey: 'slot-2',
- title: 'Hello 2',
-};
-
-describe('~/vue_shared/components/keep_alive_slots.vue', () => {
- let wrapper;
-
- const createSlotContent = ({ slotKey, title }) => `
- <div data-testid="slot-child" data-slot-id="${slotKey}">
- <h1>${title}</h1>
- <input type="text" />
- </div>
- `;
- const createComponent = (props = {}) => {
- wrapper = mountExtended(KeepAliveSlots, {
- propsData: props,
- slots: {
- [SLOT_1.slotKey]: createSlotContent(SLOT_1),
- [SLOT_2.slotKey]: createSlotContent(SLOT_2),
- },
- });
- };
-
- const findRenderedSlots = () =>
- wrapper.findAllByTestId('slot-child').wrappers.map((x) => ({
- title: x.find('h1').text(),
- inputValue: x.find('input').element.value,
- isVisible: x.isVisible(),
- }));
-
- describe('default', () => {
- beforeEach(() => {
- createComponent();
- });
-
- it('doesnt show anything', () => {
- expect(findRenderedSlots()).toEqual([]);
- });
-
- describe('when slotKey is changed', () => {
- beforeEach(async () => {
- wrapper.setProps({ slotKey: SLOT_1.slotKey });
- await nextTick();
- });
-
- it('shows slot', () => {
- expect(findRenderedSlots()).toEqual([
- {
- title: SLOT_1.title,
- isVisible: true,
- inputValue: '',
- },
- ]);
- });
-
- it('hides everything when slotKey cannot be found', async () => {
- wrapper.setProps({ slotKey: '' });
- await nextTick();
-
- expect(findRenderedSlots()).toEqual([
- {
- title: SLOT_1.title,
- isVisible: false,
- inputValue: '',
- },
- ]);
- });
-
- describe('when user intreracts then slotKey changes again', () => {
- beforeEach(async () => {
- wrapper.find('input').setValue('TEST');
- wrapper.setProps({ slotKey: SLOT_2.slotKey });
- await nextTick();
- });
-
- it('keeps first slot alive but hidden', () => {
- expect(findRenderedSlots()).toEqual([
- {
- title: SLOT_1.title,
- isVisible: false,
- inputValue: 'TEST',
- },
- {
- title: SLOT_2.title,
- isVisible: true,
- inputValue: '',
- },
- ]);
- });
- });
- });
- });
-
- describe('initialized with slotKey', () => {
- beforeEach(() => {
- createComponent({ slotKey: SLOT_2.slotKey });
- });
-
- it('shows slot', () => {
- expect(findRenderedSlots()).toEqual([
- {
- title: SLOT_2.title,
- isVisible: true,
- inputValue: '',
- },
- ]);
- });
- });
-});
diff --git a/spec/frontend/vue_shared/components/list_selector/deploy_key_item_spec.js b/spec/frontend/vue_shared/components/list_selector/deploy_key_item_spec.js
new file mode 100644
index 00000000000..96be5b345a1
--- /dev/null
+++ b/spec/frontend/vue_shared/components/list_selector/deploy_key_item_spec.js
@@ -0,0 +1,61 @@
+import { GlIcon, GlButton } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import DeployKeyItem from '~/vue_shared/components/list_selector/deploy_key_item.vue';
+
+describe('DeployKeyItem spec', () => {
+ let wrapper;
+
+ const MOCK_DATA = { title: 'Some key', owner: 'root', id: '123' };
+
+ const createComponent = (props) => {
+ wrapper = shallowMountExtended(DeployKeyItem, {
+ propsData: {
+ data: MOCK_DATA,
+ ...props,
+ },
+ });
+ };
+
+ const findIcon = () => wrapper.findComponent(GlIcon);
+ const findDeleteButton = () => wrapper.findComponent(GlButton);
+ const findWrapper = () => wrapper.findByTestId('deploy-key-wrapper');
+
+ beforeEach(() => createComponent());
+
+ it('renders a key icon component', () => {
+ expect(findIcon().props('name')).toBe('key');
+ });
+
+ it('renders a title and username', () => {
+ expect(wrapper.text()).toContain('Some key');
+ expect(wrapper.text()).toContain('@root');
+ });
+
+ it('does not render a delete button by default', () => {
+ expect(findDeleteButton().exists()).toBe(false);
+ });
+
+ it('emits a select event when the wrapper is clicked', () => {
+ findWrapper().trigger('click');
+
+ expect(wrapper.emitted('select')).toEqual([[MOCK_DATA.id]]);
+ });
+
+ describe('Delete button', () => {
+ beforeEach(() => createComponent({ canDelete: true }));
+
+ it('renders a delete button', () => {
+ expect(findDeleteButton().exists()).toBe(true);
+ expect(findDeleteButton().props('icon')).toBe('remove');
+ });
+
+ it('emits a delete event if the delete button is clicked', () => {
+ const stopPropagation = jest.fn();
+
+ findDeleteButton().vm.$emit('click', { stopPropagation });
+
+ expect(stopPropagation).toHaveBeenCalled();
+ expect(wrapper.emitted('delete')).toEqual([[MOCK_DATA.id]]);
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/list_selector/index_spec.js b/spec/frontend/vue_shared/components/list_selector/index_spec.js
index 11e64a91eb0..6de9a77582c 100644
--- a/spec/frontend/vue_shared/components/list_selector/index_spec.js
+++ b/spec/frontend/vue_shared/components/list_selector/index_spec.js
@@ -7,6 +7,7 @@ import { mountExtended } from 'helpers/vue_test_utils_helper';
import ListSelector from '~/vue_shared/components/list_selector/index.vue';
import UserItem from '~/vue_shared/components/list_selector/user_item.vue';
import GroupItem from '~/vue_shared/components/list_selector/group_item.vue';
+import DeployKeyItem from '~/vue_shared/components/list_selector/deploy_key_item.vue';
import groupsAutocompleteQuery from '~/graphql_shared/queries/groups_autocomplete.query.graphql';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
@@ -20,18 +21,21 @@ describe('List Selector spec', () => {
let fakeApollo;
const USERS_MOCK_PROPS = {
- title: 'Users',
projectPath: 'some/project/path',
groupPath: 'some/group/path',
type: 'users',
};
const GROUPS_MOCK_PROPS = {
- title: 'Groups',
projectPath: 'some/project/path',
type: 'groups',
};
+ const DEPLOY_KEYS_MOCK_PROPS = {
+ projectPath: 'some/project/path',
+ type: 'deployKeys',
+ };
+
const groupsAutocompleteQuerySuccess = jest.fn().mockResolvedValue(GROUPS_RESPONSE_MOCK);
const createComponent = async (props) => {
@@ -56,6 +60,7 @@ describe('List Selector spec', () => {
const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType);
const findAllUserComponents = () => wrapper.findAllComponents(UserItem);
const findAllGroupComponents = () => wrapper.findAllComponents(GroupItem);
+ const findAllDeployKeyComponents = () => wrapper.findAllComponents(DeployKeyItem);
beforeEach(() => {
jest.spyOn(Api, 'projectUsers').mockResolvedValue(USERS_RESPONSE_MOCK);
@@ -254,4 +259,46 @@ describe('List Selector spec', () => {
});
});
});
+
+ describe('Deploy keys type', () => {
+ beforeEach(() => createComponent(DEPLOY_KEYS_MOCK_PROPS));
+
+ it('renders a correct title', () => {
+ expect(findTitle().exists()).toBe(true);
+ expect(findTitle().text()).toContain('Deploy keys');
+ });
+
+ it('renders the correct icon', () => {
+ expect(findIcon().props('name')).toBe('key');
+ });
+
+ describe('selected items', () => {
+ const selectedKey = { title: 'MyKey', owner: 'peter', id: '123' };
+ const selectedItems = [selectedKey];
+ beforeEach(() => createComponent({ ...DEPLOY_KEYS_MOCK_PROPS, selectedItems }));
+
+ it('renders a heading with the total selected items', () => {
+ expect(findTitle().text()).toContain('Deploy keys');
+ expect(findTitle().text()).toContain('1');
+ });
+
+ it('renders a deploy key component for each selected item', () => {
+ expect(findAllDeployKeyComponents().length).toBe(selectedItems.length);
+ expect(findAllDeployKeyComponents().at(0).props()).toMatchObject({
+ data: selectedKey,
+ canDelete: true,
+ });
+ });
+
+ it('emits a delete event when a delete event is emitted from the deploy key component', () => {
+ const id = '123';
+ findAllDeployKeyComponents().at(0).vm.$emit('delete', id);
+
+ expect(wrapper.emitted('delete')).toEqual([[id]]);
+ });
+
+ // TODO - add a test for the select event once we have API integration
+ // https://gitlab.com/gitlab-org/gitlab/-/issues/432494
+ });
+ });
});
diff --git a/spec/frontend/vue_shared/components/markdown/header_spec.js b/spec/frontend/vue_shared/components/markdown/header_spec.js
index 40875ed5dbc..57f6d751efd 100644
--- a/spec/frontend/vue_shared/components/markdown/header_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/header_spec.js
@@ -82,6 +82,14 @@ describe('Markdown field header component', () => {
});
});
+ it('attach file button should have data-button-type attribute', () => {
+ const attachButton = findToolbarButtonByProp('icon', 'paperclip');
+
+ // Used for dropzone_input.js as `clickable` property
+ // to prevent triggers upload file by clicking on the edge of textarea
+ expect(attachButton.attributes('data-button-type')).toBe('attach-file');
+ });
+
it('hides markdown preview when previewMarkdown is false', () => {
expect(findPreviewToggle().text()).toBe('Preview');
});
diff --git a/spec/frontend/vue_shared/components/metric_images/store/actions_spec.js b/spec/frontend/vue_shared/components/metric_images/store/actions_spec.js
index 544466a22ca..626b1df5474 100644
--- a/spec/frontend/vue_shared/components/metric_images/store/actions_spec.js
+++ b/spec/frontend/vue_shared/components/metric_images/store/actions_spec.js
@@ -43,7 +43,7 @@ describe('Metrics tab store actions', () => {
it('should call success action when fetching metric images', () => {
service.getMetricImages.mockImplementation(() => Promise.resolve(fileList));
- testAction(actions.fetchImages, null, state, [
+ return testAction(actions.fetchImages, null, state, [
{ type: types.REQUEST_METRIC_IMAGES },
{
type: types.RECEIVE_METRIC_IMAGES_SUCCESS,
@@ -80,7 +80,7 @@ describe('Metrics tab store actions', () => {
it('should call success action when uploading an image', () => {
service.uploadMetricImage.mockImplementation(() => Promise.resolve(fileList[0]));
- testAction(actions.uploadImage, payload, state, [
+ return testAction(actions.uploadImage, payload, state, [
{ type: types.REQUEST_METRIC_UPLOAD },
{
type: types.RECEIVE_METRIC_UPLOAD_SUCCESS,
@@ -112,7 +112,7 @@ describe('Metrics tab store actions', () => {
it('should call success action when updating an image', () => {
service.updateMetricImage.mockImplementation(() => Promise.resolve());
- testAction(actions.updateImage, payload, state, [
+ return testAction(actions.updateImage, payload, state, [
{ type: types.REQUEST_METRIC_UPLOAD },
{
type: types.RECEIVE_METRIC_UPDATE_SUCCESS,
@@ -140,7 +140,7 @@ describe('Metrics tab store actions', () => {
it('should call success action when deleting an image', () => {
service.deleteMetricImage.mockImplementation(() => Promise.resolve());
- testAction(actions.deleteImage, payload, state, [
+ return testAction(actions.deleteImage, payload, state, [
{
type: types.RECEIVE_METRIC_DELETE_SUCCESS,
payload,
@@ -151,7 +151,7 @@ describe('Metrics tab store actions', () => {
describe('initial data', () => {
it('should set the initial data correctly', () => {
- testAction(actions.setInitialData, initialData, state, [
+ return testAction(actions.setInitialData, initialData, state, [
{ type: types.SET_INITIAL_DATA, payload: initialData },
]);
});
diff --git a/spec/frontend/vue_shared/components/new_resource_dropdown/new_resource_dropdown_spec.js b/spec/frontend/vue_shared/components/new_resource_dropdown/new_resource_dropdown_spec.js
index 7efc0e162b8..a67276ac64a 100644
--- a/spec/frontend/vue_shared/components/new_resource_dropdown/new_resource_dropdown_spec.js
+++ b/spec/frontend/vue_shared/components/new_resource_dropdown/new_resource_dropdown_spec.js
@@ -11,6 +11,7 @@ import searchProjectsWithinGroupQuery from '~/issues/list/queries/search_project
import { DASH_SCOPE, joinPaths } from '~/lib/utils/url_utility';
import { DEBOUNCE_DELAY } from '~/vue_shared/components/filtered_search_bar/constants';
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
+import { stubComponent } from 'helpers/stub_component';
import {
emptySearchProjectsQueryResponse,
emptySearchProjectsWithinGroupQueryResponse,
@@ -42,6 +43,7 @@ describe('NewResourceDropdown component', () => {
queryResponse = searchProjectsQueryResponse,
mountFn = shallowMount,
propsData = {},
+ stubs = {},
} = {}) => {
const requestHandlers = [[query, jest.fn().mockResolvedValue(queryResponse)]];
const apolloProvider = createMockApollo(requestHandlers);
@@ -49,6 +51,9 @@ describe('NewResourceDropdown component', () => {
wrapper = mountFn(NewResourceDropdown, {
apolloProvider,
propsData,
+ stubs: {
+ ...stubs,
+ },
});
};
@@ -81,13 +86,18 @@ describe('NewResourceDropdown component', () => {
});
it('focuses on input when dropdown is shown', async () => {
- mountComponent({ mountFn: mount });
-
- const inputSpy = jest.spyOn(findInput().vm, 'focusInput');
+ const inputMock = jest.fn();
+ mountComponent({
+ stubs: {
+ GlSearchBoxByType: stubComponent(GlSearchBoxByType, {
+ methods: { focusInput: inputMock },
+ }),
+ },
+ });
await showDropdown();
- expect(inputSpy).toHaveBeenCalledTimes(1);
+ expect(inputMock).toHaveBeenCalledTimes(1);
});
describe.each`
diff --git a/spec/frontend/vue_shared/components/number_to_human_size/number_to_human_size_spec.js b/spec/frontend/vue_shared/components/number_to_human_size/number_to_human_size_spec.js
new file mode 100644
index 00000000000..6dd22211c96
--- /dev/null
+++ b/spec/frontend/vue_shared/components/number_to_human_size/number_to_human_size_spec.js
@@ -0,0 +1,47 @@
+import { shallowMount } from '@vue/test-utils';
+import NumberToHumanSize from '~/vue_shared/components/number_to_human_size/number_to_human_size.vue';
+import { numberToHumanSize } from '~/lib/utils/number_utils';
+
+describe('NumberToHumanSize', () => {
+ /** @type {import('@vue/test-utils').Wrapper} */
+ let wrapper;
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(NumberToHumanSize, {
+ propsData: {
+ ...props,
+ },
+ });
+ };
+
+ it('formats the value', () => {
+ const value = 1024;
+ createComponent({ value });
+
+ const expectedValue = numberToHumanSize(value, 1);
+ expect(wrapper.text()).toBe(expectedValue);
+ });
+
+ it('handles number of fraction digits', () => {
+ const value = 1024 + 254;
+ const fractionDigits = 2;
+ createComponent({ value, fractionDigits });
+
+ const expectedValue = numberToHumanSize(value, fractionDigits);
+ expect(wrapper.text()).toBe(expectedValue);
+ });
+
+ describe('plain-zero', () => {
+ it('hides label for zero values', () => {
+ createComponent({ value: 0, plainZero: true });
+ expect(wrapper.text()).toBe('0');
+ });
+
+ it('shows text for non-zero values', () => {
+ const value = 163;
+ const expectedValue = numberToHumanSize(value, 1);
+ createComponent({ value, plainZero: true });
+ expect(wrapper.text()).toBe(expectedValue);
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/source_viewer/components/chunk_new_spec.js b/spec/frontend/vue_shared/components/source_viewer/components/chunk_new_spec.js
index c7b2363026a..cd18058abec 100644
--- a/spec/frontend/vue_shared/components/source_viewer/components/chunk_new_spec.js
+++ b/spec/frontend/vue_shared/components/source_viewer/components/chunk_new_spec.js
@@ -62,6 +62,7 @@ describe('Chunk component', () => {
it('renders highlighted content', () => {
expect(findContent().text()).toBe(CHUNK_2.highlightedContent);
+ expect(findContent().attributes('style')).toBe('margin-left: 96px;');
});
});
});
diff --git a/spec/frontend/vue_shared/components/source_viewer/highlight_util_spec.js b/spec/frontend/vue_shared/components/source_viewer/highlight_util_spec.js
index 49e3083f8ed..c84a39274f8 100644
--- a/spec/frontend/vue_shared/components/source_viewer/highlight_util_spec.js
+++ b/spec/frontend/vue_shared/components/source_viewer/highlight_util_spec.js
@@ -6,6 +6,7 @@ import { LINES_PER_CHUNK, NEWLINE } from '~/vue_shared/components/source_viewer/
jest.mock('highlight.js/lib/core', () => ({
highlight: jest.fn().mockReturnValue({ value: 'highlighted content' }),
registerLanguage: jest.fn(),
+ getLanguage: jest.fn(),
}));
jest.mock('~/vue_shared/components/source_viewer/plugins/index', () => ({
@@ -28,11 +29,37 @@ describe('Highlight utility', () => {
expect(registerPlugins).toHaveBeenCalled();
});
+ describe('sub-languages', () => {
+ const languageDefinition = {
+ subLanguage: 'xml',
+ contains: [{ subLanguage: 'javascript' }, { subLanguage: 'typescript' }],
+ };
+
+ beforeEach(async () => {
+ jest.spyOn(hljs, 'getLanguage').mockReturnValue(languageDefinition);
+ await highlight(fileType, rawContent, language);
+ });
+
+ it('registers the primary sub-language', () => {
+ expect(hljs.registerLanguage).toHaveBeenCalledWith(
+ languageDefinition.subLanguage,
+ expect.any(Function),
+ );
+ });
+
+ it.each(languageDefinition.contains)(
+ 'registers the rest of the sub-languages',
+ ({ subLanguage }) => {
+ expect(hljs.registerLanguage).toHaveBeenCalledWith(subLanguage, expect.any(Function));
+ },
+ );
+ });
+
it('highlights the content', () => {
expect(hljs.highlight).toHaveBeenCalledWith(rawContent, { language });
});
- it('splits the content into chunks', () => {
+ it('splits the content into chunks', async () => {
const contentArray = Array.from({ length: 140 }, () => 'newline'); // simulate 140 lines of code
const chunks = [
@@ -52,7 +79,7 @@ describe('Highlight utility', () => {
},
];
- expect(highlight(fileType, contentArray.join(NEWLINE), language)).toEqual(
+ expect(await highlight(fileType, contentArray.join(NEWLINE), language)).toEqual(
expect.arrayContaining(chunks),
);
});
@@ -71,7 +98,7 @@ describe('unsupported languages', () => {
expect(hljs.highlight).not.toHaveBeenCalled();
});
- it('does not return a result', () => {
- expect(highlight(fileType, rawContent, unsupportedLanguage)).toBe(undefined);
+ it('does not return a result', async () => {
+ expect(await highlight(fileType, rawContent, unsupportedLanguage)).toBe(undefined);
});
});
diff --git a/spec/frontend/vue_shared/components/source_viewer/mock_data.js b/spec/frontend/vue_shared/components/source_viewer/mock_data.js
index cfff3a15b77..c98f945fc54 100644
--- a/spec/frontend/vue_shared/components/source_viewer/mock_data.js
+++ b/spec/frontend/vue_shared/components/source_viewer/mock_data.js
@@ -79,6 +79,7 @@ export const BLAME_DATA_QUERY_RESPONSE_MOCK = {
titleHtml: 'Upload New File',
message: 'Upload New File',
authoredDate: '2022-10-31T10:38:30+00:00',
+ authorName: 'Peter',
authorGravatar: 'path/to/gravatar',
webPath: '/commit/1234',
author: {},
diff --git a/spec/frontend/vue_shared/components/source_viewer/source_viewer_new_spec.js b/spec/frontend/vue_shared/components/source_viewer/source_viewer_new_spec.js
index ee7164515f6..86dc9afaacc 100644
--- a/spec/frontend/vue_shared/components/source_viewer/source_viewer_new_spec.js
+++ b/spec/frontend/vue_shared/components/source_viewer/source_viewer_new_spec.js
@@ -1,11 +1,15 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { setHTMLFixture } from 'helpers/fixtures';
import SourceViewer from '~/vue_shared/components/source_viewer/source_viewer_new.vue';
import Chunk from '~/vue_shared/components/source_viewer/components/chunk_new.vue';
-import { EVENT_ACTION, EVENT_LABEL_VIEWER } from '~/vue_shared/components/source_viewer/constants';
+import {
+ EVENT_ACTION,
+ EVENT_LABEL_VIEWER,
+ CODEOWNERS_FILE_NAME,
+} from '~/vue_shared/components/source_viewer/constants';
import Tracking from '~/tracking';
import LineHighlighter from '~/blob/line_highlighter';
import addBlobLinksTracking from '~/blob/blob_links_tracking';
@@ -13,6 +17,7 @@ import waitForPromises from 'helpers/wait_for_promises';
import blameDataQuery from '~/vue_shared/components/source_viewer/queries/blame_data.query.graphql';
import Blame from '~/vue_shared/components/source_viewer/components/blame_info.vue';
import * as utils from '~/vue_shared/components/source_viewer/utils';
+import CodeownersValidation from 'ee_component/blob/components/codeowners_validation.vue';
import {
BLOB_DATA_MOCK,
@@ -43,16 +48,17 @@ describe('Source Viewer component', () => {
const blameInfo =
BLAME_DATA_QUERY_RESPONSE_MOCK.data.project.repository.blobs.nodes[0].blame.groups;
- const createComponent = ({ showBlame = true } = {}) => {
+ const createComponent = ({ showBlame = true, blob = {} } = {}) => {
fakeApollo = createMockApollo([[blameDataQuery, blameDataQueryHandlerSuccess]]);
wrapper = shallowMountExtended(SourceViewer, {
apolloProvider: fakeApollo,
mocks: { $route: { hash } },
propsData: {
- blob: BLOB_DATA_MOCK,
+ blob: { ...blob, ...BLOB_DATA_MOCK },
chunks: CHUNKS_MOCK,
projectPath: 'test',
+ currentRef: 'main',
showBlame,
},
});
@@ -111,22 +117,18 @@ describe('Source Viewer component', () => {
});
it('calls the query only once per chunk', async () => {
- jest.spyOn(wrapper.vm.$apollo, 'query');
-
// We trigger the `appear` event multiple times here in order to simulate the user scrolling past the chunk more than once.
// In this scenario we only want to query the backend once.
await triggerChunkAppear();
await triggerChunkAppear();
- expect(wrapper.vm.$apollo.query).toHaveBeenCalledTimes(1);
+ expect(blameDataQueryHandlerSuccess).toHaveBeenCalledTimes(1);
});
it('requests blame information for overlapping chunk', async () => {
- jest.spyOn(wrapper.vm.$apollo, 'query');
-
await triggerChunkAppear(1);
- expect(wrapper.vm.$apollo.query).toHaveBeenCalledTimes(2);
+ expect(blameDataQueryHandlerSuccess).toHaveBeenCalledTimes(2);
expect(blameDataQueryHandlerSuccess).toHaveBeenCalledWith(
expect.objectContaining({ fromLine: 71, toLine: 110 }),
);
@@ -156,4 +158,20 @@ describe('Source Viewer component', () => {
expect(lineHighlighter.highlightHash).toHaveBeenCalledWith(hash);
});
});
+
+ describe('Codeowners validation', () => {
+ const findCodeownersValidation = () => wrapper.findComponent(CodeownersValidation);
+
+ it('does not render codeowners validation when file is not CODEOWNERS', async () => {
+ await createComponent();
+ await nextTick();
+ expect(findCodeownersValidation().exists()).toBe(false);
+ });
+
+ it('renders codeowners validation when file is CODEOWNERS', async () => {
+ await createComponent({ blob: { name: CODEOWNERS_FILE_NAME } });
+ await nextTick();
+ expect(findCodeownersValidation().exists()).toBe(true);
+ });
+ });
});
diff --git a/spec/frontend/vue_shared/components/time_ago_tooltip_spec.js b/spec/frontend/vue_shared/components/time_ago_tooltip_spec.js
index 41cf1d2b2e8..21c58d662e3 100644
--- a/spec/frontend/vue_shared/components/time_ago_tooltip_spec.js
+++ b/spec/frontend/vue_shared/components/time_ago_tooltip_spec.js
@@ -2,9 +2,9 @@ import { shallowMount } from '@vue/test-utils';
import { GlTruncate } from '@gitlab/ui';
import timezoneMock from 'timezone-mock';
-import { formatDate, getTimeago } from '~/lib/utils/datetime_utility';
-import { DATE_ONLY_FORMAT } from '~/lib/utils/datetime/constants';
+import { getTimeago } from '~/lib/utils/datetime_utility';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import { DATE_ONLY_FORMAT } from '~/lib/utils/datetime/locale_dateformat';
describe('Time ago with tooltip component', () => {
let vm;
@@ -33,7 +33,7 @@ describe('Time ago with tooltip component', () => {
it('should render timeago with a bootstrap tooltip', () => {
buildVm();
- expect(vm.attributes('title')).toEqual(formatDate(timestamp));
+ expect(vm.attributes('title')).toEqual('May 8, 2017 at 2:57:39 PM GMT');
expect(vm.text()).toEqual(timeAgoTimestamp);
});
diff --git a/spec/frontend/vue_shared/components/vuex_module_provider_spec.js b/spec/frontend/vue_shared/components/vuex_module_provider_spec.js
deleted file mode 100644
index 95f557b10c1..00000000000
--- a/spec/frontend/vue_shared/components/vuex_module_provider_spec.js
+++ /dev/null
@@ -1,39 +0,0 @@
-import { mount } from '@vue/test-utils';
-import VuexModuleProvider from '~/vue_shared/components/vuex_module_provider.vue';
-
-const TestComponent = {
- inject: ['vuexModule'],
- template: `<div data-testid="vuexModule">{{ vuexModule }}</div> `,
-};
-
-const TEST_VUEX_MODULE = 'testVuexModule';
-
-describe('~/vue_shared/components/vuex_module_provider', () => {
- let wrapper;
-
- const findProvidedVuexModule = () => wrapper.find('[data-testid="vuexModule"]').text();
-
- const createComponent = (extraParams = {}) => {
- wrapper = mount(VuexModuleProvider, {
- propsData: {
- vuexModule: TEST_VUEX_MODULE,
- },
- slots: {
- default: TestComponent,
- },
- ...extraParams,
- });
- };
-
- it('provides "vuexModule" set from prop', () => {
- createComponent();
- expect(findProvidedVuexModule()).toBe(TEST_VUEX_MODULE);
- });
-
- it('provides "vuexModel" set from "vuex-module" prop when using @vue/compat', () => {
- createComponent({
- propsData: { 'vuex-module': TEST_VUEX_MODULE },
- });
- expect(findProvidedVuexModule()).toBe(TEST_VUEX_MODULE);
- });
-});
diff --git a/spec/frontend/vue_shared/directives/track_event_spec.js b/spec/frontend/vue_shared/directives/track_event_spec.js
index fc69e884258..8b4a68e394a 100644
--- a/spec/frontend/vue_shared/directives/track_event_spec.js
+++ b/spec/frontend/vue_shared/directives/track_event_spec.js
@@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import Tracking from '~/tracking';
import TrackEvent from '~/vue_shared/directives/track_event';
@@ -10,34 +10,53 @@ describe('TrackEvent directive', () => {
const clickButton = () => wrapper.find('button').trigger('click');
- const createComponent = (trackingOptions) =>
- Vue.component('DummyElement', {
- directives: {
- TrackEvent,
+ const DummyTrackComponent = Vue.component('DummyTrackComponent', {
+ directives: {
+ TrackEvent,
+ },
+ props: {
+ category: {
+ type: String,
+ required: false,
+ default: '',
},
- data() {
- return {
- trackingOptions,
- };
+ action: {
+ type: String,
+ required: false,
+ default: '',
},
- template: '<button v-track-event="trackingOptions"></button>',
- });
+ label: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+ template: '<button v-track-event="{ category, action, label }"></button>',
+ });
- const mountComponent = (trackingOptions) => shallowMount(createComponent(trackingOptions));
+ const mountComponent = ({ propsData = {} } = {}) => {
+ wrapper = shallowMount(DummyTrackComponent, {
+ propsData,
+ });
+ };
it('does not track the event if required arguments are not provided', () => {
- wrapper = mountComponent();
+ mountComponent();
clickButton();
expect(Tracking.event).not.toHaveBeenCalled();
});
- it('tracks event on click if tracking info provided', () => {
- wrapper = mountComponent({
- category: 'Tracking',
- action: 'click_trackable_btn',
- label: 'Trackable Info',
+ it('tracks event on click if tracking info provided', async () => {
+ mountComponent({
+ propsData: {
+ category: 'Tracking',
+ action: 'click_trackable_btn',
+ label: 'Trackable Info',
+ },
});
+
+ await nextTick();
clickButton();
expect(Tracking.event).toHaveBeenCalledWith('Tracking', 'click_trackable_btn', {
diff --git a/spec/frontend/vue_shared/issuable/create/components/issuable_label_selector_spec.js b/spec/frontend/vue_shared/issuable/create/components/issuable_label_selector_spec.js
index 1a490359040..94234a03664 100644
--- a/spec/frontend/vue_shared/issuable/create/components/issuable_label_selector_spec.js
+++ b/spec/frontend/vue_shared/issuable/create/components/issuable_label_selector_spec.js
@@ -16,6 +16,7 @@ const fullPath = '/full-path';
const labelsFilterBasePath = '/labels-filter-base-path';
const initialLabels = [];
const issuableType = 'issue';
+const issuableSupportsLockOnMerge = false;
const labelType = WORKSPACE_PROJECT;
const variant = VARIANT_EMBEDDED;
const workspaceType = WORKSPACE_PROJECT;
@@ -36,6 +37,7 @@ describe('IssuableLabelSelector', () => {
labelsFilterBasePath,
initialLabels,
issuableType,
+ issuableSupportsLockOnMerge,
labelType,
variant,
workspaceType,
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 47da111b604..98a87ddbcce 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
@@ -6,6 +6,7 @@ import IssuableItem from '~/vue_shared/issuable/list/components/issuable_item.vu
import WorkItemTypeIcon from '~/work_items/components/work_item_type_icon.vue';
import IssuableAssignees from '~/issuable/components/issue_assignees.vue';
+import { localeDateFormat } from '~/lib/utils/datetime/locale_dateformat';
import { mockIssuable, mockRegularLabel } from '../mock_data';
const createComponent = ({
@@ -168,15 +169,20 @@ describe('IssuableItem', () => {
it('returns timestamp based on `issuable.updatedAt` when the issue is open', () => {
wrapper = createComponent();
- expect(findTimestampWrapper().attributes('title')).toBe('Sep 10, 2020 11:41am UTC');
+ expect(findTimestampWrapper().attributes('title')).toBe(
+ localeDateFormat.asDateTimeFull.format(mockIssuable.updatedAt),
+ );
});
it('returns timestamp based on `issuable.closedAt` when the issue is closed', () => {
+ const closedAt = '2020-06-18T11:30:00Z';
wrapper = createComponent({
- issuable: { ...mockIssuable, closedAt: '2020-06-18T11:30:00Z', state: 'closed' },
+ issuable: { ...mockIssuable, closedAt, state: 'closed' },
});
- expect(findTimestampWrapper().attributes('title')).toBe('Jun 18, 2020 11:30am UTC');
+ expect(findTimestampWrapper().attributes('title')).toBe(
+ localeDateFormat.asDateTimeFull.format(closedAt),
+ );
});
it('returns timestamp based on `issuable.updatedAt` when the issue is closed but `issuable.closedAt` is undefined', () => {
@@ -184,7 +190,9 @@ describe('IssuableItem', () => {
issuable: { ...mockIssuable, closedAt: undefined, state: 'closed' },
});
- expect(findTimestampWrapper().attributes('title')).toBe('Sep 10, 2020 11:41am UTC');
+ expect(findTimestampWrapper().attributes('title')).toBe(
+ localeDateFormat.asDateTimeFull.format(mockIssuable.updatedAt),
+ );
});
});
@@ -409,7 +417,9 @@ describe('IssuableItem', () => {
const createdAtEl = wrapper.find('[data-testid="issuable-created-at"]');
expect(createdAtEl.exists()).toBe(true);
- expect(createdAtEl.attributes('title')).toBe('Jun 29, 2020 1:52pm UTC');
+ expect(createdAtEl.attributes('title')).toBe(
+ localeDateFormat.asDateTimeFull.format(mockIssuable.createdAt),
+ );
expect(createdAtEl.text()).toBe(wrapper.vm.createdAt);
});
@@ -535,7 +545,9 @@ describe('IssuableItem', () => {
const timestampEl = wrapper.find('[data-testid="issuable-timestamp"]');
- expect(timestampEl.attributes('title')).toBe('Sep 10, 2020 11:41am UTC');
+ expect(timestampEl.attributes('title')).toBe(
+ localeDateFormat.asDateTimeFull.format(mockIssuable.updatedAt),
+ );
expect(timestampEl.text()).toBe(wrapper.vm.formattedTimestamp);
});
@@ -549,13 +561,16 @@ describe('IssuableItem', () => {
});
it('renders issuable closedAt info and does not render updatedAt info', () => {
+ const closedAt = '2022-06-18T11:30:00Z';
wrapper = createComponent({
- issuable: { ...mockIssuable, closedAt: '2022-06-18T11:30:00Z', state: 'closed' },
+ issuable: { ...mockIssuable, closedAt, state: 'closed' },
});
const timestampEl = wrapper.find('[data-testid="issuable-timestamp"]');
- expect(timestampEl.attributes('title')).toBe('Jun 18, 2022 11:30am UTC');
+ expect(timestampEl.attributes('title')).toBe(
+ localeDateFormat.asDateTimeFull.format(closedAt),
+ );
expect(timestampEl.text()).toBe(wrapper.vm.formattedTimestamp);
});
});
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 51aae9b4512..a2a059d5b18 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
@@ -4,6 +4,7 @@ import VueDraggable from 'vuedraggable';
import { nextTick } from 'vue';
import { TEST_HOST } from 'helpers/test_constants';
+import { DRAG_DELAY } from '~/sortable/constants';
import IssuableItem from '~/vue_shared/issuable/list/components/issuable_item.vue';
import IssuableListRoot from '~/vue_shared/issuable/list/components/issuable_list_root.vue';
@@ -476,6 +477,11 @@ describe('IssuableListRoot', () => {
expect(findIssuableItem().classes()).toContain('gl-cursor-grab');
});
+ it('sets delay and delayOnTouchOnly attributes on list', () => {
+ expect(findVueDraggable().vm.$attrs.delay).toBe(DRAG_DELAY);
+ expect(findVueDraggable().vm.$attrs.delayOnTouchOnly).toBe(true);
+ });
+
it('emits a "reorder" event when user updates the issue order', () => {
const oldIndex = 4;
const newIndex = 6;
diff --git a/spec/frontend/vue_shared/issuable/sidebar/components/issuable_sidebar_root_spec.js b/spec/frontend/vue_shared/issuable/sidebar/components/issuable_sidebar_root_spec.js
index f2509aead77..d5c6ece8cb5 100644
--- a/spec/frontend/vue_shared/issuable/sidebar/components/issuable_sidebar_root_spec.js
+++ b/spec/frontend/vue_shared/issuable/sidebar/components/issuable_sidebar_root_spec.js
@@ -1,3 +1,4 @@
+import { GlButton, GlIcon } from '@gitlab/ui';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import { nextTick } from 'vue';
import Cookies from '~/lib/utils/cookies';
@@ -18,6 +19,10 @@ const createComponent = () => {
<button class="js-todo">Todo</button>
`,
},
+ stubs: {
+ GlButton,
+ GlIcon,
+ },
});
};
@@ -62,9 +67,8 @@ describe('IssuableSidebarRoot', () => {
const buttonEl = findToggleSidebarButton();
expect(buttonEl.exists()).toBe(true);
- expect(buttonEl.attributes('title')).toBe('Toggle sidebar');
- expect(buttonEl.find('span').text()).toBe('Collapse sidebar');
- expect(wrapper.findByTestId('icon-collapse').isVisible()).toBe(true);
+ expect(buttonEl.attributes('title')).toBe('Collapse sidebar');
+ expect(wrapper.findByTestId('chevron-double-lg-right-icon').isVisible()).toBe(true);
});
describe('when collapsing the sidebar', () => {
@@ -116,12 +120,12 @@ describe('IssuableSidebarRoot', () => {
assertPageLayoutClasses({ isExpanded: false });
});
- it('renders sidebar toggle button with text and icon', () => {
+ it('renders sidebar toggle button with title and icon', () => {
const buttonEl = findToggleSidebarButton();
expect(buttonEl.exists()).toBe(true);
- expect(buttonEl.attributes('title')).toBe('Toggle sidebar');
- expect(wrapper.findByTestId('icon-expand').isVisible()).toBe(true);
+ expect(buttonEl.attributes('title')).toBe('Expand sidebar');
+ expect(wrapper.findByTestId('chevron-double-lg-left-icon').isVisible()).toBe(true);
});
});
diff --git a/spec/frontend/vue_shared/new_namespace/new_namespace_page_spec.js b/spec/frontend/vue_shared/new_namespace/new_namespace_page_spec.js
index 109b7732539..716de45f4b4 100644
--- a/spec/frontend/vue_shared/new_namespace/new_namespace_page_spec.js
+++ b/spec/frontend/vue_shared/new_namespace/new_namespace_page_spec.js
@@ -116,21 +116,23 @@ describe('Experimental new namespace creation app', () => {
expect(findLegacyContainer().exists()).toBe(true);
});
- describe.each`
- featureFlag | isSuperSidebarCollapsed | isToggleVisible
- ${true} | ${true} | ${true}
- ${true} | ${false} | ${false}
- ${false} | ${true} | ${false}
- ${false} | ${false} | ${false}
- `('Super sidebar toggle', ({ featureFlag, isSuperSidebarCollapsed, isToggleVisible }) => {
- beforeEach(() => {
- sidebarState.isCollapsed = isSuperSidebarCollapsed;
- gon.use_new_navigation = featureFlag;
- createComponent();
+ describe('SuperSidebarToggle', () => {
+ describe('when collapsed', () => {
+ it('shows sidebar toggle', () => {
+ sidebarState.isCollapsed = true;
+ createComponent();
+
+ expect(findSuperSidebarToggle().exists()).toBe(true);
+ });
});
- it(`${isToggleVisible ? 'is visible' : 'is not visible'}`, () => {
- expect(findSuperSidebarToggle().exists()).toBe(isToggleVisible);
+ describe('when not collapsed', () => {
+ it('does not show sidebar toggle', () => {
+ sidebarState.isCollapsed = false;
+ createComponent();
+
+ expect(findSuperSidebarToggle().exists()).toBe(false);
+ });
});
});
@@ -170,17 +172,10 @@ describe('Experimental new namespace creation app', () => {
});
describe('top bar', () => {
- it('adds "top-bar-fixed" and "container-fluid" classes when new navigation enabled', () => {
- gon.use_new_navigation = true;
+ it('has "top-bar-fixed" and "container-fluid" classes', () => {
createComponent();
expect(findTopBar().classes()).toEqual(['top-bar-fixed', 'container-fluid']);
});
-
- it('does not add classes when new navigation is not enabled', () => {
- createComponent();
-
- expect(findTopBar().classes()).toEqual([]);
- });
});
});
diff --git a/spec/frontend/vue_shared/security_reports/components/manage_via_mr_spec.js b/spec/frontend/vue_shared/security_reports/components/manage_via_mr_spec.js
index f3d0d66cdd1..2b36344cfa8 100644
--- a/spec/frontend/vue_shared/security_reports/components/manage_via_mr_spec.js
+++ b/spec/frontend/vue_shared/security_reports/components/manage_via_mr_spec.js
@@ -2,7 +2,7 @@ import { GlButton } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
-import { featureToMutationMap } from 'ee_else_ce/security_configuration/components/constants';
+import { featureToMutationMap } from 'ee_else_ce/security_configuration/constants';
import createMockApollo from 'helpers/mock_apollo_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';