diff --git a/app/controllers/time_entries/reports_controller.rb b/app/controllers/time_entries/reports_controller.rb index cda839b2ab..98f9f36470 100644 --- a/app/controllers/time_entries/reports_controller.rb +++ b/app/controllers/time_entries/reports_controller.rb @@ -135,27 +135,39 @@ class TimeEntries::ReportsController < ApplicationController # Add list and boolean custom fields as available criterias custom_fields = (@project.nil? ? WorkPackageCustomField.for_all : @project.all_work_package_custom_fields) - custom_fields.select { |cf| %w(list bool).include? cf.field_format }.each do |cf| - @available_criterias["cf_#{cf.id}"] = { sql: "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'WorkPackage' AND c.customized_id = #{WorkPackage.table_name}.id)", - format: cf.field_format, - label: cf.name } - end if @project + + if @project + custom_fields.select { |cf| %w(list bool).include? cf.field_format }.each do |cf| + @available_criterias["cf_#{cf.id}"] = { sql: "(SELECT c.value FROM #{CustomValue.table_name} c + WHERE c.custom_field_id = #{cf.id} + AND c.customized_type = 'WorkPackage' + AND c.customized_id = #{WorkPackage.table_name}.id)", + format: cf, + label: cf.name } + end + end # Add list and boolean time entry custom fields TimeEntryCustomField.all.select { |cf| %w(list bool).include? cf.field_format }.each do |cf| - @available_criterias["cf_#{cf.id}"] = { sql: "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'TimeEntry' AND c.customized_id = #{TimeEntry.table_name}.id)", - format: cf.field_format, + @available_criterias["cf_#{cf.id}"] = { sql: "(SELECT c.value FROM #{CustomValue.table_name} c + WHERE c.custom_field_id = #{cf.id} + AND c.customized_type = 'TimeEntry' + AND c.customized_id = #{TimeEntry.table_name}.id)", + format: cf, label: cf.name } end # Add list and boolean time entry activity custom fields TimeEntryActivityCustomField.all.select { |cf| %w(list bool).include? cf.field_format }.each do |cf| - @available_criterias["cf_#{cf.id}"] = { sql: "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'Enumeration' AND c.customized_id = #{TimeEntry.table_name}.activity_id)", - format: cf.field_format, + @available_criterias["cf_#{cf.id}"] = { sql: "(SELECT c.value FROM #{CustomValue.table_name} c + WHERE c.custom_field_id = #{cf.id} + AND c.customized_type = 'Enumeration' + AND c.customized_id = #{TimeEntry.table_name}.activity_id)", + format: cf, label: cf.name } end - call_hook(:controller_timelog_available_criterias, available_criterias: @available_criterias, project: @project) + call_hook(:controller_timelog_available_criterias, available_criterias: @available_criterias, project: @project) @available_criterias end diff --git a/app/helpers/custom_fields_helper.rb b/app/helpers/custom_fields_helper.rb index 3b553edd0e..811b486a9d 100644 --- a/app/helpers/custom_fields_helper.rb +++ b/app/helpers/custom_fields_helper.rb @@ -1,4 +1,5 @@ #-- encoding: UTF-8 + #-- copyright # OpenProject is a project management system. # Copyright (C) 2012-2017 the OpenProject Foundation (OPF) @@ -47,7 +48,7 @@ module CustomFieldsHelper field_name = "#{name}[custom_field_values][#{custom_field.id}]" field_id = "#{name}_custom_field_values_#{custom_field.id}" - field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format) + field_format = OpenProject::CustomFieldFormat.find_by_name(custom_field.field_format) tag = case field_format.try(:edit_as) when 'date' @@ -114,7 +115,7 @@ module CustomFieldsHelper def custom_field_tag_for_bulk_edit(name, custom_field, project=nil) field_name = "#{name}[custom_field_values][#{custom_field.id}]" field_id = "#{name}_custom_field_values_#{custom_field.id}" - field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format) + field_format = OpenProject::CustomFieldFormat.find_by_name(custom_field.field_format) case field_format.try(:edit_as) when 'date' styled_text_field_tag(field_name, '', id: field_id, size: 10) + @@ -135,27 +136,32 @@ module CustomFieldsHelper # Return a string used to display a custom value def show_value(custom_value) return '' unless custom_value - format_value(custom_value.value, custom_value.custom_field.field_format) + custom_value.formatted_value end # Return a string used to display a custom value - def format_value(value, field_format) - Redmine::CustomFieldFormat.format_value(value, field_format) # Proxy + def format_value(value, custom_field) + custom_value = CustomValue.new(custom_field: custom_field, + value: value) + + custom_value.formatted_value end # Return an array of custom field formats which can be used in select_tag def custom_field_formats_for_select(custom_field) - Redmine::CustomFieldFormat.as_select(custom_field.class.customized_class.name) + OpenProject::CustomFieldFormat + .all_for_field(custom_field) + .sort_by(&:order) + .map do |custom_field_format| + [label_for_custom_field_format(custom_field_format.name), custom_field_format.name] + end end - # Renders the custom_values in api views - def render_api_custom_values(custom_values, api) - api.array :custom_fields do - custom_values.each do |custom_value| - api.custom_field id: custom_value.custom_field_id, name: custom_value.custom_field.name do - api.value custom_value.value - end - end - end unless custom_values.empty? + def label_for_custom_field_format(format_string) + format = OpenProject::CustomFieldFormat.find_by_name(format_string) + + if format + format.label.is_a?(Proc) ? format.label.call : I18n.t(format.label) + end end end diff --git a/app/models/custom_field.rb b/app/models/custom_field.rb index 245864c962..31b5d0832d 100644 --- a/app/models/custom_field.rb +++ b/app/models/custom_field.rb @@ -49,7 +49,7 @@ class CustomField < ActiveRecord::Base errors.add(:name, :taken) if name.in?(taken_names) end - validates_inclusion_of :field_format, in: Redmine::CustomFieldFormat.available_formats + validates_inclusion_of :field_format, in: OpenProject::CustomFieldFormat.available_formats validate :validate_default_value @@ -96,6 +96,10 @@ class CustomField < ActiveRecord::Base end end + def required? + is_required? + end + def possible_values_options(obj = nil) case field_format when 'user', 'version' diff --git a/app/models/custom_value.rb b/app/models/custom_value.rb index 058d500358..22a4233d75 100644 --- a/app/models/custom_value.rb +++ b/app/models/custom_value.rb @@ -1,4 +1,5 @@ #-- encoding: UTF-8 + #-- copyright # OpenProject is a project management system. # Copyright (C) 2012-2017 the OpenProject Foundation (OPF) @@ -28,18 +29,6 @@ #++ class CustomValue < ActiveRecord::Base - FORMAT_STRATEGIES = { - 'string' => CustomValue::StringStrategy, - 'text' => CustomValue::StringStrategy, - 'int' => CustomValue::IntStrategy, - 'float' => CustomValue::FloatStrategy, - 'date' => CustomValue::DateStrategy, - 'bool' => CustomValue::BoolStrategy, - 'user' => CustomValue::UserStrategy, - 'version' => CustomValue::VersionStrategy, - 'list' => CustomValue::ListStrategy - }.freeze - belongs_to :custom_field belongs_to :customized, polymorphic: true @@ -48,23 +37,16 @@ class CustomValue < ActiveRecord::Base validate :validate_type_of_value validate :validate_length_of_value - # returns the value of this custom value, but converts it according to the field_format - # of the custom field beforehand - def typed_value - strategy.typed_value - end - - def editable? - custom_field.editable? - end - - def visible? - custom_field.visible? - end + delegate :typed_value, + :formatted_value, + to: :strategy - def required? - custom_field.is_required? - end + delegate :editable?, + :visible?, + :required?, + :max_length, + :min_length, + to: :custom_field def to_s value.to_s @@ -79,7 +61,7 @@ class CustomValue < ActiveRecord::Base protected def validate_presence_of_required_value - errors.add(:value, :blank) if custom_field.is_required? && !strategy.value_present? + errors.add(:value, :blank) if custom_field.required? && !strategy.value_present? end def validate_format_of_value @@ -98,15 +80,23 @@ class CustomValue < ActiveRecord::Base end def validate_length_of_value - if value.present? && custom_field.min_length.present? && custom_field.max_length.present? - errors.add(:value, :too_short, count: custom_field.min_length) if custom_field.min_length > 0 and value.length < custom_field.min_length - errors.add(:value, :too_long, count: custom_field.max_length) if custom_field.max_length > 0 and value.length > custom_field.max_length + if value.present? && (min_length.present? || max_length.present?) + validate_min_length_of_value + validate_max_length_of_value end end private + def validate_min_length_of_value + errors.add(:value, :too_short, count: min_length) if min_length > 0 && value.length < min_length + end + + def validate_max_length_of_value + errors.add(:value, :too_long, count: max_length) if max_length > 0 && value.length > max_length + end + def strategy - @strategy ||= FORMAT_STRATEGIES[custom_field.field_format].new(self) + @strategy ||= OpenProject::CustomFieldFormat.find_by_name(custom_field.field_format).formatter.new(self) end end diff --git a/app/models/custom_value/ar_object_strategy.rb b/app/models/custom_value/ar_object_strategy.rb index 3e022b4e70..79aae8a19d 100644 --- a/app/models/custom_value/ar_object_strategy.rb +++ b/app/models/custom_value/ar_object_strategy.rb @@ -39,6 +39,10 @@ class CustomValue::ARObjectStrategy < CustomValue::FormatStrategy end end + def formatted_value + typed_value.to_s + end + def parse_value(val) if val.is_a?(ar_class) self.memoized_typed_value = val diff --git a/app/models/custom_value/bool_strategy.rb b/app/models/custom_value/bool_strategy.rb index 3fd1e75088..b9cc16b986 100644 --- a/app/models/custom_value/bool_strategy.rb +++ b/app/models/custom_value/bool_strategy.rb @@ -1,4 +1,5 @@ #-- encoding: UTF-8 + #-- copyright # OpenProject is a project management system. # Copyright (C) 2012-2017 the OpenProject Foundation (OPF) @@ -41,6 +42,11 @@ class CustomValue::BoolStrategy < CustomValue::FormatStrategy ActiveRecord::Type::Boolean.new.cast(value) end + def formatted_value + is_true = ActiveRecord::Type::Boolean.new.cast(value) + I18n.t(is_true ? :general_text_Yes : :general_text_No) + end + def parse_value(val) parsed_val = if !present?(val) nil @@ -53,8 +59,7 @@ class CustomValue::BoolStrategy < CustomValue::FormatStrategy super(parsed_val) end - def validate_type_of_value - end + def validate_type_of_value; end private diff --git a/app/models/custom_value/date_strategy.rb b/app/models/custom_value/date_strategy.rb index 96e040f239..1852b80c7e 100644 --- a/app/models/custom_value/date_strategy.rb +++ b/app/models/custom_value/date_strategy.rb @@ -1,4 +1,5 @@ #-- encoding: UTF-8 + #-- copyright # OpenProject is a project management system. # Copyright (C) 2012-2017 the OpenProject Foundation (OPF) @@ -34,6 +35,12 @@ class CustomValue::DateStrategy < CustomValue::FormatStrategy end end + def formatted_value + format_date(value.to_date) + rescue + value.to_s + end + def validate_type_of_value return nil if value.is_a? Date diff --git a/app/models/custom_value/float_strategy.rb b/app/models/custom_value/float_strategy.rb index 79665cc983..6bd8167d6a 100644 --- a/app/models/custom_value/float_strategy.rb +++ b/app/models/custom_value/float_strategy.rb @@ -1,4 +1,5 @@ #-- encoding: UTF-8 + #-- copyright # OpenProject is a project management system. # Copyright (C) 2012-2017 the OpenProject Foundation (OPF) @@ -28,12 +29,18 @@ #++ class CustomValue::FloatStrategy < CustomValue::FormatStrategy + include ActionView::Helpers::NumberHelper + def typed_value unless value.blank? value.to_f end end + def formatted_value + number_with_delimiter(value.to_s) + end + def validate_type_of_value Kernel.Float(value) nil diff --git a/app/models/custom_value/format_strategy.rb b/app/models/custom_value/format_strategy.rb index 895a2b3136..e3a7a12033 100644 --- a/app/models/custom_value/format_strategy.rb +++ b/app/models/custom_value/format_strategy.rb @@ -1,4 +1,5 @@ #-- encoding: UTF-8 + #-- copyright # OpenProject is a project management system. # Copyright (C) 2012-2017 the OpenProject Foundation (OPF) @@ -45,6 +46,12 @@ class CustomValue::FormatStrategy raise 'SubclassResponsibility' end + # Returns the value of the CustomValue formatted to a string + # representation. + def formatted_value + value.to_s + end + # Parses the value to # 1) have a unified representation for different inputs # 2) memoize typed values (if the subclass descides to do so diff --git a/app/models/work_package/pdf_export/work_package_list_to_pdf.rb b/app/models/work_package/pdf_export/work_package_list_to_pdf.rb index b63f026f87..4d6deeaf3e 100644 --- a/app/models/work_package/pdf_export/work_package_list_to_pdf.rb +++ b/app/models/work_package/pdf_export/work_package_list_to_pdf.rb @@ -171,11 +171,11 @@ class WorkPackage::PdfExport::WorkPackageListToPdf end def make_custom_field_value(work_package, column) - value = work_package - .custom_values - .detect { |v| v.custom_field_id == column.custom_field.id } + values = work_package + .custom_values + .select { |v| v.custom_field_id == column.custom_field.id } - pdf.make_cell show_value(value), + pdf.make_cell values.map(&:formatted_value).join(', '), padding: cell_padding end end diff --git a/app/views/custom_fields/_tab.html.erb b/app/views/custom_fields/_tab.html.erb index 36582e2a7c..9b0f4e7872 100644 --- a/app/views/custom_fields/_tab.html.erb +++ b/app/views/custom_fields/_tab.html.erb @@ -107,7 +107,7 @@ See doc/COPYRIGHT.rdoc for more details. <% (@custom_fields_by_type[tab[:name]] || []).sort.each do |custom_field| -%> <%= link_to h(custom_field.name), edit_custom_field_path(custom_field), lang: custom_field.name_locale %> - <%= Redmine::CustomFieldFormat.label_for(custom_field.field_format) %> + <%= label_for_custom_field_format(custom_field.field_format) %> <%= checked_image custom_field.is_required? %> <% if tab[:name] == 'WorkPackageCustomField' %> <%= checked_image custom_field.is_for_all? %> diff --git a/app/views/time_entries/reports/_report_criteria.html.erb b/app/views/time_entries/reports/_report_criteria.html.erb index e049c3e4f6..a945f466e9 100644 --- a/app/views/time_entries/reports/_report_criteria.html.erb +++ b/app/views/time_entries/reports/_report_criteria.html.erb @@ -28,6 +28,7 @@ See doc/COPYRIGHT.rdoc for more details. ++#%> <% @hours.collect {|h| h[criterias[level]].to_s}.uniq.each do |value| %> + <% hours_for_value = select_hours(hours, criterias[level], value) -%> <% next if hours_for_value.empty? -%> diff --git a/config/initializers/custom_field_format.rb b/config/initializers/custom_field_format.rb index d574401319..213a6899b9 100644 --- a/config/initializers/custom_field_format.rb +++ b/config/initializers/custom_field_format.rb @@ -1,4 +1,5 @@ #-- encoding: UTF-8 + #-- copyright # OpenProject is a project management system. # Copyright (C) 2012-2017 the OpenProject Foundation (OPF) @@ -27,38 +28,45 @@ # See doc/COPYRIGHT.rdoc for more details. #++ -Redmine::CustomFieldFormat.map do |fields| - fields.register Redmine::CustomFieldFormat.new('string', - label: :label_string, - order: 1) - fields.register Redmine::CustomFieldFormat.new('text', - label: :label_text, - order: 2) - fields.register Redmine::CustomFieldFormat.new('int', - label: :label_integer, - order: 3) - fields.register Redmine::CustomFieldFormat.new('float', - label: :label_float, - order: 4) - fields.register Redmine::CustomFieldFormat.new('list', - label: :label_list, - order: 5) - fields.register Redmine::CustomFieldFormat.new('date', - label: :label_date, - order: 6) - fields.register Redmine::CustomFieldFormat.new('bool', - label: :label_boolean, - order: 7) - fields.register Redmine::CustomFieldFormat.new('user', - label: Proc.new { User.model_name.human }, - only: %w(WorkPackage TimeEntry - Version Project), - edit_as: 'list', - order: 8) - fields.register Redmine::CustomFieldFormat.new('version', - label: Proc.new { Version.model_name.human }, - only: %w(WorkPackage TimeEntry - Version Project), - edit_as: 'list', - order: 9) +OpenProject::CustomFieldFormat.map do |fields| + fields.register OpenProject::CustomFieldFormat.new('string', + label: :label_string, + order: 1) + fields.register OpenProject::CustomFieldFormat.new('text', + label: :label_text, + order: 2) + fields.register OpenProject::CustomFieldFormat.new('int', + label: :label_integer, + order: 3, + formatter: CustomValue::IntStrategy) + fields.register OpenProject::CustomFieldFormat.new('float', + label: :label_float, + order: 4, + formatter: CustomValue::FloatStrategy) + fields.register OpenProject::CustomFieldFormat.new('list', + label: :label_list, + order: 5, + formatter: CustomValue::ListStrategy) + fields.register OpenProject::CustomFieldFormat.new('date', + label: :label_date, + order: 6, + formatter: CustomValue::DateStrategy) + fields.register OpenProject::CustomFieldFormat.new('bool', + label: :label_boolean, + order: 7, + formatter: CustomValue::BoolStrategy) + fields.register OpenProject::CustomFieldFormat.new('user', + label: Proc.new { User.model_name.human }, + only: %w(WorkPackage TimeEntry + Version Project), + edit_as: 'list', + order: 8, + formatter: CustomValue::UserStrategy) + fields.register OpenProject::CustomFieldFormat.new('version', + label: Proc.new { Version.model_name.human }, + only: %w(WorkPackage TimeEntry + Version Project), + edit_as: 'list', + order: 9, + formatter: CustomValue::VersionStrategy) end diff --git a/lib/custom_field_form_builder.rb b/lib/custom_field_form_builder.rb index 301e8349fc..5e9e718e73 100644 --- a/lib/custom_field_form_builder.rb +++ b/lib/custom_field_form_builder.rb @@ -54,7 +54,7 @@ class CustomFieldFormBuilder < TabularFormBuilder name: custom_field_field_name, id: custom_field_field_id) - field_format = Redmine::CustomFieldFormat.find_by_name(object.custom_field.field_format) + field_format = OpenProject::CustomFieldFormat.find_by_name(object.custom_field.field_format) case field_format.try(:edit_as) when 'date' diff --git a/lib/open_project.rb b/lib/open_project.rb index 1a4d596434..4e9701535e 100644 --- a/lib/open_project.rb +++ b/lib/open_project.rb @@ -1,4 +1,5 @@ #-- encoding: UTF-8 + #-- copyright # OpenProject is a project management system. # Copyright (C) 2012-2017 the OpenProject Foundation (OPF) @@ -30,7 +31,7 @@ require 'redmine/menu_manager' require 'redmine/activity' require 'redmine/search' -require 'redmine/custom_field_format' +require 'open_project/custom_field_format' require 'redmine/mime_type' require 'redmine/core_ext' require 'open_project/design' diff --git a/lib/redmine/custom_field_format.rb b/lib/open_project/custom_field_format.rb similarity index 54% rename from lib/redmine/custom_field_format.rb rename to lib/open_project/custom_field_format.rb index 367decb4dd..50810b76a2 100644 --- a/lib/redmine/custom_field_format.rb +++ b/lib/open_project/custom_field_format.rb @@ -1,4 +1,5 @@ #-- encoding: UTF-8 + #-- copyright # OpenProject is a project management system. # Copyright (C) 2012-2017 the OpenProject Foundation (OPF) @@ -27,50 +28,22 @@ # See doc/COPYRIGHT.rdoc for more details. #++ -module Redmine +module OpenProject class CustomFieldFormat include Redmine::I18n - include ActionView::Helpers::NumberHelper cattr_accessor :available @@available = {} - attr_accessor :name, :order, :label, :edit_as, :class_names + attr_accessor :name, :order, :label, :edit_as, :class_names, :formatter - def initialize(name, label:, order:, edit_as: name, only: nil) + def initialize(name, label:, order:, edit_as: name, only: nil, formatter: CustomValue::StringStrategy) self.name = name self.label = label self.order = order self.edit_as = edit_as self.class_names = only - end - - def format(value) - send "format_as_#{name}", value - end - - def format_as_date(value) - format_date(value.to_date); rescue; value end - - def format_as_bool(value) - is_true = ActiveRecord::Type::Boolean.new.cast(value) - l(is_true ? :general_text_Yes : :general_text_No) - end - - ['string', 'text', 'int', 'list'].each do |name| - define_method("format_as_#{name}") {|value| - return value.to_s - } - end - - def format_as_float(value) - number_with_delimiter(value.to_s) - end - - ['user', 'version'].each do |name| - define_method("format_as_#{name}") {|value| - return value.blank? ? '' : name.classify.constantize.find_by(id: value.to_i).to_s - } + self.formatter = formatter end class << self @@ -91,30 +64,12 @@ module Redmine @@available[name.to_s] end - def label_for(name) - format = @@available[name.to_s] - format.label.is_a?(Proc) ? format.label.call : l(format.label) if format - end - - # Return an array of custom field formats which can be used in select_tag - def as_select(class_name = nil) - fields = @@available.values - fields = fields.select { |field| field.class_names.nil? || field.class_names.include?(class_name) } - fields.sort {|a, b| - a.order <=> b.order - }.map {|custom_field_format| - [label_for(custom_field_format.name), custom_field_format.name] - } - end - - def format_value(value, field_format) - return '' unless value && !value.empty? + def all_for_field(custom_field) + class_name = custom_field.class.customized_class.name - if format_type = find_by_name(field_format) - format_type.format(value) - else - value - end + available + .values + .select { |field| field.class_names.nil? || field.class_names.include?(class_name) } end end end diff --git a/lib/open_project/journal_formatter/custom_field.rb b/lib/open_project/journal_formatter/custom_field.rb index 461e943067..6c3bbb442c 100644 --- a/lib/open_project/journal_formatter/custom_field.rb +++ b/lib/open_project/journal_formatter/custom_field.rb @@ -71,8 +71,8 @@ class OpenProject::JournalFormatter::CustomField < ::JournalFormatter::Base end def format_single(custom_field, values) - old_value = format_value(values.first, custom_field.field_format) if values.first - value = format_value(values.last, custom_field.field_format) if values.last + old_value = format_value(values.first, custom_field) if values.first + value = format_value(values.last, custom_field) if values.last [old_value, value] end @@ -89,7 +89,7 @@ class OpenProject::JournalFormatter::CustomField < ::JournalFormatter::Base String(values) .split(",") .map(&:strip) - .map { |value| format_value value, custom_field.field_format } + .map { |value| format_value value, custom_field } .join(", ") .presence end diff --git a/spec/helpers/custom_fields_helper_spec.rb b/spec/helpers/custom_fields_helper_spec.rb deleted file mode 100644 index f6cb9eba85..0000000000 --- a/spec/helpers/custom_fields_helper_spec.rb +++ /dev/null @@ -1,41 +0,0 @@ -#-- encoding: UTF-8 -#-- copyright -# OpenProject is a project management system. -# Copyright (C) 2012-2017 the OpenProject Foundation (OPF) -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License version 3. -# -# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: -# Copyright (C) 2006-2017 Jean-Philippe Lang -# Copyright (C) 2010-2013 the ChiliProject Team -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# See doc/COPYRIGHT.rdoc for more details. -#++ -require 'spec_helper' - -describe CustomFieldsHelper, type: :helper do - include OpenProject::FormTagHelper - include CustomFieldsHelper - include Redmine::I18n - - it 'should format boolean value' do - I18n.locale = 'en' - expect(format_value('1', 'bool')).to eq 'Yes' - expect(format_value('0', 'bool')).to eq 'No' - end -end diff --git a/spec/lib/api/v3/utilities/custom_field_injector_spec.rb b/spec/lib/api/v3/utilities/custom_field_injector_spec.rb index a2f87b5162..eb1d86ffca 100644 --- a/spec/lib/api/v3/utilities/custom_field_injector_spec.rb +++ b/spec/lib/api/v3/utilities/custom_field_injector_spec.rb @@ -42,7 +42,7 @@ describe ::API::V3::Utilities::CustomFieldInjector do describe 'TYPE_MAP' do it 'supports all available formats' do - Redmine::CustomFieldFormat.available_formats.each do |format| + OpenProject::CustomFieldFormat.available_formats.each do |format| expect(described_class::TYPE_MAP[format]).not_to be_nil end end diff --git a/spec/lib/journal_formatter/custom_field_spec.rb b/spec/lib/journal_formatter/custom_field_spec.rb index 8345df06a1..ca241b05ff 100644 --- a/spec/lib/journal_formatter/custom_field_spec.rb +++ b/spec/lib/journal_formatter/custom_field_spec.rb @@ -45,41 +45,41 @@ describe OpenProject::JournalFormatter::CustomField do describe '#render' do describe 'WITH the first value beeing nil, and the second a valid value as string' do let(:values) { [nil, '1'] } - let(:formatted_value) { format_value(values.last, custom_field.field_format) } + let(:formatted_value) { format_value(values.last, custom_field) } - let(:expected) { + let(:expected) do I18n.t(:text_journal_set_to, label: "#{custom_field.name}", value: "#{formatted_value}") - } + end it { expect(instance.render(key, values)).to eq(expected) } end describe 'WITH the first value beeing a valid value as a string, and the second beeing a valid value as a string' do let(:values) { ['0', '1'] } - let(:old_formatted_value) { format_value(values.first, custom_field.field_format) } - let(:new_formatted_value) { format_value(values.last, custom_field.field_format) } + let(:old_formatted_value) { format_value(values.first, custom_field) } + let(:new_formatted_value) { format_value(values.last, custom_field) } - let(:expected) { + let(:expected) do I18n.t(:text_journal_changed, label: "#{custom_field.name}", old: "#{old_formatted_value}", new: "#{new_formatted_value}") - } + end it { expect(instance.render(key, values)).to eq(expected) } end describe 'WITH the first value beeing a valid value as a string, and the second beeing nil' do let(:values) { ['0', nil] } - let(:formatted_value) { format_value(values.first, custom_field.field_format) } + let(:formatted_value) { format_value(values.first, custom_field) } - let(:expected) { + let(:expected) do I18n.t(:text_journal_deleted, label: "#{custom_field.name}", old: "#{formatted_value}") - } + end it { expect(instance.render(key, values)).to eq(expected) } end @@ -88,11 +88,11 @@ describe OpenProject::JournalFormatter::CustomField do WITH no html requested" do let(:values) { [nil, '1'] } - let(:expected) { + let(:expected) do I18n.t(:text_journal_set_to, - label: "#{custom_field.name}", - value: "#{ format_value(values.last, custom_field.field_format) }") - } + label: custom_field.name, + value: format_value(values.last, custom_field)) + end it { expect(instance.render(key, values, no_html: true)).to eq(expected) } end @@ -101,12 +101,12 @@ describe OpenProject::JournalFormatter::CustomField do WITH no html requested" do let(:values) { ['0', '1'] } - let(:expected) { + let(:expected) do I18n.t(:text_journal_changed, - label: "#{custom_field.name}", - old: "#{ format_value(values.first, custom_field.field_format) }", - new: "#{ format_value(values.last, custom_field.field_format) }") - } + label: custom_field.name, + old: format_value(values.first, custom_field), + new: format_value(values.last, custom_field)) + end it { expect(instance.render(key, values, no_html: true)).to eq(expected) } end @@ -115,11 +115,11 @@ describe OpenProject::JournalFormatter::CustomField do WITH no html requested" do let(:values) { ['0', nil] } - let(:expected) { + let(:expected) do I18n.t(:text_journal_deleted, - label: "#{custom_field.name}", - old: "#{ format_value(values.first, custom_field.field_format) }") - } + label: custom_field.name, + old: format_value(values.first, custom_field)) + end it { expect(instance.render(key, values, no_html: true)).to eq(expected) } end @@ -129,11 +129,11 @@ describe OpenProject::JournalFormatter::CustomField do let(:values) { [nil, '1'] } let(:key) { 'custom_values0' } - let(:expected) { + let(:expected) do I18n.t(:text_journal_set_to, label: "#{I18n.t(:label_deleted_custom_field)}", - value: "#{ values.last }") - } + value: "#{values.last}") + end it { expect(instance.render(key, values)).to eq(expected) } end @@ -143,12 +143,12 @@ describe OpenProject::JournalFormatter::CustomField do let(:values) { ['0', '1'] } let(:key) { 'custom_values0' } - let(:expected) { + let(:expected) do I18n.t(:text_journal_changed, label: "#{I18n.t(:label_deleted_custom_field)}", - old: "#{ values.first }", - new: "#{ values.last }") - } + old: "#{values.first}", + new: "#{values.last}") + end it { expect(instance.render(key, values)).to eq(expected) } end @@ -158,11 +158,11 @@ describe OpenProject::JournalFormatter::CustomField do let(:values) { ['0', nil] } let(:key) { 'custom_values0' } - let(:expected) { + let(:expected) do I18n.t(:text_journal_deleted, label: "#{I18n.t(:label_deleted_custom_field)}", - old: "#{ values.first }") - } + old: "#{values.first}") + end it { expect(instance.render(key, values)).to eq(expected) } end diff --git a/spec/lib/redmine/custom_field_format_spec.rb b/spec/lib/redmine/custom_field_format_spec.rb deleted file mode 100644 index 0694ffce5d..0000000000 --- a/spec/lib/redmine/custom_field_format_spec.rb +++ /dev/null @@ -1,41 +0,0 @@ -#-- copyright -# OpenProject is a project management system. -# Copyright (C) 2012-2017 the OpenProject Foundation (OPF) -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License version 3. -# -# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: -# Copyright (C) 2006-2017 Jean-Philippe Lang -# Copyright (C) 2010-2013 the ChiliProject Team -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# See doc/COPYRIGHT.rdoc for more details. -#++ - -require 'spec_helper' - -describe Redmine::CustomFieldFormat do - let(:instance) { described_class.new 'test', label: 'blubs', order: '1' } - - describe '#format_value' do - it 'returns a localized float' do - I18n.with_locale(:de) do - expect(instance.format_as_float('5.67')).to eql '5,67' - end - end - end -end diff --git a/spec/models/custom_value/bool_strategy_spec.rb b/spec/models/custom_value/bool_strategy_spec.rb index af4e793784..e9b06c27f3 100644 --- a/spec/models/custom_value/bool_strategy_spec.rb +++ b/spec/models/custom_value/bool_strategy_spec.rb @@ -29,6 +29,7 @@ require 'spec_helper' describe CustomValue::BoolStrategy do + let(:instance) { described_class.new(custom_value) } let(:custom_value) do double('CustomValue', value: value) @@ -64,7 +65,7 @@ describe CustomValue::BoolStrategy do end describe '#typed_value' do - subject { described_class.new(custom_value).typed_value } + subject { instance.typed_value } context 'value corresponds to true' do let(:value) { '1' } @@ -97,8 +98,60 @@ describe CustomValue::BoolStrategy do end end + describe '#formatted_value' do + subject { instance.formatted_value } + + context 'value is present string' do + let(:value) { '1' } + + it 'is the true string' do + is_expected.to eql I18n.t(:general_text_Yes) + end + end + + context 'value is zero string' do + let(:value) { '0' } + + it 'is the false string' do + is_expected.to eql I18n.t(:general_text_No) + end + end + + context 'value is true' do + let(:value) { true } + + it 'is the true string' do + is_expected.to eql I18n.t(:general_text_Yes) + end + end + + context 'value is false' do + let(:value) { false } + + it 'is the false string' do + is_expected.to eql I18n.t(:general_text_No) + end + end + + context 'value is nil' do + let(:value) { nil } + + it 'is the false string' do + is_expected.to eql I18n.t(:general_text_No) + end + end + + context 'value is blank' do + let(:value) { '' } + + it 'is the false string' do + is_expected.to eql I18n.t(:general_text_No) + end + end + end + describe '#validate_type_of_value' do - subject { described_class.new(custom_value).validate_type_of_value } + subject { instance.validate_type_of_value } context 'value corresponds to true' do let(:value) { '1' } @@ -130,7 +183,7 @@ describe CustomValue::BoolStrategy do end describe '#parse_value' do - subject { described_class.new(custom_value).parse_value(value) } + subject { instance.parse_value(value) } ActiveRecord::Type::Boolean::FALSE_VALUES.each do |falsey_value| context "for #{falsey_value}" do diff --git a/spec/models/custom_value/date_strategy_spec.rb b/spec/models/custom_value/date_strategy_spec.rb index 1b58c04380..0e40c99934 100644 --- a/spec/models/custom_value/date_strategy_spec.rb +++ b/spec/models/custom_value/date_strategy_spec.rb @@ -29,13 +29,14 @@ require 'spec_helper' describe CustomValue::DateStrategy do - let(:custom_value) { + let(:instance) { described_class.new(custom_value) } + let(:custom_value) do double('CustomValue', value: value) - } + end describe '#typed_value' do - subject { described_class.new(custom_value).typed_value } + subject { instance.typed_value } context 'value is some date string' do let(:value) { '2015-01-03' } @@ -53,8 +54,36 @@ describe CustomValue::DateStrategy do end end + describe '#formatted_value' do + subject { instance.formatted_value } + + context 'value is some date string' do + let(:value) { '2015-01-03' } + + it 'is the date' do + is_expected.to eql value + end + end + + context 'value is blank' do + let(:value) { '' } + + it 'is a blank string' do + is_expected.to eql value + end + end + + context 'value is nil' do + let(:value) { nil } + + it 'is a blank string' do + is_expected.to eql '' + end + end + end + describe '#validate_type_of_value' do - subject { described_class.new(custom_value).validate_type_of_value } + subject { instance.validate_type_of_value } context 'value is valid date string' do let(:value) { '2015-01-03' } diff --git a/spec/models/custom_value/float_strategy_spec.rb b/spec/models/custom_value/float_strategy_spec.rb index 88b4f3fed4..eccca0dae2 100644 --- a/spec/models/custom_value/float_strategy_spec.rb +++ b/spec/models/custom_value/float_strategy_spec.rb @@ -29,13 +29,14 @@ require 'spec_helper' describe CustomValue::FloatStrategy do - let(:custom_value) { + let(:instance) { described_class.new(custom_value) } + let(:custom_value) do double('CustomValue', value: value) - } + end describe '#typed_value' do - subject { described_class.new(custom_value).typed_value } + subject { instance.typed_value } context 'value is some float string' do let(:value) { '3.14' } @@ -53,8 +54,42 @@ describe CustomValue::FloatStrategy do end end + describe '#formatted_value' do + subject { instance.formatted_value } + + context 'value is some float string' do + let(:value) { '3.14' } + + it 'is the float string' do + is_expected.to eql value + end + + it 'is localized' do + I18n.with_locale(:de) do + is_expected.to eql '3,14' + end + end + end + + context 'value is blank' do + let(:value) { '' } + + it 'is a blank string' do + is_expected.to eql value + end + end + + context 'value is nil' do + let(:value) { nil } + + it 'is a blank string' do + is_expected.to eql '' + end + end + end + describe '#validate_type_of_value' do - subject { described_class.new(custom_value).validate_type_of_value } + subject { instance.validate_type_of_value } context 'value is float string in decimal notation' do let(:value) { '3.14' } diff --git a/spec/models/custom_value/int_strategy_spec.rb b/spec/models/custom_value/int_strategy_spec.rb index 4097da3156..3674c9fcd6 100644 --- a/spec/models/custom_value/int_strategy_spec.rb +++ b/spec/models/custom_value/int_strategy_spec.rb @@ -29,13 +29,14 @@ require 'spec_helper' describe CustomValue::IntStrategy do - let(:custom_value) { + let(:instance) { described_class.new(custom_value) } + let(:custom_value) do double('CustomValue', value: value) - } + end describe '#typed_value' do - subject { described_class.new(custom_value).typed_value } + subject { instance.typed_value } context 'value is some float string' do let(:value) { '10' } @@ -53,8 +54,27 @@ describe CustomValue::IntStrategy do end end + describe '#formatted_value' do + subject { instance.typed_value } + + context 'value is some int string' do + let(:value) { '10' } + it { is_expected.to eql(10) } + end + + context 'value is blank' do + let(:value) { '' } + it { is_expected.to be_nil } + end + + context 'value is nil' do + let(:value) { nil } + it { is_expected.to be_nil } + end + end + describe '#validate_type_of_value' do - subject { described_class.new(custom_value).validate_type_of_value } + subject { instance.validate_type_of_value } context 'value is positive int string' do let(:value) { '10' } diff --git a/spec/models/custom_value/list_strategy_spec.rb b/spec/models/custom_value/list_strategy_spec.rb index a661f9d6bb..c71419dcdc 100644 --- a/spec/models/custom_value/list_strategy_spec.rb +++ b/spec/models/custom_value/list_strategy_spec.rb @@ -29,6 +29,7 @@ require 'spec_helper' describe CustomValue::ListStrategy do + let(:instance) { described_class.new(custom_value) } let(:custom_field) { FactoryGirl.create :list_wp_custom_field } let(:custom_value) do double("CustomField", value: value, custom_field: custom_field, customized: customized) @@ -37,7 +38,7 @@ describe CustomValue::ListStrategy do let(:customized) { double('customized') } describe '#parse_value/#typed_value' do - subject { described_class.new(custom_value) } + subject { instance } context 'with a CustomOption' do let(:value) { custom_field.custom_options.first } @@ -89,8 +90,49 @@ describe CustomValue::ListStrategy do end end + describe '#formatted_value' do + subject { instance.formatted_value } + + context 'with a CustomOption' do + let(:value) { custom_field.custom_options.first } + + it 'is the custom option to_s (without db access)' do + instance.parse_value(value) + + expect(CustomOption) + .to_not receive(:where) + + expect(subject).to eql value.to_s + end + end + + context 'with an id string' do + let(:value) { custom_field.custom_options.first.id.to_s } + + it 'is the custom option to_s (with db access)' do + expect(subject).to eql custom_field.custom_options.first.to_s + end + end + + context 'value is blank' do + let(:value) { '' } + + it 'is blank' do + expect(subject).to eql value + end + end + + context 'value is nil' do + let(:value) { nil } + + it 'is blank' do + expect(subject).to eql '' + end + end + end + describe '#validate_type_of_value' do - subject { described_class.new(custom_value).validate_type_of_value } + subject { instance.validate_type_of_value } context 'value is included' do let(:value) { custom_field.custom_options.first.id.to_s } diff --git a/spec/models/custom_value/string_strategy_spec.rb b/spec/models/custom_value/string_strategy_spec.rb index f9e36fd86c..da9f1786ee 100644 --- a/spec/models/custom_value/string_strategy_spec.rb +++ b/spec/models/custom_value/string_strategy_spec.rb @@ -29,13 +29,14 @@ require 'spec_helper' describe CustomValue::StringStrategy do - let(:custom_value) { + let(:instance) { described_class.new(custom_value) } + let(:custom_value) do double('CustomValue', value: value) - } + end describe '#typed_value' do - subject { described_class.new(custom_value).typed_value } + subject { instance.typed_value } context 'value is some string' do let(:value) { 'foo bar!' } @@ -53,8 +54,36 @@ describe CustomValue::StringStrategy do end end + describe '#formatted_value' do + subject { instance.formatted_value } + + context 'value is some string' do + let(:value) { 'foo bar!' } + + it 'is the string' do + is_expected.to eql value + end + end + + context 'value is blank' do + let(:value) { '' } + + it 'is a blank string' do + is_expected.to eql value + end + end + + context 'value is nil' do + let(:value) { nil } + + it 'is a blank string' do + is_expected.to eql '' + end + end + end + describe '#validate_type_of_value' do - subject { described_class.new(custom_value).validate_type_of_value } + subject { instance.validate_type_of_value } context 'value is some string' do let(:value) { 'foo bar!' } diff --git a/spec/models/custom_value/user_strategy_spec.rb b/spec/models/custom_value/user_strategy_spec.rb index 1cd08e2081..0965211bb5 100644 --- a/spec/models/custom_value/user_strategy_spec.rb +++ b/spec/models/custom_value/user_strategy_spec.rb @@ -29,6 +29,7 @@ require 'spec_helper' describe CustomValue::UserStrategy do + let(:instance) { described_class.new(custom_value) } let(:custom_value) do double('CustomValue', value: value, @@ -40,7 +41,7 @@ describe CustomValue::UserStrategy do let(:user) { FactoryGirl.build_stubbed(:user) } describe '#parse_value/#typed_value' do - subject { described_class.new(custom_value) } + subject { instance } context 'with a user' do let(:value) { user } @@ -97,8 +98,54 @@ describe CustomValue::UserStrategy do end end + describe '#formatted_value' do + subject { instance.formatted_value } + + context 'with a User' do + let(:value) { user } + + it 'is the user to_s (without db access)' do + instance.parse_value(value) + + expect(User) + .to_not receive(:find_by) + + expect(subject).to eql value.to_s + end + end + + context 'with an id string' do + let(:value) { user.id.to_s } + + it 'is the user to_s (with db access)' do + allow(User) + .to receive(:find_by) + .with(id: user.id.to_s) + .and_return(user) + + expect(subject).to eql user.to_s + end + end + + context 'value is blank' do + let(:value) { '' } + + it 'is blank and does not look for the user' do + expect(subject).to eql '' + end + end + + context 'value is nil' do + let(:value) { nil } + + it 'is blank and does not look for the user' do + expect(subject).to eql '' + end + end + end + describe '#validate_type_of_value' do - subject { described_class.new(custom_value).validate_type_of_value } + subject { instance.validate_type_of_value } let(:allowed_ids) { %w(12 13) } before do diff --git a/spec/models/custom_value/version_strategy_spec.rb b/spec/models/custom_value/version_strategy_spec.rb index 1a9be1c0ec..c421125c1e 100644 --- a/spec/models/custom_value/version_strategy_spec.rb +++ b/spec/models/custom_value/version_strategy_spec.rb @@ -29,18 +29,19 @@ require 'spec_helper' describe CustomValue::VersionStrategy do - let(:custom_value) { + let(:instance) { described_class.new(custom_value) } + let(:custom_value) do double('CustomValue', value: value, custom_field: custom_field, customized: customized) - } + end let(:customized) { double('customized') } let(:custom_field) { FactoryGirl.build(:custom_field) } let(:version) { FactoryGirl.build_stubbed(:version) } describe '#parse_value/#typed_value' do - subject { described_class.new(custom_value) } + subject { instance } context 'with a version' do let(:value) { version } @@ -97,8 +98,60 @@ describe CustomValue::VersionStrategy do end end + describe '#formatted_value' do + subject { instance.formatted_value } + + context 'with a version' do + let(:value) { version } + + it 'is the version to_s (without db access)' do + expect(Version) + .to_not receive(:find_by) + + instance.parse_value(value) + + is_expected.to eql value.to_s + end + end + + context 'with an id string' do + let(:value) { version.id.to_s } + + it 'is the version to_s (with db access)' do + allow(Version) + .to receive(:find_by) + .with(id: version.id.to_s) + .and_return(version) + + is_expected.to eql version.to_s + end + end + + context 'value is blank' do + let(:value) { '' } + + it 'is blank and does not look for the version' do + expect(Version) + .to_not receive(:find_by) + + is_expected.to eql '' + end + end + + context 'value is nil' do + let(:value) { nil } + + it 'is blank and does not look for the version' do + expect(Version) + .to_not receive(:find_by) + + is_expected.to eql '' + end + end + end + describe '#validate_type_of_value' do - subject { described_class.new(custom_value).validate_type_of_value } + subject { instance.validate_type_of_value } let(:allowed_ids) { %w(12 13) } before do diff --git a/spec_legacy/unit/custom_field_user_format_spec.rb b/spec_legacy/unit/custom_field_user_format_spec.rb index 7a0ed61313..6d95e37ca0 100644 --- a/spec_legacy/unit/custom_field_user_format_spec.rb +++ b/spec_legacy/unit/custom_field_user_format_spec.rb @@ -28,7 +28,7 @@ #++ require 'legacy_spec_helper' -describe 'CustomFieldFormat' do # TODO: what is this? +describe 'UserCustomField' do before do @project = FactoryGirl.create :valid_project role = FactoryGirl.create :role, permissions: [:view_work_packages, :edit_work_packages]