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
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-04-27 15:16:04 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-04-27 15:16:04 +0300
commit5cb0fa35e709bcd7f9d69e050010e44092a48623 (patch)
tree9a10a2a58b3129b6dae59d2aa320f00b1e4a2953 /spec
parent996683657578757cf42ef7478a5c3b9874b312f0 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/features/groups/issues_spec.rb4
-rw-r--r--spec/features/issues/csv_spec.rb1
-rw-r--r--spec/features/issues/rss_spec.rb2
-rw-r--r--spec/features/merge_requests/rss_spec.rb4
-rw-r--r--spec/features/merge_requests/user_exports_as_csv_spec.rb1
-rw-r--r--spec/features/projects/container_registry_spec.rb14
-rw-r--r--spec/finders/merge_requests_finder_spec.rb36
-rw-r--r--spec/frontend/ci/runner/components/runner_platforms_radio_group_spec.js10
-rw-r--r--spec/frontend/issuable/components/csv_import_export_buttons_spec.js86
-rw-r--r--spec/frontend/issues/list/components/empty_state_without_any_issues_spec.js5
-rw-r--r--spec/frontend/issues/list/components/issues_list_app_spec.js98
-rw-r--r--spec/frontend/issues/show/components/task_list_item_actions_spec.js17
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/pages/list_spec.js53
-rw-r--r--spec/frontend/vue_merge_request_widget/components/approvals/approvals_summary_spec.js27
-rw-r--r--spec/frontend/vue_shared/components/user_avatar/user_avatar_list_spec.js29
-rw-r--r--spec/helpers/packages_helper_spec.rb56
-rw-r--r--spec/models/protected_tag/create_access_level_spec.rb12
-rw-r--r--spec/support/shared_examples/features/rss_shared_examples.rb1
-rw-r--r--spec/support/shared_examples/models/concerns/protected_ref_access_examples.rb61
-rw-r--r--spec/support/shared_examples/models/concerns/protected_tag_access_examples.rb21
20 files changed, 381 insertions, 157 deletions
diff --git a/spec/features/groups/issues_spec.rb b/spec/features/groups/issues_spec.rb
index 9f6fa146972..6d0d768d356 100644
--- a/spec/features/groups/issues_spec.rb
+++ b/spec/features/groups/issues_spec.rb
@@ -23,6 +23,10 @@ RSpec.describe 'Group issues page', feature_category: :subgroups do
context 'rss feed' do
let(:access_level) { ProjectFeature::ENABLED }
+ before do
+ click_button 'Actions'
+ end
+
context 'when signed in' do
let(:user) do
user_in_group.ensure_feed_token
diff --git a/spec/features/issues/csv_spec.rb b/spec/features/issues/csv_spec.rb
index 8629201459f..21d5041b210 100644
--- a/spec/features/issues/csv_spec.rb
+++ b/spec/features/issues/csv_spec.rb
@@ -16,6 +16,7 @@ RSpec.describe 'Issues csv', :js, feature_category: :team_planning do
def request_csv(params = {})
visit project_issues_path(project, params)
+ click_button 'Actions'
click_button 'Export as CSV'
click_on 'Export issues'
end
diff --git a/spec/features/issues/rss_spec.rb b/spec/features/issues/rss_spec.rb
index 75e7cd03a65..eb45d3c8d8b 100644
--- a/spec/features/issues/rss_spec.rb
+++ b/spec/features/issues/rss_spec.rb
@@ -23,6 +23,7 @@ RSpec.describe 'Project Issues RSS', :js, feature_category: :team_planning do
before do
sign_in(user)
visit path
+ click_button 'Actions'
end
it_behaves_like "it has an RSS link with current_user's feed token"
@@ -32,6 +33,7 @@ RSpec.describe 'Project Issues RSS', :js, feature_category: :team_planning do
context 'when signed out' do
before do
visit path
+ click_button 'Actions'
end
it_behaves_like "it has an RSS link without a feed token"
diff --git a/spec/features/merge_requests/rss_spec.rb b/spec/features/merge_requests/rss_spec.rb
index 9c9f46278f6..5f697a4f79d 100644
--- a/spec/features/merge_requests/rss_spec.rb
+++ b/spec/features/merge_requests/rss_spec.rb
@@ -25,7 +25,7 @@ RSpec.describe 'Project Merge Requests RSS', feature_category: :code_review_work
visit path
end
- it_behaves_like "it has an RSS button with current_user's feed token"
+ it_behaves_like "it has an RSS link with current_user's feed token"
it_behaves_like "an autodiscoverable RSS feed with current_user's feed token"
end
@@ -34,7 +34,7 @@ RSpec.describe 'Project Merge Requests RSS', feature_category: :code_review_work
visit path
end
- it_behaves_like "it has an RSS button without a feed token"
+ it_behaves_like "it has an RSS link without a feed token"
it_behaves_like "an autodiscoverable RSS feed without a feed token"
end
diff --git a/spec/features/merge_requests/user_exports_as_csv_spec.rb b/spec/features/merge_requests/user_exports_as_csv_spec.rb
index 23ac1b264ad..57d6ee69923 100644
--- a/spec/features/merge_requests/user_exports_as_csv_spec.rb
+++ b/spec/features/merge_requests/user_exports_as_csv_spec.rb
@@ -14,6 +14,7 @@ RSpec.describe 'Merge Requests > Exports as CSV', :js, feature_category: :code_r
context 'button is clicked' do
before do
+ click_button 'Actions'
click_button 'Export as CSV'
end
diff --git a/spec/features/projects/container_registry_spec.rb b/spec/features/projects/container_registry_spec.rb
index e8ac34d4355..5306a9f15c6 100644
--- a/spec/features/projects/container_registry_spec.rb
+++ b/spec/features/projects/container_registry_spec.rb
@@ -30,6 +30,20 @@ RSpec.describe 'Container Registry', :js, feature_category: :projects do
expect(page).to have_title _('Container Registry')
end
+ it 'does not have link to settings' do
+ visit_container_registry
+
+ expect(page).not_to have_link _('Configure in settings')
+ end
+
+ it 'has link to settings when user is maintainer' do
+ project.add_maintainer(user)
+
+ visit_container_registry
+
+ expect(page).to have_link _('Configure in settings')
+ end
+
context 'when there are no image repositories' do
it 'list page has no container title' do
visit_container_registry
diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb
index aa167fe7aba..6d576bc8e38 100644
--- a/spec/finders/merge_requests_finder_spec.rb
+++ b/spec/finders/merge_requests_finder_spec.rb
@@ -498,16 +498,40 @@ RSpec.describe MergeRequestsFinder, feature_category: :code_review_workflow do
create(:approval, merge_request: merge_request3, user: user2)
end
- it 'for approved' do
- merge_requests = described_class.new(user, { approved: true }).execute
+ context 'when flag `mr_approved_filter` is disabled' do
+ before do
+ stub_feature_flags(mr_approved_filter: false)
+ end
- expect(merge_requests).to contain_exactly(merge_request3)
+ it 'for approved' do
+ merge_requests = described_class.new(user, { approved: true }).execute
+
+ expect(merge_requests).to contain_exactly(merge_request1, merge_request2, merge_request3, merge_request4, merge_request5)
+ end
+
+ it 'for not approved' do
+ merge_requests = described_class.new(user, { approved: false }).execute
+
+ expect(merge_requests).to contain_exactly(merge_request1, merge_request2, merge_request3, merge_request4, merge_request5)
+ end
end
- it 'for not approved' do
- merge_requests = described_class.new(user, { approved: false }).execute
+ context 'when flag `mr_approved_filter` is enabled' do
+ before do
+ stub_feature_flags(mr_approved_filter: true)
+ end
+
+ it 'for approved' do
+ merge_requests = described_class.new(user, { approved: true }).execute
+
+ expect(merge_requests).to contain_exactly(merge_request3)
+ end
+
+ it 'for not approved' do
+ merge_requests = described_class.new(user, { approved: false }).execute
- expect(merge_requests).to contain_exactly(merge_request1, merge_request2, merge_request4, merge_request5)
+ expect(merge_requests).to contain_exactly(merge_request1, merge_request2, merge_request4, merge_request5)
+ end
end
end
diff --git a/spec/frontend/ci/runner/components/runner_platforms_radio_group_spec.js b/spec/frontend/ci/runner/components/runner_platforms_radio_group_spec.js
index d419b34df1b..6d11e5879f6 100644
--- a/spec/frontend/ci/runner/components/runner_platforms_radio_group_spec.js
+++ b/spec/frontend/ci/runner/components/runner_platforms_radio_group_spec.js
@@ -6,19 +6,12 @@ import {
LINUX_PLATFORM,
MACOS_PLATFORM,
WINDOWS_PLATFORM,
- AWS_PLATFORM,
DOCKER_HELP_URL,
KUBERNETES_HELP_URL,
} from '~/ci/runner/constants';
import RunnerPlatformsRadioGroup from '~/ci/runner/components/runner_platforms_radio_group.vue';
-const mockProvide = {
- awsImgPath: 'awsLogo.svg',
- dockerImgPath: 'dockerLogo.svg',
- kubernetesImgPath: 'kubernetesLogo.svg',
-};
-
describe('RunnerPlatformsRadioGroup', () => {
let wrapper;
@@ -35,7 +28,6 @@ describe('RunnerPlatformsRadioGroup', () => {
value: null,
...props,
},
- provide: mockProvide,
...options,
});
};
@@ -51,7 +43,6 @@ describe('RunnerPlatformsRadioGroup', () => {
['Linux', null],
['macOS', null],
['Windows', null],
- ['AWS', expect.any(String)],
['Docker', expect.any(String)],
['Kubernetes', expect.any(String)],
]);
@@ -69,7 +60,6 @@ describe('RunnerPlatformsRadioGroup', () => {
${'Linux'} | ${LINUX_PLATFORM}
${'macOS'} | ${MACOS_PLATFORM}
${'Windows'} | ${WINDOWS_PLATFORM}
- ${'AWS'} | ${AWS_PLATFORM}
`('user can select "$text"', async ({ text, value }) => {
const radio = findFormRadioByText(text);
expect(radio.props('value')).toBe(value);
diff --git a/spec/frontend/issuable/components/csv_import_export_buttons_spec.js b/spec/frontend/issuable/components/csv_import_export_buttons_spec.js
index a861148abb6..0e2f71fa3ee 100644
--- a/spec/frontend/issuable/components/csv_import_export_buttons_spec.js
+++ b/spec/frontend/issuable/components/csv_import_export_buttons_spec.js
@@ -1,5 +1,4 @@
-import { GlButton, GlDropdown } from '@gitlab/ui';
-import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
+import { createMockDirective } from 'helpers/vue_mock_directive';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import CsvExportModal from '~/issuable/components/csv_export_modal.vue';
import CsvImportExportButtons from '~/issuable/components/csv_import_export_buttons.vue';
@@ -33,8 +32,7 @@ describe('CsvImportExportButtons', () => {
});
}
- const findExportCsvButton = () => wrapper.findComponent(GlButton);
- const findImportDropdown = () => wrapper.findComponent(GlDropdown);
+ const findExportCsvButton = () => wrapper.findByRole('menuitem', { name: 'Export as CSV' });
const findImportCsvButton = () => wrapper.findByRole('menuitem', { name: 'Import CSV' });
const findImportFromJiraLink = () => wrapper.findByRole('menuitem', { name: 'Import from Jira' });
const findExportCsvModal = () => wrapper.findComponent(CsvExportModal);
@@ -50,13 +48,6 @@ describe('CsvImportExportButtons', () => {
expect(findExportCsvButton().exists()).toBe(true);
});
- it('export button has a tooltip', () => {
- const tooltip = getBinding(findExportCsvButton().element, 'gl-tooltip');
-
- expect(tooltip).toBeDefined();
- expect(tooltip.value).toBe('Export as CSV');
- });
-
it('renders the export modal', () => {
expect(findExportCsvModal().props()).toMatchObject({ exportCsvPath, issuableCount });
});
@@ -64,7 +55,7 @@ describe('CsvImportExportButtons', () => {
it('opens the export modal', () => {
findExportCsvButton().trigger('click');
- expect(glModalDirective).toHaveBeenCalledWith(wrapper.vm.exportModalId);
+ expect(glModalDirective).toHaveBeenCalled();
});
});
@@ -83,79 +74,38 @@ describe('CsvImportExportButtons', () => {
});
describe('when the showImportButton=true', () => {
- beforeEach(() => {
+ it('renders the import csv menu item', () => {
wrapper = createComponent({ showImportButton: true });
- });
-
- it('displays the import dropdown', () => {
- expect(findImportDropdown().exists()).toBe(true);
- });
- it('renders the import csv menu item', () => {
expect(findImportCsvButton().exists()).toBe(true);
});
- describe('when showLabel=false', () => {
- beforeEach(() => {
- wrapper = createComponent({ showImportButton: true, showLabel: false });
- });
-
- it('hides button text', () => {
- expect(findImportDropdown().props()).toMatchObject({
- text: 'Import issues',
- textSrOnly: true,
- });
- });
-
- it('import button has a tooltip', () => {
- const tooltip = getBinding(findImportDropdown().element, 'gl-tooltip');
-
- expect(tooltip).toBeDefined();
- expect(tooltip.value).toBe('Import issues');
- });
- });
-
- describe('when showLabel=true', () => {
- beforeEach(() => {
- wrapper = createComponent({ showImportButton: true, showLabel: true });
- });
-
- it('displays a button text', () => {
- expect(findImportDropdown().props()).toMatchObject({
- text: 'Import issues',
- textSrOnly: false,
- });
- });
-
- it('import button has no tooltip', () => {
- const tooltip = getBinding(findImportDropdown().element, 'gl-tooltip');
-
- expect(tooltip.value).toBe(null);
- });
- });
-
it('renders the import modal', () => {
+ wrapper = createComponent({ showImportButton: true });
+
expect(findImportCsvModal().exists()).toBe(true);
});
it('opens the import modal', () => {
+ wrapper = createComponent({ showImportButton: true });
+
findImportCsvButton().trigger('click');
- expect(glModalDirective).toHaveBeenCalledWith(wrapper.vm.importModalId);
+ expect(glModalDirective).toHaveBeenCalled();
});
describe('import from jira link', () => {
const projectImportJiraPath = 'gitlab-org/gitlab-test/-/import/jira';
- beforeEach(() => {
- wrapper = createComponent({
- showImportButton: true,
- canEdit: true,
- projectImportJiraPath,
+ describe('when canEdit=true', () => {
+ beforeEach(() => {
+ wrapper = createComponent({
+ showImportButton: true,
+ canEdit: true,
+ projectImportJiraPath,
+ });
});
- });
- describe('when canEdit=true', () => {
it('renders the import dropdown item', () => {
expect(findImportFromJiraLink().exists()).toBe(true);
});
@@ -182,8 +132,8 @@ describe('CsvImportExportButtons', () => {
wrapper = createComponent({ showImportButton: false });
});
- it('does not display the import dropdown', () => {
- expect(findImportDropdown().exists()).toBe(false);
+ it('does not render the import csv menu item', () => {
+ expect(findImportCsvButton().exists()).toBe(false);
});
it('does not render the import modal', () => {
diff --git a/spec/frontend/issues/list/components/empty_state_without_any_issues_spec.js b/spec/frontend/issues/list/components/empty_state_without_any_issues_spec.js
index 0a2e4e7c671..4ea3a39f15b 100644
--- a/spec/frontend/issues/list/components/empty_state_without_any_issues_spec.js
+++ b/spec/frontend/issues/list/components/empty_state_without_any_issues_spec.js
@@ -1,4 +1,4 @@
-import { GlEmptyState, GlLink } from '@gitlab/ui';
+import { GlDropdown, GlEmptyState, GlLink } from '@gitlab/ui';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import CsvImportExportButtons from '~/issuable/components/csv_import_export_buttons.vue';
import EmptyStateWithoutAnyIssues from '~/issues/list/components/empty_state_without_any_issues.vue';
@@ -26,6 +26,7 @@ describe('EmptyStateWithoutAnyIssues component', () => {
};
const findCsvImportExportButtons = () => wrapper.findComponent(CsvImportExportButtons);
+ const findCsvImportExportDropdown = () => wrapper.findComponent(GlDropdown);
const findGlEmptyState = () => wrapper.findComponent(GlEmptyState);
const findGlLink = () => wrapper.findComponent(GlLink);
const findIssuesHelpPageLink = () =>
@@ -135,6 +136,7 @@ describe('EmptyStateWithoutAnyIssues component', () => {
it('renders', () => {
mountComponent({ props: { showCsvButtons: true } });
+ expect(findCsvImportExportDropdown().props('text')).toBe('Import issues');
expect(findCsvImportExportButtons().props()).toMatchObject({
exportCsvPath: defaultProps.exportCsvPathWithQuery,
issuableCount: 0,
@@ -146,6 +148,7 @@ describe('EmptyStateWithoutAnyIssues component', () => {
it('does not render', () => {
mountComponent({ props: { showCsvButtons: false } });
+ expect(findCsvImportExportDropdown().exists()).toBe(false);
expect(findCsvImportExportButtons().exists()).toBe(false);
});
});
diff --git a/spec/frontend/issues/list/components/issues_list_app_spec.js b/spec/frontend/issues/list/components/issues_list_app_spec.js
index 61dbc9afeeb..d1b796c5aa6 100644
--- a/spec/frontend/issues/list/components/issues_list_app_spec.js
+++ b/spec/frontend/issues/list/components/issues_list_app_spec.js
@@ -1,4 +1,4 @@
-import { GlButton } from '@gitlab/ui';
+import { GlButton, GlDropdown } from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
import { mount, shallowMount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
@@ -11,6 +11,7 @@ import getIssuesCountsQuery from 'ee_else_ce/issues/list/queries/get_issues_coun
import createMockApollo from 'helpers/mock_apollo_helper';
import setWindowLocation from 'helpers/set_window_location_helper';
import { TEST_HOST } from 'helpers/test_constants';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import {
getIssuesCountsQueryResponse,
@@ -126,12 +127,16 @@ describe('CE IssuesListApp component', () => {
const mockIssuesQueryResponse = jest.fn().mockResolvedValue(defaultQueryResponse);
const mockIssuesCountsQueryResponse = jest.fn().mockResolvedValue(getIssuesCountsQueryResponse);
+ const findCalendarButton = () =>
+ wrapper.findByRole('menuitem', { name: IssuesListApp.i18n.calendarLabel });
const findCsvImportExportButtons = () => wrapper.findComponent(CsvImportExportButtons);
+ const findDropdown = () => wrapper.findComponent(GlDropdown);
const findIssuableByEmail = () => wrapper.findComponent(IssuableByEmail);
+ const findGlButton = () => wrapper.findComponent(GlButton);
const findGlButtons = () => wrapper.findAllComponents(GlButton);
- const findGlButtonAt = (index) => findGlButtons().at(index);
const findIssuableList = () => wrapper.findComponent(IssuableList);
const findNewResourceDropdown = () => wrapper.findComponent(NewResourceDropdown);
+ const findRssButton = () => wrapper.findByRole('menuitem', { name: IssuesListApp.i18n.rssLabel });
const findLabelsToken = () =>
findIssuableList()
@@ -232,63 +237,66 @@ describe('CE IssuesListApp component', () => {
});
describe('header action buttons', () => {
- it('renders rss button', async () => {
- wrapper = mountComponent({ mountFn: mount });
- await waitForPromises();
+ describe('actions dropdown', () => {
+ it('renders', () => {
+ wrapper = mountComponent({ mountFn: mount });
- expect(findGlButtonAt(0).props('icon')).toBe('rss');
- expect(findGlButtonAt(0).attributes()).toMatchObject({
- href: defaultProvide.rssPath,
- 'aria-label': IssuesListApp.i18n.rssLabel,
+ expect(findDropdown().props()).toMatchObject({
+ category: 'tertiary',
+ icon: 'ellipsis_v',
+ text: 'Actions',
+ textSrOnly: true,
+ });
});
- });
- it('renders calendar button', async () => {
- wrapper = mountComponent({ mountFn: mount });
- await waitForPromises();
+ describe('csv import/export buttons', () => {
+ describe('when user is signed in', () => {
+ beforeEach(() => {
+ setWindowLocation('?search=refactor&state=opened');
- expect(findGlButtonAt(1).props('icon')).toBe('calendar');
- expect(findGlButtonAt(1).attributes()).toMatchObject({
- href: defaultProvide.calendarPath,
- 'aria-label': IssuesListApp.i18n.calendarLabel,
- });
- });
+ wrapper = mountComponent({
+ provide: { initialSortBy: CREATED_DESC, isSignedIn: true },
+ mountFn: mount,
+ });
- describe('csv import/export component', () => {
- describe('when user is signed in', () => {
- beforeEach(() => {
- setWindowLocation('?search=refactor&state=opened');
+ return waitForPromises();
+ });
- wrapper = mountComponent({
- provide: { initialSortBy: CREATED_DESC, isSignedIn: true },
- mountFn: mount,
+ it('renders', () => {
+ expect(findCsvImportExportButtons().props()).toMatchObject({
+ exportCsvPath: `${defaultProvide.exportCsvPath}?search=refactor&state=opened`,
+ issuableCount: 1,
+ });
});
+ });
- return waitForPromises();
+ describe('when user is not signed in', () => {
+ it('does not render', () => {
+ wrapper = mountComponent({ provide: { isSignedIn: false }, mountFn: mount });
+
+ expect(findCsvImportExportButtons().exists()).toBe(false);
+ });
});
- it('renders', () => {
- expect(findCsvImportExportButtons().props()).toMatchObject({
- exportCsvPath: `${defaultProvide.exportCsvPath}?search=refactor&state=opened`,
- issuableCount: 1,
+ describe('when in a group context', () => {
+ it('does not render', () => {
+ wrapper = mountComponent({ provide: { isProject: false }, mountFn: mount });
+
+ expect(findCsvImportExportButtons().exists()).toBe(false);
});
});
});
- describe('when user is not signed in', () => {
- it('does not render', () => {
- wrapper = mountComponent({ provide: { isSignedIn: false }, mountFn: mount });
+ it('renders RSS button link', () => {
+ wrapper = mountComponent({ mountFn: mountExtended });
- expect(findCsvImportExportButtons().exists()).toBe(false);
- });
+ expect(findRssButton().attributes('href')).toBe(defaultProvide.rssPath);
});
- describe('when in a group context', () => {
- it('does not render', () => {
- wrapper = mountComponent({ provide: { isProject: false }, mountFn: mount });
+ it('renders calendar button link', () => {
+ wrapper = mountComponent({ mountFn: mountExtended });
- expect(findCsvImportExportButtons().exists()).toBe(false);
- });
+ expect(findCalendarButton().attributes('href')).toBe(defaultProvide.calendarPath);
});
});
@@ -296,7 +304,7 @@ describe('CE IssuesListApp component', () => {
it('renders when user has permissions', () => {
wrapper = mountComponent({ provide: { canBulkUpdate: true }, mountFn: mount });
- expect(findGlButtonAt(2).text()).toBe('Edit issues');
+ expect(findGlButton().text()).toBe('Edit issues');
});
it('does not render when user does not have permissions', () => {
@@ -309,7 +317,7 @@ describe('CE IssuesListApp component', () => {
wrapper = mountComponent({ provide: { canBulkUpdate: true }, mountFn: mount });
jest.spyOn(eventHub, '$emit');
- findGlButtonAt(2).vm.$emit('click');
+ findGlButton().vm.$emit('click');
await waitForPromises();
expect(eventHub.$emit).toHaveBeenCalledWith('issuables:enableBulkEdit');
@@ -320,8 +328,8 @@ describe('CE IssuesListApp component', () => {
it('renders when user has permissions', () => {
wrapper = mountComponent({ provide: { showNewIssueLink: true }, mountFn: mount });
- expect(findGlButtonAt(2).text()).toBe('New issue');
- expect(findGlButtonAt(2).attributes('href')).toBe(defaultProvide.newIssuePath);
+ expect(findGlButton().text()).toBe('New issue');
+ expect(findGlButton().attributes('href')).toBe(defaultProvide.newIssuePath);
});
it('does not render when user does not have permissions', () => {
diff --git a/spec/frontend/issues/show/components/task_list_item_actions_spec.js b/spec/frontend/issues/show/components/task_list_item_actions_spec.js
index 8caa5236796..7dacbefaeff 100644
--- a/spec/frontend/issues/show/components/task_list_item_actions_spec.js
+++ b/spec/frontend/issues/show/components/task_list_item_actions_spec.js
@@ -1,4 +1,4 @@
-import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
+import { GlDisclosureDropdown, GlDisclosureDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import TaskListItemActions from '~/issues/show/components/task_list_item_actions.vue';
import eventHub from '~/issues/show/event_hub';
@@ -6,9 +6,9 @@ import eventHub from '~/issues/show/event_hub';
describe('TaskListItemActions component', () => {
let wrapper;
- const findGlDropdown = () => wrapper.findComponent(GlDropdown);
- const findConvertToTaskItem = () => wrapper.findAllComponents(GlDropdownItem).at(0);
- const findDeleteItem = () => wrapper.findAllComponents(GlDropdownItem).at(1);
+ const findGlDropdown = () => wrapper.findComponent(GlDisclosureDropdown);
+ const findConvertToTaskItem = () => wrapper.findAllComponents(GlDisclosureDropdownItem).at(0);
+ const findDeleteItem = () => wrapper.findAllComponents(GlDisclosureDropdownItem).at(1);
const mountComponent = () => {
const li = document.createElement('li');
@@ -20,6 +20,7 @@ describe('TaskListItemActions component', () => {
provide: { canUpdate: true },
attachTo: document.querySelector('div'),
});
+ wrapper.vm.$refs.dropdown.close = jest.fn();
};
beforeEach(() => {
@@ -30,8 +31,8 @@ describe('TaskListItemActions component', () => {
expect(findGlDropdown().props()).toMatchObject({
category: 'tertiary',
icon: 'ellipsis_v',
- right: true,
- text: TaskListItemActions.i18n.taskActions,
+ placement: 'right',
+ toggleText: TaskListItemActions.i18n.taskActions,
textSrOnly: true,
});
});
@@ -39,7 +40,7 @@ describe('TaskListItemActions component', () => {
it('emits event when `Convert to task` dropdown item is clicked', () => {
jest.spyOn(eventHub, '$emit');
- findConvertToTaskItem().vm.$emit('click');
+ findConvertToTaskItem().vm.$emit('action');
expect(eventHub.$emit).toHaveBeenCalledWith('convert-task-list-item', '3:1-3:10');
});
@@ -47,7 +48,7 @@ describe('TaskListItemActions component', () => {
it('emits event when `Delete` dropdown item is clicked', () => {
jest.spyOn(eventHub, '$emit');
- findDeleteItem().vm.$emit('click');
+ findDeleteItem().vm.$emit('action');
expect(eventHub.$emit).toHaveBeenCalledWith('delete-task-list-item', '3:1-3:10');
});
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/pages/list_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/pages/list_spec.js
index 5c353deacbb..1823bbfe533 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/pages/list_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/pages/list_spec.js
@@ -1,8 +1,8 @@
-import { GlSkeletonLoader, GlSprintf, GlAlert } from '@gitlab/ui';
+import { GlSkeletonLoader, GlSprintf, GlAlert, GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
-
import VueApollo from 'vue-apollo';
+import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import getContainerRepositoriesQuery from 'shared_queries/container_registry/get_container_repositories.query.graphql';
@@ -16,6 +16,7 @@ import {
DELETE_IMAGE_SUCCESS_MESSAGE,
DELETE_IMAGE_ERROR_MESSAGE,
SORT_FIELDS,
+ SETTINGS_TEXT,
} from '~/packages_and_registries/container_registry/explorer/constants';
import deleteContainerRepositoryMutation from '~/packages_and_registries/container_registry/explorer/graphql/mutations/delete_container_repository.mutation.graphql';
import getContainerRepositoriesDetails from '~/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repositories_details.query.graphql';
@@ -48,6 +49,7 @@ describe('List Page', () => {
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
const findCliCommands = () => wrapper.findComponent(CliCommands);
+ const findSettingsLink = () => wrapper.findComponent(GlButton);
const findProjectEmptyState = () => wrapper.findComponent(ProjectEmptyState);
const findGroupEmptyState = () => wrapper.findComponent(GroupEmptyState);
const findRegistryHeader = () => wrapper.findComponent(RegistryHeader);
@@ -110,6 +112,9 @@ describe('List Page', () => {
...dockerCommands,
};
},
+ directives: {
+ GlTooltip: createMockDirective('gl-tooltip'),
+ },
});
};
@@ -122,6 +127,42 @@ describe('List Page', () => {
expect(findRegistryHeader().props()).toMatchObject({
imagesCount: 2,
metadataLoading: false,
+ helpPagePath: '',
+ hideExpirationPolicyData: false,
+ showCleanupPolicyLink: false,
+ expirationPolicy: {},
+ cleanupPoliciesSettingsPath: '',
+ });
+ });
+
+ describe('link to settings', () => {
+ beforeEach(() => {
+ const config = {
+ showContainerRegistrySettings: true,
+ cleanupPoliciesSettingsPath: 'bar',
+ };
+ mountComponent({ config });
+ });
+
+ it('is rendered', () => {
+ expect(findSettingsLink().exists()).toBe(true);
+ });
+
+ it('has the right icon', () => {
+ expect(findSettingsLink().props('icon')).toBe('settings');
+ });
+
+ it('has the right attributes', () => {
+ expect(findSettingsLink().attributes()).toMatchObject({
+ 'aria-label': SETTINGS_TEXT,
+ href: 'bar',
+ });
+ });
+
+ it('sets tooltip with right label', () => {
+ const tooltip = getBinding(findSettingsLink().element, 'gl-tooltip');
+
+ expect(tooltip.value).toBe(SETTINGS_TEXT);
});
});
@@ -235,6 +276,14 @@ describe('List Page', () => {
expect(findCliCommands().exists()).toBe(false);
});
+
+ it('link to settings is not visible', async () => {
+ mountComponent({ resolver, config });
+
+ await waitForApolloRequestRender();
+
+ expect(findSettingsLink().exists()).toBe(false);
+ });
});
});
diff --git a/spec/frontend/vue_merge_request_widget/components/approvals/approvals_summary_spec.js b/spec/frontend/vue_merge_request_widget/components/approvals/approvals_summary_spec.js
index 8c6b3cc464c..2c8d8b11b94 100644
--- a/spec/frontend/vue_merge_request_widget/components/approvals/approvals_summary_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/approvals/approvals_summary_spec.js
@@ -105,4 +105,31 @@ describe('MRWidget approvals summary', () => {
expect(wrapper.findComponent(UserAvatarList).exists()).toBe(false);
});
});
+
+ describe('user avatars list layout', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('does not add top padding initially', () => {
+ const avatarsList = findAvatars();
+
+ expect(avatarsList.classes()).not.toContain('gl-pt-1');
+ });
+
+ it('adds some top padding when the list is expanded', async () => {
+ const avatarsList = findAvatars();
+ await avatarsList.vm.$emit('expanded');
+
+ expect(avatarsList.classes()).toContain('gl-pt-1');
+ });
+
+ it('removes the top padding when the list collapsed', async () => {
+ const avatarsList = findAvatars();
+ await avatarsList.vm.$emit('expanded');
+ await avatarsList.vm.$emit('collapsed');
+
+ expect(avatarsList.classes()).not.toContain('gl-pt-1');
+ });
+ });
});
diff --git a/spec/frontend/vue_shared/components/user_avatar/user_avatar_list_spec.js b/spec/frontend/vue_shared/components/user_avatar/user_avatar_list_spec.js
index 1754292cb63..075cb753301 100644
--- a/spec/frontend/vue_shared/components/user_avatar/user_avatar_list_spec.js
+++ b/spec/frontend/vue_shared/components/user_avatar/user_avatar_list_spec.js
@@ -148,6 +148,13 @@ describe('UserAvatarList', () => {
expect(links.length).toEqual(TEST_BREAKPOINT);
});
+ it('does not emit any event on mount', async () => {
+ factory();
+ await nextTick();
+
+ expect(wrapper.emitted()).toEqual({});
+ });
+
describe('with expand clicked', () => {
beforeEach(() => {
factory();
@@ -160,13 +167,25 @@ describe('UserAvatarList', () => {
expect(links.length).toEqual(props.items.length);
});
- it('with collapse clicked, it renders avatars up to breakpoint', async () => {
- clickButton();
+ it('emits the `expanded` event', () => {
+ expect(wrapper.emitted('expanded')).toHaveLength(1);
+ });
- await nextTick();
- const links = wrapper.findAllComponents(UserAvatarLink);
+ describe('with collapse clicked', () => {
+ beforeEach(() => {
+ clickButton();
+ });
+
+ it('renders avatars up to breakpoint', async () => {
+ await nextTick();
+ const links = wrapper.findAllComponents(UserAvatarLink);
+
+ expect(links.length).toEqual(TEST_BREAKPOINT);
+ });
- expect(links.length).toEqual(TEST_BREAKPOINT);
+ it('emits the `collapsed` event', () => {
+ expect(wrapper.emitted('collapsed')).toHaveLength(1);
+ });
});
});
});
diff --git a/spec/helpers/packages_helper_spec.rb b/spec/helpers/packages_helper_spec.rb
index dcc5e336253..ae8a7f0c14c 100644
--- a/spec/helpers/packages_helper_spec.rb
+++ b/spec/helpers/packages_helper_spec.rb
@@ -129,6 +129,62 @@ RSpec.describe PackagesHelper, feature_category: :package_registry do
end
end
+ describe '#show_container_registry_settings' do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:admin) { create(:admin) }
+
+ before do
+ allow(helper).to receive(:current_user) { user }
+ end
+
+ subject { helper.show_container_registry_settings(project) }
+
+ context 'with container registry config enabled' do
+ before do
+ stub_config(registry: { enabled: true })
+ end
+
+ context 'when user has permission' do
+ before do
+ allow(Ability).to receive(:allowed?).with(user, :admin_container_image, project).and_return(true)
+ end
+
+ it { is_expected.to be(true) }
+ end
+
+ context 'when user does not have permission' do
+ before do
+ allow(Ability).to receive(:allowed?).with(user, :admin_container_image, project).and_return(false)
+ end
+
+ it { is_expected.to be(false) }
+ end
+ end
+
+ context 'with container registry config disabled' do
+ before do
+ stub_config(registry: { enabled: false })
+ end
+
+ context 'when user has permission' do
+ before do
+ allow(Ability).to receive(:allowed?).with(user, :admin_container_image, project).and_return(true)
+ end
+
+ it { is_expected.to be(false) }
+ end
+
+ context 'when user does not have permission' do
+ before do
+ allow(Ability).to receive(:allowed?).with(user, :admin_container_image, project).and_return(false)
+ end
+
+ it { is_expected.to be(false) }
+ end
+ end
+ end
+
describe '#show_group_package_registry_settings' do
let_it_be(:group) { create(:group) }
let_it_be(:user) { create(:user) }
diff --git a/spec/models/protected_tag/create_access_level_spec.rb b/spec/models/protected_tag/create_access_level_spec.rb
index 566f8695388..77a7d15b597 100644
--- a/spec/models/protected_tag/create_access_level_spec.rb
+++ b/spec/models/protected_tag/create_access_level_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe ProtectedTag::CreateAccessLevel, feature_category: :source_code_management do
+ include_examples 'protected tag access'
+
describe 'associations' do
it { is_expected.to belong_to(:deploy_key) }
end
@@ -10,16 +12,6 @@ RSpec.describe ProtectedTag::CreateAccessLevel, feature_category: :source_code_m
describe 'validations', :aggregate_failures do
let_it_be(:protected_tag) { create(:protected_tag) }
- it 'verifies access levels' do
- is_expected.to validate_inclusion_of(:access_level).in_array(
- [
- Gitlab::Access::MAINTAINER,
- Gitlab::Access::DEVELOPER,
- Gitlab::Access::NO_ACCESS
- ]
- )
- end
-
context 'when deploy key enabled for the project' do
let(:deploy_key) { create(:deploy_key, projects: [protected_tag.project]) }
diff --git a/spec/support/shared_examples/features/rss_shared_examples.rb b/spec/support/shared_examples/features/rss_shared_examples.rb
index 29ecbd0dc0e..f6566214e32 100644
--- a/spec/support/shared_examples/features/rss_shared_examples.rb
+++ b/spec/support/shared_examples/features/rss_shared_examples.rb
@@ -43,6 +43,7 @@ RSpec.shared_examples "updates atom feed link" do |type|
it "for #{type}" do
sign_in(user)
visit path
+ click_button 'Actions', match: :first
link = find_link('Subscribe to RSS feed')
params = CGI.parse(URI.parse(link[:href]).query)
diff --git a/spec/support/shared_examples/models/concerns/protected_ref_access_examples.rb b/spec/support/shared_examples/models/concerns/protected_ref_access_examples.rb
new file mode 100644
index 00000000000..53ebc207128
--- /dev/null
+++ b/spec/support/shared_examples/models/concerns/protected_ref_access_examples.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'protected ref access' do |association|
+ let_it_be(:project) { create(:project) }
+ let_it_be(:protected_ref) { create(association, project: project) } # rubocop:disable Rails/SaveBang
+
+ it { is_expected.to validate_inclusion_of(:access_level).in_array(described_class.allowed_access_levels) }
+
+ it { is_expected.to validate_presence_of(:access_level) }
+
+ context 'when not role?' do
+ before do
+ allow(subject).to receive(:role?).and_return(false)
+ end
+
+ it { is_expected.not_to validate_presence_of(:access_level) }
+ end
+
+ describe '#check_access' do
+ let_it_be(:current_user) { create(:user) }
+
+ let(:access_level) { ::Gitlab::Access::DEVELOPER }
+
+ before_all do
+ project.add_maintainer(current_user)
+ end
+
+ subject do
+ described_class.new(
+ association => protected_ref,
+ access_level: access_level
+ )
+ end
+
+ context 'when current_user is nil' do
+ it { expect(subject.check_access(nil)).to eq(false) }
+ end
+
+ context 'when access_level is NO_ACCESS' do
+ let(:access_level) { ::Gitlab::Access::NO_ACCESS }
+
+ it { expect(subject.check_access(current_user)).to eq(false) }
+ end
+
+ context 'when current_user can push_code to project and access_level is permitted' do
+ before do
+ allow(current_user).to receive(:can?).with(:push_code, project).and_return(true)
+ end
+
+ it { expect(subject.check_access(current_user)).to eq(true) }
+ end
+
+ context 'when current_user cannot push_code to project' do
+ before do
+ allow(current_user).to receive(:can?).with(:push_code, project).and_return(false)
+ end
+
+ it { expect(subject.check_access(current_user)).to eq(false) }
+ end
+ end
+end
diff --git a/spec/support/shared_examples/models/concerns/protected_tag_access_examples.rb b/spec/support/shared_examples/models/concerns/protected_tag_access_examples.rb
new file mode 100644
index 00000000000..49f616d5a59
--- /dev/null
+++ b/spec/support/shared_examples/models/concerns/protected_tag_access_examples.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'protected tag access' do
+ include_examples 'protected ref access', :protected_tag
+
+ let_it_be(:protected_tag) { create(:protected_tag) }
+
+ it { is_expected.to belong_to(:protected_tag) }
+
+ describe '#project' do
+ before do
+ allow(protected_tag).to receive(:project)
+ end
+
+ it 'delegates project to protected_tag association' do
+ described_class.new(protected_tag: protected_tag).project
+
+ expect(protected_tag).to have_received(:project)
+ end
+ end
+end