提交 698ab7c4 编写于 作者: G GitLab Bot

Add latest changes from gitlab-org/gitlab@master

上级 817adcd0
......@@ -80,12 +80,7 @@ class CloseReopenReportToggle {
{
input: this.button,
valueAttribute: 'data-url',
inputAttribute: 'href',
},
{
input: this.button,
valueAttribute: 'data-method',
inputAttribute: 'data-method',
inputAttribute: 'data-endpoint',
},
],
};
......
......@@ -11,7 +11,7 @@ import { __ } from './locale';
export default class Issue {
constructor() {
if ($('a.btn-close').length) this.initIssueBtnEventListeners();
if ($('.btn-close, .btn-reopen').length) this.initIssueBtnEventListeners();
if ($('.js-close-blocked-issue-warning').length) this.initIssueWarningBtnEventListener();
......@@ -32,8 +32,8 @@ export default class Issue {
Issue.initRelatedBranches();
}
this.closeButtons = $('a.btn-close');
this.reopenButtons = $('a.btn-reopen');
this.closeButtons = $('.btn-close');
this.reopenButtons = $('.btn-reopen');
this.initCloseReopenReport();
......@@ -103,7 +103,7 @@ export default class Issue {
// NOTE: data attribute seems unnecessary but is actually necessary
return $('.js-issuable-buttons[data-action="close-reopen"]').on(
'click',
'a.btn-close, a.btn-reopen, a.btn-close-anyway',
'.btn-close, .btn-reopen, .btn-close-anyway',
e => {
e.preventDefault();
e.stopImmediatePropagation();
......@@ -120,7 +120,7 @@ export default class Issue {
} else {
this.disableCloseReopenButton($button);
const url = $button.attr('href');
const url = $button.data('endpoint');
return axios
.put(url)
......
/* eslint-disable func-names, no-underscore-dangle, consistent-return */
import $ from 'jquery';
import axios from './lib/utils/axios_utils';
import { __ } from '~/locale';
import createFlash from '~/flash';
import TaskList from './task_list';
......@@ -65,9 +66,17 @@ MergeRequest.prototype.showAllCommits = function() {
MergeRequest.prototype.initMRBtnListeners = function() {
const _this = this;
return $('a.btn-close, a.btn-reopen').on('click', function(e) {
return $('.btn-close, .btn-reopen').on('click', function(e) {
const $this = $(this);
const shouldSubmit = $this.hasClass('btn-comment');
if ($this.hasClass('js-btn-issue-action')) {
const url = $this.data('endpoint');
return axios
.put(url)
.then(() => window.location.reload())
.catch(() => createFlash(__('Something went wrong.')));
}
if (shouldSubmit && $this.data('submitted')) {
return;
}
......
......@@ -125,9 +125,13 @@ export default {
canToggleIssueState() {
return (
this.getNoteableData.current_user.can_update &&
this.getNoteableData.state !== constants.MERGED
this.getNoteableData.state !== constants.MERGED &&
!this.closedAndLocked
);
},
closedAndLocked() {
return !this.isOpen && this.isLocked(this.getNoteableData);
},
endpoint() {
return this.getNoteableData.create_note_path;
},
......
......@@ -205,7 +205,6 @@ export const closeIssue = ({ commit, dispatch, state }) => {
commit(types.CLOSE_ISSUE);
dispatch('emitStateChangedEvent', data);
dispatch('toggleStateButtonLoading', false);
dispatch('toggleBlockedIssueWarning', false);
});
};
......
......@@ -17,7 +17,10 @@ export default {
project: {
type: Object,
required: true,
validator: p => Number.isFinite(p.id) && isString(p.name) && isString(p.name_with_namespace),
validator: p =>
(Number.isFinite(p.id) || isString(p.id)) &&
isString(p.name) &&
(isString(p.name_with_namespace) || isString(p.nameWithNamespace)),
},
selected: {
type: Boolean,
......@@ -30,8 +33,11 @@ export default {
},
},
computed: {
projectNameWithNamespace() {
return this.project.nameWithNamespace || this.project.name_with_namespace;
},
truncatedNamespace() {
return truncateNamespace(this.project.name_with_namespace);
return truncateNamespace(this.projectNameWithNamespace);
},
highlightedProjectName() {
return highlight(this.project.name, this.matcher);
......@@ -58,7 +64,7 @@ export default {
<div class="d-flex flex-wrap project-namespace-name-container">
<div
v-if="truncatedNamespace"
:title="project.name_with_namespace"
:title="projectNameWithNamespace"
class="text-secondary text-truncate js-project-namespace"
>
{{ truncatedNamespace }}
......
......@@ -41,7 +41,8 @@ export default {
},
totalResults: {
type: Number,
required: true,
required: false,
default: 0,
},
},
data() {
......
......@@ -33,12 +33,13 @@ module Registrations
def hide_advanced_issues
return unless current_user.user_preference.novice?
return unless learn_gitlab.available?
settings = cookies[:onboarding_issues_settings]
return unless settings
Boards::UpdateService.new(learn_gitlab.project, current_user, label_ids: [learn_gitlab.label.id]).execute(learn_gitlab.board)
end
modified_settings = Gitlab::Json.parse(settings).merge(hideAdvanced: true)
cookies[:onboarding_issues_settings] = modified_settings.to_json
def learn_gitlab
@learn_gitlab ||= LearnGitlab.new(current_user)
end
end
end
......@@ -367,15 +367,6 @@ module IssuablesHelper
end
end
def issuable_close_reopen_button_method(issuable)
case issuable
when Issue
''
when MergeRequest
'put'
end
end
def issuable_author_is_current_user(issuable)
issuable.author == current_user
end
......
......@@ -13,9 +13,6 @@ class Namespace < ApplicationRecord
include Gitlab::Utils::StrongMemoize
include IgnorableColumns
ignore_column :plan_id, remove_with: '13.1', remove_after: '2020-06-22'
ignore_column :trial_ends_on, remove_with: '13.2', remove_after: '2020-07-22'
# Prevent users from creating unreasonably deep level of nesting.
# The number 20 was taken based on maximum nesting level of
# Android repo (15) + some extra backup.
......
- is_current_user = issuable_author_is_current_user(issuable)
- display_issuable_type = issuable_display_type(issuable)
- button_method = issuable_close_reopen_button_method(issuable)
- are_close_and_open_buttons_hidden = issuable_button_hidden?(issuable, true) && issuable_button_hidden?(issuable, false)
- add_blocked_class = false
- if defined? warn_before_close
......@@ -8,11 +7,13 @@
- if is_current_user
- if can_update
= link_to _("Close %{display_issuable_type}") % { display_issuable_type: display_issuable_type }, close_issuable_path(issuable), method: button_method,
class: "d-none d-sm-none d-md-block btn btn-grouped btn-close js-btn-issue-action #{issuable_button_visibility(issuable, true)} #{(add_blocked_class ? 'btn-issue-blocked' : '')}", title: _("Close %{display_issuable_type}") % { display_issuable_type: display_issuable_type }, data: { qa_selector: 'close_issue_button' }
%button{ class: "d-none d-sm-none d-md-block btn btn-grouped btn-close js-btn-issue-action #{issuable_button_visibility(issuable, true)} #{(add_blocked_class ? 'btn-issue-blocked' : '')}",
data: { remote: 'true', endpoint: close_issuable_path(issuable), qa_selector: 'close_issue_button' } }
= _("Close %{display_issuable_type}") % { display_issuable_type: display_issuable_type }
- if can_reopen
= link_to _("Reopen %{display_issuable_type}") % { display_issuable_type: display_issuable_type }, reopen_issuable_path(issuable), method: button_method,
class: "d-none d-sm-none d-md-block btn btn-grouped btn-reopen js-btn-issue-action #{issuable_button_visibility(issuable, false)}", title: _("Reopen %{display_issuable_type}") % { display_issuable_type: display_issuable_type }, data: { qa_selector: 'reopen_issue_button' }
%button{ class: "d-none d-sm-none d-md-block btn btn-grouped btn-reopen js-btn-issue-action #{issuable_button_visibility(issuable, false)}",
data: { remote: 'true', endpoint: reopen_issuable_path(issuable), qa_selector: 'reopen_issue_button' } }
= _("Reopen %{display_issuable_type}") % { display_issuable_type: display_issuable_type }
- else
- if can_update && !are_close_and_open_buttons_hidden
= render 'shared/issuable/close_reopen_report_toggle', issuable: issuable, warn_before_close: add_blocked_class
......
......@@ -4,14 +4,13 @@
- button_responsive_class = 'd-none d-sm-none d-md-block'
- button_class = "#{button_responsive_class} btn btn-grouped js-issuable-close-button js-btn-issue-action issuable-close-button"
- toggle_class = "#{button_responsive_class} btn btn-nr dropdown-toggle js-issuable-close-toggle"
- button_method = issuable_close_reopen_button_method(issuable)
- add_blocked_class = false
- if defined? warn_before_close
- add_blocked_class = !issuable.closed? && warn_before_close
.float-left.btn-group.prepend-left-10.issuable-close-dropdown.droplab-dropdown.js-issuable-close-dropdown
= link_to "#{display_button_action} #{display_issuable_type}", close_reopen_issuable_path(issuable),
method: button_method, class: "#{button_class} btn-#{button_action} #{(add_blocked_class ? 'btn-issue-blocked' : '')}", title: "#{display_button_action} #{display_issuable_type}", data: { qa_selector: 'close_issue_button' }
%button{ class: "#{button_class} btn-#{button_action} #{(add_blocked_class ? 'btn-issue-blocked' : '')}", data: { qa_selector: 'close_issue_button', endpoint: close_reopen_issuable_path(issuable) } }
#{display_button_action} #{display_issuable_type}
= button_tag type: 'button', class: "#{toggle_class} btn-#{button_action}-color",
data: { 'dropdown-trigger' => '#issuable-close-menu' }, 'aria-label' => _('Toggle dropdown') do
......@@ -20,7 +19,7 @@
%ul#issuable-close-menu.js-issuable-close-menu.dropdown-menu{ data: { dropdown: true } }
%li.close-item{ class: "#{issuable_button_visibility(issuable, true) || 'droplab-item-selected'}",
data: { text: _("Close %{display_issuable_type}") % { display_issuable_type: display_issuable_type }, url: close_issuable_path(issuable),
button_class: "#{button_class} btn-close", toggle_class: "#{toggle_class} btn-close-color", method: button_method } }
button_class: "#{button_class} btn-close", toggle_class: "#{toggle_class} btn-close-color" } }
%button.btn.btn-transparent
= icon('check', class: 'icon')
.description
......@@ -30,7 +29,7 @@
%li.reopen-item{ class: "#{issuable_button_visibility(issuable, false) || 'droplab-item-selected'}",
data: { text: _("Reopen %{display_issuable_type}") % { display_issuable_type: display_issuable_type }, url: reopen_issuable_path(issuable),
button_class: "#{button_class} btn-reopen", toggle_class: "#{toggle_class} btn-reopen-color", method: button_method } }
button_class: "#{button_class} btn-reopen", toggle_class: "#{toggle_class} btn-reopen-color" } }
%button.btn.btn-transparent
= icon('check', class: 'icon')
.description
......
---
title: Remove broken hyperlink from close and reopen button
merge_request: 22220
author: Lee Tickett
type: fixed
# frozen_string_literal: true
class MigrateVulnerabilityDismissalFeedback < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
MIGRATION = 'UpdateVulnerabilitiesFromDismissalFeedback'
BATCH_SIZE = 500
DELAY_INTERVAL = 2.minutes.to_i
class Vulnerability < ActiveRecord::Base
self.table_name = 'vulnerabilities'
self.inheritance_column = :_type_disabled
include ::EachBatch
end
def up
return unless Gitlab.ee?
Vulnerability.select('project_id').group(:project_id).each_batch(of: BATCH_SIZE, column: "project_id") do |project_batch, index|
batch_delay = (index - 1) * BATCH_SIZE * DELAY_INTERVAL
project_batch.each_with_index do |project, project_batch_index|
project_delay = project_batch_index * DELAY_INTERVAL
migrate_in(batch_delay + project_delay, MIGRATION, project[:project_id])
end
end
end
def down
# nothing to do
end
end
......@@ -14032,6 +14032,7 @@ COPY "schema_migrations" (version) FROM STDIN;
20200519141534
20200519171058
20200519194042
20200519201128
20200520103514
20200521022725
20200521225327
......
......@@ -201,6 +201,20 @@ project.repository_read_only = true; project.save
project.update!(repository_read_only: true)
```
### Transfer project from one namespace to another
```ruby
p= Project.find_by_full_path('')
# To set the owner of the project
current_user= p.creator
# Namespace where you want this to be moved.
namespace = Namespace.find_by_full_path("")
::Projects::TransferService.new(p, current_user).execute(namespace)
```
### Bulk update service integration password for _all_ projects
For example, change the Jira user's password for all projects that have the Jira
......
......@@ -103,7 +103,8 @@ ruby:
### Go example
Use the following job in `.gitlab-ci.yml`:
Use the following job in `.gitlab-ci.yml`, and ensure you use `-set-exit-code`,
otherwise the pipeline will be marked successful, even if the tests fail:
```yaml
## Use https://github.com/jstemmer/go-junit-report to generate a JUnit report with go
......@@ -111,7 +112,7 @@ golang:
stage: test
script:
- go get -u github.com/jstemmer/go-junit-report
- go test -v 2>&1 | go-junit-report > report.xml
- go test -v 2>&1 | go-junit-report -set-exit-code > report.xml
artifacts:
reports:
junit: report.xml
......
......@@ -2,6 +2,8 @@
Development guides that are specific to CI/CD are listed here.
If you are creating new CI/CD templates, please read [the development guide for GitLab CI/CD templates](templates.md).
## CI Architecture overview
The following is a simplified diagram of the CI architecture. Some details are left out in order to focus on
......
# Development guide for GitLab CI/CD templates
This document explains how to develop [GitLab CI/CD templates](../../ci/examples/README.md).
## Place the template file in a relevant directory
All template files reside in the `lib/gitlab/ci/templates` directory, and are categorized by the following sub-directories:
| Sub-directroy | Content | [Selectable in UI](#make-sure-the-new-template-can-be-selected-in-ui) |
|---------------|--------------------------------------------------------------|-----------------------------------------------------------------------|
| `/Jobs/*` | Auto DevOps related jobs | Yes |
| `/Pages/*` | Static site generators for GitLab Pages (for example Jekyll) | Yes |
| `/Security/*` | Security related jobs | Yes |
| `/Verify/*` | Verify/testing related jobs | Yes |
| `/Worklows/*` | Common uses of the `workflow:` keyword | No |
| `/*` (root) | General templates | Yes |
## Criteria
The file must follow the [`.gitlab-ci.yml` syntax](../../ci/yaml/README.md).
Verify it's valid by pasting it into the [CI lint tool](https://gitlab.com/gitlab-org/gitlab/-/ci/lint).
Also, all templates must be named with the `*.gitlab-ci.yml` suffix.
### Backward compatibility
A template might be dynamically included with the `include:template:` keyword. If
you make a change to an *existing* template, you must make sure that it won't break
CI/CD in existing projects.
## Testing
Each CI/CD template must be tested in order to make sure that it's safe to be published.
### Manual QA
It's always good practice to test the template in a minimal demo project.
To do so, please follow the following steps:
1. Create a public sample project on <http://gitlab.com>.
1. Add a `.gitlab-ci.yml` to the project with the proposed template.
1. Run pipelines and make sure that everything runs properly, in all possible cases
(merge request pipelines, schedules, and so on).
1. Link to the project in the description of the merge request that is adding a new template.
This is useful information for reviewers to make sure the template is safe to be merged.
### Make sure the new template can be selected in UI
Templates located under some directories are also [selectable in the **New file** UI](#place-the-template-file-in-a-relevant-directory).
When you add a template into one of those directories, make sure that it correctly appears in the dropdown:
![CI/CD template selection](img/ci_template_selection_v13_1.png)
### Write an RSpec test
You should write an RSpec test to make sure that pipeline jobs will be generated correctly:
1. Add a test file at `spec/lib/gitlab/ci/templates/<template-category>/<template-name>_spec.rb`
1. Test that pipeline jobs are properly created via `Ci::CreatePipelineService`.
## Security
A template could contain malicious code. For example, a template that contains the `export` shell command in a job
might accidentally expose project secret variables in a job log.
If you're unsure if it's secure or not, you need to ask security experts for cross-validation.
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
# rubocop: disable Style/Documentation
class UpdateVulnerabilitiesFromDismissalFeedback
def perform(project_id)
end
end
end
end
Gitlab::BackgroundMigration::UpdateVulnerabilitiesFromDismissalFeedback.prepend_if_ee('EE::Gitlab::BackgroundMigration::UpdateVulnerabilitiesFromDismissalFeedback')
# Development guide for GitLab CI templates
Please follow [the development guideline](../../../../doc/development/cicd/templates.md)
# frozen_string_literal: true
class LearnGitlab
PROJECT_NAME = 'Learn GitLab'.freeze
BOARD_NAME = 'GitLab onboarding'.freeze
LABEL_NAME = 'Novice'.freeze
def initialize(current_user)
@current_user = current_user
end
def available?
project && board && label
end
def project
@project ||= current_user.projects.find_by_name(PROJECT_NAME)
end
def board
return unless project
@board ||= project.boards.find_by_name(BOARD_NAME)
end
def label
return unless project
@label ||= project.labels.find_by_name(LABEL_NAME)
end
private
attr_reader :current_user
end
......@@ -20115,6 +20115,9 @@ msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
msgid "SecurityReports|Unable to add %{invalidProjectsMessage}"
msgstr ""
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
......@@ -21196,6 +21199,9 @@ msgstr ""
msgid "Something went wrong, unable to search projects"
msgstr ""
msgid "Something went wrong."
msgstr ""
msgid "Something went wrong. Please try again."
msgstr ""
......
......@@ -99,34 +99,51 @@ describe Registrations::ExperienceLevelsController do
end
describe 'applying the chosen level' do
context "when an 'onboarding_issues_settings' cookie does not exist" do
let(:params) { super().merge(experience_level: :novice) }
it 'does not change the cookie' do
expect { subject }.not_to change { cookies[:onboarding_issues_settings] }
end
end
context "when an 'onboarding_issues_settings' cookie does exist" do
context 'when a "Learn GitLab" project is available' do
before do
request.cookies[:onboarding_issues_settings] = '{}'
allow_next_instance_of(LearnGitlab) do |learn_gitlab|
allow(learn_gitlab).to receive(:available?).and_return(true)
allow(learn_gitlab).to receive(:label).and_return(double(id: 1))
end
end
context 'when novice' do
let(:params) { super().merge(experience_level: :novice) }
it "adds a 'hideAdvanced' setting to the cookie" do
expect { subject }.to change { Gitlab::Json.parse(cookies[:onboarding_issues_settings])['hideAdvanced'] }.from(nil).to(true)
it 'adds a BoardLabel' do
expect_next_instance_of(Boards::UpdateService) do |service|
expect(service).to receive(:execute)
end
subject
end
end
context 'when experienced' do
let(:params) { super().merge(experience_level: :experienced) }
it 'does not change the cookie' do
expect { subject }.not_to change { cookies[:onboarding_issues_settings] }
it 'does not add a BoardLabel' do
expect(Boards::UpdateService).not_to receive(:new)
subject
end
end
end
context 'when no "Learn GitLab" project exists' do
let(:params) { super().merge(experience_level: :novice) }
before do
allow_next_instance_of(LearnGitlab) do |learn_gitlab|
allow(learn_gitlab).to receive(:available?).and_return(false)
end
end
it 'does not add a BoardLabel' do
expect(Boards::UpdateService).not_to receive(:new)
subject
end
end
end
end
......
......@@ -10,7 +10,7 @@ RSpec.describe 'Issuables Close/Reopen/Report toggle' do
let(:human_model_name) { issuable.model_name.human.downcase }
it 'shows toggle' do
expect(page).to have_link("Close #{human_model_name}")
expect(page).to have_button("Close #{human_model_name}")
expect(page).to have_selector('.issuable-close-dropdown')
end
......@@ -63,7 +63,7 @@ RSpec.describe 'Issuables Close/Reopen/Report toggle' do
let(:issuable) { create(:issue, :closed, :locked, project: project) }
it 'hides the reopen button' do
expect(page).not_to have_link('Reopen issue')
expect(page).not_to have_button('Reopen issue')
end
context 'when the issue author is the current user' do
......@@ -72,7 +72,7 @@ RSpec.describe 'Issuables Close/Reopen/Report toggle' do
end
it 'hides the reopen button' do
expect(page).not_to have_link('Reopen issue')
expect(page).not_to have_button('Reopen issue')
end
end
end
......@@ -91,8 +91,8 @@ RSpec.describe 'Issuables Close/Reopen/Report toggle' do
it 'only shows the `Report abuse` and `New issue` buttons' do
expect(page).to have_link('Report abuse')
expect(page).to have_link('New issue')
expect(page).not_to have_link('Close issue')
expect(page).not_to have_link('Reopen issue')
expect(page).not_to have_button('Close issue')
expect(page).not_to have_button('Reopen issue')
expect(page).not_to have_link('Edit')
end
end
......@@ -120,8 +120,8 @@ RSpec.describe 'Issuables Close/Reopen/Report toggle' do
it 'shows only the `Report abuse` and `Edit` button' do
expect(page).to have_link('Report abuse')
expect(page).to have_link('Edit')
expect(page).not_to have_link('Close merge request')
expect(page).not_to have_link('Reopen merge request')
expect(page).not_to have_button('Close merge request')
expect(page).not_to have_button('Reopen merge request')
end
context 'when the merge request author is the current user' do
......@@ -130,8 +130,8 @@ RSpec.describe 'Issuables Close/Reopen/Report toggle' do
it 'shows only the `Edit` button' do
expect(page).to have_link('Edit')
expect(page).not_to have_link('Report abuse')
expect(page).not_to have_link('Close merge request')
expect(page).not_to have_link('Reopen merge request')
expect(page).not_to have_button('Close merge request')
expect(page).not_to have_button('Reopen merge request')
end
end
end
......@@ -149,8 +149,8 @@ RSpec.describe 'Issuables Close/Reopen/Report toggle' do
it 'only shows a `Report abuse` button' do
expect(page).to have_link('Report abuse')
expect(page).not_to have_link('Close merge request')
expect(page).not_to have_link('Reopen merge request')
expect(page).not_to have_button('Close merge request')
expect(page).not_to have_button('Reopen merge request')
expect(page).not_to have_link('Edit')
end
end
......
......@@ -15,7 +15,7 @@ RSpec.describe 'User closes a merge requests', :js do
end
it 'closes a merge request' do
click_link('Close merge request', match: :first)
click_button('Close merge request', match: :first)
expect(page).to have_content(merge_request.title)
expect(page).to have_content('Closed by')
......
......@@ -15,7 +15,7 @@ RSpec.describe 'User reopens a merge requests', :js do
end
it 'reopens a merge request' do
click_link('Reopen merge request', match: :first)
click_button('Reopen merge request', match: :first)
page.within('.status-box') do
expect(page).to have_content('Open')
......
......@@ -103,7 +103,7 @@ RSpec.describe 'Task Lists' do
wait_for_requests
expect(page).to have_selector(".md .task-list .task-list-item .task-list-item-checkbox")
expect(page).to have_selector('a.btn-close')
expect(page).to have_selector('.btn-close')
end
it 'is only editable by author' do
......
......@@ -274,12 +274,7 @@ describe('CloseReopenReportToggle', () => {
{
input: button,
valueAttribute: 'data-url',
inputAttribute: 'href',
},
{
input: button,
valueAttribute: 'data-method',
inputAttribute: 'data-method',
inputAttribute: 'data-endpoint',
},
],
});
......
......@@ -74,6 +74,16 @@ describe('ProjectListItem component', () => {
expect(renderedNamespace).toBe('a / ... / e /');
});
it(`renders a simple namespace name of a GraphQL project`, () => {
options.propsData.project.name_with_namespace = undefined;
options.propsData.project.nameWithNamespace = 'test';
wrapper = shallowMount(Component, options);
const renderedNamespace = trimText(wrapper.find('.js-project-namespace').text());
expect(renderedNamespace).toBe('test /');
});
it(`renders the project name`, () => {
options.propsData.project.name = 'my-test-project';
......
# frozen_string_literal: true
require 'spec_helper'
describe LearnGitlab do
let_it_be(:current_user) { create(:user) }
let_it_be(:learn_gitlab_project) { create(:project, name: LearnGitlab::PROJECT_NAME) }
let_it_be(:learn_gitlab_board) { create(:board, project: learn_gitlab_project, name: LearnGitlab::BOARD_NAME) }
let_it_be(:learn_gitlab_label) { create(:label, project: learn_gitlab_project, name: LearnGitlab::LABEL_NAME) }
before do
learn_gitlab_project.add_developer(current_user)
end
describe '.available?' do
using RSpec::Parameterized::TableSyntax
where(:project, :board, :label, :expected_result) do
nil | nil | nil | nil
nil | nil | true | nil
nil | true | nil | nil
nil | true | true | nil
true | nil | nil | nil
true | nil | true | nil
true | true | nil | nil
true | true | true | true
end
with_them do
before do
allow_next_instance_of(described_class) do |learn_gitlab|
allow(learn_gitlab).to receive(:project).and_return(project)
allow(learn_gitlab).to receive(:board).and_return(board)
allow(learn_gitlab).to receive(:label).and_return(label)
end
end
subject { described_class.new(current_user).available? }
it { is_expected.to be expected_result }
end
end
describe '.project' do
subject { described_class.new(current_user).project }
it { is_expected.to eq learn_gitlab_project }
end
describe '.board' do
subject { described_class.new(current_user).board }
it { is_expected.to eq learn_gitlab_board }
end
describe '.label' do
subject { described_class.new(current_user).label }
it { is_expected.to eq learn_gitlab_label }
end
end
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册