Commit 3cca3c39 authored by Alex Kalderimis's avatar Alex Kalderimis Committed by Patrick Bair

Introduce a DB color type

Represents `Label#color` as a `Gitlab::Color`, which is a domain
object for a hex color.
parent 06645d16
......@@ -61,7 +61,7 @@ module LabelsHelper
render_label_text(
label.name,
suffix: suffix,
css_class: "gl-label-text #{text_color_class_for_bg(label.color)}",
css_class: "gl-label-text #{label.text_color_class}",
bg_color: label.color
)
end
......@@ -114,30 +114,8 @@ module LabelsHelper
end
end
def text_color_class_for_bg(bg_color)
if light_color?(bg_color)
'gl-label-text-dark'
else
'gl-label-text-light'
end
end
def text_color_for_bg(bg_color)
if light_color?(bg_color)
'#333333'
else
'#FFFFFF'
end
end
def light_color?(color)
if color.length == 4
r, g, b = color[1, 4].scan(/./).map { |v| (v * 2).hex }
else
r, g, b = color[1, 7].scan(/.{2}/).map(&:hex)
end
(r + g + b) > 500
::Gitlab::Color.of(bg_color).contrast
end
def labels_filter_path_with_defaults(only_group_labels: false, include_ancestor_groups: true, include_descendant_groups: false)
......
......@@ -12,8 +12,9 @@ class Label < ApplicationRecord
cache_markdown_field :description, pipeline: :single_line
DEFAULT_COLOR = '#6699cc'
DEFAULT_COLOR = ::Gitlab::Color.of('#6699cc')
attribute :color, ::Gitlab::Database::Type::Color.new
default_value_for :color, DEFAULT_COLOR
has_many :lists, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
......@@ -22,9 +23,9 @@ class Label < ApplicationRecord
has_many :issues, through: :label_links, source: :target, source_type: 'Issue'
has_many :merge_requests, through: :label_links, source: :target, source_type: 'MergeRequest'
before_validation :strip_whitespace_from_title_and_color
before_validation :strip_whitespace_from_title
validates :color, color: true, allow_blank: false
validates :color, color: true, presence: true
# Don't allow ',' for label titles
validates :title, presence: true, format: { with: /\A[^,]+\z/ }
......@@ -212,7 +213,7 @@ class Label < ApplicationRecord
end
def text_color
LabelsHelper.text_color_for_bg(self.color)
color.contrast
end
def title=(value)
......@@ -285,8 +286,8 @@ class Label < ApplicationRecord
CGI.unescapeHTML(Sanitize.clean(value.to_s))
end
def strip_whitespace_from_title_and_color
%w(color title).each { |attr| self[attr] = self[attr]&.strip }
def strip_whitespace_from_title
self[:title] = title&.strip
end
end
......
......@@ -14,6 +14,10 @@ class LabelPresenter < Gitlab::View::Presenter::Delegated
end
end
def text_color_class
"gl-label-text-#{label.color.contrast.luminosity}"
end
def destroy_path
case label
when GroupLabel then group_label_path(label.group, label)
......
......@@ -57,7 +57,8 @@ module Analytics
def html_description(event)
options = {}
if event.label_based?
options[:label_html] = render_label(event.label, link: '', small: true, tooltip: true)
label = event.label.present
options[:label_html] = render_label(label, link: '', small: true, tooltip: true)
end
content_tag(:p) { event.html_description(options).html_safe }
......
......@@ -4,7 +4,9 @@ class LabelEntity < Grape::Entity
expose :id
expose :title
expose :color
expose :color do |label|
label.color.to_s
end
expose :description
expose :group_id
expose :project_id
......
......@@ -2,162 +2,8 @@
module Labels
class BaseService < ::BaseService
COLOR_NAME_TO_HEX = {
black: '#000000',
silver: '#C0C0C0',
gray: '#808080',
white: '#FFFFFF',
maroon: '#800000',
red: '#FF0000',
purple: '#800080',
fuchsia: '#FF00FF',
green: '#008000',
lime: '#00FF00',
olive: '#808000',
yellow: '#FFFF00',
navy: '#000080',
blue: '#0000FF',
teal: '#008080',
aqua: '#00FFFF',
orange: '#FFA500',
aliceblue: '#F0F8FF',
antiquewhite: '#FAEBD7',
aquamarine: '#7FFFD4',
azure: '#F0FFFF',
beige: '#F5F5DC',
bisque: '#FFE4C4',
blanchedalmond: '#FFEBCD',
blueviolet: '#8A2BE2',
brown: '#A52A2A',
burlywood: '#DEB887',
cadetblue: '#5F9EA0',
chartreuse: '#7FFF00',
chocolate: '#D2691E',
coral: '#FF7F50',
cornflowerblue: '#6495ED',
cornsilk: '#FFF8DC',
crimson: '#DC143C',
darkblue: '#00008B',
darkcyan: '#008B8B',
darkgoldenrod: '#B8860B',
darkgray: '#A9A9A9',
darkgreen: '#006400',
darkgrey: '#A9A9A9',
darkkhaki: '#BDB76B',
darkmagenta: '#8B008B',
darkolivegreen: '#556B2F',
darkorange: '#FF8C00',
darkorchid: '#9932CC',
darkred: '#8B0000',
darksalmon: '#E9967A',
darkseagreen: '#8FBC8F',
darkslateblue: '#483D8B',
darkslategray: '#2F4F4F',
darkslategrey: '#2F4F4F',
darkturquoise: '#00CED1',
darkviolet: '#9400D3',
deeppink: '#FF1493',
deepskyblue: '#00BFFF',
dimgray: '#696969',
dimgrey: '#696969',
dodgerblue: '#1E90FF',
firebrick: '#B22222',
floralwhite: '#FFFAF0',
forestgreen: '#228B22',
gainsboro: '#DCDCDC',
ghostwhite: '#F8F8FF',
gold: '#FFD700',
goldenrod: '#DAA520',
greenyellow: '#ADFF2F',
grey: '#808080',
honeydew: '#F0FFF0',
hotpink: '#FF69B4',
indianred: '#CD5C5C',
indigo: '#4B0082',
ivory: '#FFFFF0',
khaki: '#F0E68C',
lavender: '#E6E6FA',
lavenderblush: '#FFF0F5',
lawngreen: '#7CFC00',
lemonchiffon: '#FFFACD',
lightblue: '#ADD8E6',
lightcoral: '#F08080',
lightcyan: '#E0FFFF',
lightgoldenrodyellow: '#FAFAD2',
lightgray: '#D3D3D3',
lightgreen: '#90EE90',
lightgrey: '#D3D3D3',
lightpink: '#FFB6C1',
lightsalmon: '#FFA07A',
lightseagreen: '#20B2AA',
lightskyblue: '#87CEFA',
lightslategray: '#778899',
lightslategrey: '#778899',
lightsteelblue: '#B0C4DE',
lightyellow: '#FFFFE0',
limegreen: '#32CD32',
linen: '#FAF0E6',
mediumaquamarine: '#66CDAA',
mediumblue: '#0000CD',
mediumorchid: '#BA55D3',
mediumpurple: '#9370DB',
mediumseagreen: '#3CB371',
mediumslateblue: '#7B68EE',
mediumspringgreen: '#00FA9A',
mediumturquoise: '#48D1CC',
mediumvioletred: '#C71585',
midnightblue: '#191970',
mintcream: '#F5FFFA',
mistyrose: '#FFE4E1',
moccasin: '#FFE4B5',
navajowhite: '#FFDEAD',
oldlace: '#FDF5E6',
olivedrab: '#6B8E23',
orangered: '#FF4500',
orchid: '#DA70D6',
palegoldenrod: '#EEE8AA',
palegreen: '#98FB98',
paleturquoise: '#AFEEEE',
palevioletred: '#DB7093',
papayawhip: '#FFEFD5',
peachpuff: '#FFDAB9',
peru: '#CD853F',
pink: '#FFC0CB',
plum: '#DDA0DD',
powderblue: '#B0E0E6',
rosybrown: '#BC8F8F',
royalblue: '#4169E1',
saddlebrown: '#8B4513',
salmon: '#FA8072',
sandybrown: '#F4A460',
seagreen: '#2E8B57',
seashell: '#FFF5EE',
sienna: '#A0522D',
skyblue: '#87CEEB',
slateblue: '#6A5ACD',
slategray: '#708090',
slategrey: '#708090',
snow: '#FFFAFA',
springgreen: '#00FF7F',
steelblue: '#4682B4',
tan: '#D2B48C',
thistle: '#D8BFD8',
tomato: '#FF6347',
turquoise: '#40E0D0',
violet: '#EE82EE',
wheat: '#F5DEB3',
whitesmoke: '#F5F5F5',
yellowgreen: '#9ACD32',
rebeccapurple: '#663399'
}.freeze
def convert_color_name_to_hex
color = params[:color]
color_name = color.strip.downcase
return color if color_name.start_with?('#')
COLOR_NAME_TO_HEX[color_name.to_sym] || color
::Gitlab::Color.of(params[:color])
end
end
end
......@@ -12,11 +12,13 @@
# end
#
class ColorValidator < ActiveModel::EachValidator
PATTERN = /\A\#(?:[0-9A-Fa-f]{3}){1,2}\Z/.freeze
def validate_each(record, attribute, value)
unless value =~ PATTERN
record.errors.add(attribute, "must be a valid color code")
case value
when NilClass then return
when ::Gitlab::Color then return if value.valid?
when ::String then return if ::Gitlab::Color.new(value).valid?
end
record.errors.add(attribute, "must be a valid color code")
end
end
......@@ -13,11 +13,11 @@ module EE
render_label_text(
label.scoped_label_key,
css_class: "gl-label-text #{text_color_class_for_bg(label.color)}",
css_class: "gl-label-text #{label.text_color_class}",
bg_color: label.color
) + render_label_text(
label.scoped_label_value,
css_class: "gl-label-text-scoped #{('gl-label-text-dark' if light_color?(label.color))}",
css_class: "gl-label-text-scoped #{label.text_color_class}",
suffix: suffix
)
end
......
......@@ -61,7 +61,7 @@ RSpec.describe BulkImports::Groups::Pipelines::EpicsPipeline do
expect(group.epics.first.labels.count).to eq(1)
expect(label.title).to eq('title')
expect(label.description).to eq('description')
expect(label.color).to eq('#cd2c5c')
expect(label.color).to be_color('#cd2c5c')
end
it 'imports epic system note metadata' do
......
......@@ -51,7 +51,7 @@ RSpec.describe Gitlab::ImportExport::Group::LegacyTreeRestorer do
expect(group.epics.first.labels.count).to eq(1)
expect(label.title).to eq('title')
expect(label.description).to eq('description')
expect(label.color).to eq('#cd2c5c')
expect(label.color).to be_color('#cd2c5c')
end
end
......
......@@ -107,7 +107,7 @@ RSpec.describe Gitlab::ImportExport::Group::LegacyTreeSaver do
epic_label = epic_json['label_links'].first['label']
expect(epic_label['title']).to eq(label.title)
expect(epic_label['description']).to eq(label.description)
expect(epic_label['color']).to eq(label.color)
expect(epic_label['color']).to be_color(label.color)
end
end
......
......@@ -59,7 +59,7 @@ RSpec.describe Gitlab::ImportExport::Group::TreeRestorer do
expect(group.epics.first.labels.count).to eq(1)
expect(label.title).to eq('title')
expect(label.description).to eq('description')
expect(label.color).to eq('#cd2c5c')
expect(label.color).to be_color('#cd2c5c')
end
end
......
......@@ -105,7 +105,7 @@ RSpec.describe Gitlab::ImportExport::Group::TreeSaver do
epic_label = epic_json['label_links'].first['label']
expect(epic_label['title']).to eq(label.title)
expect(epic_label['description']).to eq(label.description)
expect(epic_label['color']).to eq(label.color)
expect(epic_label['color']).to be_color(label.color)
end
end
......
......@@ -32,7 +32,7 @@ RSpec.describe Security::AutoFixLabelService do
expect(result).to be_success
expect(label.title).to eq(title)
expect(label.color).to eq(color)
expect(label.color).to be_color(color)
expect(label.description).to eq(description)
end
end
......
......@@ -3,7 +3,11 @@
module API
module Entities
class LabelBasic < Grape::Entity
expose :id, :name, :color, :description, :description_html, :text_color
expose :id, :name, :description, :description_html, :text_color
expose :color do |label, options|
label.color.to_s
end
end
end
end
# frozen_string_literal: true
module Gitlab
class Color
PATTERN = /\A\#(?:[0-9A-Fa-f]{3}){1,2}\Z/.freeze
def initialize(value)
@value = value&.strip&.freeze
end
module Constants
DARK = Color.new('#333333')
LIGHT = Color.new('#FFFFFF')
COLOR_NAME_TO_HEX = {
black: '#000000',
silver: '#C0C0C0',
gray: '#808080',
white: '#FFFFFF',
maroon: '#800000',
red: '#FF0000',
purple: '#800080',
fuchsia: '#FF00FF',
green: '#008000',
lime: '#00FF00',
olive: '#808000',
yellow: '#FFFF00',
navy: '#000080',
blue: '#0000FF',
teal: '#008080',
aqua: '#00FFFF',
orange: '#FFA500',
aliceblue: '#F0F8FF',
antiquewhite: '#FAEBD7',
aquamarine: '#7FFFD4',
azure: '#F0FFFF',
beige: '#F5F5DC',
bisque: '#FFE4C4',
blanchedalmond: '#FFEBCD',
blueviolet: '#8A2BE2',
brown: '#A52A2A',
burlywood: '#DEB887',
cadetblue: '#5F9EA0',
chartreuse: '#7FFF00',
chocolate: '#D2691E',
coral: '#FF7F50',
cornflowerblue: '#6495ED',
cornsilk: '#FFF8DC',
crimson: '#DC143C',
darkblue: '#00008B',
darkcyan: '#008B8B',
darkgoldenrod: '#B8860B',
darkgray: '#A9A9A9',
darkgreen: '#006400',
darkgrey: '#A9A9A9',
darkkhaki: '#BDB76B',
darkmagenta: '#8B008B',
darkolivegreen: '#556B2F',
darkorange: '#FF8C00',
darkorchid: '#9932CC',
darkred: '#8B0000',
darksalmon: '#E9967A',
darkseagreen: '#8FBC8F',
darkslateblue: '#483D8B',
darkslategray: '#2F4F4F',
darkslategrey: '#2F4F4F',
darkturquoise: '#00CED1',
darkviolet: '#9400D3',
deeppink: '#FF1493',
deepskyblue: '#00BFFF',
dimgray: '#696969',
dimgrey: '#696969',
dodgerblue: '#1E90FF',
firebrick: '#B22222',
floralwhite: '#FFFAF0',
forestgreen: '#228B22',
gainsboro: '#DCDCDC',
ghostwhite: '#F8F8FF',
gold: '#FFD700',
goldenrod: '#DAA520',
greenyellow: '#ADFF2F',
grey: '#808080',
honeydew: '#F0FFF0',
hotpink: '#FF69B4',
indianred: '#CD5C5C',
indigo: '#4B0082',
ivory: '#FFFFF0',
khaki: '#F0E68C',
lavender: '#E6E6FA',
lavenderblush: '#FFF0F5',
lawngreen: '#7CFC00',
lemonchiffon: '#FFFACD',
lightblue: '#ADD8E6',
lightcoral: '#F08080',
lightcyan: '#E0FFFF',
lightgoldenrodyellow: '#FAFAD2',
lightgray: '#D3D3D3',
lightgreen: '#90EE90',
lightgrey: '#D3D3D3',
lightpink: '#FFB6C1',
lightsalmon: '#FFA07A',
lightseagreen: '#20B2AA',
lightskyblue: '#87CEFA',
lightslategray: '#778899',
lightslategrey: '#778899',
lightsteelblue: '#B0C4DE',
lightyellow: '#FFFFE0',
limegreen: '#32CD32',
linen: '#FAF0E6',
mediumaquamarine: '#66CDAA',
mediumblue: '#0000CD',
mediumorchid: '#BA55D3',
mediumpurple: '#9370DB',
mediumseagreen: '#3CB371',
mediumslateblue: '#7B68EE',
mediumspringgreen: '#00FA9A',
mediumturquoise: '#48D1CC',
mediumvioletred: '#C71585',
midnightblue: '#191970',
mintcream: '#F5FFFA',
mistyrose: '#FFE4E1',
moccasin: '#FFE4B5',
navajowhite: '#FFDEAD',
oldlace: '#FDF5E6',
olivedrab: '#6B8E23',
orangered: '#FF4500',
orchid: '#DA70D6',
palegoldenrod: '#EEE8AA',
palegreen: '#98FB98',
paleturquoise: '#AFEEEE',
palevioletred: '#DB7093',
papayawhip: '#FFEFD5',
peachpuff: '#FFDAB9',
peru: '#CD853F',
pink: '#FFC0CB',
plum: '#DDA0DD',
powderblue: '#B0E0E6',
rosybrown: '#BC8F8F',
royalblue: '#4169E1',
saddlebrown: '#8B4513',
salmon: '#FA8072',
sandybrown: '#F4A460',
seagreen: '#2E8B57',
seashell: '#FFF5EE',
sienna: '#A0522D',
skyblue: '#87CEEB',
slateblue: '#6A5ACD',
slategray: '#708090',
slategrey: '#708090',
snow: '#FFFAFA',
springgreen: '#00FF7F',
steelblue: '#4682B4',
tan: '#D2B48C',
thistle: '#D8BFD8',
tomato: '#FF6347',
turquoise: '#40E0D0',
violet: '#EE82EE',
wheat: '#F5DEB3',
whitesmoke: '#F5F5F5',
yellowgreen: '#9ACD32',
rebeccapurple: '#663399'
}.stringify_keys.transform_values { Color.new(_1) }.freeze
end
def self.of(color)
raise ArgumentError, 'No color spec' unless color
return color if color.is_a?(self)
color = color.to_s.strip
Constants::COLOR_NAME_TO_HEX[color.downcase] || new(color)
end
def to_s
@value.to_s
end
def as_json(_options = nil)
to_s
end
def eql(other)
return false unless other.is_a?(self.class)
to_s == other.to_s
end
alias_method :==, :eql
def valid?
PATTERN.match?(@value)
end
def light?
valid? && rgb.sum > 500
end
def luminosity
return :light if light?
:dark
end
def contrast
return Constants::DARK if light?
Constants::LIGHT
end
private
def rgb
return [] unless valid?
@rgb ||= begin
if @value.length == 4
@value[1, 4].scan(/./).map { |v| (v * 2).hex }
else
@value[1, 7].scan(/.{2}/).map(&:hex)
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Database
module Type
class Color < ActiveModel::Type::Value
def serialize(value)
value.to_s if value
end
def serializable?(value)
value.nil? || value.is_a?(::String) || value.is_a?(::Gitlab::Color)
end
def cast_value(value)
::Gitlab::Color.new(value.to_s)
end
end
end
end
end
......@@ -114,16 +114,16 @@ RSpec.describe LabelsHelper do
describe 'text_color_for_bg' do
it 'uses light text on dark backgrounds' do
expect(text_color_for_bg('#222E2E')).to eq('#FFFFFF')
expect(text_color_for_bg('#222E2E')).to be_color('#FFFFFF')
end
it 'uses dark text on light backgrounds' do
expect(text_color_for_bg('#EEEEEE')).to eq('#333333')
expect(text_color_for_bg('#EEEEEE')).to be_color('#333333')
end
it 'supports RGB triplets' do
expect(text_color_for_bg('#FFF')).to eq '#333333'
expect(text_color_for_bg('#000')).to eq '#FFFFFF'
expect(text_color_for_bg('#FFF')).to be_color '#333333'
expect(text_color_for_bg('#000')).to be_color '#FFFFFF'
end
end
......
......@@ -277,7 +277,7 @@ RSpec.describe Banzai::Filter::References::LabelReferenceFilter do
end
context 'References with html entities' do
let!(:label) { create(:label, name: '&lt;html&gt;', project: project) }
let!(:label) { create(:label, title: '&lt;html&gt;', project: project) }
it 'links to a valid reference' do
doc = reference_filter('See ~"&lt;html&gt;"')
......
......@@ -43,7 +43,7 @@ RSpec.describe BulkImports::Common::Pipelines::LabelsPipeline do
expect(label.title).to eq('Label 1')
expect(label.description).to eq('Label 1')
expect(label.color).to eq('#6699cc')
expect(label.color).to be_color('#6699cc')
expect(File.directory?(tmpdir)).to eq(false)
end
end
......
# frozen_string_literal: true
require 'fast_spec_helper'
RSpec.describe Gitlab::Color do
describe ".of" do
described_class::Constants::COLOR_NAME_TO_HEX.each do |name, value|
it "parses #{name} to #{value}" do
expect(described_class.of(name)).to eq(value)
end
end
it 'parses hex literals as colors' do
expect(described_class.of('#fff')).to eq(described_class.new('#fff'))
expect(described_class.of('#fefefe')).to eq(described_class.new('#fefefe'))
end
it 'raises if the input is nil' do
expect { described_class.of(nil) }.to raise_error(ArgumentError)
end
it 'returns an invalid color if the input is not valid' do
expect(described_class.of('unknown color')).not_to be_valid
end
end
describe '#new' do
it 'handles nil values' do
expect(described_class.new(nil)).to eq(described_class.new(nil))
end
it 'strips input' do
expect(described_class.new(' abc ')).to eq(described_class.new('abc'))
end
end
describe '#valid?' do
described_class::Constants::COLOR_NAME_TO_HEX.each_key do |name|
specify "#{name} is a valid color" do
expect(described_class.of(name)).to be_valid
end
end
specify '#fff is a valid color' do
expect(described_class.new('#fff')).to be_valid
end
specify '#ffffff is a valid color' do
expect(described_class.new('#ffffff')).to be_valid
end
specify '#ABCDEF is a valid color' do
expect(described_class.new('#ABCDEF')).to be_valid
end
specify '#123456 is a valid color' do
expect(described_class.new('#123456')).to be_valid
end
specify '#1234567 is not a valid color' do
expect(described_class.new('#1234567')).not_to be_valid
end
specify 'fff is not a valid color' do
expect(described_class.new('fff')).not_to be_valid
end
specify '#deadbeaf is not a valid color' do
expect(described_class.new('#deadbeaf')).not_to be_valid
end
specify '#a1b2c3 is a valid color' do
expect(described_class.new('#a1b2c3')).to be_valid
end
specify 'nil is not a valid color' do
expect(described_class.new(nil)).not_to be_valid
end
end
describe '#light?' do
specify '#fff is light' do
expect(described_class.new('#fff')).to be_light
end
specify '#a7a7a7 is light' do
expect(described_class.new('#a7a7a7')).to be_light
end
specify '#a6a7a7 is dark' do
expect(described_class.new('#a6a7a7')).not_to be_light
end
specify '#000 is dark' do
expect(described_class.new('#000')).not_to be_light
end
specify 'invalid colors are not light' do
expect(described_class.new('not-a-color')).not_to be_light
end
end
describe '#contrast' do
context 'with light colors' do
it 'is dark' do
%w[#fff #fefefe #a7a7a7].each do |hex|
expect(described_class.new(hex)).to have_attributes(
contrast: described_class::Constants::DARK,
luminosity: :light
)
end
end
end
context 'with dark colors' do
it 'is light' do
%w[#000 #a6a7a7].each do |hex|
expect(described_class.new(hex)).to have_attributes(
contrast: described_class::Constants::LIGHT,
luminosity: :dark
)
end
end
end
end
describe 'as_json' do
it 'serializes correctly' do
expect(described_class.new('#f0f1f2').as_json).to eq('#f0f1f2')
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ::Gitlab::Database::Type::Color do
subject(:type) { described_class.new }
let(:color) { ::Gitlab::Color.of('red') }
it 'serializes by calling #to_s' do
expect(type.serialize(color)).to eq(color.to_s)
end
it 'serializes nil to nil' do
expect(type.serialize(nil)).to be_nil
end
it 'casts by calling Color::new' do
expect(type.cast('#fff')).to eq(::Gitlab::Color.new('#fff'))
end
it 'accepts colors as arguments to cast' do
expect(type.cast(color)).to eq(color)
end
it 'allows nil database values' do
expect(type.cast(nil)).to be_nil
end
it 'tells us what is serializable' do
[nil, 'foo', color].each do |value|
expect(type.serializable?(value)).to be true
end
end
it 'tells us what is not serializable' do
[0, 3.2, true, Time.current, { some: 'hash' }].each do |value|
expect(type.serializable?(value)).to be false
end
end
end
......@@ -67,24 +67,21 @@ RSpec.describe Label do
label = described_class.new(color: ' #abcdef ')
label.valid?
expect(label.color).to eq('#abcdef')
expect(label.color).to be_color('#abcdef')
end
it 'uses default color if color is missing' do
label = described_class.new(color: nil)
expect(label.color).to be(Label::DEFAULT_COLOR)
expect(label.color).to be_color(Label::DEFAULT_COLOR)
end
end
describe '#text_color' do
it 'uses default color if color is missing' do
expect(LabelsHelper).to receive(:text_color_for_bg).with(Label::DEFAULT_COLOR)
.and_return(spy)
label = described_class.new(color: nil)
label.text_color
expect(label.text_color).to eq(Label::DEFAULT_COLOR.contrast)
end
end
......
......@@ -140,7 +140,7 @@ RSpec.describe API::GroupLabels do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['name']).to eq(group_label1.name)
expect(json_response['color']).to eq(group_label1.color)
expect(json_response['color']).to be_color(group_label1.color)
expect(json_response['description']).to eq(group_label1.description)
end
end
......@@ -156,7 +156,7 @@ RSpec.describe API::GroupLabels do
expect(response).to have_gitlab_http_status(:created)
expect(json_response['name']).to eq(valid_new_label_title)
expect(json_response['color']).to eq('#FFAABB')
expect(json_response['color']).to be_color('#FFAABB')
expect(json_response['description']).to eq('test')
end
......@@ -169,7 +169,7 @@ RSpec.describe API::GroupLabels do
expect(response).to have_gitlab_http_status(:created)
expect(json_response['name']).to eq(valid_new_label_title)
expect(json_response['color']).to eq('#FFAABB')
expect(json_response['color']).to be_color('#FFAABB')
expect(json_response['description']).to be_nil
end
......@@ -276,7 +276,7 @@ RSpec.describe API::GroupLabels do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['name']).to eq(valid_new_label_title)
expect(json_response['color']).to eq('#FFFFFF')
expect(json_response['color']).to be_color('#FFFFFF')
expect(json_response['description']).to eq('test')
end
......@@ -332,7 +332,7 @@ RSpec.describe API::GroupLabels do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['name']).to eq(valid_new_label_title)
expect(json_response['color']).to eq('#FFFFFF')
expect(json_response['color']).to be_color('#FFFFFF')
expect(json_response['description']).to eq('test')
end
......
......@@ -34,7 +34,7 @@ RSpec.describe API::Labels do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['name']).to eq(valid_label_title_2)
expect(json_response['color']).to eq(label1.color)
expect(json_response['color']).to be_color(label1.color)
end
it "returns 200 if colors is changed (#{route_type} route)" do
......@@ -42,7 +42,7 @@ RSpec.describe API::Labels do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['name']).to eq(label1.name)
expect(json_response['color']).to eq('#FFFFFF')
expect(json_response['color']).to be_color('#FFFFFF')
end
it "returns 200 if a priority is added (#{route_type} route)" do
......@@ -86,7 +86,7 @@ RSpec.describe API::Labels do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['name']).to eq(valid_label_title_2)
expect(json_response['color']).to eq('#FFFFFF')
expect(json_response['color']).to be_color('#FFFFFF')
expect(json_response['description']).to eq('test')
end
......@@ -266,8 +266,8 @@ RSpec.describe API::Labels do
'open_merge_requests_count' => 0,
'name' => group_label.name,
'description' => nil,
'color' => a_string_matching(/^#\h{6}$/),
'text_color' => a_string_matching(/^#\h{6}$/),
'color' => a_valid_color,
'text_color' => a_valid_color,
'priority' => nil,
'subscribed' => false,
'is_project_label' => false)
......@@ -277,8 +277,8 @@ RSpec.describe API::Labels do
'open_merge_requests_count' => 1,
'name' => priority_label.name,
'description' => nil,
'color' => a_string_matching(/^#\h{6}$/),
'text_color' => a_string_matching(/^#\h{6}$/),
'color' => a_valid_color,
'text_color' => a_valid_color,
'priority' => 3,
'subscribed' => false,
'is_project_label' => true)
......@@ -336,7 +336,7 @@ RSpec.describe API::Labels do
expect(response).to have_gitlab_http_status(:created)
expect(json_response['name']).to eq(valid_label_title_2)
expect(json_response['color']).to eq('#FFAABB')
expect(json_response['color']).to be_color('#FFAABB')
expect(json_response['description']).to eq('test')
expect(json_response['priority']).to eq(2)
end
......@@ -350,7 +350,7 @@ RSpec.describe API::Labels do
expect(response).to have_gitlab_http_status(:created)
expect(json_response['name']).to eq(valid_label_title_2)
expect(json_response['color']).to eq('#FFAABB')
expect(json_response['color']).to be_color('#FFAABB')
expect(json_response['description']).to be_nil
expect(json_response['priority']).to be_nil
end
......@@ -365,7 +365,7 @@ RSpec.describe API::Labels do
expect(response).to have_gitlab_http_status(:created)
expect(json_response['name']).to eq(valid_label_title_2)
expect(json_response['color']).to eq('#FFAABB')
expect(json_response['color']).to be_color('#FFAABB')
expect(json_response['description']).to be_nil
expect(json_response['priority']).to eq(3)
end
......@@ -552,7 +552,7 @@ RSpec.describe API::Labels do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['name']).to eq(label1.name)
expect(json_response['color']).to eq(label1.color)
expect(json_response['color']).to be_color(label1.color.to_s)
end
context 'if group label already exists' do
......
......@@ -40,7 +40,7 @@ RSpec.describe LabelSerializer do
expect(subject.keys).to eq([:id, :title, :color, :project_id, :text_color])
expect(subject[:id]).to eq(resource.id)
expect(subject[:title]).to eq(resource.title)
expect(subject[:color]).to eq(resource.color)
expect(subject[:color]).to be_color(resource.color)
expect(subject[:text_color]).to eq(resource.text_color)
expect(subject[:project_id]).to eq(resource.project_id)
end
......
......@@ -14,7 +14,7 @@ RSpec.describe Labels::CreateService do
let(:unknown_color) { 'unknown' }
let(:no_color) { '' }
let(:expected_saved_color) { hex_color }
let(:expected_saved_color) { ::Gitlab::Color.of(hex_color) }
context 'in a project' do
context 'with color in hex-code' do
......@@ -47,7 +47,6 @@ RSpec.describe Labels::CreateService do
context 'with color surrounded by spaces' do
it 'creates a label' do
label = described_class.new(params_with(spaced_color)).execute(project: project)
expect(label).to be_persisted
expect(label.color).to eq expected_saved_color
end
......
......@@ -202,7 +202,7 @@ RSpec.describe Labels::PromoteService do
expect(new_label.title).to eq(promoted_label_name)
expect(new_label.description).to eq(promoted_description)
expect(new_label.color).to eq(promoted_color)
expect(new_label.color).to be_color(promoted_color)
end
it_behaves_like 'promoting a project label to a group label'
......
......@@ -13,7 +13,7 @@ RSpec.describe Labels::UpdateService do
let(:unknown_color) { 'unknown' }
let(:no_color) { '' }
let(:expected_saved_color) { hex_color }
let(:expected_saved_color) { ::Gitlab::Color.of(hex_color) }
before do
@label = Labels::CreateService.new(title: 'Initial', color: '#000000').execute(project: project)
......
......@@ -23,11 +23,11 @@ RSpec.describe Projects::CreateService, '#execute' do
end
it 'creates labels on project creation' do
created_label = project.labels.last
expect(created_label.type).to eq('ProjectLabel')
expect(created_label.project_id).to eq(project.id)
expect(created_label.title).to eq('bug')
expect(project.labels).to include have_attributes(
type: eq('ProjectLabel'),
project_id: eq(project.id),
title: eq('bug')
)
end
context 'using gitlab project import' do
......
# frozen_string_literal: true
# Assert that this value is a valid color equal to the argument
#
# ```
# expect(value).to be_color('#fff')
# ```
RSpec::Matchers.define :be_color do |expected|
match do |actual|
next false unless actual.present?
if expected
::Gitlab::Color.of(actual) == ::Gitlab::Color.of(expected)
else
::Gitlab::Color.of(actual).valid?
end
end
end
RSpec::Matchers.alias_matcher :a_valid_color, :be_color
......@@ -70,7 +70,7 @@ RSpec.shared_examples 'incident management label service' do
expect(execute).to be_success
expect(execute.payload).to eq(label: label)
expect(label.title).to eq(title)
expect(label.color).to eq(color)
expect(label.color).to be_color(color)
expect(label.description).to eq(description)
end
end
......
......@@ -23,7 +23,12 @@ RSpec.describe ColorValidator do
'#ffff' | false
'#000111222' | false
'invalid' | false
'red' | false
'000' | false
nil | true # use presence to validate non-nil
'' | false
Time.current | false
::Gitlab::Color.of(:red) | true
end
with_them do
......@@ -41,4 +46,22 @@ RSpec.describe ColorValidator do
Timeout.timeout(5.seconds) { subject.valid? }
end.not_to raise_error
end
context 'when color must be present' do
subject do
Class.new do
include ActiveModel::Model
include ActiveModel::Validations
attr_accessor :color
validates :color, color: true, presence: true
end.new
end
it 'rejects nil' do
subject.color = nil
expect(subject).not_to be_valid
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