redmineup 1.1.3 → 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,156 @@
1
+ # ActiveRecord mixins
2
+
3
+ This page documents the model mixins provided by `redmineup`.
4
+
5
+ ## `up_acts_as_taggable`
6
+
7
+ Adds polymorphic tagging support.
8
+
9
+ ### Migration
10
+
11
+ ```ruby
12
+ class CreateTags < ActiveRecord::Migration[6.1]
13
+ def change
14
+ ActiveRecord::Base.create_taggable_table
15
+ end
16
+ end
17
+ ```
18
+
19
+ Useful migration helpers:
20
+
21
+ - `ActiveRecord::Base.create_taggable_table`
22
+ - `ActiveRecord::Base.drop_taggable_table`
23
+ - `ActiveRecord::Base.add_tags_column(:tags, name: :color, type: :integer, default: nil)`
24
+ - `ActiveRecord::Base.remove_tags_columns(:tags, :color)`
25
+
26
+ ### Model
27
+
28
+ ```ruby
29
+ class Product < ApplicationRecord
30
+ up_acts_as_taggable
31
+ end
32
+ ```
33
+
34
+ ### Runtime API
35
+
36
+ - `record.tag_list` / `record.tag_list = 'a, b'`
37
+ - `record.save_tags` (called via callbacks)
38
+ - `Model.available_tags(project: @project, limit: 30)`
39
+ - `Model.tagged_with(['tag1', 'tag2'])`
40
+ - `Model.find_related_tags('tag1')`
41
+
42
+ ## `up_acts_as_draftable`
43
+
44
+ Adds draft save/restore workflow for unsaved or changed records.
45
+
46
+ ### Migration
47
+
48
+ ```ruby
49
+ class CreateDrafts < ActiveRecord::Migration[6.1]
50
+ def change
51
+ ActiveRecord::Base.create_drafts_table
52
+ end
53
+ end
54
+ ```
55
+
56
+ Helpers:
57
+
58
+ - `ActiveRecord::Base.create_drafts_table`
59
+ - `ActiveRecord::Base.drop_drafts_table`
60
+
61
+ ### Model
62
+
63
+ ```ruby
64
+ class WikiPage < ApplicationRecord
65
+ up_acts_as_draftable parent: :wiki
66
+ end
67
+ ```
68
+
69
+ ### Runtime API
70
+
71
+ - `record.save_draft`
72
+ - `record.update_draft(attrs)`
73
+ - `Model.drafts(User.current)`
74
+ - `Model.from_draft(draft_or_id)`
75
+
76
+ ## `up_acts_as_viewed`
77
+
78
+ Tracks unique views by user (or IP) and total views.
79
+
80
+ ### Migration
81
+
82
+ ```ruby
83
+ class CreateViewings < ActiveRecord::Migration[6.1]
84
+ def change
85
+ ActiveRecord::Base.create_viewings_table
86
+ end
87
+ end
88
+ ```
89
+
90
+ Optional helpers for models with embedded counters:
91
+
92
+ - `ActiveRecord::Base.generate_viewings_columns(table)`
93
+ - `Model.add_viewings_columns`
94
+ - `Model.remove_viewings_columns`
95
+ - `ActiveRecord::Base.drop_viewings_table`
96
+
97
+ ### Model
98
+
99
+ ```ruby
100
+ class Question < ApplicationRecord
101
+ up_acts_as_viewed
102
+ end
103
+ ```
104
+
105
+ ### Runtime API
106
+
107
+ - `record.view(request.remote_ip, User.current)`
108
+ - `record.viewed?`
109
+ - `record.view_count`
110
+ - `record.viewed_by?(request.remote_ip, User.current)`
111
+
112
+ ## `up_acts_as_votable` and `up_acts_as_voter`
113
+
114
+ Adds voting capabilities for content and users.
115
+
116
+ ### Migration
117
+
118
+ ```ruby
119
+ class CreateVotes < ActiveRecord::Migration[6.1]
120
+ def change
121
+ ActiveRecord::Base.create_votable_table
122
+ end
123
+ end
124
+ ```
125
+
126
+ Helpers:
127
+
128
+ - `ActiveRecord::Base.create_votable_table`
129
+ - `ActiveRecord::Base.drop_votable_table`
130
+
131
+ ### Models
132
+
133
+ ```ruby
134
+ class Question < ApplicationRecord
135
+ up_acts_as_votable
136
+ end
137
+
138
+ class User < ApplicationRecord
139
+ up_acts_as_voter
140
+ end
141
+ ```
142
+
143
+ ## `up_acts_as_priceable`
144
+
145
+ Generates currency-aware `*_to_s` methods for price fields.
146
+
147
+ ### Model
148
+
149
+ ```ruby
150
+ class Deal < ApplicationRecord
151
+ up_acts_as_priceable :amount, :tax_amount, :subtotal, :total
152
+ end
153
+ ```
154
+
155
+ This creates methods like `amount_to_s`, `tax_amount_to_s`, etc., backed by
156
+ `Redmineup::MoneyHelper#object_price`.
@@ -0,0 +1,71 @@
1
+ # Assets, money, and shared utilities
2
+
3
+ ## Asset helpers
4
+
5
+ Provided by `Redmineup::ExternalAssetsHelper`:
6
+
7
+ - `redmineup_assets`
8
+ - `select2_assets` (alias for `redmineup_assets`)
9
+ - `chartjs_assets`
10
+
11
+ `redmineup_assets` includes:
12
+
13
+ - `consumer.js`
14
+ - `select2.js`
15
+ - `select2_helpers.js`
16
+ - `redmineup.css`
17
+
18
+ The gem also hooks `view_layouts_base_html_head` and renders
19
+ `redmineup/_additional_assets`, so shared assets can be included globally.
20
+ Helpers are idempotent and safe to call from plugin views.
21
+
22
+ ## Money and currency
23
+
24
+ ### Settings API
25
+
26
+ `Redmineup::Settings::Money` provides access to plugin-level money settings:
27
+
28
+ - `default_currency`
29
+ - `major_currencies`
30
+ - `default_tax`
31
+ - `tax_type`, `tax_exclusive?`
32
+ - `thousands_delimiter`
33
+ - `decimal_separator`
34
+ - `disable_taxes?`
35
+
36
+ ### Formatting helpers
37
+
38
+ `Redmineup::MoneyHelper` provides:
39
+
40
+ - `price_to_currency(price, currency, options = {})`
41
+ - `object_price(record, :price)`
42
+ - `prices_collection_by_currency(hash, hide_zeros: true)`
43
+ - `collection_for_currencies_select`
44
+ - `all_currencies`
45
+
46
+ `up_acts_as_priceable` uses these helpers to generate `*_to_s` methods on
47
+ models.
48
+
49
+ ### Admin UI
50
+
51
+ `Redmineup::Currency.add_admin_money_menu` registers a Redmine admin menu item
52
+ that points to:
53
+
54
+ - `redmineup_settings_path(:money)`
55
+
56
+ ## Misc shared helpers and patches
57
+
58
+ - `Redmineup::CalendarsHelper#calendar_day_css_classes`
59
+ - Liquid patch for numeric conversion in `Liquid::StandardFilters`
60
+ - Liquid filters/drops under `lib/redmineup/liquid/**`
61
+ - Compatibility and ActionCable patches loaded at gem boot
62
+
63
+ ## Version requirements in plugins
64
+
65
+ Plugins typically guard compatibility in `init.rb`:
66
+
67
+ ```ruby
68
+ requires_redmineup version_or_higher: '1.1.5'
69
+ ```
70
+
71
+ Use this to fail fast when a plugin depends on newer helper or API behavior.
@@ -0,0 +1,108 @@
1
+ # Tagging and Select2 helpers
2
+
3
+ This page covers view helpers used by RedmineUP plugins for tag rendering,
4
+ tag editing, and select2-backed filters.
5
+
6
+ ## Tag rendering helpers
7
+
8
+ Provided by `Redmineup::TagsHelper`:
9
+
10
+ - `tag_link(url, tag_name, options = {})`
11
+ - `tag_links(tag_list, options = {}) { |tag| url }`
12
+ - `tag_cloud(tags, classes) { |tag, css_class| ... }`
13
+ - `tag_cloud_links(tags, key, options = {})`
14
+ - `tag_separator`
15
+
16
+ ### Recommended pattern
17
+
18
+ Define `*_tag_url` per plugin context:
19
+
20
+ ```ruby
21
+ def contact_tag_url(tag_name, options = {})
22
+ {
23
+ controller: 'contacts',
24
+ action: 'index',
25
+ set_filter: 1,
26
+ fields: [:tags],
27
+ values: { tags: [tag_name] },
28
+ operators: { tags: '=' }
29
+ }.merge(options)
30
+ end
31
+ ```
32
+
33
+ Then render the cloud via convention:
34
+
35
+ ```erb
36
+ <%= tag_cloud_links(Contact.available_tags(project: @project), :contact) %>
37
+ ```
38
+
39
+ `tag_cloud_links(tags, :contact)` expects `contact_tag_url(name)` to be defined
40
+ in the current view context.
41
+
42
+ ## Tag editors with Select2
43
+
44
+ Provided by `Redmineup::FormTagHelper`:
45
+
46
+ - `select2_tag(name, option_tags = nil, options = {})`
47
+ - `select2(...)` (alias)
48
+ - `tag_list_field_tag(name, value = [], options = {})`
49
+ - `transform_to_select2(type, options = {})`
50
+
51
+ `ActionView::Helpers::FormBuilder` is extended with:
52
+
53
+ - `f.tag_list_field(:tag_list, **options)`
54
+
55
+ ### Example: form object API
56
+
57
+ ```erb
58
+ <%= f.tag_list_field :tag_list, width: '95%' %>
59
+ ```
60
+
61
+ ### Example: standalone field API
62
+
63
+ ```erb
64
+ <%= tag_list_field_tag 'product[tag_list]', @product.tag_list, taggable_type: 'Product' %>
65
+ ```
66
+
67
+ ### Example: query/filter select2 transform
68
+
69
+ ```erb
70
+ <%= transform_to_select2 'product_tags',
71
+ url: auto_complete_taggable_tags_path(project_id: @project, taggable_type: 'Product') %>
72
+ ```
73
+
74
+ ## Tag autocomplete endpoint
75
+
76
+ `redmineup` adds a shared route:
77
+
78
+ - `GET /auto_completes/taggable_tags`
79
+
80
+ Named route:
81
+
82
+ - `auto_complete_taggable_tags_path`
83
+
84
+ Typical params used by plugins:
85
+
86
+ - `taggable_type`: model class name (`Contact`, `Person`, `Product`, `Issue`, `DriveEntry`)
87
+ - `project_id`: optional project scope
88
+ - `limit`: optional result size limit
89
+
90
+ Response format:
91
+
92
+ ```json
93
+ [
94
+ { "id": "important", "text": "important" },
95
+ { "id": "vip", "text": "vip" }
96
+ ]
97
+ ```
98
+
99
+ ## Real plugin usage
100
+
101
+ Patterns above are used by:
102
+
103
+ - `redmine_contacts` (`contact_tag_url`, `contact_tag_links`, `f.tag_list_field`)
104
+ - `redmine_people` (`people_tag_url`, `tag_cloud_links`, bulk edit tag forms)
105
+ - `redmine_products` (`product_tag_url`, `tag_list_field_tag`, select2 filters)
106
+ - `redmine_questions` (`question_tag_url`, `tag_cloud_links`)
107
+ - `redmine_drive` (`drive_entry_tag_url`, select2 tag filters)
108
+ - `redmineup_tags` (issue tag forms with `tag_list_field_tag`)
@@ -1,3 +1,5 @@
1
+ require 'digest/md5'
2
+
1
3
  module Redmineup
2
4
  module ActsAsTaggable #:nodoc:
3
5
  class Tag < ActiveRecord::Base #:nodoc:
@@ -17,6 +19,10 @@ module Redmineup
17
19
  where("LOWER(name) LIKE LOWER(?)", name).first || create(:name => name)
18
20
  end
19
21
 
22
+ def self.color_from_name(str)
23
+ "##{Digest::MD5.hexdigest(str.to_s)[0..5]}"
24
+ end
25
+
20
26
  def ==(object)
21
27
  super || (object.is_a?(Tag) && name == object.name)
22
28
  end
@@ -30,11 +36,15 @@ module Redmineup
30
36
  end
31
37
 
32
38
  def color
33
- return ('#' + "%06x" % super) unless super.nil?
39
+ if self.class.column_names.include?('color')
40
+ db_color = read_attribute(:color)
41
+ return ('#' + "%06x" % db_color) unless db_color.nil?
42
+ end
43
+ self.class.color_from_name(name)
34
44
  end
35
45
 
36
46
  def color=(color)
37
- write_attribute(:color, color.from(1).hex) unless color.blank?
47
+ write_attribute(:color, color.sub(/\A#/, '').hex) unless color.blank?
38
48
  end
39
49
 
40
50
  class << self
@@ -74,7 +84,7 @@ module Redmineup
74
84
  having = 'COUNT(*) > 0'
75
85
  having = [having, at_least, at_most].compact.join(' AND ')
76
86
  group_by = "#{Tag.table_name}.id, #{Tag.table_name}.name"
77
- # group_by << " AND #{having}" unless having.blank?
87
+ group_by << ", #{Tag.table_name}.color" if column_names.include?('color')
78
88
 
79
89
  select_condition = if self.column_names.include?('color')
80
90
  "#{Tag.table_name}.id, #{Tag.table_name}.name, #{Tag.table_name}.color, COUNT(*) AS count"
@@ -52,6 +52,7 @@ module Redmineup
52
52
  if !self.connection.table_exists?(tag_name_table)
53
53
  self.connection.create_table(tag_name_table) do |t|
54
54
  t.column :name, :string
55
+ t.column :color, :integer
55
56
  end
56
57
  end
57
58
 
@@ -64,6 +65,7 @@ module Redmineup
64
65
  # You should make sure that the column created is
65
66
  # long enough to store the required class names.
66
67
  t.column :taggable_type, :string
68
+ t.column :color, :integer, default: nil
67
69
 
68
70
  t.column :created_at, :datetime
69
71
  end
@@ -82,16 +84,16 @@ module Redmineup
82
84
  #
83
85
  # E.g.: ActiveRecord::Base.add_tags_column(:tags, name: :color, type: :string, default: nil)
84
86
  def add_tags_column(table_name = :tags, **options)
85
- tag_column = options || { name: :color, type: :int, default: nil }
87
+ tag_column = options
86
88
  tag_column.assert_valid_keys(:name, :type, :default)
87
89
 
88
90
  raise ArgumentError, "Table `#{table_name}' not found" unless self.connection.table_exists?(table_name)
89
91
 
90
92
  self.table_name = table_name
91
- unless self.connection.column_exists?(table_name, tag_column[:name])
92
- self.connection.add_column table_name, tag_column[:name], tag_column[:type], default: tag_column[:default]
93
- self.reset_column_information
94
- end
93
+ return if connection.column_exists?(table_name, tag_column[:name])
94
+
95
+ connection.add_column table_name, tag_column[:name], tag_column[:type], default: tag_column[:default]
96
+ reset_column_information
95
97
  end
96
98
 
97
99
  # Remove the up_acts_as_taggable specific columns
@@ -147,6 +149,7 @@ module Redmineup
147
149
  group_fields = ''
148
150
  group_fields << ", #{Tag.table_name}.created_at" if Tag.respond_to?(:created_at)
149
151
  group_fields << ", #{Tag.table_name}.updated_at" if Tag.respond_to?(:updated_at)
152
+ group_fields << ", #{Tag.table_name}.color" if Tag.column_names.include?('color')
150
153
 
151
154
  if base_class.respond_to?(:visible_condition)
152
155
  visible_condition = base_class.visible_condition(User.current)
@@ -1,4 +1,14 @@
1
1
  module Redmineup
2
- class Engine < Rails::Engine
3
- end
2
+ class Engine < Rails::Engine
3
+ config.to_prepare do
4
+ next unless defined?(::Redmine::Plugin)
5
+
6
+ require 'auto_completes_controller'
7
+
8
+ require File.expand_path('../redmineup/patches/compatibility_patch', __dir__)
9
+ require File.expand_path('../redmineup/patches/auto_completes_controller_patch', __dir__)
10
+
11
+ require File.expand_path('../redmineup/hooks/views_layouts_hook', __dir__)
12
+ end
13
+ end
4
14
  end
@@ -2,12 +2,18 @@ module Redmineup
2
2
  module ExternalAssetsHelper
3
3
  include ActionView::Helpers::JavaScriptHelper
4
4
 
5
+ def redmineup_assets
6
+ return if @redmineup_assets_included
7
+
8
+ @redmineup_assets_included = true
9
+ javascript_include_tag('consumer', plugin: GEM_NAME) +
10
+ javascript_include_tag('select2', plugin: GEM_NAME) +
11
+ javascript_include_tag('select2_helpers', plugin: GEM_NAME) +
12
+ stylesheet_link_tag('redmineup', plugin: GEM_NAME)
13
+ end
14
+
5
15
  def select2_assets
6
- return if @select2_tag_included
7
- @select2_tag_included = true
8
- javascript_include_tag('select2', plugin: GEM_NAME) +
9
- stylesheet_link_tag('select2', plugin: GEM_NAME) +
10
- javascript_include_tag('select2_helpers', plugin: GEM_NAME)
16
+ redmineup_assets
11
17
  end
12
18
 
13
19
  def chartjs_assets
@@ -49,6 +49,31 @@ module Redmineup
49
49
 
50
50
  alias select2 select2_tag
51
51
 
52
+ # Renders a select2 tag field for a tag list (standalone, no form object).
53
+ # Analogous to text_field_tag.
54
+ #
55
+ # ==== Example
56
+ # tag_list_field_tag 'entry[tag_list]', entry.tag_list,
57
+ # url: auto_complete_taggable_tags_path(taggable_type: 'DriveEntry')
58
+ def tag_list_field_tag(name, value = [], options = {})
59
+ url = options.delete(:url)
60
+ taggable_type = options.delete(:taggable_type)
61
+ width = options.delete(:width)
62
+ project_id = options.delete(:project_id)
63
+ placeholder = options.delete(:placeholder)
64
+ select2_tag(
65
+ name,
66
+ options_for_select(value, value),
67
+ {
68
+ multiple: true,
69
+ width: width || '95%',
70
+ url: url || auto_complete_taggable_tags_path(taggable_type: taggable_type, project_id: project_id),
71
+ placeholder: placeholder || l(:lable_redmineup_add_tag),
72
+ tags: true
73
+ }.merge(options)
74
+ )
75
+ end
76
+
52
77
  # Transforms select filters of +type+ fields into select2
53
78
  #
54
79
  # ==== Examples
@@ -86,3 +111,20 @@ module Redmineup
86
111
 
87
112
  end
88
113
  end
114
+
115
+ # Extends Rails FormBuilder with tag_list_field, analogous to f.text_field.
116
+ #
117
+ # ==== Example
118
+ # <%= f.tag_list_field :tag_list %>
119
+ ActionView::Helpers::FormBuilder.class_eval do
120
+ def tag_list_field(method, **options)
121
+ url = options.fetch(:url) {
122
+ @template.auto_complete_taggable_tags_path(taggable_type: @object.class.name)
123
+ }
124
+ @template.tag_list_field_tag(
125
+ "#{@object_name}[#{method}]",
126
+ @object.public_send(method),
127
+ {url: url}.merge(options.except(:url))
128
+ )
129
+ end
130
+ end
@@ -9,5 +9,46 @@ module Redmineup
9
9
  yield tag, classes[index]
10
10
  end
11
11
  end
12
+
13
+ def tag_link(url, tag_name, options = {})
14
+ count = options.delete(:count)
15
+ inner = link_to(tag_name, url, options)
16
+ inner << content_tag('span', "(#{count})", class: 'tag-count') if count
17
+ css = monochrome? ? 'tag-label' : 'tag-label-color'
18
+ style = monochrome? ? {} : { style: "background-color: #{Redmineup::ActsAsTaggable::Tag.color_from_name(tag_name)}" }
19
+ content_tag(:span, inner, { class: css }.merge(style))
20
+ end
21
+
22
+ def tag_separator
23
+ monochrome? ? ', ' : ' '
24
+ end
25
+
26
+ def monochrome?
27
+ @monochrome ||= defined?(RedmineupTags) && !RedmineupTags.use_colors?
28
+ end
29
+
30
+ def tag_links(tag_list, options = {}, &url_block)
31
+ return if tag_list.blank?
32
+ content_tag(:span, class: 'tag_list') do
33
+ safe_join(tag_list.map { |tag| tag_link(url_block.call(tag), tag, options.dup) }, tag_separator)
34
+ end
35
+ end
36
+
37
+ # Renders span.tag_list from an array of tag objects (respond to .name and .count).
38
+ # Resolves URL via `{key}_tag_url(name)` in the current view context.
39
+ #
40
+ # tag_cloud_links(contact.available_tags, :contact) # → contact_tag_url(name)
41
+ # tag_cloud_links(tags_cloud, :people) # → people_tag_url(name)
42
+ def tag_cloud_links(tags, key, options = {})
43
+ return if tags.blank?
44
+ url_method = :"#{key}_tag_url"
45
+ raise NoMethodError, "#{url_method} is not defined in view context" unless respond_to?(url_method, true)
46
+ content_tag(:span, class: 'tag_list') do
47
+ safe_join(tags.map { |tag|
48
+ tag_link(send(url_method, tag.name), tag.name, options.merge(count: tag.count).dup)
49
+ }, tag_separator)
50
+ end
51
+ end
52
+
12
53
  end
13
54
  end
@@ -3,11 +3,7 @@
3
3
  module Redmineup
4
4
  module Hooks
5
5
  class ViewsLayoutsHook < Redmine::Hook::ViewListener
6
- def view_layouts_base_html_head(_context = {})
7
- stylesheet_link_tag(:calendars, plugin: 'redmineup') +
8
- stylesheet_link_tag(:money, plugin: 'redmineup') +
9
- javascript_include_tag(:consumer, plugin: 'redmineup')
10
- end
6
+ render_on :view_layouts_base_html_head, partial: 'redmineup/additional_assets'
11
7
  end
12
8
  end
13
9
  end
@@ -0,0 +1,19 @@
1
+ module Redmineup
2
+ module Patches
3
+ module AutoCompletesControllerPatch
4
+ DEFAULT_TAGS_LIMIT = 10
5
+
6
+ def taggable_tags
7
+
8
+ limit = params.delete(:limit) || DEFAULT_TAGS_LIMIT
9
+ klass = Object.const_get(params[:taggable_type].camelcase) if params[:taggable_type].present?
10
+ tags = klass && klass.available_tags(params.merge(limit: limit)) || Redmineup::ActsAsTaggable::Tag.limit(limit)
11
+ render json: tags.map { |tag| { id: tag.name, text: tag.name } }
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ unless AutoCompletesController.included_modules.include?(Redmineup::Patches::AutoCompletesControllerPatch)
18
+ AutoCompletesController.include(Redmineup::Patches::AutoCompletesControllerPatch)
19
+ end
@@ -1,3 +1,3 @@
1
1
  module Redmineup
2
- VERSION = '1.1.3'
2
+ VERSION = '1.1.5'
3
3
  end
data/lib/redmineup.rb CHANGED
@@ -106,6 +106,7 @@ if defined?(ActionView::Base)
106
106
  ActionView::Base.send :include, Redmineup::CalendarsHelper
107
107
  ActionView::Base.send :include, Redmineup::ExternalAssetsHelper
108
108
  ActionView::Base.send :include, Redmineup::FormTagHelper
109
+ ActionView::Base.send :include, Redmineup::TagsHelper
109
110
  end
110
111
 
111
112
  def requires_redmineup(arg)
@@ -34,16 +34,19 @@ class TagTest < ActiveSupport::TestCase
34
34
  end
35
35
 
36
36
  def test_color
37
- colors = %w(#aaaaaa #bbbbbb)
38
- colorized = [tags(:bug), tags(:feature)]
39
- colorized.each do |tag|
40
- assert_equal colors.include?(tag.color), true
41
- end
37
+ assert_equal '#aaaaaa', tags(:feature).color # 11184810 = 0xaaaaaa
38
+ assert_equal '#bbbbbb', tags(:bug).color # 12303291 = 0xbbbbbb
39
+ # no DB color — falls back to color_from_name, not nil
40
+ assert_equal Redmineup::ActsAsTaggable::Tag.color_from_name('error'), tags(:error).color
41
+ assert_equal Redmineup::ActsAsTaggable::Tag.color_from_name('question'), tags(:question).color
42
+ end
42
43
 
43
- not_colorized = [tags(:error), tags(:question)]
44
- not_colorized.each do |tag|
45
- assert_nil tag.color
46
- end
44
+ def test_color_from_name
45
+ color = Redmineup::ActsAsTaggable::Tag.color_from_name('error')
46
+ assert_match(/\A#[0-9a-f]{6}\z/, color)
47
+ assert_equal color, Redmineup::ActsAsTaggable::Tag.color_from_name('error') # deterministic
48
+ assert_not_equal color, Redmineup::ActsAsTaggable::Tag.color_from_name('other')
49
+ assert_match(/\A#[0-9a-f]{6}\z/, Redmineup::ActsAsTaggable::Tag.color_from_name(nil))
47
50
  end
48
51
 
49
52
  def test_tag_is_equal_to_itself
@@ -0,0 +1,41 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class TagsHelperViewTest < ActionView::TestCase
4
+ include Redmineup::TagsHelper
5
+ include Redmineup::FormTagHelper
6
+
7
+ def stub_tag_url(name)
8
+ "/stub_tags/#{name}" # URL stub for tag_cloud_links(tags, :stub)
9
+ end
10
+
11
+ def test_tag_link
12
+ result = tag_link('/tags/error', 'error', count: 3)
13
+ assert_includes result, 'tag-label-color'
14
+ assert_includes result, 'background-color: #'
15
+ assert_includes result, '(3)'
16
+ end
17
+
18
+ def test_tag_links
19
+ result = tag_links(['error', 'bug']) { |t| "/tags/#{t}" }
20
+ assert_includes result, 'class="tag_list"'
21
+ assert_nil tag_links([]) { |t| "/tags/#{t}" }
22
+ end
23
+
24
+ def test_tag_cloud_links
25
+ result = tag_cloud_links(Issue.tag_counts, :stub)
26
+ assert_includes result, 'class="tag_list"'
27
+ assert_includes result, '/stub_tags/'
28
+ assert_nil tag_cloud_links([], :stub)
29
+ assert_raises(NoMethodError) { tag_cloud_links(Issue.tag_counts, :unknown_xyz) }
30
+ end
31
+
32
+ def test_tag_list_field_tag
33
+ result = tag_list_field_tag('issue[tag_list]', ['bug'],
34
+ url: '/auto_completes/taggable_tags',
35
+ width: '60%', tags: false,
36
+ placeholder: '+ add tag')
37
+ assert_includes result, 'name="issue[tag_list][]"'
38
+ assert_includes result, '"width":"60%"'
39
+ assert_includes result, '"tags":false'
40
+ end
41
+ end