Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-05-10 09:10:28 +00:00
parent 9d485c177e
commit d5f14b5e2c
27 changed files with 349 additions and 238 deletions

View file

@ -1 +1 @@
6941c499e077fe2303dd5c31a08807d14ad7a616
529ef59e73a21d1abc54833c4edbe92cbcc9fb64

View file

@ -40,11 +40,6 @@ import issueSetMilestoneMutation from '../graphql/issue_set_milestone.mutation.g
import listsIssuesQuery from '../graphql/lists_issues.query.graphql';
import * as types from './mutation_types';
const notImplemented = () => {
/* eslint-disable-next-line @gitlab/require-i18n-strings */
throw new Error('Not implemented!');
};
export const gqlClient = createGqClient(
{},
{
@ -737,28 +732,4 @@ export default {
unsetError: ({ commit }) => {
commit(types.SET_ERROR, undefined);
},
fetchBacklog: () => {
notImplemented();
},
bulkUpdateIssues: () => {
notImplemented();
},
fetchIssue: () => {
notImplemented();
},
toggleIssueSubscription: () => {
notImplemented();
},
showPage: () => {
notImplemented();
},
toggleEmptyState: () => {
notImplemented();
},
};

View file

@ -9,9 +9,7 @@ export const GENERATE_DEFAULT_LISTS_FAILURE = 'GENERATE_DEFAULT_LISTS_FAILURE';
export const RECEIVE_BOARD_LISTS_SUCCESS = 'RECEIVE_BOARD_LISTS_SUCCESS';
export const RECEIVE_BOARD_LISTS_FAILURE = 'RECEIVE_BOARD_LISTS_FAILURE';
export const SHOW_PROMOTION_LIST = 'SHOW_PROMOTION_LIST';
export const REQUEST_ADD_LIST = 'REQUEST_ADD_LIST';
export const RECEIVE_ADD_LIST_SUCCESS = 'RECEIVE_ADD_LIST_SUCCESS';
export const RECEIVE_ADD_LIST_ERROR = 'RECEIVE_ADD_LIST_ERROR';
export const MOVE_LIST = 'MOVE_LIST';
export const UPDATE_LIST_FAILURE = 'UPDATE_LIST_FAILURE';
export const TOGGLE_LIST_COLLAPSED = 'TOGGLE_LIST_COLLAPSED';
@ -20,19 +18,11 @@ export const REMOVE_LIST_FAILURE = 'REMOVE_LIST_FAILURE';
export const REQUEST_ITEMS_FOR_LIST = 'REQUEST_ITEMS_FOR_LIST';
export const RECEIVE_ITEMS_FOR_LIST_FAILURE = 'RECEIVE_ITEMS_FOR_LIST_FAILURE';
export const RECEIVE_ITEMS_FOR_LIST_SUCCESS = 'RECEIVE_ITEMS_FOR_LIST_SUCCESS';
export const REQUEST_ADD_ISSUE = 'REQUEST_ADD_ISSUE';
export const RECEIVE_ADD_ISSUE_SUCCESS = 'RECEIVE_ADD_ISSUE_SUCCESS';
export const RECEIVE_ADD_ISSUE_ERROR = 'RECEIVE_ADD_ISSUE_ERROR';
export const UPDATE_BOARD_ITEM = 'UPDATE_BOARD_ITEM';
export const REMOVE_BOARD_ITEM = 'REMOVE_BOARD_ITEM';
export const REQUEST_UPDATE_ISSUE = 'REQUEST_UPDATE_ISSUE';
export const MUTATE_ISSUE_SUCCESS = 'MUTATE_ISSUE_SUCCESS';
export const RECEIVE_UPDATE_ISSUE_SUCCESS = 'RECEIVE_UPDATE_ISSUE_SUCCESS';
export const RECEIVE_UPDATE_ISSUE_ERROR = 'RECEIVE_UPDATE_ISSUE_ERROR';
export const ADD_BOARD_ITEM_TO_LIST = 'ADD_BOARD_ITEM_TO_LIST';
export const REMOVE_BOARD_ITEM_FROM_LIST = 'REMOVE_BOARD_ITEM_FROM_LIST';
export const SET_CURRENT_PAGE = 'SET_CURRENT_PAGE';
export const TOGGLE_EMPTY_STATE = 'TOGGLE_EMPTY_STATE';
export const SET_ACTIVE_ID = 'SET_ACTIVE_ID';
export const UPDATE_BOARD_ITEM_BY_ID = 'UPDATE_BOARD_ITEM_BY_ID';
export const SET_ASSIGNEE_LOADING = 'SET_ASSIGNEE_LOADING';

View file

@ -6,11 +6,6 @@ import { formatIssue } from '../boards_util';
import { issuableTypes } from '../constants';
import * as mutationTypes from './mutation_types';
const notImplemented = () => {
/* eslint-disable-next-line @gitlab/require-i18n-strings */
throw new Error('Not implemented!');
};
const updateListItemsCount = ({ state, listId, value }) => {
const list = state.boardLists[listId];
if (state.issuableType === issuableTypes.epic) {
@ -94,18 +89,10 @@ export default {
state.error = s__('Boards|An error occurred while generating lists. Please reload the page.');
},
[mutationTypes.REQUEST_ADD_LIST]: () => {
notImplemented();
},
[mutationTypes.RECEIVE_ADD_LIST_SUCCESS]: (state, list) => {
Vue.set(state.boardLists, list.id, list);
},
[mutationTypes.RECEIVE_ADD_LIST_ERROR]: () => {
notImplemented();
},
[mutationTypes.MOVE_LIST]: (state, { movedList, listAtNewIndex }) => {
const { boardLists } = state;
Vue.set(boardLists, movedList.id, movedList);
@ -172,35 +159,11 @@ export default {
state.isSettingAssignees = isLoading;
},
[mutationTypes.REQUEST_ADD_ISSUE]: () => {
notImplemented();
},
[mutationTypes.RECEIVE_ADD_ISSUE_SUCCESS]: () => {
notImplemented();
},
[mutationTypes.RECEIVE_ADD_ISSUE_ERROR]: () => {
notImplemented();
},
[mutationTypes.MUTATE_ISSUE_SUCCESS]: (state, { issue }) => {
const issueId = getIdFromGraphQLId(issue.id);
Vue.set(state.boardItems, issueId, formatIssue({ ...issue, id: issueId }));
},
[mutationTypes.REQUEST_UPDATE_ISSUE]: () => {
notImplemented();
},
[mutationTypes.RECEIVE_UPDATE_ISSUE_SUCCESS]: () => {
notImplemented();
},
[mutationTypes.RECEIVE_UPDATE_ISSUE_ERROR]: () => {
notImplemented();
},
[mutationTypes.ADD_BOARD_ITEM_TO_LIST]: (
state,
{ itemId, listId, moveBeforeId, moveAfterId, atIndex },
@ -220,14 +183,6 @@ export default {
Vue.delete(state.boardItems, itemId);
},
[mutationTypes.SET_CURRENT_PAGE]: () => {
notImplemented();
},
[mutationTypes.TOGGLE_EMPTY_STATE]: () => {
notImplemented();
},
[mutationTypes.REQUEST_GROUP_PROJECTS]: (state, fetchNext) => {
Vue.set(state, 'groupProjectsFlags', {
[fetchNext ? 'isLoadingMore' : 'isLoading']: true,

View file

@ -33,6 +33,10 @@ const focusFirstInvalidInput = (e) => {
}
};
const getInputElement = (el) => {
return el.querySelector('input') || el;
};
const isEveryFieldValid = (form) => Object.values(form.fields).every(({ state }) => state === true);
const createValidator = (context, feedbackMap) => ({ el, reportInvalidInput = false }) => {
@ -91,8 +95,9 @@ export default function initValidation(customFeedbackMap = {}) {
const elDataMap = new WeakMap();
return {
inserted(el, binding, { context }) {
inserted(element, binding, { context }) {
const { arg: showGlobalValidation } = binding;
const el = getInputElement(element);
const { form: formEl } = el;
const validate = createValidator(context, feedbackMap);
@ -121,7 +126,8 @@ export default function initValidation(customFeedbackMap = {}) {
validate({ el, reportInvalidInput: showGlobalValidation });
},
update(el, binding) {
update(element, binding) {
const el = getInputElement(element);
const { arg: showGlobalValidation } = binding;
const { validate, isTouched, isBlurred } = elDataMap.get(el);
const showValidationFeedback = showGlobalValidation || (isTouched && isBlurred);

View file

@ -103,7 +103,7 @@ module Issuable
end
scope :assigned_to, ->(u) do
assignees_table = Arel::Table.new("#{to_ability_name}_assignees")
sql = assignees_table.project('true').where(assignees_table[:user_id].in(u)).where(Arel::Nodes::SqlLiteral.new("#{to_ability_name}_id = #{to_ability_name}s.id"))
sql = assignees_table.project('true').where(assignees_table[:user_id].in(u.id)).where(Arel::Nodes::SqlLiteral.new("#{to_ability_name}_id = #{to_ability_name}s.id"))
where("EXISTS (#{sql.to_sql})")
end
# rubocop:enable GitlabSecurity/SqlInjection

View file

@ -381,7 +381,7 @@ class MergeRequest < ApplicationRecord
scope :review_requested_to, ->(user) do
where(
reviewers_subquery
.where(Arel::Table.new("#{to_ability_name}_reviewers")[:user_id].eq(user))
.where(Arel::Table.new("#{to_ability_name}_reviewers")[:user_id].eq(user.id))
.exists
)
end
@ -389,7 +389,7 @@ class MergeRequest < ApplicationRecord
scope :no_review_requested_to, ->(user) do
where(
reviewers_subquery
.where(Arel::Table.new("#{to_ability_name}_reviewers")[:user_id].eq(user))
.where(Arel::Table.new("#{to_ability_name}_reviewers")[:user_id].eq(user.id))
.exists
.not
)

View file

@ -1,34 +0,0 @@
-# Shortcut to Project > Activity
%li.hidden
= link_to activity_project_path(@project), title: _('Activity'), class: 'shortcuts-project-activity' do
%span
= _('Activity')
-# Shortcut to Repository > Graph (formerly, Network)
- if project_nav_tab? :network
%li.hidden
= link_to project_network_path(@project, current_ref), title: _('Network'), class: 'shortcuts-network' do
= _('Graph')
-# Shortcut to Issues > New Issue
- if project_nav_tab?(:issues)
%li.hidden
= link_to new_project_issue_path(@project), class: 'shortcuts-new-issue' do
= _('Create a new issue')
-# Shortcut to Pipelines > Jobs
- if project_nav_tab? :builds
%li.hidden
= link_to project_jobs_path(@project), title: _('Jobs'), class: 'shortcuts-builds' do
= _('Jobs')
-# Shortcut to commits page
- if project_nav_tab? :commits
%li.hidden
= link_to project_commits_path(@project), title: _('Commits'), class: 'shortcuts-commits' do
= _('Commits')
-# Shortcut to issue boards
- if project_nav_tab?(:issues)
%li.hidden
= link_to _('Issue Boards'), project_boards_path(@project), title: _('Issue Boards'), class: 'shortcuts-issue-boards'

View file

@ -11,4 +11,5 @@
- if sidebar.render_raw_menus_partial
= render sidebar.render_raw_menus_partial
= render partial: 'shared/nav/sidebar_hidden_menu_item', collection: sidebar.hidden_menu&.items
= render 'shared/sidebar_toggle_button'

View file

@ -0,0 +1,3 @@
%li.hidden
= link_to sidebar_hidden_menu_item.link, **sidebar_hidden_menu_item.container_html_options do
= sidebar_hidden_menu_item.title

View file

@ -9,6 +9,8 @@ class RemoveDuplicateLabelsFromProject < ActiveRecord::Migration[6.0]
disable_ddl_transaction!
class BackupLabel < Label
self.inheritance_column = :_type_disabled
self.table_name = 'backup_labels'
end

View file

@ -537,7 +537,7 @@ inactive_users.each do |user|
end
```
### Find Max permissions for project/group
### Find a user's max permissions for project/group
```ruby
user = User.find_by_username 'username'

View file

@ -190,7 +190,7 @@ GET /users
]
```
Users on GitLab [Premium or higher](https://about.gitlab.com/pricing/) also see the `shared_runners_minutes_limit`, `extra_shared_runners_minutes_limit`, and `using_license_seat` parameters.
Users on GitLab [Premium or higher](https://about.gitlab.com/pricing/) also see the `shared_runners_minutes_limit`, `extra_shared_runners_minutes_limit`, `is_auditor`, and `using_license_seat` parameters.
```json
[
@ -199,6 +199,7 @@ Users on GitLab [Premium or higher](https://about.gitlab.com/pricing/) also see
...
"shared_runners_minutes_limit": 133,
"extra_shared_runners_minutes_limit": 133,
"is_auditor": false,
"using_license_seat": true
...
}
@ -359,12 +360,13 @@ NOTE:
The `plan` and `trial` parameters are only available on GitLab Enterprise Edition.
Users on GitLab [Premium or higher](https://about.gitlab.com/pricing/) also see
the `shared_runners_minutes_limit`, and `extra_shared_runners_minutes_limit` parameters.
the `shared_runners_minutes_limit`, `is_auditor`, and `extra_shared_runners_minutes_limit` parameters.
```json
{
"id": 1,
"username": "john_smith",
"is_auditor": false,
"shared_runners_minutes_limit": 133,
"extra_shared_runners_minutes_limit": 133,
...
@ -628,6 +630,8 @@ GET /user
}
```
Users on GitLab [Premium or higher](https://about.gitlab.com/pricing/) also see the `shared_runners_minutes_limit`, `extra_shared_runners_minutes_limit`, `is_auditor`, and `using_license_seat` parameters.
## User status
Get the status of the currently signed in user.

View file

@ -52,11 +52,9 @@ the `author` field. GitLab team members **should not**.
a changelog entry regardless of these guidelines if the contributor wants one.
Example: "Fixed a typo on the search results page."
- Any docs-only changes **should not** have a changelog entry.
- Any change behind a feature flag **disabled** by default **should not** have a changelog entry.
- Any change behind a feature flag that is **enabled** by default **should** have a changelog entry.
- For changes related to feature flags, use [feature flag guide](feature_flags/index.md#changelog) to determine the changelog entry.
- Any change that adds new Usage Data metrics, sets the status of existing ones to `removed`, and changes that need to be documented in Product Intelligence [Metrics Dictionary](usage_ping/dictionary.md) **should** have a changelog entry.
- A change that adds snowplow events **should** have a changelog entry -
- A change that [removes a feature flag, or removes a feature and its feature flag](feature_flags/index.md) **must** have a changelog entry.
- A fix for a regression introduced and then fixed in the same release (i.e.,
fixing a bug introduced during a monthly release candidate) **should not**
have a changelog entry.

View file

@ -15,12 +15,6 @@ This document provides guidelines on how to use feature flags
in the GitLab codebase to conditionally enable features
and test them.
Features that are developed and merged behind a feature flag
should not include a changelog entry. A changelog entry with `type: added` should be included in the merge
request removing the feature flag or the merge request where the default value of
the feature flag is set to enabled. If the feature contains any database migrations, it
*should* include a changelog entry for the database changes.
WARNING:
All newly-introduced feature flags should be [disabled by default](https://about.gitlab.com/handbook/product-development-flow/feature-flag-lifecycle/#feature-flags-in-gitlab-development).
@ -55,7 +49,7 @@ should be leveraged:
a specific project and ensure that there are no issues with the implementation.
1. When the feature is ready to be announced, create a merge request that adds
documentation about the feature, including [documentation for the feature flag itself](../documentation/feature_flags.md),
and a changelog entry. In the same merge request either flip the feature flag to
and a [changelog entry](#changelog). In the same merge request either flip the feature flag to
be **on by default** or remove it entirely in order to enable the new behavior.
One might be tempted to think that feature flags will delay the release of a
@ -461,6 +455,24 @@ as follows:
Feature.remove(:feature_flag_name)
```
## Changelog
- Any change behind a feature flag **disabled** by default **should not** have a changelog entry.
- **Exception:** database migrations **should** have a changelog entry.
- Any change related to a feature flag itself (flag removal, default-on setting) **should** have a changelog entry.
Use the flowchart to determine the changelog entry type.
```mermaid
graph LR
A[flag: default off] -->|'added' / 'changed'| B(flag: default on)
B -->|'other'| C(remove flag, keep new code)
B -->|'removed' / 'changed'| D(remove flag, keep old code)
A -->|'added' / 'changed'| C
A -->|no changelog| D
```
- Any change behind a feature flag that is **enabled** by default **should** have a changelog entry.
## Feature flags in tests
Introducing a feature flag into the codebase creates an additional code path that should be tested.

View file

@ -569,7 +569,10 @@ To get you started quickly, GitLab provides the configuration file
[`gitlab-api-fuzzing-config.yml`](https://gitlab.com/gitlab-org/security-products/analyzers/api-fuzzing/-/blob/master/gitlab-api-fuzzing-config.yml).
This file has several testing profiles that perform various numbers of tests. The run time of each
profile increases as the test numbers go up. To use a configuration file, add it to your
repository's root as `.gitlab-api-fuzzing.yml`.
repository as `.gitlab/gitlab-api-fuzzing-config.yml`.
NOTE:
+In GitLab 13.11 and earlier, the configuration file was `.gitlab-api-fuzzing.yml` in the repository's root. In GitLab 13.12 and later, it is `.gitlab/gitlab-api-fuzzing-config.yml` in the repository's root.
| Profile | Fuzz Tests (per parameter) |
|:---------|:-----------|

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 68 KiB

View file

@ -150,10 +150,8 @@ the following:
![Security Center Dashboard with projects](img/security_center_dashboard_v13_4.png)
You can access the Security Center from the menu
bar at the top of the page. Under **More**, select **Security**.
![Security Center navigation link](img/security_center_dashboard_link_v12_4.png)
To view the Security Center, from the navigation bar at the top of the page, select
**More > Security**.
### Adding projects to the Security Center

View file

@ -0,0 +1,95 @@
# frozen_string_literal: true
module Sidebars
module Projects
module Menus
class HiddenMenu < ::Sidebars::Menu
override :configure_menu_items
def configure_menu_items
add_item(activity_menu_item)
add_item(graph_menu_item)
add_item(new_issue_menu_item)
add_item(jobs_menu_item)
add_item(commits_menu_item)
add_item(issue_boards_menu_item)
true
end
private
def activity_menu_item
::Sidebars::MenuItem.new(
title: _('Activity'),
link: activity_project_path(context.project),
active_routes: {},
container_html_options: { class: 'shortcuts-project-activity' },
item_id: :activity
)
end
def graph_menu_item
return unless can?(context.current_user, :download_code, context.project)
return if context.project.empty_repo?
::Sidebars::MenuItem.new(
title: _('Graph'),
link: project_network_path(context.project, context.current_ref),
active_routes: {},
container_html_options: { class: 'shortcuts-network' },
item_id: :graph
)
end
def new_issue_menu_item
return unless can?(context.current_user, :read_issue, context.project)
::Sidebars::MenuItem.new(
title: _('Create a new issue'),
link: new_project_issue_path(context.project),
active_routes: {},
container_html_options: { class: 'shortcuts-new-issue' },
item_id: :new_issue
)
end
def jobs_menu_item
return unless can?(context.current_user, :read_build, context.project)
::Sidebars::MenuItem.new(
title: _('Jobs'),
link: project_jobs_path(context.project),
active_routes: {},
container_html_options: { class: 'shortcuts-builds' },
item_id: :jobs
)
end
def commits_menu_item
return unless can?(context.current_user, :download_code, context.project)
return if context.project.empty_repo?
::Sidebars::MenuItem.new(
title: _('Commits'),
link: project_commits_path(context.project),
active_routes: {},
container_html_options: { class: 'shortcuts-commits' },
item_id: :commits
)
end
def issue_boards_menu_item
return unless can?(context.current_user, :read_issue, context.project)
::Sidebars::MenuItem.new(
title: _('Issue Boards'),
link: project_boards_path(context.project),
active_routes: {},
container_html_options: { class: 'shortcuts-issue-boards' },
item_id: :issue_boards
)
end
end
end
end
end

View file

@ -6,6 +6,7 @@ module Sidebars
override :configure_menus
def configure_menus
set_scope_menu(Sidebars::Projects::Menus::ScopeMenu.new(context))
set_hidden_menu(Sidebars::Projects::Menus::HiddenMenu.new(context))
add_menu(Sidebars::Projects::Menus::ProjectInformationMenu.new(context))
add_menu(Sidebars::Projects::Menus::LearnGitlabMenu.new(context))
@ -27,11 +28,6 @@ module Sidebars
add_menu(Sidebars::Projects::Menus::SettingsMenu.new(context))
end
override :render_raw_menus_partial
def render_raw_menus_partial
'layouts/nav/sidebar/project_menus'
end
override :aria_label
def aria_label
_('Project navigation')

View file

@ -10199,9 +10199,6 @@ msgstr ""
msgid "DastProfiles|Password form field"
msgstr ""
msgid "DastProfiles|Please enter a valid timeout value"
msgstr ""
msgid "DastProfiles|Profile name"
msgstr ""

View file

@ -40,12 +40,6 @@ import {
jest.mock('~/flash');
const expectNotImplemented = (action) => {
it('is not implemented', () => {
expect(action).toThrow(new Error('Not implemented!'));
});
};
// We need this helper to make sure projectPath is including
// subgroups when the movIssue action is called.
const getProjectPath = (path) => path.split('#')[0];
@ -1825,27 +1819,3 @@ describe('unsetError', () => {
});
});
});
describe('fetchBacklog', () => {
expectNotImplemented(actions.fetchBacklog);
});
describe('bulkUpdateIssues', () => {
expectNotImplemented(actions.bulkUpdateIssues);
});
describe('fetchIssue', () => {
expectNotImplemented(actions.fetchIssue);
});
describe('toggleIssueSubscription', () => {
expectNotImplemented(actions.toggleIssueSubscription);
});
describe('showPage', () => {
expectNotImplemented(actions.showPage);
});
describe('toggleEmptyState', () => {
expectNotImplemented(actions.toggleEmptyState);
});

View file

@ -13,12 +13,6 @@ import {
mockList,
} from '../mock_data';
const expectNotImplemented = (action) => {
it('is not implemented', () => {
expect(action).toThrow(new Error('Not implemented!'));
});
};
describe('Board Store Mutations', () => {
let state;
@ -158,10 +152,6 @@ describe('Board Store Mutations', () => {
});
});
describe('REQUEST_ADD_LIST', () => {
expectNotImplemented(mutations.REQUEST_ADD_LIST);
});
describe('RECEIVE_ADD_LIST_SUCCESS', () => {
it('adds list to boardLists state', () => {
mutations.RECEIVE_ADD_LIST_SUCCESS(state, mockLists[0]);
@ -172,10 +162,6 @@ describe('Board Store Mutations', () => {
});
});
describe('RECEIVE_ADD_LIST_ERROR', () => {
expectNotImplemented(mutations.RECEIVE_ADD_LIST_ERROR);
});
describe('MOVE_LIST', () => {
it('updates boardLists state with reordered lists', () => {
state = {
@ -341,10 +327,6 @@ describe('Board Store Mutations', () => {
});
});
describe('REQUEST_ADD_ISSUE', () => {
expectNotImplemented(mutations.REQUEST_ADD_ISSUE);
});
describe('UPDATE_BOARD_ITEM_BY_ID', () => {
const issueId = '1';
const prop = 'id';
@ -386,14 +368,6 @@ describe('Board Store Mutations', () => {
});
});
describe('RECEIVE_ADD_ISSUE_SUCCESS', () => {
expectNotImplemented(mutations.RECEIVE_ADD_ISSUE_SUCCESS);
});
describe('RECEIVE_ADD_ISSUE_ERROR', () => {
expectNotImplemented(mutations.RECEIVE_ADD_ISSUE_ERROR);
});
describe('MUTATE_ISSUE_SUCCESS', () => {
it('updates issue in issues state', () => {
const issues = {
@ -434,18 +408,6 @@ describe('Board Store Mutations', () => {
});
});
describe('REQUEST_UPDATE_ISSUE', () => {
expectNotImplemented(mutations.REQUEST_UPDATE_ISSUE);
});
describe('RECEIVE_UPDATE_ISSUE_SUCCESS', () => {
expectNotImplemented(mutations.RECEIVE_UPDATE_ISSUE_SUCCESS);
});
describe('RECEIVE_UPDATE_ISSUE_ERROR', () => {
expectNotImplemented(mutations.RECEIVE_UPDATE_ISSUE_ERROR);
});
describe('ADD_BOARD_ITEM_TO_LIST', () => {
beforeEach(() => {
setBoardsListsState();
@ -540,14 +502,6 @@ describe('Board Store Mutations', () => {
});
});
describe('SET_CURRENT_PAGE', () => {
expectNotImplemented(mutations.SET_CURRENT_PAGE);
});
describe('TOGGLE_EMPTY_STATE', () => {
expectNotImplemented(mutations.TOGGLE_EMPTY_STATE);
});
describe('REQUEST_GROUP_PROJECTS', () => {
it('Should set isLoading in groupProjectsFlags to true in state when fetchNext is false', () => {
mutations[types.REQUEST_GROUP_PROJECTS](state, false);

View file

@ -4,12 +4,18 @@ import validation from '~/vue_shared/directives/validation';
describe('validation directive', () => {
let wrapper;
const createComponent = ({ inputAttributes, showValidation } = {}) => {
const createComponent = ({ inputAttributes, showValidation, template } = {}) => {
const defaultInputAttributes = {
type: 'text',
required: true,
};
const defaultTemplate = `
<form>
<input v-validation:[showValidation] name="exampleField" v-bind="attributes" />
</form>
`;
const component = {
directives: {
validation: validation(),
@ -29,11 +35,7 @@ describe('validation directive', () => {
},
};
},
template: `
<form>
<input v-validation:[showValidation] name="exampleField" v-bind="attributes" />
</form>
`,
template: template || defaultTemplate,
};
wrapper = shallowMount(component, { attachTo: document.body });
@ -48,6 +50,12 @@ describe('validation directive', () => {
const findForm = () => wrapper.find('form');
const findInput = () => wrapper.find('input');
const setValueAndTriggerValidation = (value) => {
const input = findInput();
input.setValue(value);
input.trigger('blur');
};
describe.each([true, false])(
'with fields untouched and "showValidation" set to "%s"',
(showValidation) => {
@ -78,12 +86,6 @@ describe('validation directive', () => {
`(
'with input-attributes set to $inputAttributes',
({ inputAttributes, validValue, invalidValue }) => {
const setValueAndTriggerValidation = (value) => {
const input = findInput();
input.setValue(value);
input.trigger('blur');
};
beforeEach(() => {
createComponent({ inputAttributes });
});
@ -129,4 +131,52 @@ describe('validation directive', () => {
});
},
);
describe('with group elements', () => {
const template = `
<form>
<div v-validation:[showValidation]>
<input name="exampleField" v-bind="attributes" />
</div>
</form>
`;
beforeEach(() => {
createComponent({
template,
inputAttributes: {
required: true,
},
});
});
describe('with invalid value', () => {
beforeEach(() => {
setValueAndTriggerValidation('');
});
it('should set correct field state', () => {
expect(getFormData().fields.exampleField).toEqual({
state: false,
feedback: expect.any(String),
});
});
it('should set correct feedback', () => {
expect(getFormData().fields.exampleField.feedback).toBe('Please fill out this field.');
});
});
describe('with valid value', () => {
beforeEach(() => {
setValueAndTriggerValidation('hello');
});
it('set the correct state', () => {
expect(getFormData().fields.exampleField).toEqual({
state: true,
feedback: '',
});
});
});
});
});

View file

@ -0,0 +1,102 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Sidebars::Projects::Menus::HiddenMenu do
let_it_be(:project) { create(:project, :repository) }
let(:user) { project.owner }
let(:context) { Sidebars::Projects::Context.new(current_user: user, container: project, current_ref: project.repository.root_ref) }
describe '#render?' do
subject { described_class.new(context) }
context 'when menu does not have any menu items' do
it 'returns false' do
allow(subject).to receive(:has_items?).and_return(false)
expect(subject.render?).to be false
end
end
context 'when menu has menu items' do
it 'returns true' do
expect(subject.render?).to be true
end
end
end
describe 'Menu items' do
subject { described_class.new(context).items.index { |e| e.item_id == item_id } }
shared_examples 'access rights checks' do
specify { is_expected.not_to be_nil }
describe 'when the user does not have access' do
let(:user) { nil }
specify { is_expected.to be_nil }
end
end
describe 'Activity' do
let(:item_id) { :activity }
context 'when user has access to the project' do
specify { is_expected.not_to be_nil }
describe 'when the user is not present' do
let(:user) { nil }
specify { is_expected.not_to be_nil }
end
end
end
describe 'Graph' do
let(:item_id) { :graph }
context 'when project repository is empty' do
before do
allow(project).to receive(:empty_repo?).and_return(true)
end
specify { is_expected.to be_nil }
end
it_behaves_like 'access rights checks'
end
describe 'New Issue' do
let(:item_id) { :new_issue }
it_behaves_like 'access rights checks'
end
describe 'Jobs' do
let(:item_id) { :jobs }
it_behaves_like 'access rights checks'
end
describe 'Commits' do
let(:item_id) { :commits }
context 'when project repository is empty' do
before do
allow(project).to receive(:empty_repo?).and_return(true)
end
specify { is_expected.to be_nil }
end
it_behaves_like 'access rights checks'
end
describe 'Issue Boards' do
let(:item_id) { :issue_boards }
it_behaves_like 'access rights checks'
end
end
end

View file

@ -1090,5 +1090,43 @@ RSpec.describe 'layouts/nav/sidebar/_project' do
end
end
describe 'Hidden menus' do
it 'has a link to the Activity page' do
render
expect(rendered).to have_link('Activity', href: activity_project_path(project), class: 'shortcuts-project-activity', visible: false)
end
it 'has a link to the Graph page' do
render
expect(rendered).to have_link('Graph', href: project_network_path(project, current_ref), class: 'shortcuts-network', visible: false)
end
it 'has a link to the New Issue page' do
render
expect(rendered).to have_link('Create a new issue', href: new_project_issue_path(project), class: 'shortcuts-new-issue', visible: false)
end
it 'has a link to the Jobs page' do
render
expect(rendered).to have_link('Jobs', href: project_jobs_path(project), class: 'shortcuts-builds', visible: false)
end
it 'has a link to the Commits page' do
render
expect(rendered).to have_link('Commits', href: project_commits_path(project), class: 'shortcuts-commits', visible: false)
end
it 'has a link to the Issue Boards page' do
render
expect(rendered).to have_link('Issue Boards', href: project_boards_path(project), class: 'shortcuts-issue-boards', visible: false)
end
end
it_behaves_like 'sidebar includes snowplow attributes', 'render', 'projects_side_navigation', 'projects_side_navigation'
end