Commit 290ca339 authored by Douwe Maan's avatar Douwe Maan

Merge branch 'feature/customizable-favicon' into 'master'

Customizable favicon

Closes #15661

See merge request gitlab-org/gitlab-ce!14497
parents 7b562c97 366e1331
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.4.4-golang-1.9-git-2.17-chrome-65.0-node-8.x-yarn-1.2-postgresql-9.6" image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.4.4-golang-1.9-git-2.17-chrome-65.0-node-8.x-yarn-1.2-postgresql-9.6-graphicsmagick-1.3.29"
.dedicated-runner: &dedicated-runner .dedicated-runner: &dedicated-runner
retry: 1 retry: 1
......
...@@ -108,6 +108,7 @@ gem 'hamlit', '~> 2.6.1' ...@@ -108,6 +108,7 @@ gem 'hamlit', '~> 2.6.1'
# Files attachments # Files attachments
gem 'carrierwave', '~> 1.2' gem 'carrierwave', '~> 1.2'
gem 'mini_magick'
# Drag and Drop UI # Drag and Drop UI
gem 'dropzonejs-rails', '~> 0.7.1' gem 'dropzonejs-rails', '~> 0.7.1'
......
...@@ -499,6 +499,7 @@ GEM ...@@ -499,6 +499,7 @@ GEM
mime-types-data (~> 3.2015) mime-types-data (~> 3.2015)
mime-types-data (3.2016.0521) mime-types-data (3.2016.0521)
mimemagic (0.3.0) mimemagic (0.3.0)
mini_magick (4.8.0)
mini_mime (1.0.0) mini_mime (1.0.0)
mini_portile2 (2.3.0) mini_portile2 (2.3.0)
minitest (5.7.0) minitest (5.7.0)
...@@ -1082,6 +1083,7 @@ DEPENDENCIES ...@@ -1082,6 +1083,7 @@ DEPENDENCIES
loofah (~> 2.2) loofah (~> 2.2)
mail_room (~> 0.9.1) mail_room (~> 0.9.1)
method_source (~> 0.8) method_source (~> 0.8)
mini_magick
minitest (~> 5.7.0) minitest (~> 5.7.0)
mousetrap-rails (~> 1.4.6) mousetrap-rails (~> 1.4.6)
mysql2 (~> 0.4.10) mysql2 (~> 0.4.10)
......
...@@ -384,6 +384,49 @@ export const backOff = (fn, timeout = 60000) => { ...@@ -384,6 +384,49 @@ export const backOff = (fn, timeout = 60000) => {
}); });
}; };
export const createOverlayIcon = (iconPath, overlayPath) => {
const faviconImage = document.createElement('img');
return new Promise((resolve) => {
faviconImage.onload = () => {
const size = 32;
const canvas = document.createElement('canvas');
canvas.width = size;
canvas.height = size;
const context = canvas.getContext('2d');
context.clearRect(0, 0, size, size);
context.drawImage(
faviconImage, 0, 0, faviconImage.width, faviconImage.height, 0, 0, size, size,
);
const overlayImage = document.createElement('img');
overlayImage.onload = () => {
context.drawImage(
overlayImage, 0, 0, overlayImage.width, overlayImage.height, 0, 0, size, size,
);
const faviconWithOverlayUrl = canvas.toDataURL();
resolve(faviconWithOverlayUrl);
};
overlayImage.src = overlayPath;
};
faviconImage.src = iconPath;
});
};
export const setFaviconOverlay = (overlayPath) => {
const faviconEl = document.getElementById('favicon');
if (!faviconEl) { return null; }
const iconPath = faviconEl.getAttribute('data-original-href');
return createOverlayIcon(iconPath, overlayPath).then(faviconWithOverlayUrl => faviconEl.setAttribute('href', faviconWithOverlayUrl));
};
export const setFavicon = (faviconPath) => { export const setFavicon = (faviconPath) => {
const faviconEl = document.getElementById('favicon'); const faviconEl = document.getElementById('favicon');
if (faviconEl && faviconPath) { if (faviconEl && faviconPath) {
...@@ -393,8 +436,9 @@ export const setFavicon = (faviconPath) => { ...@@ -393,8 +436,9 @@ export const setFavicon = (faviconPath) => {
export const resetFavicon = () => { export const resetFavicon = () => {
const faviconEl = document.getElementById('favicon'); const faviconEl = document.getElementById('favicon');
const originalFavicon = faviconEl ? faviconEl.getAttribute('href') : null;
if (faviconEl) { if (faviconEl) {
const originalFavicon = faviconEl.getAttribute('data-original-href');
faviconEl.setAttribute('href', originalFavicon); faviconEl.setAttribute('href', originalFavicon);
} }
}; };
...@@ -403,10 +447,9 @@ export const setCiStatusFavicon = pageUrl => ...@@ -403,10 +447,9 @@ export const setCiStatusFavicon = pageUrl =>
axios.get(pageUrl) axios.get(pageUrl)
.then(({ data }) => { .then(({ data }) => {
if (data && data.favicon) { if (data && data.favicon) {
setFavicon(data.favicon); return setFaviconOverlay(data.favicon);
} else {
resetFavicon();
} }
return resetFavicon();
}) })
.catch(resetFavicon); .catch(resetFavicon);
......
...@@ -36,7 +36,7 @@ import { ...@@ -36,7 +36,7 @@ import {
notify, notify,
SourceBranchRemovalStatus, SourceBranchRemovalStatus,
} from './dependencies'; } from './dependencies';
import { setFavicon } from '../lib/utils/common_utils'; import { setFaviconOverlay } from '../lib/utils/common_utils';
export default { export default {
el: '#js-vue-mr-widget', el: '#js-vue-mr-widget',
...@@ -159,8 +159,9 @@ export default { ...@@ -159,8 +159,9 @@ export default {
}, },
setFaviconHelper() { setFaviconHelper() {
if (this.mr.ciStatusFaviconPath) { if (this.mr.ciStatusFaviconPath) {
setFavicon(this.mr.ciStatusFaviconPath); return setFaviconOverlay(this.mr.ciStatusFaviconPath);
} }
return Promise.resolve();
}, },
fetchDeployments() { fetchDeployments() {
return this.service.fetchDeployments() return this.service.fetchDeployments()
......
...@@ -513,7 +513,7 @@ const fileNameIcons = { ...@@ -513,7 +513,7 @@ const fileNameIcons = {
'credits.md': 'credits', 'credits.md': 'credits',
'credits.md.rendered': 'credits', 'credits.md.rendered': 'credits',
'.flowconfig': 'flow', '.flowconfig': 'flow',
'favicon.ico': 'favicon', 'favicon.png': 'favicon',
'karma.conf.js': 'karma', 'karma.conf.js': 'karma',
'karma.conf.ts': 'karma', 'karma.conf.ts': 'karma',
'karma.conf.coffee': 'karma', 'karma.conf.coffee': 'karma',
......
...@@ -41,6 +41,13 @@ class Admin::AppearancesController < Admin::ApplicationController ...@@ -41,6 +41,13 @@ class Admin::AppearancesController < Admin::ApplicationController
redirect_to admin_appearances_path, notice: 'Header logo was succesfully removed.' redirect_to admin_appearances_path, notice: 'Header logo was succesfully removed.'
end end
def favicon
@appearance.remove_favicon!
@appearance.save
redirect_to admin_appearances_path, notice: 'Favicon was succesfully removed.'
end
private private
# Use callbacks to share common setup or constraints between actions. # Use callbacks to share common setup or constraints between actions.
...@@ -61,6 +68,8 @@ class Admin::AppearancesController < Admin::ApplicationController ...@@ -61,6 +68,8 @@ class Admin::AppearancesController < Admin::ApplicationController
logo_cache logo_cache
header_logo header_logo
header_logo_cache header_logo_cache
favicon
favicon_cache
new_project_guidelines new_project_guidelines
updated_by updated_by
] ]
......
...@@ -2,7 +2,7 @@ module UploadsActions ...@@ -2,7 +2,7 @@ module UploadsActions
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
include SendFileUpload include SendFileUpload
UPLOAD_MOUNTS = %w(avatar attachment file logo header_logo).freeze UPLOAD_MOUNTS = %w(avatar attachment file logo header_logo favicon).freeze
def create def create
link_to_file = UploadService.new(model, params[:file], uploader_class).execute link_to_file = UploadService.new(model, params[:file], uploader_class).execute
...@@ -31,6 +31,11 @@ module UploadsActions ...@@ -31,6 +31,11 @@ module UploadsActions
disposition = uploader.image_or_video? ? 'inline' : 'attachment' disposition = uploader.image_or_video? ? 'inline' : 'attachment'
uploaders = [uploader, *uploader.versions.values]
uploader = uploaders.find { |version| version.filename == params[:filename] }
return render_404 unless uploader
send_upload(uploader, attachment: uploader.filename, disposition: disposition) send_upload(uploader, attachment: uploader.filename, disposition: disposition)
end end
......
module FaviconHelper
def favicon_extension_whitelist
FaviconUploader::EXTENSION_WHITELIST
.map { |extension| "'.#{extension}'"}
.to_sentence
end
end
...@@ -39,10 +39,7 @@ module PageLayoutHelper ...@@ -39,10 +39,7 @@ module PageLayoutHelper
end end
def favicon def favicon
return 'favicon-yellow.ico' if Gitlab::Utils.to_boolean(ENV['CANARY']) Gitlab::Favicon.main
return 'favicon-blue.ico' if Rails.env.development?
'favicon.ico'
end end
def page_image def page_image
......
...@@ -14,6 +14,7 @@ class Appearance < ActiveRecord::Base ...@@ -14,6 +14,7 @@ class Appearance < ActiveRecord::Base
mount_uploader :logo, AttachmentUploader mount_uploader :logo, AttachmentUploader
mount_uploader :header_logo, AttachmentUploader mount_uploader :header_logo, AttachmentUploader
mount_uploader :favicon, FaviconUploader
# Overrides CacheableAttributes.current_without_cache # Overrides CacheableAttributes.current_without_cache
def self.current_without_cache def self.current_without_cache
......
...@@ -265,7 +265,7 @@ class JiraService < IssueTrackerService ...@@ -265,7 +265,7 @@ class JiraService < IssueTrackerService
title: title, title: title,
status: status, status: status,
icon: { icon: {
title: 'GitLab', url16x16: asset_url('favicon.ico', host: gitlab_config.url) title: 'GitLab', url16x16: asset_url(Gitlab::Favicon.main, host: gitlab_config.url)
} }
} }
} }
......
...@@ -7,16 +7,7 @@ class StatusEntity < Grape::Entity ...@@ -7,16 +7,7 @@ class StatusEntity < Grape::Entity
expose :details_path expose :details_path
expose :favicon do |status| expose :favicon do |status|
dir = Gitlab::Favicon.status_overlay(status.favicon)
if Gitlab::Utils.to_boolean(ENV['CANARY'])
File.join('ci_favicons', 'canary')
elsif Rails.env.development?
File.join('ci_favicons', 'dev')
else
'ci_favicons'
end
ActionController::Base.helpers.image_path(File.join(dir, "#{status.favicon}.ico"))
end end
expose :action, if: -> (status, _) { status.has_action? } do expose :action, if: -> (status, _) { status.has_action? } do
......
class FaviconUploader < AttachmentUploader
EXTENSION_WHITELIST = %w[png ico].freeze
include CarrierWave::MiniMagick
version :favicon_main do
process resize_to_fill: [32, 32]
process convert: 'png'
def full_filename(filename)
filename_for_different_format(super(filename), 'png')
end
end
def extension_whitelist
EXTENSION_WHITELIST
end
private
def filename_for_different_format(filename, format)
filename.chomp(File.extname(filename)) + ".#{format}"
end
end
# Extra methods for uploader # Extra methods for uploader
module UploaderHelper module UploaderHelper
IMAGE_EXT = %w[png jpg jpeg gif bmp tiff].freeze IMAGE_EXT = %w[png jpg jpeg gif bmp tiff ico].freeze
# We recommend using the .mp4 format over .mov. Videos in .mov format can # We recommend using the .mp4 format over .mov. Videos in .mov format can
# still be used but you really need to make sure they are served with the # still be used but you really need to make sure they are served with the
# proper MIME type video/mp4 and not video/quicktime or your videos won't play # proper MIME type video/mp4 and not video/quicktime or your videos won't play
......
...@@ -11,13 +11,32 @@ ...@@ -11,13 +11,32 @@
= image_tag @appearance.header_logo_url, class: 'appearance-light-logo-preview' = image_tag @appearance.header_logo_url, class: 'appearance-light-logo-preview'
- if @appearance.persisted? - if @appearance.persisted?
%br %br
= link_to 'Remove header logo', header_logos_admin_appearances_path, data: { confirm: "Header logo will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-logo" = link_to 'Remove header logo', header_logos_admin_appearances_path, data: { confirm: "Header logo will be removed. Are you sure?"}, method: :delete, class: "btn btn-inverted btn-remove btn-sm remove-logo"
%hr %hr
= f.hidden_field :header_logo_cache = f.hidden_field :header_logo_cache
= f.file_field :header_logo, class: "" = f.file_field :header_logo, class: ""
.hint .hint
Maximum file size is 1MB. Pages are optimized for a 28px tall header logo Maximum file size is 1MB. Pages are optimized for a 28px tall header logo
%fieldset.app_logo
%legend
Favicon:
.form-group.row
= f.label :favicon, 'Favicon', class: 'col-sm-2 col-form-label'
.col-sm-10
- if @appearance.favicon?
= image_tag @appearance.favicon.favicon_main.url, class: 'appearance-light-logo-preview'
- if @appearance.persisted?
%br
= link_to 'Remove favicon', favicon_admin_appearances_path, data: { confirm: "Favicon will be removed. Are you sure?"}, method: :delete, class: "btn btn-inverted btn-remove btn-sm remove-logo"
%hr
= f.hidden_field :favicon_cache
= f.file_field :favicon, class: ''
.hint
Maximum file size is 1MB. Allowed image formats are #{favicon_extension_whitelist}.
%br
The resulting favicons will be cropped to be square and scaled down to a size of 32x32 px.
%fieldset.sign-in %fieldset.sign-in
%legend %legend
Sign in/Sign up pages: Sign in/Sign up pages:
...@@ -38,7 +57,7 @@ ...@@ -38,7 +57,7 @@
= image_tag @appearance.logo_url, class: 'appearance-logo-preview' = image_tag @appearance.logo_url, class: 'appearance-logo-preview'
- if @appearance.persisted? - if @appearance.persisted?
%br %br
= link_to 'Remove logo', logo_admin_appearances_path, data: { confirm: "Logo will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-logo" = link_to 'Remove logo', logo_admin_appearances_path, data: { confirm: "Logo will be removed. Are you sure?"}, method: :delete, class: "btn btn-inverted btn-remove btn-sm remove-logo"
%hr %hr
= f.hidden_field :logo_cache = f.hidden_field :logo_cache
= f.file_field :logo, class: "" = f.file_field :logo, class: ""
......
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
%title= page_title(site_name) %title= page_title(site_name)
%meta{ name: "description", content: page_description } %meta{ name: "description", content: page_description }
= favicon_link_tag favicon, id: 'favicon' = favicon_link_tag favicon, id: 'favicon', data: { original_href: favicon }, type: 'image/png'
= stylesheet_link_tag "application", media: "all" = stylesheet_link_tag "application", media: "all"
= stylesheet_link_tag "print", media: "print" = stylesheet_link_tag "print", media: "print"
......
---
title: Allow changing the default favicon to a custom icon.
merge_request: 14497
author: Alexis Reigel
type: added
...@@ -15,3 +15,5 @@ Mime::Type.register "video/ogg", :ogv ...@@ -15,3 +15,5 @@ Mime::Type.register "video/ogg", :ogv
Mime::Type.unregister :json Mime::Type.unregister :json
Mime::Type.register 'application/json', :json, [LfsRequest::CONTENT_TYPE, 'application/json'] Mime::Type.register 'application/json', :json, [LfsRequest::CONTENT_TYPE, 'application/json']
Mime::Type.register 'image/x-icon', :ico
MiniMagick.configure do |config|
config.cli = :graphicsmagick
end
en:
errors:
messages:
carrierwave_processing_error: failed to be processed
carrierwave_integrity_error: is not of an allowed file type
carrierwave_download_error: could not be downloaded
extension_whitelist_error: "You are not allowed to upload %{extension} files, allowed types: %{allowed_types}"
extension_blacklist_error: "You are not allowed to upload %{extension} files, prohibited types: %{prohibited_types}"
content_type_whitelist_error: "You are not allowed to upload %{content_type} files"
content_type_blacklist_error: "You are not allowed to upload %{content_type} files"
rmagick_processing_error: "Failed to manipulate with rmagick, maybe it is not an image?"
mini_magick_processing_error: "Failed to manipulate with MiniMagick, maybe it is not an image? Original Error: %{e}"
min_size_error: "File size should be greater than %{min_size}"
max_size_error: "File size should be less than %{max_size}"
...@@ -102,6 +102,7 @@ namespace :admin do ...@@ -102,6 +102,7 @@ namespace :admin do
get :preview_sign_in get :preview_sign_in
delete :logo delete :logo
delete :header_logos delete :header_logos
delete :favicon
end end
end end
......
...@@ -17,7 +17,7 @@ scope path: :uploads do ...@@ -17,7 +17,7 @@ scope path: :uploads do
# Appearance # Appearance
get "-/system/:model/:mounted_as/:id/:filename", get "-/system/:model/:mounted_as/:id/:filename",
to: "uploads#show", to: "uploads#show",
constraints: { model: /appearance/, mounted_as: /logo|header_logo/, filename: /.+/ } constraints: { model: /appearance/, mounted_as: /logo|header_logo|favicon/, filename: /.+/ }
# Project markdown uploads # Project markdown uploads
get ":namespace_id/:project_id/:secret/:filename", get ":namespace_id/:project_id/:secret/:filename",
......
class AddFaviconToAppearances < ActiveRecord::Migration
DOWNTIME = false
def change
add_column :appearances, :favicon, :string
end
end
...@@ -38,6 +38,7 @@ ActiveRecord::Schema.define(version: 20180603190921) do ...@@ -38,6 +38,7 @@ ActiveRecord::Schema.define(version: 20180603190921) do
t.integer "cached_markdown_version" t.integer "cached_markdown_version"
t.text "new_project_guidelines" t.text "new_project_guidelines"
t.text "new_project_guidelines_html" t.text "new_project_guidelines_html"
t.string "favicon"
end end
create_table "application_setting_terms", force: :cascade do |t| create_table "application_setting_terms", force: :cascade do |t|
......
...@@ -49,6 +49,7 @@ Learn how to install, configure, update, and maintain your GitLab instance. ...@@ -49,6 +49,7 @@ Learn how to install, configure, update, and maintain your GitLab instance.
#### Customizing GitLab's appearance #### Customizing GitLab's appearance
- [Header logo](../customization/branded_page_and_email_header.md): Change the logo on all pages and email headers. - [Header logo](../customization/branded_page_and_email_header.md): Change the logo on all pages and email headers.
- [Favicon](../customization/favicon.md): Change the default favicon to your own logo.
- [Branded login page](../customization/branded_login_page.md): Customize the login page with your own logo, title, and description. - [Branded login page](../customization/branded_login_page.md): Customize the login page with your own logo, title, and description.
- [Welcome message](../customization/welcome_message.md): Add a custom welcome message to the sign-in page. - [Welcome message](../customization/welcome_message.md): Add a custom welcome message to the sign-in page.
- ["New Project" page](../customization/new_project_page.md): Customize the text to be displayed on the page that opens whenever your users create a new project. - ["New Project" page](../customization/new_project_page.md): Customize the text to be displayed on the page that opens whenever your users create a new project.
......
# Changing the favicon
> [Introduced][ce-14497] in GitLab 11.0.
[ce-14497]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14497
Navigate to the **Admin** area and go to the **Appearance** page.
Upload the custom favicon (**Favicon**) in the section **Favicon**.
![appearance](favicon/appearance.png)
After saving the page, the new favicon will be shown in the browser. The main
favicon as well as the CI status icons will show the custom icon:
![custom_favicon](favicon/custom_favicon.png)
...@@ -58,7 +58,7 @@ Currently the following names are reserved as top level groups: ...@@ -58,7 +58,7 @@ Currently the following names are reserved as top level groups:
- dashboard - dashboard
- deploy.html - deploy.html
- explore - explore
- favicon.ico - favicon.png
- groups - groups
- header_logo_dark.png - header_logo_dark.png
- header_logo_light.png - header_logo_light.png
......
module Gitlab
class Favicon
class << self
def main
return appearance_favicon.favicon_main.url if appearance_favicon.exists?
image_name =
if Gitlab::Utils.to_boolean(ENV['CANARY'])
'favicon-yellow.png'
elsif Rails.env.development?
'favicon-blue.png'
else
'favicon.png'
end
ActionController::Base.helpers.image_path(image_name)
end
def status_overlay(status_name)
path = File.join(
'ci_favicons',
"#{status_name}.png"
)
ActionController::Base.helpers.image_path(path)
end
def available_status_names
@available_status_names ||= begin
Dir.glob(Rails.root.join('app', 'assets', 'images', 'ci_favicons', '*.png'))
.map { |file| File.basename(file, '.png') }
.sort
end
end
private
def appearance
RequestStore.store[:appearance] ||= (Appearance.current || Appearance.new)
end
def appearance_favicon
appearance.favicon
end
end
end
end
...@@ -30,7 +30,7 @@ module Gitlab ...@@ -30,7 +30,7 @@ module Gitlab
dashboard dashboard
deploy.html deploy.html
explore explore
favicon.ico favicon.png
files files
groups groups
health_check health_check
......
...@@ -265,7 +265,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do ...@@ -265,7 +265,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state 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 match_asset_path "/assets/ci_favicons/#{status.favicon}.ico" expect(json_response['favicon']).to match_asset_path "/assets/ci_favicons/#{status.favicon}.png"
end end
end end
......
...@@ -701,7 +701,7 @@ describe Projects::MergeRequestsController do ...@@ -701,7 +701,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 match_asset_path "/assets/ci_favicons/#{status.favicon}.ico" expect(json_response['favicon']).to match_asset_path "/assets/ci_favicons/#{status.favicon}.png"
end end
end end
......
...@@ -253,7 +253,7 @@ describe Projects::PipelinesController do ...@@ -253,7 +253,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 match_asset_path("/assets/ci_favicons/#{status.favicon}.ico") expect(json_response['favicon']).to match_asset_path("/assets/ci_favicons/#{status.favicon}.png")
end end
end end
......
...@@ -136,7 +136,7 @@ describe UploadsController do ...@@ -136,7 +136,7 @@ describe UploadsController do
context 'for PNG files' do context 'for PNG files' do
it 'returns Content-Disposition: inline' do it 'returns Content-Disposition: inline' do
note = create(:note, :with_attachment, project: project) note = create(:note, :with_attachment, project: project)
get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'image.png' get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'dk.png'
expect(response['Content-Disposition']).to start_with('inline;') expect(response['Content-Disposition']).to start_with('inline;')
end end
...@@ -145,7 +145,7 @@ describe UploadsController do ...@@ -145,7 +145,7 @@ describe UploadsController do
context 'for SVG files' do context 'for SVG files' do
it 'returns Content-Disposition: attachment' do it 'returns Content-Disposition: attachment' do
note = create(:note, :with_svg_attachment, project: project) note = create(:note, :with_svg_attachment, project: project)
get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'image.svg' get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'unsanitized.svg'
expect(response['Content-Disposition']).to start_with('attachment;') expect(response['Content-Disposition']).to start_with('attachment;')
end end
...@@ -164,7 +164,7 @@ describe UploadsController do ...@@ -164,7 +164,7 @@ describe UploadsController do
end end
it "redirects to the sign in page" do it "redirects to the sign in page" do
get :show, model: "user", mounted_as: "avatar", id: user.id, filename: "image.png" get :show, model: "user", mounted_as: "avatar", id: user.id, filename: "dk.png"
expect(response).to redirect_to(new_user_session_path) expect(response).to redirect_to(new_user_session_path)
end end
...@@ -172,14 +172,14 @@ describe UploadsController do ...@@ -172,14 +172,14 @@ describe UploadsController do
context "when the user isn't blocked" do context "when the user isn't blocked" do
it "responds with status 200" do it "responds with status 200" do
get :show, model: "user", mounted_as: "avatar", id: user.id, filename: "image.png" get :show, model: "user", mounted_as: "avatar", id: user.id, filename: "dk.png"
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
end end
it_behaves_like 'content not cached without revalidation' do it_behaves_like 'content not cached without revalidation' do
subject do subject do
get :show, model: 'user', mounted_as: 'avatar', id: user.id, filename: 'image.png' get :show, model: 'user', mounted_as: 'avatar', id: user.id, filename: 'dk.png'
response response
end end
...@@ -189,14 +189,14 @@ describe UploadsController do ...@@ -189,14 +189,14 @@ describe UploadsController do
context "when not signed in" do context "when not signed in" do
it "responds with status 200" do it "responds with status 200" do
get :show, model: "user", mounted_as: "avatar", id: user.id, filename: "image.png" get :show, model: "user", mounted_as: "avatar", id: user.id, filename: "dk.png"
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
end end
it_behaves_like 'content not cached without revalidation' do it_behaves_like 'content not cached without revalidation' do
subject do subject do
get :show, model: 'user', mounted_as: 'avatar', id: user.id, filename: 'image.png' get :show, model: 'user', mounted_as: 'avatar', id: user.id, filename: 'dk.png'
response response
end end
...@@ -214,14 +214,14 @@ describe UploadsController do ...@@ -214,14 +214,14 @@ describe UploadsController do
context "when not signed in" do context "when not signed in" do
it "responds with status 200" do it "responds with status 200" do
get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png" get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png"
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
end end
it_behaves_like 'content not cached without revalidation' do it_behaves_like 'content not cached without revalidation' do
subject do subject do
get :show, model: 'project', mounted_as: 'avatar', id: project.id, filename: 'image.png' get :show, model: 'project', mounted_as: 'avatar', id: project.id, filename: 'dk.png'
response response
end end
...@@ -234,14 +234,14 @@ describe UploadsController do ...@@ -234,14 +234,14 @@ describe UploadsController do
end end
it "responds with status 200" do it "responds with status 200" do
get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png" get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png"
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
end end
it_behaves_like 'content not cached without revalidation' do it_behaves_like 'content not cached without revalidation' do
subject do subject do
get :show, model: 'project', mounted_as: 'avatar', id: project.id, filename: 'image.png' get :show, model: 'project', mounted_as: 'avatar', id: project.id, filename: 'dk.png'
response response
end end
...@@ -256,7 +256,7 @@ describe UploadsController do ...@@ -256,7 +256,7 @@ describe UploadsController do
context "when not signed in" do context "when not signed in" do
it "redirects to the sign in page" do it "redirects to the sign in page" do
get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png" get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png"
expect(response).to redirect_to(new_user_session_path) expect(response).to redirect_to(new_user_session_path)
end end
...@@ -279,7 +279,7 @@ describe UploadsController do ...@@ -279,7 +279,7 @@ describe UploadsController do
end end
it "redirects to the sign in page" do it "redirects to the sign in page" do
get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png" get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png"
expect(response).to redirect_to(new_user_session_path) expect(response).to redirect_to(new_user_session_path)
end end
...@@ -287,14 +287,14 @@ describe UploadsController do ...@@ -287,14 +287,14 @@ describe UploadsController do
context "when the user isn't blocked" do context "when the user isn't blocked" do
it "responds with status 200" do it "responds with status 200" do
get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png" get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png"
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
end end
it_behaves_like 'content not cached without revalidation' do it_behaves_like 'content not cached without revalidation' do
subject do subject do
get :show, model: 'project', mounted_as: 'avatar', id: project.id, filename: 'image.png' get :show, model: 'project', mounted_as: 'avatar', id: project.id, filename: 'dk.png'
response response
end end
...@@ -304,7 +304,7 @@ describe UploadsController do ...@@ -304,7 +304,7 @@ describe UploadsController do
context "when the user doesn't have access to the project" do context "when the user doesn't have access to the project" do
it "responds with status 404" do it "responds with status 404" do
get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png" get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png"
expect(response).to have_gitlab_http_status(404) expect(response).to have_gitlab_http_status(404)
end end
...@@ -319,14 +319,14 @@ describe UploadsController do ...@@ -319,14 +319,14 @@ describe UploadsController do
context "when the group is public" do context "when the group is public" do
context "when not signed in" do context "when not signed in" do
it "responds with status 200" do it "responds with status 200" do
get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png" get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "dk.png"
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
end end
it_behaves_like 'content not cached without revalidation' do it_behaves_like 'content not cached without revalidation' do
subject do subject do
get :show, model: 'group', mounted_as: 'avatar', id: group.id, filename: 'image.png' get :show, model: 'group', mounted_as: 'avatar', id: group.id, filename: 'dk.png'
response response
end end
...@@ -339,14 +339,14 @@ describe UploadsController do ...@@ -339,14 +339,14 @@ describe UploadsController do
end end
it "responds with status 200" do it "responds with status 200" do
get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png" get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "dk.png"
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
end end
it_behaves_like 'content not cached without revalidation' do it_behaves_like 'content not cached without revalidation' do
subject do subject do
get :show, model: 'group', mounted_as: 'avatar', id: group.id, filename: 'image.png' get :show, model: 'group', mounted_as: 'avatar', id: group.id, filename: 'dk.png'
response response
end end
...@@ -375,7 +375,7 @@ describe UploadsController do ...@@ -375,7 +375,7 @@ describe UploadsController do
end end
it "redirects to the sign in page" do it "redirects to the sign in page" do
get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png" get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "dk.png"
expect(response).to redirect_to(new_user_session_path) expect(response).to redirect_to(new_user_session_path)
end end
...@@ -383,14 +383,14 @@ describe UploadsController do ...@@ -383,14 +383,14 @@ describe UploadsController do
context "when the user isn't blocked" do context "when the user isn't blocked" do
it "responds with status 200" do it "responds with status 200" do
get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png" get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "dk.png"
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
end end
it_behaves_like 'content not cached without revalidation' do it_behaves_like 'content not cached without revalidation' do
subject do subject do
get :show, model: 'group', mounted_as: 'avatar', id: group.id, filename: 'image.png' get :show, model: 'group', mounted_as: 'avatar', id: group.id, filename: 'dk.png'
response response
end end
...@@ -400,7 +400,7 @@ describe UploadsController do ...@@ -400,7 +400,7 @@ describe UploadsController do
context "when the user doesn't have access to the project" do context "when the user doesn't have access to the project" do
it "responds with status 404" do it "responds with status 404" do
get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png" get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "dk.png"
expect(response).to have_gitlab_http_status(404) expect(response).to have_gitlab_http_status(404)
end end
...@@ -420,14 +420,14 @@ describe UploadsController do ...@@ -420,14 +420,14 @@ describe UploadsController do
context "when not signed in" do context "when not signed in" do
it "responds with status 200" do it "responds with status 200" do
get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png" get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png"
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
end end
it_behaves_like 'content not cached without revalidation' do it_behaves_like 'content not cached without revalidation' do
subject do subject do
get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'image.png' get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'dk.png'
response response
end end
...@@ -440,14 +440,14 @@ describe UploadsController do ...@@ -440,14 +440,14 @@ describe UploadsController do
end end
it "responds with status 200" do it "responds with status 200" do
get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png" get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png"
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
end end
it_behaves_like 'content not cached without revalidation' do it_behaves_like 'content not cached without revalidation' do
subject do subject do
get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'image.png' get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'dk.png'
response response
end end
...@@ -462,7 +462,7 @@ describe UploadsController do ...@@ -462,7 +462,7 @@ describe UploadsController do
context "when not signed in" do context "when not signed in" do
it "redirects to the sign in page" do it "redirects to the sign in page" do
get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png" get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png"
expect(response).to redirect_to(new_user_session_path) expect(response).to redirect_to(new_user_session_path)
end end
...@@ -485,7 +485,7 @@ describe UploadsController do ...@@ -485,7 +485,7 @@ describe UploadsController do
end end
it "redirects to the sign in page" do it "redirects to the sign in page" do
get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png" get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png"
expect(response).to redirect_to(new_user_session_path) expect(response).to redirect_to(new_user_session_path)
end end
...@@ -493,14 +493,14 @@ describe UploadsController do ...@@ -493,14 +493,14 @@ describe UploadsController do
context "when the user isn't blocked" do context "when the user isn't blocked" do
it "responds with status 200" do it "responds with status 200" do
get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png" get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png"
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
end end
it_behaves_like 'content not cached without revalidation' do it_behaves_like 'content not cached without revalidation' do
subject do subject do
get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'image.png' get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'dk.png'
response response
end end
...@@ -510,7 +510,7 @@ describe UploadsController do ...@@ -510,7 +510,7 @@ describe UploadsController do
context "when the user doesn't have access to the project" do context "when the user doesn't have access to the project" do
it "responds with status 404" do it "responds with status 404" do
get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png" get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png"
expect(response).to have_gitlab_http_status(404) expect(response).to have_gitlab_http_status(404)
end end
...@@ -560,5 +560,43 @@ describe UploadsController do ...@@ -560,5 +560,43 @@ describe UploadsController do
end end
end end
end end
context 'original filename or a version filename must match' do
let!(:appearance) { create :appearance, favicon: fixture_file_upload(Rails.root.join('spec/fixtures/dk.png'), 'image/png') }
context 'has a valid filename on the original file' do
it 'successfully returns the file' do
get :show, model: 'appearance', mounted_as: 'favicon', id: appearance.id, filename: 'dk.png'
expect(response).to have_gitlab_http_status(200)
expect(response.header['Content-Disposition']).to end_with 'filename="dk.png"'
end
end
context 'has an invalid filename on the original file' do
it 'returns a 404' do
get :show, model: 'appearance', mounted_as: 'favicon', id: appearance.id, filename: 'bogus.png'
expect(response).to have_gitlab_http_status(404)
end
end
context 'has a valid filename on the version file' do
it 'successfully returns the file' do
get :show, model: 'appearance', mounted_as: 'favicon', id: appearance.id, filename: 'favicon_main_dk.png'
expect(response).to have_gitlab_http_status(200)
expect(response.header['Content-Disposition']).to end_with 'filename="favicon_main_dk.png"'
end
end
context 'has an invalid filename on the version file' do
it 'returns a 404' do
get :show, model: 'appearance', mounted_as: 'favicon', id: appearance.id, filename: 'favicon_bogusversion_dk.png'
expect(response).to have_gitlab_http_status(404)
end
end
end
end end
end end
...@@ -76,6 +76,26 @@ feature 'Admin Appearance' do ...@@ -76,6 +76,26 @@ feature 'Admin Appearance' do
expect(page).not_to have_css(header_logo_selector) expect(page).not_to have_css(header_logo_selector)
end end
scenario 'Favicon' do
sign_in(create(:admin))
visit admin_appearances_path
attach_file(:appearance_favicon, logo_fixture)
click_button 'Save'
expect(page).to have_css('.appearance-light-logo-preview')
click_link 'Remove favicon'
expect(page).not_to have_css('.appearance-light-logo-preview')
# allowed file types
attach_file(:appearance_favicon, Rails.root.join('spec', 'fixtures', 'sanitized.svg'))
click_button 'Save'
expect(page).to have_content 'Favicon You are not allowed to upload "svg" files, allowed types: png, ico'
end
def expect_custom_sign_in_appearance(appearance) def expect_custom_sign_in_appearance(appearance)
expect(page).to have_content appearance.title expect(page).to have_content appearance.title
expect(page).to have_content appearance.description expect(page).to have_content appearance.description
......
...@@ -12,7 +12,7 @@ feature 'Merge request > User creates image diff notes', :js do ...@@ -12,7 +12,7 @@ feature 'Merge request > User creates image diff notes', :js do
# Stub helper to return any blob file as image from public app folder. # Stub helper to return any blob file as image from public app folder.
# This is necessary to run this specs since we don't display repo images in capybara. # This is necessary to run this specs since we don't display repo images in capybara.
allow_any_instance_of(DiffHelper).to receive(:diff_file_blob_raw_url).and_return('/apple-touch-icon.png') allow_any_instance_of(DiffHelper).to receive(:diff_file_blob_raw_url).and_return('/apple-touch-icon.png')
allow_any_instance_of(DiffHelper).to receive(:diff_file_old_blob_raw_url).and_return('/favicon.ico') allow_any_instance_of(DiffHelper).to receive(:diff_file_old_blob_raw_url).and_return('/favicon.png')
end end
context 'create commit diff notes' do context 'create commit diff notes' do
......
...@@ -40,23 +40,6 @@ describe PageLayoutHelper do ...@@ -40,23 +40,6 @@ describe PageLayoutHelper do
end end
end end
describe 'favicon' do
it 'defaults to favicon.ico' do
allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new('production'))
expect(helper.favicon).to eq 'favicon.ico'
end
it 'has blue favicon for development' do
allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new('development'))
expect(helper.favicon).to eq 'favicon-blue.ico'
end
it 'has yellow favicon for canary' do
stub_env('CANARY', 'true')
expect(helper.favicon).to eq 'favicon-yellow.ico'
end
end
describe 'page_image' do describe 'page_image' do
it 'defaults to the GitLab logo' do it 'defaults to the GitLab logo' do
expect(helper.page_image).to match_asset_path 'assets/gitlab_logo.png' expect(helper.page_image).to match_asset_path 'assets/gitlab_logo.png'
......
...@@ -20,7 +20,7 @@ export default { ...@@ -20,7 +20,7 @@ export default {
group: 'success', group: 'success',
has_details: true, has_details: true,
details_path: '/root/ci-mock/-/jobs/4757', details_path: '/root/ci-mock/-/jobs/4757',
favicon: '/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico', favicon: '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
action: { action: {
icon: 'retry', icon: 'retry',
title: 'Retry', title: 'Retry',
...@@ -78,7 +78,7 @@ export default { ...@@ -78,7 +78,7 @@ export default {
group: 'success', group: 'success',
has_details: true, has_details: true,
details_path: '/root/ci-mock/pipelines/140', details_path: '/root/ci-mock/pipelines/140',
favicon: '/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico', favicon: '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
}, },
duration: 6, duration: 6,
finished_at: '2017-06-01T17:32:00.042Z', finished_at: '2017-06-01T17:32:00.042Z',
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import * as commonUtils from '~/lib/utils/common_utils'; import * as commonUtils from '~/lib/utils/common_utils';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import { faviconDataUrl, overlayDataUrl, faviconWithOverlayDataUrl } from './mock_data';
describe('common_utils', () => { describe('common_utils', () => {
describe('parseUrl', () => { describe('parseUrl', () => {
...@@ -395,6 +396,7 @@ describe('common_utils', () => { ...@@ -395,6 +396,7 @@ describe('common_utils', () => {
const favicon = document.createElement('link'); const favicon = document.createElement('link');
favicon.setAttribute('id', 'favicon'); favicon.setAttribute('id', 'favicon');
favicon.setAttribute('href', 'default/favicon'); favicon.setAttribute('href', 'default/favicon');
favicon.setAttribute('data-default-href', 'default/favicon');
document.body.appendChild(favicon); document.body.appendChild(favicon);
}); });
...@@ -413,7 +415,7 @@ describe('common_utils', () => { ...@@ -413,7 +415,7 @@ describe('common_utils', () => {
beforeEach(() => { beforeEach(() => {
const favicon = document.createElement('link'); const favicon = document.createElement('link');
favicon.setAttribute('id', 'favicon'); favicon.setAttribute('id', 'favicon');
favicon.setAttribute('href', 'default/favicon'); favicon.setAttribute('data-original-href', 'default/favicon');
document.body.appendChild(favicon); document.body.appendChild(favicon);
}); });
...@@ -421,12 +423,43 @@ describe('common_utils', () => { ...@@ -421,12 +423,43 @@ describe('common_utils', () => {
document.body.removeChild(document.getElementById('favicon')); document.body.removeChild(document.getElementById('favicon'));
}); });
it('should reset page favicon to tanuki', () => { it('should reset page favicon to the default icon', () => {
const favicon = document.getElementById('favicon');
favicon.setAttribute('href', 'new/favicon');
commonUtils.resetFavicon(); commonUtils.resetFavicon();
expect(document.getElementById('favicon').getAttribute('href')).toEqual('default/favicon'); expect(document.getElementById('favicon').getAttribute('href')).toEqual('default/favicon');
}); });
}); });
describe('createOverlayIcon', () => {
it('should return the favicon with the overlay', (done) => {
commonUtils.createOverlayIcon(faviconDataUrl, overlayDataUrl).then((url) => {
expect(url).toEqual(faviconWithOverlayDataUrl);
done();
});
});
});
describe('setFaviconOverlay', () => {
beforeEach(() => {
const favicon = document.createElement('link');
favicon.setAttribute('id', 'favicon');
favicon.setAttribute('data-original-href', faviconDataUrl);
document.body.appendChild(favicon);
});
afterEach(() => {
document.body.removeChild(document.getElementById('favicon'));
});
it('should set page favicon to provided favicon overlay', (done) => {
commonUtils.setFaviconOverlay(overlayDataUrl).then(() => {
expect(document.getElementById('favicon').getAttribute('href')).toEqual(faviconWithOverlayDataUrl);
done();
});
});
});
describe('setCiStatusFavicon', () => { describe('setCiStatusFavicon', () => {
const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/-/jobs/1/status.json`; const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/-/jobs/1/status.json`;
let mock; let mock;
...@@ -434,6 +467,8 @@ describe('common_utils', () => { ...@@ -434,6 +467,8 @@ describe('common_utils', () => {
beforeEach(() => { beforeEach(() => {
const favicon = document.createElement('link'); const favicon = document.createElement('link');
favicon.setAttribute('id', 'favicon'); favicon.setAttribute('id', 'favicon');
favicon.setAttribute('href', 'null');
favicon.setAttribute('data-original-href', faviconDataUrl);
document.body.appendChild(favicon); document.body.appendChild(favicon);
mock = new MockAdapter(axios); mock = new MockAdapter(axios);
}); });
...@@ -449,7 +484,7 @@ describe('common_utils', () => { ...@@ -449,7 +484,7 @@ describe('common_utils', () => {
commonUtils.setCiStatusFavicon(BUILD_URL) commonUtils.setCiStatusFavicon(BUILD_URL)
.then(() => { .then(() => {
const favicon = document.getElementById('favicon'); const favicon = document.getElementById('favicon');
expect(favicon.getAttribute('href')).toEqual('null'); expect(favicon.getAttribute('href')).toEqual(faviconDataUrl);
done(); done();
}) })
// Error is already caught in catch() block of setCiStatusFavicon, // Error is already caught in catch() block of setCiStatusFavicon,
...@@ -458,16 +493,14 @@ describe('common_utils', () => { ...@@ -458,16 +493,14 @@ describe('common_utils', () => {
}); });
it('should set page favicon to CI status favicon based on provided status', (done) => { it('should set page favicon to CI status favicon based on provided status', (done) => {
const FAVICON_PATH = '//icon_status_success';
mock.onGet(BUILD_URL).reply(200, { mock.onGet(BUILD_URL).reply(200, {
favicon: FAVICON_PATH, favicon: overlayDataUrl,
}); });
commonUtils.setCiStatusFavicon(BUILD_URL) commonUtils.setCiStatusFavicon(BUILD_URL)
.then(() => { .then(() => {
const favicon = document.getElementById('favicon'); const favicon = document.getElementById('favicon');
expect(favicon.getAttribute('href')).toEqual(FAVICON_PATH); expect(favicon.getAttribute('href')).toEqual(faviconWithOverlayDataUrl);
done(); done();
}) })
.catch(done.fail); .catch(done.fail);
......
export const faviconDataUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAACcFBMVEX////iQyniQyniQyniQyniQyniQyniQyniQynhRiriQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniRCniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQynhQiniQiniQiniQinhQinpUSjqUSjqTyjqTyjqTyjlSCniRCniQynjRCjqTyjsZSjrWyj8oib9kSb8pyb9pib8oyb8fyb3ZSb4Zib8fCb8oyb8oyb8oyb8pCb8cSbiQyn7bCb8cib8oyb8oSb8bSbtVSjpTij8nyb8oyb8oyb8lCb2Yyf3ZCf8mCb8oyb8oyb8oyb8iib8bSbiRCn8gyb8oyb8eCbpTinrUSj8oyb8oyb8oyb8pSb8bib4Zif0YCf8byb8oyb8oyb8oyb7oib8oyb8nCbjRSn9bib8ayb8nib8oyb8oyb8oyb8kSbpTyjpTyj8jib8oyb8oyb8oyb8fib0Xyf2ZSb8gCb8oyb6pSb8oyb8dib+cCbgQCnjRSn8cCb8oib8oyb8oyb8oybqUCjnSyn8bCb8oyb8oyb8oyb8myb2YyfyXyf8oyb8oyb8hibhQSn+bib8iSb8oyb8qCb+fSbmSSnqTyj8oib9pCb1YifxXyf7pSb8oCb8pCb+mCb0fCf8pSb7hSXvcSjiQyniQinqTyj9kCb9bib9byb+cCbqUSjiRCnsVCj+cSb8pib8bCb8bSbgQCn7bCb8bibjRSn8oyb8ayb8oib8aib8pCbjRCn8pybhQinhQSn8pSb7ayb7aSb6aib8eib///8IbM+7AAAAr3RSTlMBA3NtX2vT698HGQcRLwWLiXnv++3V+eEd/R8HE2V/Y5HjyefdFw99YWfJ+/3nwQP78/HvX1VTQ/kdA2HzbQXj9fX79/3DGf379/33T/v99/f7ba33+/f1+9/18/v59V339flzF/H9+fX3/fMhBwOh9/v5/fmvBV/z+fP3Awnp9/f38+UFgff7+/37+4c77/f7/flFz/f59dFr7/v98Wnr+/f3I5/197EDBU1ZAwUD8/kLUwAAAAFiS0dEAIgFHUgAAAAHdElNRQfhBQoLHiBV6/1lAAACHUlEQVQ4y41TZXsTQRCe4FAIUigN7m7FXY+iLRQKBG2x4g7BjhZ3Le7uMoEkFJprwyQk0CC/iZnNhUZaHt4vt6/szO7cHcD/wFKjZrJWq3YMq1M3eVc9rFzXR2yQkuA3RGxkjZLGiEk9miA2tURJs1RsnhhokYYtzaU13WZDbBVnW1sjo43J2vI6tZ0lLtFeAh1M0lECneI7dGYtrUtk3RUVIKaEJR25qw27yT0s3W0qEHuPlB4RradivXo7GX36xnbo51SQ+fWHARmCgYMGDxkaxbD3SssYPmIkwKgPLrfA87EETTg/fVaSa/SYsQDjSsd7DcGEsr+BieVKmaRNBsjUtClTfUI900y/5Mt05c8oJQKYSURZ2UqYFa0w283M588JEM2BuRwI5EqT8nmmXzZf4l8XsGNfCIv4QcHFklhiBpaqAsuC4tghj+ySyOdjeJYrP7RCCuR/E5tWAqxaLcmCNSyujdxjHZdbn8UHoA0bN/GoNm8hjQJb/ZzYpo6w3TB27JRduxxqrA7YzbWCezixN8RD2Oc2/Ptlfx7o5uT1A4XMiwzj4HfEikNe7+Ew0ZGjeuW70eEYaeHjxomTiKd++E4XnKGz8d+HDufOB3Ky3RcwdNF1qZiKLyf/B44r2tWf15wV143cwI2qfi8dbtKtX6Hbd+6G74EDqkTm/QcPH/0ufFyNLXjy9NnzF9Xb8BJevYY38C+8fZcg/AF3QTYemVkCwwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNy0wNS0xMFQxMTozMDozMiswMjowMMzup8UAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTctMDUtMTBUMTE6MzA6MzIrMDI6MDC9sx95AAAAAElFTkSuQmCC';
export const overlayDataUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAA85JREFUWAntVllIVGEUPv/9b46O41KplYN7PeRkti8TjQlhCUGh3MmeQugpIsGKAi2soIcIooiohxYKK2daqDAlIpIiWwxtQaJcaHE0d5tMrbn37z9XRqfR0TvVW56Hudf//uec72zfEWBCJjIwkYGJDPzvGSD/KgExN3Oi2Q+2DJgSDYQEMwItVGH1iZGmJw/Si1y+/PwVAMYYib22MYc/8hVQFgKDEfYoId0KYzagAQebsos/ewMZoeB9wdffcTYpQSaCTWHKoqSQaDk7zkIt0+aCUR8BelEHrf3dUNv9AcqbnsHtT5UKB/hTASh0SLYjnjb/CIDRJi0XiFAaJOpCD8zLpdb4NB66b1OfelthX815dtdRRfiti2aAXLvVLiMQ6olGyztGDkSo4JGGXk8/QFdGpYzpHG2GBQTDhtgVhPEaVbbVpvI6GJz22rv4TcAfrYI1x7Rj5MWWAppomKFVVb2302SFzUkZHAbkG+0b1+Gh77yNYjrmqnWTrLBLRxdvBWv8qlFujH/kYjJYyvLkj71t78zAUvzMAMnHhpN4zf9UREJhd8omyssxu1IgazQDwDnHUcNuH6vhPIE1fmuBzHt74Hn7W89jWGtcAjoaIDOFrdcMYJBkgOCoaRF0Lj0oglddDbCj6tRvKjphEpgjkzEQs2YAKsNxMzjn3nKurhzK+Ly7xe28ua8TwgMMcHJZnvvT0BPtEEKM4tDJ+C8GvIIk4ylINIXVZ0EUKJxYuh3mhCeokbudl6TtVc88dfBdLwbyaWB6zQCYQJpBYSrDGQxBQ/ZWRM2B+VNmQnVnHWx7elyNuL2/R336co7KyJR8CL9oLgEuFlREevWUkEl6uGwpVEG4FBm0OEf9N10NMgPlvWYAuNVwsWDKvcUNYsHUWTCZ13ysyFEXe6TO6aC8CUr9IiK+A05TQrc8yjwmxARHeeMAPlfQJw+AQRwu0YhL/GDXi9NwufG+S8dYkuYMqIb4SsWthotlNMOUCOM6r+G9cqXxPmd1dqrBav/o1zJy2l5/NUjJA/VORwYuFnOUaTQcPs9wMqwV++Xv8oADxKAcZ8nLPr8AoGW+xR6HSqYk3GodAz2QNj0V+Gr26dT9ASNH5239Pf0gktVNWZca8ZvfAFBprWS6hSu1pqt++Y0PD+WIwDAhIWQGtzvSHDbcodfFUFB9hg1Gjs5LXqIdFL+acFBl+FddqYwdxsWC3I70OvgfUaA65zhq2O2c8VxYcyIGFTVlXegYtvCXANCQZJMobjVcLMjtSK/IcEgyOOe8Ve5w7ryKDefp2P3+C/5ohv8HZmVLAAAAAElFTkSuQmCC';
export const faviconWithOverlayDataUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAGt0lEQVRYR8WWf3DT5R3H35/vN0lDiCztSuiPAEnTFhSOUamIzFGokwMH55g0E845yjbP6+4qIoiHY6JjnHLeRI6h9jgQpQcD3ImH2u00eHBwjGthKLI1TSm26S8oKYVS0vT7/X52z7ckTUhqC/yx55/kks/zeb+ez6/nIWYm/B8XJQB4SGq6lL+CJA47vvRtvWs2D0nNl/Kf0qBZxx6u23arv0QAAIHivK8BynB4ffa7BgDQXJx/ngGnw+uThgTwP5ZnMocoJAxVxZDVZ+0L5n5WF75TkMafjLdJxpSg2E+gqW1X7zk3rbpaifhLiEBgTv4mEFbpBoTyu01DoDj/dQAv9rvjtdnp/k3Yx9rgAMV5QYCsAAwAgg6vL/1OTy/2BYrzzwLIBWACuNHhrXPG+otGoKaw0JA58kqGJtOFfgPS8yWT4sz88nzj7UIIfz+wd0mRdEZPLMnp2V/8R0+JrhLbBYFHJvwWzBUxYgqYNzhG+zfEhm24MIE5ectBtP0W+y0Or29FcoDifHFSRxwAcMrh9c0YrmisXaA4r0V0U8xvopgDDq9PpCQ+Ag0/zbEbNUNbMiG9fTwkDTsKHpJa2t1Zmiw1AqLg+tMZ+R6WVVtnZ2qP6Ib+FIjh05G3lsDrB4xjUIbRDeM+WZLJYZ4B1rKMzKPm/fdyzs9qg6WT225IMnPcuYjxbPZhn57qaA00zc4/QYT7b1b/wAZmDYSLjsN1WcmiM+6jXz7JTCs1aNPASBjrtrCGOXVBLK9ph72772bc0REZcsQlkEVoOxblhaFBH0Bxi6GBWFNC8gpV0XqYSe/hI85R9o1zxr/QaZbdbmuW9oRzljRrzBRkW9JhMaTgYugKzl35DlXNJ/Fp43FImoZnz7T0ln7bLihM0g85N627vkWPgLrbvYyCvAP1+rRIWETA5QsyQlcJYOCbMRasWpALtljwSsFyeJxFYsoNWqdN1y/ildM78Y/WGjxx8TL+ol3oluy8VupKe7cfoNLdCJkdqEUPOmBJ5ksJoae91mBps5lQ6pkIm20MPiz6A3KsmcNukDe/3Ye3zh3A77Q2XqcGjslLz88i/nB8pkpSoL8nAFSTBpUN4qSxS5KB5jOGUOniCebmzFQcevSN2xKP+Fp7ajt21f8TOxU/5i45JZFS6XwcTB9HxZgUnGTRNgk31x5jet+aGU7jWw+UweOcPeyTxxoqrGL25+UwdjehSvnmOVIqcz4C8y8GAABcQwjnYI5NheikhQWT+EZmDh2ev/l7cz4U2cGmYyg78TYqVH87Kbtd1wFY4hsVQAt14zu2RiDaTUZMf/BHWD35STx37wDv94k1dLeh7MRmvDZ1GR5Inxg17dX6MPnjZfh5X6tGSqXrV2B8ACIx98UNGOlV4CxCuA6zqIeq9FQ8c68bhx7ZiIK06CQdVF+Il3y1Hq03gnDfk4Uj8zbH2T51dCPOtlW39Q+iPTl2VSMfwKPiKw8aTuhgpl1Zdqxzj8PphRWwm21xZjv9VcgYkYb52dP132PFbSYr/la0DpNtrrg9a2oqsKfB2zlwG+4nSe1z7QDjaQBi2Eh6J4QRwimYt43LwOsuB2oX7YLVMCLqTAya3xx/EwZJxtYHy3WhyMkHExebXz3zAbbXfdo7AFBRaMAz1Ypa6XoaoPejKRGteZm6D3SlWVdOcOHo/Lfj2u9aXw+WHNmA00G/DiFEO0Jd+meyk0fIf/+vLfik6Xhj4qN0v7i5HCY1bBQPk+ij9GSzNbzYNdH03kMrscARfzvHQgiBocSFTVHVCrW+u+WrpK9iCIgS1rRK93oG/1GkRJVIup8KMNs1Sw/1rUtALD36ZzRca8XeJDmPtRc18vDn5SCJViYHENY3IZTK3JkE7RAYtpdkp3bAaJeOzN+CsSMTX+wqa7ih9sbVSLI2WV3znihAJYXZPThA7M6KQoM2MniyhUxTioxTpKLMadjx8Jqh5k3S//8d9GOh92XWmP/aXLKvfHgA0ZTklL0jj9m6UR6L5+9bjFWTPLcFIWbCY1+8pHb0drWybJ4aWLQrODyAWJndzoyylNyGg0hL+bV7Ll4rKIWB5CFBxMlLj21SL4W6QjDQjwOL9n4tNt0+AADPfo+UqgXPHJLSJrkso7F6ylLMy56OFMmYACIKblvtQext8Iqp0swyLYiI3zEAbs6Ml3cXv/p3Y+ryq5KcnSKb1Jmj75P7X0Rm/UV0tvO86r/WIhORwszvkmHEehH2WMo7ikDUQUWhoaIG+NNc96Os8eMEmklE2Qy2ANTO0OrA+CwFOFBfsq8pWZ7+B25aDBxvPp+QAAAAAElFTkSuQmCC';
...@@ -20,7 +20,7 @@ export default { ...@@ -20,7 +20,7 @@ export default {
has_details: true, has_details: true,
details_path: '/root/ci-mock/pipelines/123', details_path: '/root/ci-mock/pipelines/123',
favicon: favicon:
'/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico', '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
}, },
duration: 9, duration: 9,
finished_at: '2017-04-19T14:30:27.542Z', finished_at: '2017-04-19T14:30:27.542Z',
...@@ -40,7 +40,7 @@ export default { ...@@ -40,7 +40,7 @@ export default {
has_details: true, has_details: true,
details_path: '/root/ci-mock/builds/4153', details_path: '/root/ci-mock/builds/4153',
favicon: favicon:
'/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico', '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
action: { action: {
icon: 'retry', icon: 'retry',
title: 'Retry', title: 'Retry',
...@@ -65,7 +65,7 @@ export default { ...@@ -65,7 +65,7 @@ export default {
has_details: true, has_details: true,
details_path: '/root/ci-mock/builds/4153', details_path: '/root/ci-mock/builds/4153',
favicon: favicon:
'/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico', '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
action: { action: {
icon: 'retry', icon: 'retry',
title: 'Retry', title: 'Retry',
...@@ -85,7 +85,7 @@ export default { ...@@ -85,7 +85,7 @@ export default {
has_details: true, has_details: true,
details_path: '/root/ci-mock/pipelines/123#test', details_path: '/root/ci-mock/pipelines/123#test',
favicon: favicon:
'/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico', '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
}, },
path: '/root/ci-mock/pipelines/123#test', path: '/root/ci-mock/pipelines/123#test',
dropdown_path: '/root/ci-mock/pipelines/123/stage.json?stage=test', dropdown_path: '/root/ci-mock/pipelines/123/stage.json?stage=test',
...@@ -105,7 +105,7 @@ export default { ...@@ -105,7 +105,7 @@ export default {
has_details: true, has_details: true,
details_path: '/root/ci-mock/builds/4166', details_path: '/root/ci-mock/builds/4166',
favicon: favicon:
'/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico', '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
action: { action: {
icon: 'retry', icon: 'retry',
title: 'Retry', title: 'Retry',
...@@ -130,7 +130,7 @@ export default { ...@@ -130,7 +130,7 @@ export default {
has_details: true, has_details: true,
details_path: '/root/ci-mock/builds/4166', details_path: '/root/ci-mock/builds/4166',
favicon: favicon:
'/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico', '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
action: { action: {
icon: 'retry', icon: 'retry',
title: 'Retry', title: 'Retry',
...@@ -152,7 +152,7 @@ export default { ...@@ -152,7 +152,7 @@ export default {
has_details: true, has_details: true,
details_path: '/root/ci-mock/builds/4159', details_path: '/root/ci-mock/builds/4159',
favicon: favicon:
'/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico', '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
action: { action: {
icon: 'retry', icon: 'retry',
title: 'Retry', title: 'Retry',
...@@ -177,7 +177,7 @@ export default { ...@@ -177,7 +177,7 @@ export default {
has_details: true, has_details: true,
details_path: '/root/ci-mock/builds/4159', details_path: '/root/ci-mock/builds/4159',
favicon: favicon:
'/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico', '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
action: { action: {
icon: 'retry', icon: 'retry',
title: 'Retry', title: 'Retry',
...@@ -197,7 +197,7 @@ export default { ...@@ -197,7 +197,7 @@ export default {
has_details: true, has_details: true,
details_path: '/root/ci-mock/pipelines/123#deploy', details_path: '/root/ci-mock/pipelines/123#deploy',
favicon: favicon:
'/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico', '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
}, },
path: '/root/ci-mock/pipelines/123#deploy', path: '/root/ci-mock/pipelines/123#deploy',
dropdown_path: '/root/ci-mock/pipelines/123/stage.json?stage=deploy', dropdown_path: '/root/ci-mock/pipelines/123/stage.json?stage=deploy',
......
...@@ -5,6 +5,7 @@ import notify from '~/lib/utils/notify'; ...@@ -5,6 +5,7 @@ import notify from '~/lib/utils/notify';
import { stateKey } from '~/vue_merge_request_widget/stores/state_maps'; import { stateKey } from '~/vue_merge_request_widget/stores/state_maps';
import mountComponent from 'spec/helpers/vue_mount_component_helper'; import mountComponent from 'spec/helpers/vue_mount_component_helper';
import mockData from './mock_data'; import mockData from './mock_data';
import { faviconDataUrl, overlayDataUrl, faviconWithOverlayDataUrl } from '../lib/utils/mock_data';
const returnPromise = data => new Promise((resolve) => { const returnPromise = data => new Promise((resolve) => {
resolve({ resolve({
...@@ -273,6 +274,7 @@ describe('mrWidgetOptions', () => { ...@@ -273,6 +274,7 @@ describe('mrWidgetOptions', () => {
beforeEach(() => { beforeEach(() => {
const favicon = document.createElement('link'); const favicon = document.createElement('link');
favicon.setAttribute('id', 'favicon'); favicon.setAttribute('id', 'favicon');
favicon.setAttribute('data-original-href', faviconDataUrl);
document.body.appendChild(favicon); document.body.appendChild(favicon);
faviconElement = document.getElementById('favicon'); faviconElement = document.getElementById('favicon');
...@@ -282,10 +284,13 @@ describe('mrWidgetOptions', () => { ...@@ -282,10 +284,13 @@ describe('mrWidgetOptions', () => {
document.body.removeChild(document.getElementById('favicon')); document.body.removeChild(document.getElementById('favicon'));
}); });
it('should call setFavicon method', () => { it('should call setFavicon method', (done) => {
vm.setFaviconHelper(); vm.mr.ciStatusFaviconPath = overlayDataUrl;
vm.setFaviconHelper().then(() => {
expect(faviconElement.getAttribute('href')).toEqual(vm.mr.ciStatusFaviconPath); expect(faviconElement.getAttribute('href')).toEqual(faviconWithOverlayDataUrl);
done();
})
.catch(done.fail);
}); });
it('should not call setFavicon when there is no ciStatusFaviconPath', () => { it('should not call setFavicon when there is no ciStatusFaviconPath', () => {
......
require 'rails_helper'
RSpec.describe Gitlab::Favicon, :request_store do
describe '.main' do
it 'defaults to favicon.png' do
allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new('production'))
expect(described_class.main).to match_asset_path '/assets/favicon.png'
end
it 'has blue favicon for development' do
allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new('development'))
expect(described_class.main).to match_asset_path '/assets/favicon-blue.png'
end
it 'has yellow favicon for canary' do
stub_env('CANARY', 'true')
expect(described_class.main).to match_asset_path 'favicon-yellow.png'
end
it 'uses the custom favicon if a favicon appearance is present' do
create :appearance, favicon: fixture_file_upload(Rails.root.join('spec/fixtures/dk.png'))
expect(described_class.main).to match %r{/uploads/-/system/appearance/favicon/\d+/favicon_main_dk.png}
end
end
describe '.status_overlay' do
subject { described_class.status_overlay('favicon_status_created') }
it 'returns the overlay for the status' do
expect(subject).to match_asset_path '/assets/ci_favicons/favicon_status_created.png'
end
end
describe '.available_status_names' do
subject { described_class.available_status_names }
it 'returns the available status names' do
expect(subject).to eq %w(
favicon_status_canceled
favicon_status_created
favicon_status_failed
favicon_status_manual
favicon_status_not_found
favicon_status_pending
favicon_status_running
favicon_status_skipped
favicon_status_success
favicon_status_warning
)
end
end
end
...@@ -240,7 +240,7 @@ describe Group do ...@@ -240,7 +240,7 @@ describe Group do
it "is false if avatar is html page" do it "is false if avatar is html page" do
group.update_attribute(:avatar, 'uploads/avatar.html') group.update_attribute(:avatar, 'uploads/avatar.html')
expect(group.avatar_type).to eq(["file format is not supported. Please try one of the following supported formats: png, jpg, jpeg, gif, bmp, tiff"]) expect(group.avatar_type).to eq(["file format is not supported. Please try one of the following supported formats: png, jpg, jpeg, gif, bmp, tiff, ico"])
end end
end end
......
...@@ -2,6 +2,7 @@ require 'spec_helper' ...@@ -2,6 +2,7 @@ require 'spec_helper'
describe JiraService do describe JiraService do
include Gitlab::Routing include Gitlab::Routing
include AssetsHelpers
describe '#options' do describe '#options' do
let(:service) do let(:service) do
...@@ -164,6 +165,8 @@ describe JiraService do ...@@ -164,6 +165,8 @@ describe JiraService do
it "creates Remote Link reference in JIRA for comment" do it "creates Remote Link reference in JIRA for comment" do
@jira_service.close_issue(merge_request, ExternalIssue.new("JIRA-123", project)) @jira_service.close_issue(merge_request, ExternalIssue.new("JIRA-123", project))
favicon_path = "http://localhost/assets/#{find_asset('favicon.png').digest_path}"
# Creates comment # Creates comment
expect(WebMock).to have_requested(:post, @comment_url) expect(WebMock).to have_requested(:post, @comment_url)
# Creates Remote Link in JIRA issue fields # Creates Remote Link in JIRA issue fields
...@@ -173,7 +176,7 @@ describe JiraService do ...@@ -173,7 +176,7 @@ describe JiraService do
object: { object: {
url: "#{Gitlab.config.gitlab.url}/#{project.full_path}/commit/#{merge_request.diff_head_sha}", url: "#{Gitlab.config.gitlab.url}/#{project.full_path}/commit/#{merge_request.diff_head_sha}",
title: "GitLab: Solved by commit #{merge_request.diff_head_sha}.", title: "GitLab: Solved by commit #{merge_request.diff_head_sha}.",
icon: { title: "GitLab", url16x16: "http://localhost/favicon.ico" }, icon: { title: "GitLab", url16x16: favicon_path },
status: { resolved: true } status: { resolved: true }
} }
) )
...@@ -464,4 +467,18 @@ describe JiraService do ...@@ -464,4 +467,18 @@ describe JiraService do
end end
end end
end end
describe 'favicon urls', :request_store do
it 'includes the standard favicon' do
props = described_class.new.send(:build_remote_link_props, url: 'http://example.com', title: 'title')
expect(props[:object][:icon][:url16x16]).to match %r{^http://localhost/assets/favicon(?:-\h+).png$}
end
it 'includes returns the custom favicon' do
create :appearance, favicon: fixture_file_upload(Rails.root.join('spec/fixtures/dk.png'))
props = described_class.new.send(:build_remote_link_props, url: 'http://example.com', title: 'title')
expect(props[:object][:icon][:url16x16]).to match %r{^http://localhost/uploads/-/system/appearance/favicon/\d+/favicon_main_dk.png$}
end
end
end end
...@@ -960,7 +960,7 @@ describe Project do ...@@ -960,7 +960,7 @@ describe Project do
it 'is false if avatar is html page' do it 'is false if avatar is html page' do
project.update_attribute(:avatar, 'uploads/avatar.html') project.update_attribute(:avatar, 'uploads/avatar.html')
expect(project.avatar_type).to eq(['file format is not supported. Please try one of the following supported formats: png, jpg, jpeg, gif, bmp, tiff']) expect(project.avatar_type).to eq(['file format is not supported. Please try one of the following supported formats: png, jpg, jpeg, gif, bmp, tiff, ico'])
end end
end end
......
...@@ -1260,7 +1260,7 @@ describe User do ...@@ -1260,7 +1260,7 @@ describe User do
it 'is false if avatar is html page' do it 'is false if avatar is html page' do
user.update_attribute(:avatar, 'uploads/avatar.html') user.update_attribute(:avatar, 'uploads/avatar.html')
expect(user.avatar_type).to eq(['file format is not supported. Please try one of the following supported formats: png, jpg, jpeg, gif, bmp, tiff']) expect(user.avatar_type).to eq(['file format is not supported. Please try one of the following supported formats: png, jpg, jpeg, gif, bmp, tiff, ico'])
end end
end end
......
...@@ -39,7 +39,7 @@ describe BuildSerializer do ...@@ -39,7 +39,7 @@ describe BuildSerializer do
expect(subject[:label]).to eq('failed') expect(subject[:label]).to eq('failed')
expect(subject[:tooltip]).to eq('failed <br> (unknown failure)') expect(subject[:tooltip]).to eq('failed <br> (unknown failure)')
expect(subject[:icon]).to eq(status.icon) expect(subject[:icon]).to eq(status.icon)
expect(subject[:favicon]).to match_asset_path("/assets/ci_favicons/#{status.favicon}.ico") expect(subject[:favicon]).to match_asset_path("/assets/ci_favicons/#{status.favicon}.png")
end end
end end
...@@ -54,7 +54,7 @@ describe BuildSerializer do ...@@ -54,7 +54,7 @@ describe BuildSerializer do
expect(subject[:label]).to eq('passed') expect(subject[:label]).to eq('passed')
expect(subject[:tooltip]).to eq('passed') expect(subject[:tooltip]).to eq('passed')
expect(subject[:icon]).to eq(status.icon) expect(subject[:icon]).to eq(status.icon)
expect(subject[:favicon]).to match_asset_path("/assets/ci_favicons/#{status.favicon}.ico") expect(subject[:favicon]).to match_asset_path("/assets/ci_favicons/#{status.favicon}.png")
end end
end end
end end
......
...@@ -179,7 +179,7 @@ describe PipelineSerializer do ...@@ -179,7 +179,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 match_asset_path("/assets/ci_favicons/#{status.favicon}.ico") expect(subject[:favicon]).to match_asset_path("/assets/ci_favicons/#{status.favicon}.png")
end end
end end
end end
......
...@@ -18,17 +18,7 @@ describe StatusEntity do ...@@ -18,17 +18,7 @@ describe StatusEntity do
it 'contains status details' do it 'contains status details' do
expect(subject).to include :text, :icon, :favicon, :label, :group, :tooltip expect(subject).to include :text, :icon, :favicon, :label, :group, :tooltip
expect(subject).to include :has_details, :details_path expect(subject).to include :has_details, :details_path
expect(subject[:favicon]).to match_asset_path('/assets/ci_favicons/favicon_status_success.ico') expect(subject[:favicon]).to match_asset_path('/assets/ci_favicons/favicon_status_success.png')
end
it 'contains a dev namespaced favicon if dev env' do
allow(Rails.env).to receive(:development?) { true }
expect(entity.as_json[:favicon]).to match_asset_path('/assets/ci_favicons/dev/favicon_status_success.ico')
end
it 'contains a canary namespaced favicon if canary env' do
stub_env('CANARY', 'true')
expect(entity.as_json[:favicon]).to match_asset_path('/assets/ci_favicons/canary/favicon_status_success.ico')
end end
end end
end end
...@@ -3,6 +3,7 @@ require 'spec_helper' ...@@ -3,6 +3,7 @@ require 'spec_helper'
describe SystemNoteService do describe SystemNoteService do
include Gitlab::Routing include Gitlab::Routing
include RepoHelpers include RepoHelpers
include AssetsHelpers
set(:group) { create(:group) } set(:group) { create(:group) }
set(:project) { create(:project, :repository, group: group) } set(:project) { create(:project, :repository, group: group) }
...@@ -769,6 +770,8 @@ describe SystemNoteService do ...@@ -769,6 +770,8 @@ describe SystemNoteService do
end end
describe "new reference" do describe "new reference" do
let(:favicon_path) { "http://localhost/assets/#{find_asset('favicon.png').digest_path}" }
before do before do
allow(JIRA::Resource::Remotelink).to receive(:all).and_return([]) allow(JIRA::Resource::Remotelink).to receive(:all).and_return([])
end end
...@@ -789,7 +792,7 @@ describe SystemNoteService do ...@@ -789,7 +792,7 @@ describe SystemNoteService do
object: { object: {
url: project_commit_url(project, commit), url: project_commit_url(project, commit),
title: "GitLab: Mentioned on commit - #{commit.title}", title: "GitLab: Mentioned on commit - #{commit.title}",
icon: { title: "GitLab", url16x16: "http://localhost/favicon.ico" }, icon: { title: "GitLab", url16x16: favicon_path },
status: { resolved: false } status: { resolved: false }
} }
) )
...@@ -815,7 +818,7 @@ describe SystemNoteService do ...@@ -815,7 +818,7 @@ describe SystemNoteService do
object: { object: {
url: project_issue_url(project, issue), url: project_issue_url(project, issue),
title: "GitLab: Mentioned on issue - #{issue.title}", title: "GitLab: Mentioned on issue - #{issue.title}",
icon: { title: "GitLab", url16x16: "http://localhost/favicon.ico" }, icon: { title: "GitLab", url16x16: favicon_path },
status: { resolved: false } status: { resolved: false }
} }
) )
...@@ -841,7 +844,7 @@ describe SystemNoteService do ...@@ -841,7 +844,7 @@ describe SystemNoteService do
object: { object: {
url: project_snippet_url(project, snippet), url: project_snippet_url(project, snippet),
title: "GitLab: Mentioned on snippet - #{snippet.title}", title: "GitLab: Mentioned on snippet - #{snippet.title}",
icon: { title: "GitLab", url16x16: "http://localhost/favicon.ico" }, icon: { title: "GitLab", url16x16: favicon_path },
status: { resolved: false } status: { resolved: false }
} }
) )
......
module AssetsHelpers
# In a CI environment the assets are not compiled, as there is a CI job
# `compile-assets` that compiles them in the prepare stage for all following
# specs.
# Locally the assets are precompiled dynamically.
#
# Sprockets doesn't provide one method to access an asset for both cases.
def find_asset(asset_name)
if ENV['CI']
Sprockets::Railtie.build_environment(Rails.application, true)[asset_name]
else
Rails.application.assets.find_asset(asset_name)
end
end
end
require 'spec_helper'
RSpec.describe FaviconUploader do
include CarrierWave::Test::Matchers
let(:uploader) { described_class.new(build_stubbed(:user)) }
after do
uploader.remove!
end
def upload_fixture(filename)
fixture_file_upload(Rails.root.join('spec', 'fixtures', filename))
end
context 'versions' do
before do
uploader.store!(upload_fixture('dk.png'))
end
it 'has the correct format' do
expect(uploader.favicon_main).to be_format('png')
end
it 'has the correct dimensions' do
expect(uploader.favicon_main).to have_dimensions(32, 32)
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