Commit 4038d50d authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch 'master' into 30985-cancel-pipelines

* master:
  Fix container registry navigation menu highlights
  Resolve "Mini pipeline graph + status badge, when updating in real time don't change color and svg icon"
  Refactor group search out of global search
  disables test settings on chat notification services when repository is empty
  Disable initialization table pipeline for new merge request form
  Review changes, used eq instead of match
  Remove lighten blue and add blue-25 for background
  Fixed tests
  29595 Customize experience callout design
  Remove unneeded format block
  Fixed tests
  29595 Customize experience callout design
  Updated specs
  Remove helper
  [ci skip] Use favicon full path
  Improve gitaly_address error message
parents baaaf4e9 bbd83376
import CANCELED_SVG from 'icons/_icon_status_canceled_borderless.svg';
import CREATED_SVG from 'icons/_icon_status_created_borderless.svg';
import FAILED_SVG from 'icons/_icon_status_failed_borderless.svg';
import MANUAL_SVG from 'icons/_icon_status_manual_borderless.svg';
import PENDING_SVG from 'icons/_icon_status_pending_borderless.svg';
import RUNNING_SVG from 'icons/_icon_status_running_borderless.svg';
import SKIPPED_SVG from 'icons/_icon_status_skipped_borderless.svg';
import SUCCESS_SVG from 'icons/_icon_status_success_borderless.svg';
import WARNING_SVG from 'icons/_icon_status_warning_borderless.svg';
const StatusIconEntityMap = {
icon_status_canceled: CANCELED_SVG,
icon_status_created: CREATED_SVG,
icon_status_failed: FAILED_SVG,
icon_status_manual: MANUAL_SVG,
icon_status_pending: PENDING_SVG,
icon_status_running: RUNNING_SVG,
icon_status_skipped: SKIPPED_SVG,
icon_status_success: SUCCESS_SVG,
icon_status_warning: WARNING_SVG,
};
export {
CANCELED_SVG,
CREATED_SVG,
FAILED_SVG,
MANUAL_SVG,
PENDING_SVG,
RUNNING_SVG,
SKIPPED_SVG,
SUCCESS_SVG,
WARNING_SVG,
StatusIconEntityMap as default,
};
...@@ -368,9 +368,9 @@ ...@@ -368,9 +368,9 @@
}); });
}; };
w.gl.utils.setFavicon = (iconName) => { w.gl.utils.setFavicon = (faviconPath) => {
if (faviconEl && iconName) { if (faviconEl && faviconPath) {
faviconEl.setAttribute('href', `/assets/${iconName}.ico`); faviconEl.setAttribute('href', faviconPath);
} }
}; };
...@@ -385,8 +385,8 @@ ...@@ -385,8 +385,8 @@
url: pageUrl, url: pageUrl,
dataType: 'json', dataType: 'json',
success: function(data) { success: function(data) {
if (data && data.icon) { if (data && data.favicon) {
gl.utils.setFavicon(`ci_favicons/${data.icon}`); gl.utils.setFavicon(data.favicon);
} else { } else {
gl.utils.resetFavicon(); gl.utils.resetFavicon();
} }
......
/* global Flash */ /* global Flash */
import canceledSvg from 'icons/_icon_status_canceled_borderless.svg'; import StatusIconEntityMap from '../../ci_status_icons';
import createdSvg from 'icons/_icon_status_created_borderless.svg';
import failedSvg from 'icons/_icon_status_failed_borderless.svg';
import manualSvg from 'icons/_icon_status_manual_borderless.svg';
import pendingSvg from 'icons/_icon_status_pending_borderless.svg';
import runningSvg from 'icons/_icon_status_running_borderless.svg';
import skippedSvg from 'icons/_icon_status_skipped_borderless.svg';
import successSvg from 'icons/_icon_status_success_borderless.svg';
import warningSvg from 'icons/_icon_status_warning_borderless.svg';
export default { export default {
data() { data() {
const svgsDictionary = {
icon_status_canceled: canceledSvg,
icon_status_created: createdSvg,
icon_status_failed: failedSvg,
icon_status_manual: manualSvg,
icon_status_pending: pendingSvg,
icon_status_running: runningSvg,
icon_status_skipped: skippedSvg,
icon_status_success: successSvg,
icon_status_warning: warningSvg,
};
return { return {
builds: '', builds: '',
spinner: '<span class="fa fa-spinner fa-spin"></span>', spinner: '<span class="fa fa-spinner fa-spin"></span>',
svg: svgsDictionary[this.stage.status.icon],
}; };
}, },
...@@ -89,6 +68,9 @@ export default { ...@@ -89,6 +68,9 @@ export default {
triggerButtonClass() { triggerButtonClass() {
return `mini-pipeline-graph-dropdown-toggle has-tooltip js-builds-dropdown-button ci-status-icon-${this.stage.status.group}`; return `mini-pipeline-graph-dropdown-toggle has-tooltip js-builds-dropdown-button ci-status-icon-${this.stage.status.group}`;
}, },
svgHTML() {
return StatusIconEntityMap[this.stage.status.icon];
},
}, },
template: ` template: `
<div> <div>
...@@ -100,7 +82,7 @@ export default { ...@@ -100,7 +82,7 @@ export default {
data-toggle="dropdown" data-toggle="dropdown"
type="button" type="button"
:aria-label="stage.title"> :aria-label="stage.title">
<span v-html="svg" aria-hidden="true"></span> <span v-html="svgHTML" aria-hidden="true"></span>
<i class="fa fa-caret-down" aria-hidden="true"></i> <i class="fa fa-caret-down" aria-hidden="true"></i>
</button> </button>
<ul class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container"> <ul class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container">
......
...@@ -289,8 +289,12 @@ table.u2f-registrations { ...@@ -289,8 +289,12 @@ table.u2f-registrations {
margin: 0 auto; margin: 0 auto;
.bordered-box { .bordered-box {
border: 1px solid $border-color; border: 1px solid $blue-300;
border-radius: $border-radius-default; border-radius: $border-radius-default;
background-color: $blue-25;
position: relative;
display: flex;
justify-content: center;
} }
.landing { .landing {
...@@ -298,28 +302,59 @@ table.u2f-registrations { ...@@ -298,28 +302,59 @@ table.u2f-registrations {
margin-bottom: $gl-padding; margin-bottom: $gl-padding;
.close { .close {
margin-right: 20px; position: absolute;
} right: 20px;
opacity: 1;
.dismiss-icon {
float: right;
cursor: pointer;
color: $blue-300;
}
.dismiss-icon { &:hover {
float: right; background-color: transparent;
cursor: pointer; border: 0;
color: $cycle-analytics-dismiss-icon-color;
.dismiss-icon {
color: $blue-400;
}
}
} }
.svg-container { .svg-container {
text-align: center; margin-right: 30px;
display: inline-block;
svg { svg {
width: 136px; height: 110px;
height: 136px; vertical-align: top;
} }
} }
.user-callout-copy {
display: inline-block;
vertical-align: top;
}
} }
@media(max-width: $screen-xs-max) { @media(max-width: $screen-xs-max) {
.inner-content { text-align: center;
padding-left: 30px;
.bordered-box {
display: block;
}
.landing {
.svg-container,
.user-callout-copy {
margin: 0;
display: block;
svg {
height: 75px;
}
}
} }
} }
} }
...@@ -22,7 +22,7 @@ class ChatNotificationService < Service ...@@ -22,7 +22,7 @@ class ChatNotificationService < Service
end end
def can_test? def can_test?
valid? super && valid?
end end
def self.supported_events def self.supported_events
......
class StatusEntity < Grape::Entity class StatusEntity < Grape::Entity
include RequestAwareEntity include RequestAwareEntity
expose :icon, :favicon, :text, :label, :group expose :icon, :text, :label, :group
expose :has_details?, as: :has_details expose :has_details?, as: :has_details
expose :details_path expose :details_path
expose :favicon do |status|
ActionController::Base.helpers.image_path(File.join('ci_favicons', "#{status.favicon}.ico"))
end
end end
...@@ -7,16 +7,13 @@ module Search ...@@ -7,16 +7,13 @@ module Search
end end
def execute def execute
group = Group.find_by(id: params[:group_id]) if params[:group_id].present?
projects = ProjectsFinder.new(current_user: current_user).execute
if group
projects = projects.inside_path(group.full_path)
end
Gitlab::SearchResults.new(current_user, projects, params[:search]) Gitlab::SearchResults.new(current_user, projects, params[:search])
end end
def projects
@projects ||= ProjectsFinder.new(current_user: current_user).execute
end
def scope def scope
@scope ||= begin @scope ||= begin
allowed_scopes = %w[issues merge_requests milestones] allowed_scopes = %w[issues merge_requests milestones]
......
module Search
class GroupService < Search::GlobalService
attr_accessor :group
def initialize(user, group, params)
super(user, params)
@group = group
end
def projects
return Project.none unless group
return @projects if defined? @projects
@projects = super.inside_path(group.full_path)
end
end
end
...@@ -54,6 +54,8 @@ class SearchService ...@@ -54,6 +54,8 @@ class SearchService
Search::ProjectService.new(project, current_user, params) Search::ProjectService.new(project, current_user, params)
elsif show_snippets? elsif show_snippets?
Search::SnippetService.new(current_user, params) Search::SnippetService.new(current_user, params)
elsif group
Search::GroupService.new(current_user, group, params)
else else
Search::GlobalService.new(current_user, params) Search::GlobalService.new(current_user, params)
end end
......
...@@ -11,13 +11,13 @@ ...@@ -11,13 +11,13 @@
Project Project
- if project_nav_tab? :files - if project_nav_tab? :files
= nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare repositories tags branches releases graphs network)) do = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare projects/repositories tags branches releases graphs network)) do
= link_to project_files_path(@project), title: 'Repository', class: 'shortcuts-tree' do = link_to project_files_path(@project), title: 'Repository', class: 'shortcuts-tree' do
%span %span
Repository Repository
- if project_nav_tab? :container_registry - if project_nav_tab? :container_registry
= nav_link(controller: %w(container_registry)) do = nav_link(controller: %w[projects/registry/repositories]) do
= link_to project_container_registry_path(@project), title: 'Container Registry', class: 'shortcuts-container-registry' do = link_to project_container_registry_path(@project), title: 'Container Registry', class: 'shortcuts-container-registry' do
%span %span
Registry Registry
......
...@@ -46,7 +46,7 @@ ...@@ -46,7 +46,7 @@
-# This tab is always loaded via AJAX -# This tab is always loaded via AJAX
- if @pipelines.any? - if @pipelines.any?
#pipelines.pipelines.tab-pane #pipelines.pipelines.tab-pane
= render 'projects/merge_requests/show/pipelines', endpoint: url_for(params.merge(format: :json)) = render 'projects/merge_requests/show/pipelines', endpoint: url_for(params.merge(format: :json)), disable_initialization: true
.mr-loading-status .mr-loading-status
= spinner = spinner
......
- endpoint_path = local_assigns[:endpoint] || pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, format: :json) - endpoint_path = local_assigns[:endpoint] || pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, format: :json)
- disable_initialization = local_assigns.fetch(:disable_initialization, false)
= render 'projects/commit/pipelines_list', endpoint: endpoint_path = render 'projects/commit/pipelines_list', endpoint: endpoint_path, disable_initialization: disable_initialization
...@@ -3,12 +3,11 @@ ...@@ -3,12 +3,11 @@
%button.btn.btn-default.close.js-close-callout{ type: 'button', %button.btn.btn-default.close.js-close-callout{ type: 'button',
'aria-label' => 'Dismiss customize experience box' } 'aria-label' => 'Dismiss customize experience box' }
= icon('times', class: 'dismiss-icon', 'aria-hidden' => 'true') = icon('times', class: 'dismiss-icon', 'aria-hidden' => 'true')
.row .svg-container
.col-sm-3.col-xs-12.svg-container = custom_icon('icon_customization')
= custom_icon('icon_customization') .user-callout-copy
.col-sm-8.col-xs-12.inner-content %h4
%h4 Customize your experience
Customize your experience %p
%p Change syntax themes, default project pages, and more in preferences.
Change syntax themes, default project pages, and more in preferences. = link_to 'Check it out', profile_preferences_path, class: 'btn btn-primary js-close-callout'
= link_to 'Check it out', profile_preferences_path, class: 'btn btn-default js-close-callout'
---
title: 29595 Update callout design
merge_request:
author:
---
title: Disable test settings on chat notification services when repository is empty
merge_request: 10759
author:
...@@ -15,7 +15,7 @@ module Gitlab ...@@ -15,7 +15,7 @@ module Gitlab
end end
unless URI(address).scheme.in?(%w(tcp unix)) unless URI(address).scheme.in?(%w(tcp unix))
raise "Unsupported Gitaly address: #{address.inspect}" raise "Unsupported Gitaly address: #{address.inspect} does not use URL scheme 'tcp' or 'unix'"
end end
@addresses[name] = address @addresses[name] = address
......
...@@ -60,7 +60,7 @@ describe Projects::BuildsController do ...@@ -60,7 +60,7 @@ describe Projects::BuildsController do
expect(json_response['text']).to eq status.text expect(json_response['text']).to eq status.text
expect(json_response['label']).to eq status.label expect(json_response['label']).to eq status.label
expect(json_response['icon']).to eq status.icon expect(json_response['icon']).to eq status.icon
expect(json_response['favicon']).to eq status.favicon expect(json_response['favicon']).to eq "/assets/ci_favicons/#{status.favicon}.ico"
end end
end end
end end
...@@ -1208,7 +1208,7 @@ describe Projects::MergeRequestsController do ...@@ -1208,7 +1208,7 @@ describe Projects::MergeRequestsController do
expect(json_response['text']).to eq status.text expect(json_response['text']).to eq status.text
expect(json_response['label']).to eq status.label expect(json_response['label']).to eq status.label
expect(json_response['icon']).to eq status.icon expect(json_response['icon']).to eq status.icon
expect(json_response['favicon']).to eq status.favicon expect(json_response['favicon']).to eq "/assets/ci_favicons/#{status.favicon}.ico"
end end
end end
......
...@@ -86,7 +86,7 @@ describe Projects::PipelinesController do ...@@ -86,7 +86,7 @@ describe Projects::PipelinesController do
expect(json_response['text']).to eq status.text expect(json_response['text']).to eq status.text
expect(json_response['label']).to eq status.label expect(json_response['label']).to eq status.label
expect(json_response['icon']).to eq status.icon expect(json_response['icon']).to eq status.icon
expect(json_response['favicon']).to eq status.favicon expect(json_response['favicon']).to eq "/assets/ci_favicons/#{status.favicon}.ico"
end end
end end
end end
import * as icons from '~/ci_status_icons';
describe('CI status icons', () => {
const statuses = [
'canceled',
'created',
'failed',
'manual',
'pending',
'running',
'skipped',
'success',
'warning',
];
statuses.forEach((status) => {
it(`should export a ${status} svg`, () => {
const key = `${status.toUpperCase()}_SVG`;
expect(Object.hasOwnProperty.call(icons, key)).toBe(true);
expect(icons[key]).toMatch(/^<svg/);
});
});
describe('default export map', () => {
const entityIconNames = [
'icon_status_canceled',
'icon_status_created',
'icon_status_failed',
'icon_status_manual',
'icon_status_pending',
'icon_status_running',
'icon_status_skipped',
'icon_status_success',
'icon_status_warning',
];
entityIconNames.forEach((iconName) => {
it(`should have a '${iconName}' key`, () => {
expect(Object.hasOwnProperty.call(icons.default, iconName)).toBe(true);
});
});
});
});
...@@ -313,7 +313,7 @@ require('~/lib/utils/common_utils'); ...@@ -313,7 +313,7 @@ require('~/lib/utils/common_utils');
describe('gl.utils.setFavicon', () => { describe('gl.utils.setFavicon', () => {
it('should set page favicon to provided favicon', () => { it('should set page favicon to provided favicon', () => {
const faviconName = 'custom_favicon'; const faviconPath = '//custom_favicon';
const fakeLink = { const fakeLink = {
setAttribute() {}, setAttribute() {},
}; };
...@@ -321,9 +321,9 @@ require('~/lib/utils/common_utils'); ...@@ -321,9 +321,9 @@ require('~/lib/utils/common_utils');
spyOn(window.document, 'getElementById').and.callFake(() => fakeLink); spyOn(window.document, 'getElementById').and.callFake(() => fakeLink);
spyOn(fakeLink, 'setAttribute').and.callFake((attr, val) => { spyOn(fakeLink, 'setAttribute').and.callFake((attr, val) => {
expect(attr).toEqual('href'); expect(attr).toEqual('href');
expect(val.indexOf('/assets/custom_favicon.ico') > -1).toBe(true); expect(val.indexOf(faviconPath) > -1).toBe(true);
}); });
gl.utils.setFavicon(faviconName); gl.utils.setFavicon(faviconPath);
}); });
}); });
...@@ -345,13 +345,12 @@ require('~/lib/utils/common_utils'); ...@@ -345,13 +345,12 @@ require('~/lib/utils/common_utils');
describe('gl.utils.setCiStatusFavicon', () => { describe('gl.utils.setCiStatusFavicon', () => {
it('should set page favicon to CI status favicon based on provided status', () => { it('should set page favicon to CI status favicon based on provided status', () => {
const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/builds/1/status.json`; const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/builds/1/status.json`;
const FAVICON_PATH = 'ci_favicons/'; const FAVICON_PATH = '//icon_status_success';
const FAVICON = 'icon_status_success';
const spySetFavicon = spyOn(gl.utils, 'setFavicon').and.stub(); const spySetFavicon = spyOn(gl.utils, 'setFavicon').and.stub();
const spyResetFavicon = spyOn(gl.utils, 'resetFavicon').and.stub(); const spyResetFavicon = spyOn(gl.utils, 'resetFavicon').and.stub();
spyOn($, 'ajax').and.callFake(function (options) { spyOn($, 'ajax').and.callFake(function (options) {
options.success({ icon: FAVICON }); options.success({ favicon: FAVICON_PATH });
expect(spySetFavicon).toHaveBeenCalledWith(FAVICON_PATH + FAVICON); expect(spySetFavicon).toHaveBeenCalledWith(FAVICON_PATH);
options.success(); options.success();
expect(spyResetFavicon).toHaveBeenCalled(); expect(spyResetFavicon).toHaveBeenCalled();
options.error(); options.error();
......
...@@ -14,7 +14,6 @@ describe('UserCallout', function () { ...@@ -14,7 +14,6 @@ describe('UserCallout', function () {
this.userCallout = new UserCallout(); this.userCallout = new UserCallout();
this.closeButton = $('.js-close-callout.close'); this.closeButton = $('.js-close-callout.close');
this.userCalloutBtn = $('.js-close-callout:not(.close)'); this.userCalloutBtn = $('.js-close-callout:not(.close)');
this.userCalloutContainer = $('.user-callout');
}); });
it('hides when user clicks on the dismiss-icon', (done) => { it('hides when user clicks on the dismiss-icon', (done) => {
......
import Vue from 'vue';
import { SUCCESS_SVG } from '~/ci_status_icons';
import Stage from '~/vue_pipelines_index/components/stage';
function minify(string) {
return string.replace(/\s/g, '');
}
describe('Pipelines Stage', () => {
describe('data', () => {
let stageReturnValue;
beforeEach(() => {
stageReturnValue = Stage.data();
});
it('should return object with .builds and .spinner', () => {
expect(stageReturnValue).toEqual({
builds: '',
spinner: '<span class="fa fa-spinner fa-spin"></span>',
});
});
});
describe('computed', () => {
describe('svgHTML', function () {
let stage;
let svgHTML;
beforeEach(() => {
stage = { stage: { status: { icon: 'icon_status_success' } } };
svgHTML = Stage.computed.svgHTML.call(stage);
});
it("should return the correct icon for the stage's status", () => {
expect(svgHTML).toBe(SUCCESS_SVG);
});
});
});
describe('when mounted', () => {
let StageComponent;
let renderedComponent;
let stage;
beforeEach(() => {
stage = { status: { icon: 'icon_status_success' } };
StageComponent = Vue.extend(Stage);
renderedComponent = new StageComponent({
propsData: {
stage,
},
}).$mount();
});
it('should render the correct status svg', () => {
const minifiedComponent = minify(renderedComponent.$el.outerHTML);
const expectedSVG = minify(SUCCESS_SVG);
expect(minifiedComponent).toContain(expectedSVG);
});
});
});
require 'spec_helper' require 'spec_helper'
describe ChatNotificationService, models: true do describe ChatNotificationService, models: true do
describe "Associations" do describe 'Associations' do
before do before do
allow(subject).to receive(:activated?).and_return(true) allow(subject).to receive(:activated?).and_return(true)
end end
it { is_expected.to validate_presence_of :webhook } it { is_expected.to validate_presence_of :webhook }
end end
describe '#can_test?' do
context 'with empty repository' do
it 'returns false' do
subject.project = create(:empty_project, :empty_repo)
expect(subject.can_test?).to be false
end
end
context 'with repository' do
it 'returns true' do
subject.project = create(:project)
expect(subject.can_test?).to be true
end
end
end
end end
...@@ -38,7 +38,7 @@ describe BuildSerializer do ...@@ -38,7 +38,7 @@ describe BuildSerializer do
expect(subject[:text]).to eq(status.text) expect(subject[:text]).to eq(status.text)
expect(subject[:label]).to eq(status.label) expect(subject[:label]).to eq(status.label)
expect(subject[:icon]).to eq(status.icon) expect(subject[:icon]).to eq(status.icon)
expect(subject[:favicon]).to eq(status.favicon) expect(subject[:favicon]).to eq("/assets/ci_favicons/#{status.favicon}.ico")
end end
end end
end end
......
...@@ -144,7 +144,7 @@ describe PipelineSerializer do ...@@ -144,7 +144,7 @@ describe PipelineSerializer do
expect(subject[:text]).to eq(status.text) expect(subject[:text]).to eq(status.text)
expect(subject[:label]).to eq(status.label) expect(subject[:label]).to eq(status.label)
expect(subject[:icon]).to eq(status.icon) expect(subject[:icon]).to eq(status.icon)
expect(subject[:favicon]).to eq(status.favicon) expect(subject[:favicon]).to eq("/assets/ci_favicons/#{status.favicon}.ico")
end end
end end
end end
......
...@@ -40,27 +40,6 @@ describe Search::GlobalService, services: true do ...@@ -40,27 +40,6 @@ describe Search::GlobalService, services: true do
expect(results.objects('projects')).to match_array [found_project] expect(results.objects('projects')).to match_array [found_project]
end end
context 'nested group' do
let!(:nested_group) { create(:group, :nested) }
let!(:project) { create(:empty_project, namespace: nested_group) }
before do
project.add_master(user)
end
it 'returns result from nested group' do
results = Search::GlobalService.new(user, search: project.path).execute
expect(results.objects('projects')).to match_array [project]
end
it 'returns result from descendants when search inside group' do
results = Search::GlobalService.new(user, search: project.path, group_id: nested_group.parent).execute
expect(results.objects('projects')).to match_array [project]
end
end
end end
end end
end end
require 'spec_helper'
describe Search::GroupService, services: true do
shared_examples_for 'group search' do
context 'finding projects by name' do
let(:user) { create(:user) }
let(:term) { "Project Name" }
let(:nested_group) { create(:group, :nested) }
# These projects shouldn't be found
let!(:outside_project) { create(:empty_project, :public, name: "Outside #{term}") }
let!(:private_project) { create(:empty_project, :private, namespace: nested_group, name: "Private #{term}" )}
let!(:other_project) { create(:empty_project, :public, namespace: nested_group, name: term.reverse) }
# These projects should be found
let!(:project1) { create(:empty_project, :internal, namespace: nested_group, name: "Inner #{term} 1") }
let!(:project2) { create(:empty_project, :internal, namespace: nested_group, name: "Inner #{term} 2") }
let!(:project3) { create(:empty_project, :internal, namespace: nested_group.parent, name: "Outer #{term}") }
let(:results) { Search::GroupService.new(user, search_group, search: term).execute }
subject { results.objects('projects') }
context 'in parent group' do
let(:search_group) { nested_group.parent }
it { is_expected.to match_array([project1, project2, project3]) }
end
context 'in subgroup' do
let(:search_group) { nested_group }
it { is_expected.to match_array([project1, project2]) }
end
end
end
describe 'basic search' do
include_examples 'group search'
end
end
require 'spec_helper'
describe 'layouts/nav/_project' do
describe 'container registry tab' do
before do
stub_container_registry_config(enabled: true)
assign(:project, create(:project))
allow(view).to receive(:current_ref).and_return('master')
allow(view).to receive(:can?).and_return(true)
allow(controller).to receive(:controller_name)
.and_return('repositories')
allow(controller).to receive(:controller_path)
.and_return('projects/registry/repositories')
end
it 'has both Registry and Repository tabs' do
render
expect(rendered).to have_text 'Repository'
expect(rendered).to have_text 'Registry'
end
it 'highlights only one tab' do
render
expect(rendered).to have_css('.active', count: 1)
end
it 'highlights container registry tab only' do
render
expect(rendered).to have_css('.active', text: 'Registry')
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment