diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index df3216ca6b..9c6a56f30f 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -440,7 +440,6 @@ Style/RescueStandardError: Exclude: - 'lib/rails_admin/adapters/mongoid.rb' - 'lib/rails_admin/adapters/mongoid/bson.rb' - - 'lib/rails_admin/support/i18n.rb' # Offense count: 2 # Cop supports --auto-correct. diff --git a/app/assets/javascripts/rails_admin/ra.filter-box.js b/app/assets/javascripts/rails_admin/ra.filter-box.js index 41bd047293..aae15e99c1 100644 --- a/app/assets/javascripts/rails_admin/ra.filter-box.js +++ b/app/assets/javascripts/rails_admin/ra.filter-box.js @@ -34,33 +34,21 @@ } break; case 'date': - additional_control = - $('') - .css('display', (!field_operator || field_operator == "default") ? 'inline-block' : 'none') - .prop('name', value_name + '[]') - .prop('value', field_value[0] || '') - .add( - $('') - .css('display', (field_operator == "between") ? 'inline-block' : 'none') - .prop('name', value_name + '[]') - .prop('value', field_value[1] || '') - ) - .add( - $('') - .css('display', (field_operator == "between") ? 'inline-block' : 'none') - .prop('name', value_name + '[]') - .prop('value', field_value[2] || '') - ); case 'datetime': case 'timestamp': + case 'time': control = control || $('') .prop('name', operator_name) - .append($('').prop('selected', field_operator == "default").text(RailsAdmin.I18n.t("date"))) + .append($('').prop('selected', field_operator == "default").text(RailsAdmin.I18n.t(field_type == "time" ? "time" : "date"))) .append($('').prop('selected', field_operator == "between").text(RailsAdmin.I18n.t("between_and_"))) - .append($('').prop('selected', field_operator == "today").text(RailsAdmin.I18n.t("today"))) - .append($('').prop('selected', field_operator == "yesterday").text(RailsAdmin.I18n.t("yesterday"))) - .append($('').prop('selected', field_operator == "this_week").text(RailsAdmin.I18n.t("this_week"))) - .append($('').prop('selected', field_operator == "last_week").text(RailsAdmin.I18n.t("last_week"))) + if (field_type != 'time') { + control.append([ + $('').prop('selected', field_operator == "today").text(RailsAdmin.I18n.t("today")), + $('').prop('selected', field_operator == "yesterday").text(RailsAdmin.I18n.t("yesterday")), + $('').prop('selected', field_operator == "this_week").text(RailsAdmin.I18n.t("this_week")), + $('').prop('selected', field_operator == "last_week").text(RailsAdmin.I18n.t("last_week")), + ]) + } if (!required) { control.append([ '', @@ -68,23 +56,24 @@ $('').prop('selected', field_operator == "_null").text(RailsAdmin.I18n.t("is_blank")) ]) } - additional_control = additional_control || - $('') - .css('display', (!field_operator || field_operator == "default") ? 'inline-block' : 'none') - .prop('name', value_name + '[]') - .prop('value', field_value[0] || '') - .add( - $('') - .css('display', (field_operator == "between") ? 'inline-block' : 'none') - .prop('name', value_name + '[]') - .prop('value', field_value[1] || '') - ) - .add( - $('') - .css('display', (field_operator == "between") ? 'inline-block' : 'none') - .prop('name', value_name + '[]') - .prop('value', field_value[2] || '') - ); + additional_control = + $.map([undefined, '-∞', '∞'], function(placeholder, index){ + var visible = index == 0 ? (!field_operator || field_operator == "default") : (field_operator == "between"); + return $('') + .addClass(index == 0 ? 'default' : 'between') + .css('display', visible ? 'inline-block' : 'none') + .html( + $('') + .prop('name', value_name + '[]') + .prop('value', field_value[index] || '') + .add( + $('') + .addClass(field_type == 'date' ? 'date' : 'datetime') + .prop('size', field_type == 'date' || field_type == 'time' ? 20 : 25) + .prop('placeholder', placeholder) + ) + ); + }); break; case 'enum': var multiple_values = ((field_value instanceof Array) ? true : false) @@ -193,10 +182,18 @@ $('#filters_box').append($content); - $content.find('.date, .datetime').datetimepicker({ - locale: RailsAdmin.I18n.locale, - showTodayButton: true, - format: options['datetimepicker_format'] + $content.find('.date, .datetime').each(function() { + $(this).datetimepicker({ + date: moment($(this).siblings('[type=hidden]').val()), + locale: RailsAdmin.I18n.locale, + showTodayButton: true, + format: options['datetimepicker_format'] + }); + $(this).on('dp.change', function(e) { + if (e.date) { + $(this).siblings('[type=hidden]').val(e.date.format('YYYY-MM-DD[T]HH:mm:ss')); + } + }); }); $("hr.filters_box:hidden").show('slow'); diff --git a/app/assets/javascripts/rails_admin/ra.widgets.js b/app/assets/javascripts/rails_admin/ra.widgets.js index 534afeec54..9081149bfa 100644 --- a/app/assets/javascripts/rails_admin/ra.widgets.js +++ b/app/assets/javascripts/rails_admin/ra.widgets.js @@ -37,9 +37,15 @@ var options; options = $(this).data('options'); $.extend(options, { + date: moment($(this).siblings('[type=hidden]').val()), locale: RailsAdmin.I18n.locale }); $(this).datetimepicker(options); + $(this).on('dp.change', function(e) { + if (e.date) { + $(this).siblings('[type=hidden]').val(e.date.format('YYYY-MM-DD[T]HH:mm:ss')); + } + }); }); content.find('[data-enumeration]').each(function() { if ($(this).is('[multiple]')) { diff --git a/app/helpers/rails_admin/application_helper.rb b/app/helpers/rails_admin/application_helper.rb index 600b88989d..3a31d2832f 100644 --- a/app/helpers/rails_admin/application_helper.rb +++ b/app/helpers/rails_admin/application_helper.rb @@ -1,9 +1,5 @@ -require 'rails_admin/support/i18n' - module RailsAdmin module ApplicationHelper - include RailsAdmin::Support::I18n - def capitalize_first_letter(wording) return nil unless wording.present? && wording.is_a?(String) diff --git a/app/helpers/rails_admin/main_helper.rb b/app/helpers/rails_admin/main_helper.rb index bc56285826..3ee56d664f 100644 --- a/app/helpers/rails_admin/main_helper.rb +++ b/app/helpers/rails_admin/main_helper.rb @@ -69,7 +69,7 @@ def ordered_filter_options when :enum options[:select_options] = options_for_select(field.with(object: @abstract_model.model.new).enum, filter_hash['v']) when :date, :datetime, :time - options[:datetimepicker_format] = field.parser.to_momentjs + options[:datetimepicker_format] = field.momentjs_format end options[:label] = field.label options[:name] = field.name diff --git a/app/views/rails_admin/main/_form_datetime.html.haml b/app/views/rails_admin/main/_form_datetime.html.haml index 558c2c7fee..091688d1f2 100644 --- a/app/views/rails_admin/main/_form_datetime.html.haml +++ b/app/views/rails_admin/main/_form_datetime.html.haml @@ -1,5 +1,6 @@ .form-inline .input-group - = form.send field.view_helper, field.method_name, field.html_attributes.reverse_merge({value: field.form_value, class: 'form-control', data: {datetimepicker: true, options: field.datepicker_options.to_json}}) + = form.hidden_field(field.method_name, id: nil, value: field.form_value) + = form.text_field field.method_name, field.html_attributes.reverse_merge({class: 'form-control', data: {datetimepicker: true, options: field.datepicker_options.to_json}, name: nil, value: nil}) = form.label(field.method_name, class: 'input-group-addon') do %i.fa.fa-fw.fa-calendar diff --git a/app/views/rails_admin/main/index.html.haml b/app/views/rails_admin/main/index.html.haml index 21bd89d457..29a422ab6b 100644 --- a/app/views/rails_admin/main/index.html.haml +++ b/app/views/rails_admin/main/index.html.haml @@ -35,7 +35,7 @@ - else - '' %li - %a{href: '#', :"data-field-label" => field.label, :"data-field-name" => field.name, :"data-field-operator" => field.default_filter_operator, :"data-field-options" => field_options.html_safe, :"data-field-required" => field.required.to_s, :"data-field-type" => field.type, :"data-field-value" => "", :"data-field-datetimepicker-format" => (field.try(:parser) && field.parser.to_momentjs)}= capitalize_first_letter(field.label) + %a{href: '#', :"data-field-label" => field.label, :"data-field-name" => field.name, :"data-field-operator" => field.default_filter_operator, :"data-field-options" => field_options.html_safe, :"data-field-required" => field.required.to_s, :"data-field-type" => field.type, :"data-field-value" => "", :"data-field-datetimepicker-format" => field.try(:momentjs_format)}= capitalize_first_letter(field.label) %style - properties.select{ |p| p.column_width.present? }.each do |property| diff --git a/config/locales/rails_admin.en.yml b/config/locales/rails_admin.en.yml index d7d641b9ca..31699ee0c4 100644 --- a/config/locales/rails_admin.en.yml +++ b/config/locales/rails_admin.en.yml @@ -11,6 +11,7 @@ en: yesterday: Yesterday this_week: This week last_week: Last week + time: Time ... number: Number ... contains: Contains is_exactly: Is exactly diff --git a/lib/rails_admin/abstract_model.rb b/lib/rails_admin/abstract_model.rb index 3929995b1a..61c7cdf062 100644 --- a/lib/rails_admin/abstract_model.rb +++ b/lib/rails_admin/abstract_model.rb @@ -139,7 +139,7 @@ def build_statement_for_type_generic case @type when :date build_statement_for_date - when :datetime, :timestamp + when :datetime, :timestamp, :time build_statement_for_datetime_or_timestamp end end @@ -178,8 +178,8 @@ def build_statement_for_date def build_statement_for_datetime_or_timestamp start_date, end_date = get_filtering_duration - start_date = start_date.try(:beginning_of_day) if start_date - end_date = end_date.try(:end_of_day) if end_date + start_date = start_date.beginning_of_day if start_date.is_a?(Date) + end_date = end_date.end_of_day if end_date.is_a?(Date) range_filter(start_date, end_date) end diff --git a/lib/rails_admin/adapters/active_record.rb b/lib/rails_admin/adapters/active_record.rb index 2defcb1ab9..6972a80c11 100644 --- a/lib/rails_admin/adapters/active_record.rb +++ b/lib/rails_admin/adapters/active_record.rb @@ -205,7 +205,9 @@ def boolean_unary_operators alias_method :numeric_unary_operators, :boolean_unary_operators def range_filter(min, max) - if min && max + if min && max && min == max + ["(#{@column} = ?)", min] + elsif min && max ["(#{@column} BETWEEN ? AND ?)", min, max] elsif min ["(#{@column} >= ?)", min] diff --git a/lib/rails_admin/adapters/mongoid.rb b/lib/rails_admin/adapters/mongoid.rb index 820cccd06a..f1643a6091 100644 --- a/lib/rails_admin/adapters/mongoid.rb +++ b/lib/rails_admin/adapters/mongoid.rb @@ -283,7 +283,9 @@ def build_statement_for_belongs_to_association_or_bson_object_id end def range_filter(min, max) - if min && max + if min && max && min == max + {@column => min} + elsif min && max {@column => {'$gte' => min, '$lte' => max}} elsif min {@column => {'$gte' => min}} diff --git a/lib/rails_admin/config/fields/types/date.rb b/lib/rails_admin/config/fields/types/date.rb index 825be17f5f..022fdf3f8c 100644 --- a/lib/rails_admin/config/fields/types/date.rb +++ b/lib/rails_admin/config/fields/types/date.rb @@ -7,6 +7,10 @@ module Types class Date < RailsAdmin::Config::Fields::Types::Datetime RailsAdmin::Config::Fields::Types.register(self) + def parse_value(value) + ::Date.parse(value) if value.present? + end + register_instance_option :date_format do :long end @@ -15,13 +19,6 @@ class Date < RailsAdmin::Config::Fields::Types::Datetime [:date, :formats] end - register_instance_option :datepicker_options do - { - showTodayButton: true, - format: parser.to_momentjs, - } - end - register_instance_option :html_attributes do { required: required?, diff --git a/lib/rails_admin/config/fields/types/datetime.rb b/lib/rails_admin/config/fields/types/datetime.rb index d233a52b26..5e5c715c9c 100644 --- a/lib/rails_admin/config/fields/types/datetime.rb +++ b/lib/rails_admin/config/fields/types/datetime.rb @@ -8,27 +8,14 @@ module Types class Datetime < RailsAdmin::Config::Fields::Base RailsAdmin::Config::Fields::Types.register(self) - def parser - RailsAdmin::Support::Datetime.new(strftime_format) - end - def parse_value(value) - parser.parse_string(value) + ::Time.zone.parse(value) end def parse_input(params) params[name] = parse_value(params[name]) if params[name] end - def value - parent_value = super - if %w(DateTime Date Time).include?(parent_value.class.name) - parent_value.in_time_zone - else - parent_value - end - end - register_instance_option :date_format do :long end @@ -43,10 +30,14 @@ def value "%B %d, %Y %H:%M" end + def momentjs_format + RailsAdmin::Support::Datetime.to_momentjs(strftime_format) + end + register_instance_option :datepicker_options do { showTodayButton: true, - format: parser.to_momentjs, + format: momentjs_format, } end @@ -61,6 +52,10 @@ def value true end + register_instance_option :queryable? do + false + end + register_instance_option :formatted_value do if time = (value || default_value) ::I18n.l(time, format: strftime_format) @@ -72,6 +67,10 @@ def value register_instance_option :partial do :form_datetime end + + def form_value + value&.in_time_zone&.strftime('%FT%T') || form_default_value + end end end end diff --git a/lib/rails_admin/config/fields/types/time.rb b/lib/rails_admin/config/fields/types/time.rb index cc9a609bd6..c974539324 100644 --- a/lib/rails_admin/config/fields/types/time.rb +++ b/lib/rails_admin/config/fields/types/time.rb @@ -8,10 +8,7 @@ class Time < RailsAdmin::Config::Fields::Types::Datetime RailsAdmin::Config::Fields::Types.register(self) def parse_value(value) - parent_value = super(value) - return unless parent_value - value_with_tz = parent_value.in_time_zone - ::DateTime.parse(value_with_tz.strftime('%Y-%m-%d %H:%M:%S')) + abstract_model.model.type_for_attribute(name.to_s).serialize(super)&.change(year: 2000, month: 1, day: 1) end register_instance_option :strftime_format do diff --git a/lib/rails_admin/config/fields/types/timestamp.rb b/lib/rails_admin/config/fields/types/timestamp.rb index 8025e7ff7a..b9c63bb8b6 100644 --- a/lib/rails_admin/config/fields/types/timestamp.rb +++ b/lib/rails_admin/config/fields/types/timestamp.rb @@ -7,10 +7,6 @@ module Types class Timestamp < RailsAdmin::Config::Fields::Types::Datetime # Register field type for the type loader RailsAdmin::Config::Fields::Types.register(self) - - @format = :long - @i18n_scope = [:time, :formats] - @js_plugin_options = {} end end end diff --git a/lib/rails_admin/support/datetime.rb b/lib/rails_admin/support/datetime.rb index e175c1b66c..8e3bcb5436 100644 --- a/lib/rails_admin/support/datetime.rb +++ b/lib/rails_admin/support/datetime.rb @@ -1,5 +1,3 @@ -require 'rails_admin/support/i18n' - module RailsAdmin module Support class Datetime @@ -26,73 +24,10 @@ class Datetime }.freeze class << self - include RailsAdmin::Support::I18n - - def delocalize(date_string, format) - return date_string if ::I18n.locale.to_s == 'en' - format.to_s.scan(/%[AaBbp]/) do |match| - case match - when '%A' - english = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] - day_names.each_with_index { |d, i| date_string = date_string.gsub(/#{d}/, english[i]) } - when '%a' - english = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] - abbr_day_names.each_with_index { |d, i| date_string = date_string.gsub(/#{d}/, english[i]) } - when '%B' - english = [nil, "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"][1..-1] - month_names.each_with_index { |m, i| date_string = date_string.gsub(/#{m}/, english[i]) } - when '%b' - english = [nil, "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"][1..-1] - abbr_month_names.each_with_index { |m, i| date_string = date_string.gsub(/#{m}/, english[i]) } - when '%p' - date_string = date_string.gsub(/#{::I18n.t('date.time.am', default: "am")}/, 'am') - date_string = date_string.gsub(/#{::I18n.t('date.time.pm', default: "pm")}/, 'pm') - end + def to_momentjs(strftime_format) + strftime_format.gsub(/\w[^.(!?%)\W]{1,}/, '[\0]').gsub(/%(\w|\-\w)/) do |match| + MOMENTJS_TRANSLATIONS[match] end - date_string - end - - def normalize(date_string, format) - return unless date_string - delocalize(date_string, format) - parse_date_string(date_string) - end - - # Parse normalized date strings using time zone - def parse_date_string(date_string) - ::Time.zone.parse(date_string) - end - end - - attr_reader :strftime_format - - def initialize(strftime_format) - @strftime_format = strftime_format - end - - # Ruby to javascript formatting options translator - def to_momentjs - strftime_format.gsub(/\w[^.(!?%)\W]{1,}/, '[\0]').gsub(/%(\w|\-\w)/) do |match| - MOMENTJS_TRANSLATIONS[match] - end - end - - # Delocalize a l10n datetime strings - def delocalize(value) - self.class.delocalize(value, strftime_format) - end - - def parse_string(value) - return if value.blank? - return value if %w(DateTime Date Time).include?(value.class.name) - return if (delocalized_value = delocalize(value)).blank? - - begin - # Adjust with the correct timezone and daylight savint time - datetime_with_wrong_tz = ::DateTime.strptime(delocalized_value, strftime_format.gsub('%-d', '%d')) - Time.zone.parse(datetime_with_wrong_tz.strftime('%Y-%m-%d %H:%M:%S')) - rescue ArgumentError - nil end end end diff --git a/lib/rails_admin/support/i18n.rb b/lib/rails_admin/support/i18n.rb deleted file mode 100644 index d4d4480db7..0000000000 --- a/lib/rails_admin/support/i18n.rb +++ /dev/null @@ -1,43 +0,0 @@ -require 'i18n' - -module RailsAdmin - module Support - module I18n - def abbr_day_names - ::I18n.t('date.abbr_day_names', raise: true) - rescue ::I18n::ArgumentError - ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] - end - - def abbr_month_names - begin - names = ::I18n.t('date.abbr_month_names', raise: true) - rescue ::I18n::ArgumentError - names = [nil, "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] - end - names[1..-1] - end - - def date_format - ::I18n.t('date.formats.default', raise: true) - rescue - "%Y-%m-%d" - end - - def day_names - ::I18n.t('date.day_names', raise: true) - rescue ::I18n::ArgumentError - ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] - end - - def month_names - begin - names = ::I18n.t('date.month_names', raise: true) - rescue ::I18n::ArgumentError - names = [nil, "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"] - end - names[1..-1] - end - end - end -end diff --git a/spec/controllers/rails_admin/main_controller_spec.rb b/spec/controllers/rails_admin/main_controller_spec.rb index e5c88e7513..1b18beb50f 100644 --- a/spec/controllers/rails_admin/main_controller_spec.rb +++ b/spec/controllers/rails_admin/main_controller_spec.rb @@ -245,27 +245,18 @@ def get(action, params) end describe 'sanitize_params_for!' do - context 'in France' do + context 'with datetime' do before do - I18n.locale = :fr ActionController::Parameters.permit_all_parameters = false - RailsAdmin.config FieldTest do - configure :datetime_field do - date_format { :default } - end - end - RailsAdmin.config Comment do configure :created_at do - date_format { :default } show end end RailsAdmin.config NestedFieldTest do configure :created_at do - date_format { :default } show end end @@ -273,19 +264,19 @@ def get(action, params) controller.params = ActionController::Parameters.new( 'field_test' => { 'unallowed_field' => "I shouldn't be here", - 'datetime_field' => '1 août 2010 00:00:00', + 'datetime_field' => '2010-08-01T00:00:00', 'nested_field_tests_attributes' => { 'new_1330520162002' => { 'comment_attributes' => { 'unallowed_field' => "I shouldn't be here", - 'created_at' => '2 août 2010 00:00:00', + 'created_at' => '2010-08-02T00:00:00', }, - 'created_at' => '3 août 2010 00:00:00', + 'created_at' => '2010-08-03T00:00:00', }, }, 'comment_attributes' => { 'unallowed_field' => "I shouldn't be here", - 'created_at' => '4 août 2010 00:00:00', + 'created_at' => '2010-08-04T00:00:00', }, }, ) @@ -294,7 +285,6 @@ def get(action, params) after do ActionController::Parameters.permit_all_parameters = true - I18n.locale = :en end it 'sanitize params recursively in nested forms' do diff --git a/spec/integration/actions/edit_spec.rb b/spec/integration/actions/edit_spec.rb index ed47d8db17..e6004a1bf6 100644 --- a/spec/integration/actions/edit_spec.rb +++ b/spec/integration/actions/edit_spec.rb @@ -695,39 +695,6 @@ class HelpTest < Tableless end end - context 'on clicking save without changing anything' do - before { @datetime = 'October 08, 2015 06:45' } - context 'when config.time_zone set' do - before do - RailsAdmin.config Player do - field :datetime_field - end - @old_timezone = Time.zone - Time.zone = ActiveSupport::TimeZone.new('Central Time (US & Canada)') - end - - after do - Time.zone = @old_timezone - end - - it 'does not alter datetime fields' do - visit new_path(model_name: 'field_test') - find('#field_test_datetime_field').set(@datetime) - click_button 'Save and edit' - expect(find('#field_test_datetime_field').value).to eq(@datetime) - end - end - - context 'without config.time_zone set (default)' do - it 'does not alter datetime fields' do - visit new_path(model_name: 'field_test') - find('#field_test_datetime_field').set(@datetime) - click_button 'Save and edit' - expect(find('#field_test_datetime_field').value).to eq(@datetime) - end - end - end - context 'with errors' do before do @player = FactoryBot.create :player diff --git a/spec/integration/fields/base_spec.rb b/spec/integration/fields/base_spec.rb index 5bfdc2bd53..0e6509a67c 100644 --- a/spec/integration/fields/base_spec.rb +++ b/spec/integration/fields/base_spec.rb @@ -30,7 +30,7 @@ # So we manually cut off first newline character as a workaround here. expect(find_field('field_test[string_field]').value.gsub(/^\n/, '')).to eq('string_field default_value') expect(find_field('field_test[text_field]').value.gsub(/^\n/, '')).to eq('string_field text_field') - expect(find_field('field_test[date_field]').value).to eq(Date.today.to_s) + expect(find('[name="field_test[date_field]"]', visible: false).value).to eq(Date.today.to_s) expect(has_checked_field?('field_test[boolean_field]')).to be_truthy end diff --git a/spec/integration/fields/date_spec.rb b/spec/integration/fields/date_spec.rb new file mode 100644 index 0000000000..a95c19fc1a --- /dev/null +++ b/spec/integration/fields/date_spec.rb @@ -0,0 +1,148 @@ +require 'spec_helper' + +RSpec.describe 'Date field', type: :request do + subject { page } + before do + RailsAdmin.config FieldTest do + field :id + field :date_field + end + end + + describe 'Bootstrap Datetimepicker integration' do + describe 'for form' do + before { visit new_path({model_name: 'field_test'}.merge(params)) } + let(:params) { {} } + + it 'is initially blank', js: true do + expect(find('[name="field_test[date_field]"]', visible: false).value).to be_blank + expect(find('#field_test_date_field').value).to be_blank + end + + it 'populates the value selected by the Datetime picker into the hidden_field', js: true do + page.execute_script <<-JS + $('#field_test_date_field').data("DateTimePicker").date(moment('2015-10-08')).toggle(); + JS + expect(find('#field_test_date_field').value).to eq 'October 08, 2015' + expect(find('[name="field_test[date_field]"]', visible: false).value).to eq '2015-10-08T00:00:00' + end + + it 'ignores the time part', js: true do + page.execute_script <<-JS + $('#field_test_date_field').data("DateTimePicker").date(moment('2015-10-08 12:00:00')).toggle(); + JS + expect(find('#field_test_date_field').value).to eq 'October 08, 2015' + end + + it 'populates the value entered in the text field into the hidden_field', js: true do + fill_in 'Date field', with: 'January 2, 2021' + expect(find('[name="field_test[date_field]"]', visible: false).value).to eq '2021-01-02T00:00:00' + expect(find('#field_test_date_field').value).to eq 'January 02, 2021' + end + + context 'with locale set' do + around(:each) do |example| + original = I18n.default_locale + I18n.default_locale = :fr + example.run + I18n.default_locale = original + end + let(:params) { {field_test: {date_field: '2021-01-02T00:00:00'}} } + + it 'shows and accepts the value in the given locale', js: true do + expect(find('[name="field_test[date_field]"]', visible: false).value).to eq '2021-01-02T00:00:00' + expect(find('#field_test_date_field').value).to eq "2 janvier 2021" + fill_in 'Date field', with: '3 février 2021' + expect(find('[name="field_test[date_field]"]', visible: false).value).to eq '2021-02-03T00:00:00' + end + end + end + + describe 'for filter' do + it 'populates the value selected by the Datetime picker into the hidden_field', js: true do + visit index_path(model_name: 'field_test') + click_link 'Add filter' + click_link 'Date field' + expect(find('[name^="f[date_field]"][name$="[v][]"]', match: :first, visible: false).value).to be_blank + page.execute_script <<-JS + $('.form-control.date').data("DateTimePicker").date(moment('2015-10-08')).toggle(); + JS + expect(find('[name^="f[date_field]"][name$="[v][]"]', match: :first, visible: false).value).to eq '2015-10-08T00:00:00' + end + end + end + + describe 'filtering' do + let!(:field_tests) do + [FactoryBot.create(:field_test, date_field: Date.new(2021, 1, 2)), + FactoryBot.create(:field_test, date_field: Date.new(2021, 1, 3))] + end + + it 'correctly returns a record' do + visit index_path(model_name: 'field_test', f: {date_field: {'1' => {v: [nil, '2021-01-03T00:00:00', nil], o: 'between'}}}) + is_expected.to have_content '1 field test' + is_expected.to have_content field_tests[1].id + end + + it 'does not break when the condition is not filled' do + visit index_path(model_name: 'field_test', f: {date_field: {'1' => {v: [nil, '', ''], o: 'between'}}}) + is_expected.to have_content '2 field test' + end + + context 'with server timezone changed' do + around do |example| + original = Time.zone + Time.zone = ActiveSupport::TimeZone.new('Central Time (US & Canada)') + example.run + Time.zone = original + end + + it 'correctly returns a record' do + visit index_path(model_name: 'field_test', f: {date_field: {'1' => {v: [nil, '2021-01-03T00:00:00', nil], o: 'between'}}}) + is_expected.to have_content '1 field test' + is_expected.to have_content field_tests[1].id + end + end + end + + context 'on create' do + it 'persists the value' do + visit new_path(model_name: 'field_test') + find('[name="field_test[date_field]"]', visible: false).set('2021-01-02T00:00:00') + click_button 'Save' + expect(FieldTest.count).to eq 1 + expect(FieldTest.first.date_field).to eq Date.new(2021, 1, 2) + end + end + + context 'on update' do + let(:field_test) { FactoryBot.create :field_test, date_field: Date.new(2021, 1, 2) } + + it 'updates the value' do + visit edit_path(model_name: 'field_test', id: field_test.id) + expect(find('[name="field_test[date_field]"]', visible: false).value).to eq '2021-01-02T00:00:00' + find('[name="field_test[date_field]"]', visible: false).set('2021-02-03T00:00:00') + click_button 'Save' + field_test.reload + expect(field_test.date_field).to eq Date.new(2021, 2, 3) + end + end + + context 'with server timezone changed' do + let(:field_test) { FactoryBot.create :field_test, date_field: Date.new(2015, 10, 8) } + + around do |example| + original = Time.zone + Time.zone = ActiveSupport::TimeZone.new('Central Time (US & Canada)') + example.run + Time.zone = original + end + + it 'is not altered by just saving untouched' do + visit edit_path(model_name: 'field_test', id: field_test.id) + expect(find('[name="field_test[date_field]"]', visible: false).value).to eq '2015-10-08T00:00:00' + click_button 'Save' + expect { field_test.reload }.not_to change(field_test, :date_field) + end + end +end diff --git a/spec/integration/fields/datetime_spec.rb b/spec/integration/fields/datetime_spec.rb new file mode 100644 index 0000000000..2d91535f04 --- /dev/null +++ b/spec/integration/fields/datetime_spec.rb @@ -0,0 +1,150 @@ +require 'spec_helper' + +RSpec.describe 'Datetime field', type: :request do + subject { page } + before do + RailsAdmin.config FieldTest do + edit do + field :datetime_field + end + end + end + + describe 'Bootstrap Datetimepicker integration' do + describe 'for form' do + before { visit new_path({model_name: 'field_test'}.merge(params)) } + let(:params) { {} } + + it 'is initially blank', js: true do + expect(find('[name="field_test[datetime_field]"]', visible: false).value).to be_blank + expect(find('#field_test_datetime_field').value).to be_blank + end + + it 'populates the value selected by the Datetime picker into the hidden_field', js: true do + page.execute_script <<-JS + $('#field_test_datetime_field').data("DateTimePicker").date(moment('2015-10-08 14:00:00')).toggle(); + JS + expect(find('#field_test_datetime_field').value).to eq 'October 08, 2015 14:00' + expect(find('[name="field_test[datetime_field]"]', visible: false).value).to eq '2015-10-08T14:00:00' + end + + it 'populates the value entered in the text field into the hidden_field', js: true do + fill_in 'Datetime field', with: 'January 2, 2021 03:45' + expect(find('[name="field_test[datetime_field]"]', visible: false).value).to eq '2021-01-02T03:45:00' + expect(find('#field_test_datetime_field').value).to eq 'January 02, 2021 03:45' + end + + context 'with locale set' do + around(:each) do |example| + original = I18n.default_locale + I18n.default_locale = :fr + example.run + I18n.default_locale = original + end + let(:params) { {field_test: {datetime_field: '2021-01-02T03:45:00'}} } + + it 'shows and accepts the value in the given locale', js: true do + expect(find('[name="field_test[datetime_field]"]', visible: false).value).to eq '2021-01-02T03:45:00' + expect(find('#field_test_datetime_field').value).to eq "samedi 02 janvier 2021 03:45" + fill_in 'Datetime field', with: 'mercredi 03 février 2021 04:55' + expect(find('[name="field_test[datetime_field]"]', visible: false).value).to eq '2021-02-03T04:55:00' + end + end + end + + describe 'for filter' do + it 'populates the value selected by the Datetime picker into the hidden_field', js: true do + visit index_path(model_name: 'field_test') + click_link 'Add filter' + click_link 'Datetime field' + expect(find('[name^="f[datetime_field]"][name$="[v][]"]', match: :first, visible: false).value).to be_blank + page.execute_script <<-JS + $('.form-control.datetime').data("DateTimePicker").date(moment('2015-10-08 14:00:00')).toggle(); + JS + expect(find('[name^="f[datetime_field]"][name$="[v][]"]', match: :first, visible: false).value).to eq '2015-10-08T14:00:00' + end + end + end + + describe 'filtering' do + let!(:field_tests) do + [FactoryBot.create(:field_test, datetime_field: DateTime.new(2021, 1, 2, 3, 45)), + FactoryBot.create(:field_test, datetime_field: DateTime.new(2021, 1, 2, 4, 45))] + end + + it 'correctly returns a record' do + visit index_path(model_name: 'field_test', f: {datetime_field: {'1' => {v: [nil, '2021-01-02T04:00:00', nil], o: 'between'}}}) + is_expected.to have_content '1 field test' + is_expected.to have_content field_tests[1].id + end + + it 'does not break when the condition is not filled' do + visit index_path(model_name: 'field_test', f: {datetime_field: {'1' => {v: [nil, '', ''], o: 'between'}}}) + is_expected.to have_content '2 field test' + end + + context 'with server timezone changed' do + around do |example| + original = Time.zone + Time.zone = ActiveSupport::TimeZone.new('Central Time (US & Canada)') + example.run + Time.zone = original + end + + it 'correctly returns a record' do + visit index_path(model_name: 'field_test', f: {datetime_field: {'1' => {v: [nil, '2021-01-01T22:00:00', nil], o: 'between'}}}) + is_expected.to have_content '1 field test' + is_expected.to have_content field_tests[1].id + end + end + end + + context 'on create' do + it 'persists the value' do + visit new_path(model_name: 'field_test') + find('[name="field_test[datetime_field]"]', visible: false).set('2021-01-02T03:45:00') + click_button 'Save' + expect(FieldTest.count).to eq 1 + expect(FieldTest.first.datetime_field).to eq DateTime.new(2021, 1, 2, 3, 45) + end + end + + context 'on update' do + let(:field_test) { FactoryBot.create :field_test, datetime_field: DateTime.new(2021, 1, 2, 3, 45) } + + it 'updates the value' do + visit edit_path(model_name: 'field_test', id: field_test.id) + expect(find('[name="field_test[datetime_field]"]', visible: false).value).to eq '2021-01-02T03:45:00' + find('[name="field_test[datetime_field]"]', visible: false).set('2021-02-03T04:55:00') + click_button 'Save' + field_test.reload + expect(field_test.datetime_field).to eq DateTime.new(2021, 2, 3, 4, 55) + end + end + + context 'with server timezone changed' do + let(:field_test) { FactoryBot.create :field_test, datetime_field: DateTime.new(2015, 10, 8, 6, 45) } + + around do |example| + original = Time.zone + Time.zone = ActiveSupport::TimeZone.new('Central Time (US & Canada)') + example.run + Time.zone = original + end + + it 'treats the datetime set by the browser as local time' do + visit edit_path(model_name: 'field_test', id: field_test.id) + find('[name="field_test[datetime_field]"]', visible: false).set('2021-02-03T04:55:00') + click_button 'Save' + field_test.reload + expect(field_test.datetime_field.iso8601).to eq "2021-02-03T04:55:00-06:00" + end + + it 'is not altered by just saving untouched' do + visit edit_path(model_name: 'field_test', id: field_test.id) + expect(find('[name="field_test[datetime_field]"]', visible: false).value).to eq '2015-10-08T01:45:00' + click_button 'Save' + expect { field_test.reload }.not_to change(field_test, :datetime_field) + end + end +end diff --git a/spec/integration/fields/time_spec.rb b/spec/integration/fields/time_spec.rb new file mode 100644 index 0000000000..e7e273f537 --- /dev/null +++ b/spec/integration/fields/time_spec.rb @@ -0,0 +1,150 @@ +require 'spec_helper' + +RSpec.describe 'Time field', type: :request, active_record: true do + subject { page } + before do + RailsAdmin.config FieldTest do + edit do + field :time_field + end + end + end + + describe 'Bootstrap Datetimepicker integration' do + describe 'for form' do + before { visit new_path({model_name: 'field_test'}.merge(params)) } + let(:params) { {} } + + it 'is initially blank', js: true do + expect(find('[name="field_test[time_field]"]', visible: false).value).to be_blank + expect(find('#field_test_time_field').value).to be_blank + end + + it 'populates the value selected by the Datetime picker into the hidden_field', js: true do + page.execute_script <<-JS + $('#field_test_time_field').data("DateTimePicker").date(moment('2000-01-01 14:00:00')).toggle(); + JS + expect(find('#field_test_time_field').value).to eq '14:00' + expect(find('[name="field_test[time_field]"]', visible: false).value).to eq "#{Date.today}T14:00:00" + end + + it 'populates the value entered in the text field into the hidden_field', js: true do + fill_in 'Time field', with: '03:45' + expect(find('[name="field_test[time_field]"]', visible: false).value).to eq "#{Date.today}T03:45:00" + expect(find('#field_test_time_field').value).to eq '03:45' + end + + context 'with locale set' do + around(:each) do |example| + original = I18n.default_locale + I18n.default_locale = :fr + example.run + I18n.default_locale = original + end + let(:params) { {field_test: {time_field: '2000-01-01T03:45:00'}} } + + it 'shows and accepts the value in the given locale', js: true do + expect(find('[name="field_test[time_field]"]', visible: false).value).to eq '2000-01-01T03:45:00' + expect(find('#field_test_time_field').value).to eq "03:45" + fill_in 'Time field', with: '04:55' + expect(find('[name="field_test[time_field]"]', visible: false).value).to eq "#{Date.today}T04:55:00" + end + end + end + + describe 'for filter' do + it 'populates the value selected by the Datetime picker into the hidden_field', js: true do + visit index_path(model_name: 'field_test') + click_link 'Add filter' + click_link 'Time field' + expect(find('[name^="f[time_field]"][name$="[v][]"]', match: :first, visible: false).value).to be_blank + page.execute_script <<-JS + $('.form-control.datetime').data("DateTimePicker").date(moment('2000-01-01 14:00:00')).toggle(); + JS + expect(find('[name^="f[time_field]"][name$="[v][]"]', match: :first, visible: false).value).to eq "#{Date.today}T14:00:00" + end + end + end + + describe 'filtering' do + let!(:field_tests) do + [FactoryBot.create(:field_test, time_field: DateTime.new(2000, 1, 1, 3, 45)), + FactoryBot.create(:field_test, time_field: DateTime.new(2000, 1, 1, 4, 45))] + end + + it 'correctly returns a record' do + visit index_path(model_name: 'field_test', f: {time_field: {'1' => {v: [nil, '2000-01-01T04:00:00', nil], o: 'between'}}}) + is_expected.to have_content '1 field test' + is_expected.to have_content field_tests[1].id + end + + it 'does not break when the condition is not filled' do + visit index_path(model_name: 'field_test', f: {time_field: {'1' => {v: [nil, '', ''], o: 'between'}}}) + is_expected.to have_content '2 field test' + end + + context 'with server timezone changed' do + around do |example| + original = Time.zone + Time.zone = ActiveSupport::TimeZone.new('Central Time (US & Canada)') + example.run + Time.zone = original + end + + it 'correctly returns a record' do + visit index_path(model_name: 'field_test', f: {time_field: {'1' => {v: [nil, '2000-01-01T22:00:00', nil], o: 'between'}}}) + is_expected.to have_content '1 field test' + is_expected.to have_content field_tests[1].id + end + end + end + + context 'on create' do + it 'persists the value' do + visit new_path(model_name: 'field_test') + find('[name="field_test[time_field]"]', visible: false).set('2021-01-02T03:45:00') + click_button 'Save' + expect(FieldTest.count).to eq 1 + expect(FieldTest.first.time_field).to eq DateTime.new(2000, 1, 1, 3, 45) + end + end + + context 'on update' do + let(:field_test) { FactoryBot.create :field_test, time_field: DateTime.new(2021, 1, 2, 3, 45) } + + it 'updates the value' do + visit edit_path(model_name: 'field_test', id: field_test.id) + expect(find('[name="field_test[time_field]"]', visible: false).value).to eq '2000-01-01T03:45:00' + find('[name="field_test[time_field]"]', visible: false).set('2021-02-03T04:55:00') + click_button 'Save' + field_test.reload + expect(field_test.time_field).to eq DateTime.new(2000, 1, 1, 4, 55) + end + end + + context 'with server timezone changed' do + let(:field_test) { FactoryBot.create :field_test, time_field: DateTime.new(2000, 1, 1, 6, 45) } + + around do |example| + original = Time.zone + Time.zone = ActiveSupport::TimeZone.new('Central Time (US & Canada)') + example.run + Time.zone = original + end + + it 'treats the datetime set by the browser as local time' do + visit edit_path(model_name: 'field_test', id: field_test.id) + find('[name="field_test[time_field]"]', visible: false).set('2000-01-01T04:55:00') + click_button 'Save' + field_test.reload + expect(field_test.time_field.iso8601).to eq "2000-01-01T04:55:00-06:00" + end + + it 'is not altered by just saving untouched' do + visit edit_path(model_name: 'field_test', id: field_test.id) + expect(find('[name="field_test[time_field]"]', visible: false).value).to eq '2000-01-01T00:45:00' + click_button 'Save' + expect { field_test.reload }.not_to change(field_test, :time_field) + end + end +end diff --git a/spec/rails_admin/abstract_model_spec.rb b/spec/rails_admin/abstract_model_spec.rb index c44049032b..e4048b9f86 100644 --- a/spec/rails_admin/abstract_model_spec.rb +++ b/spec/rails_admin/abstract_model_spec.rb @@ -74,7 +74,7 @@ end end - context 'on dates with :en locale' do + context 'on dates' do before do [Date.new(2012, 1, 1), Date.new(2012, 1, 2), Date.new(2012, 1, 3), Date.new(2012, 1, 4)].each do |date| FactoryBot.create(:field_test, date_field: date) @@ -82,35 +82,30 @@ end it 'lists elements within outbound limits' do - expect(@abstract_model.all(filters: {'date_field' => {'1' => {v: ['', 'January 02, 2012', 'January 03, 2012'], o: 'between'}}}).count).to eq(2) - expect(@abstract_model.all(filters: {'date_field' => {'1' => {v: ['', 'January 02, 2012', 'January 02, 2012'], o: 'between'}}}).count).to eq(1) - expect(@abstract_model.all(filters: {'date_field' => {'1' => {v: ['', 'January 03, 2012', ''], o: 'between'}}}).count).to eq(2) - expect(@abstract_model.all(filters: {'date_field' => {'1' => {v: ['', '', 'January 02, 2012'], o: 'between'}}}).count).to eq(2) - expect(@abstract_model.all(filters: {'date_field' => {'1' => {v: ['January 02, 2012'], o: 'default'}}}).count).to eq(1) + expect(@abstract_model.all(filters: {'date_field' => {'1' => {v: ['', '2012-01-02', '2012-01-03'], o: 'between'}}}).count).to eq(2) + expect(@abstract_model.all(filters: {'date_field' => {'1' => {v: ['', '2012-01-02', '2012-01-02'], o: 'between'}}}).count).to eq(1) + expect(@abstract_model.all(filters: {'date_field' => {'1' => {v: ['', '2012-01-03', ''], o: 'between'}}}).count).to eq(2) + expect(@abstract_model.all(filters: {'date_field' => {'1' => {v: ['', '', '2012-01-02'], o: 'between'}}}).count).to eq(2) + expect(@abstract_model.all(filters: {'date_field' => {'1' => {v: ['2012-01-02'], o: 'default'}}}).count).to eq(1) end end - context 'on datetimes with :en locale' do + context 'on datetimes' do before do I18n.locale = :en FactoryBot.create(:field_test, datetime_field: Time.zone.local(2012, 1, 1, 23, 59, 59)) FactoryBot.create(:field_test, datetime_field: Time.zone.local(2012, 1, 2, 0, 0, 0)) FactoryBot.create(:field_test, datetime_field: Time.zone.local(2012, 1, 3, 23, 59, 59)) - - # TODO: Mongoid 3.0.0 mysteriously expands the range of inclusion slightly... - if defined?(Mongoid) && Mongoid::VERSION >= '3.0.0' - FactoryBot.create(:field_test, datetime_field: Time.zone.local(2012, 1, 4, 0, 0, 1)) - else - FactoryBot.create(:field_test, datetime_field: Time.zone.local(2012, 1, 4, 0, 0, 0)) - end + FactoryBot.create(:field_test, datetime_field: Time.zone.local(2012, 1, 4, 0, 0, 0)) end it 'lists elements within outbound limits' do - expect(@abstract_model.all(filters: {'datetime_field' => {'1' => {v: ['', 'January 02, 2012 12:00', 'January 03, 2012 12:00'], o: 'between'}}}).count).to eq(2) - expect(@abstract_model.all(filters: {'datetime_field' => {'1' => {v: ['', 'January 02, 2012 12:00', 'January 02, 2012 12:00'], o: 'between'}}}).count).to eq(1) - expect(@abstract_model.all(filters: {'datetime_field' => {'1' => {v: ['', 'January 03, 2012 12:00', ''], o: 'between'}}}).count).to eq(2) - expect(@abstract_model.all(filters: {'datetime_field' => {'1' => {v: ['', '', 'January 02, 2012 12:00'], o: 'between'}}}).count).to eq(2) - expect(@abstract_model.all(filters: {'datetime_field' => {'1' => {v: ['January 02, 2012 12:00'], o: 'default'}}}).count).to eq(1) + pending('Due to the JRuby SQLite3 datetime boundary issue') if RUBY_ENGINE == 'jruby' && CI_ORM == :active_record + expect(@abstract_model.all(filters: {'datetime_field' => {'1' => {v: ['', '2012-01-02T00:00:00', '2012-01-03T23:59:59'], o: 'between'}}}).count).to eq(2) + expect(@abstract_model.all(filters: {'datetime_field' => {'1' => {v: ['', '2012-01-02T00:00:00', '2012-01-03T12:00:00'], o: 'between'}}}).count).to eq(1) + expect(@abstract_model.all(filters: {'datetime_field' => {'1' => {v: ['', '2012-01-03T12:00:00', ''], o: 'between'}}}).count).to eq(2) + expect(@abstract_model.all(filters: {'datetime_field' => {'1' => {v: ['', '', '2012-01-02T12:00:00'], o: 'between'}}}).count).to eq(2) + expect(@abstract_model.all(filters: {'datetime_field' => {'1' => {v: ['2012-01-02T00:00:00'], o: 'default'}}}).count).to eq(1) end end end diff --git a/spec/rails_admin/adapters/active_record_spec.rb b/spec/rails_admin/adapters/active_record_spec.rb index 59227edc7f..df7d14ddee 100644 --- a/spec/rails_admin/adapters/active_record_spec.rb +++ b/spec/rails_admin/adapters/active_record_spec.rb @@ -503,21 +503,37 @@ def build_statement(type, value, operator) end end - describe 'date type queries' do + describe 'date/time type queries' do let(:scope) { FieldTest.all } it 'supports date type query' do - expect(predicates_for(abstract_model.send(:filter_scope, scope, 'date_field' => {'1' => {v: ['', 'February 01, 2012', 'March 01, 2012'], o: 'between'}}))).to eq(["(field_tests.date_field BETWEEN '2012-02-01' AND '2012-03-01')"]) - expect(predicates_for(abstract_model.send(:filter_scope, scope, 'date_field' => {'1' => {v: ['', 'March 01, 2012', ''], o: 'between'}}))).to eq(["(field_tests.date_field >= '2012-03-01')"]) - expect(predicates_for(abstract_model.send(:filter_scope, scope, 'date_field' => {'1' => {v: ['', '', 'February 01, 2012'], o: 'between'}}))).to eq(["(field_tests.date_field <= '2012-02-01')"]) - expect(predicates_for(abstract_model.send(:filter_scope, scope, 'date_field' => {'1' => {v: ['February 01, 2012'], o: 'default'}}))).to eq(["(field_tests.date_field BETWEEN '2012-02-01' AND '2012-02-01')"]) + expect(predicates_for(abstract_model.send(:filter_scope, scope, 'date_field' => {'1' => {v: ['', '2012-02-01', '2012-03-01'], o: 'between'}}))).to eq(["(field_tests.date_field BETWEEN '2012-02-01' AND '2012-03-01')"]) + expect(predicates_for(abstract_model.send(:filter_scope, scope, 'date_field' => {'1' => {v: ['', '2012-03-01', ''], o: 'between'}}))).to eq(["(field_tests.date_field >= '2012-03-01')"]) + expect(predicates_for(abstract_model.send(:filter_scope, scope, 'date_field' => {'1' => {v: ['', '', '2012-02-01'], o: 'between'}}))).to eq(["(field_tests.date_field <= '2012-02-01')"]) + expect(predicates_for(abstract_model.send(:filter_scope, scope, 'date_field' => {'1' => {v: ['2012-02-01'], o: 'default'}}))).to eq(["(field_tests.date_field = '2012-02-01')"]) + expect(predicates_for(abstract_model.send(:filter_scope, scope, 'date_field' => {'1' => {v: [], o: 'today'}}))).to eq(["(field_tests.date_field = '#{Date.today}')"]) + expect(predicates_for(abstract_model.send(:filter_scope, scope, 'date_field' => {'1' => {v: [], o: 'yesterday'}}))).to eq(["(field_tests.date_field = '#{Date.yesterday}')"]) + expect(predicates_for(abstract_model.send(:filter_scope, scope, 'date_field' => {'1' => {v: [], o: 'this_week'}}))).to eq(["(field_tests.date_field BETWEEN '#{Date.today.beginning_of_week}' AND '#{Date.today.end_of_week}')"]) + expect(predicates_for(abstract_model.send(:filter_scope, scope, 'date_field' => {'1' => {v: [], o: 'last_week'}}))).to eq(["(field_tests.date_field BETWEEN '#{1.week.ago.to_date.beginning_of_week}' AND '#{1.week.ago.to_date.end_of_week}')"]) end it 'supports datetime type query' do - expect(predicates_for(abstract_model.send(:filter_scope, scope, 'datetime_field' => {'1' => {v: ['', 'February 01, 2012 12:00', 'March 01, 2012 12:00'], o: 'between'}}))).to eq(predicates_for(scope.where(['(field_tests.datetime_field BETWEEN ? AND ?)', Time.utc(2012, 2, 1), Time.utc(2012, 3, 1).end_of_day]))) - expect(predicates_for(abstract_model.send(:filter_scope, scope, 'datetime_field' => {'1' => {v: ['', 'March 01, 2012 12:00', ''], o: 'between'}}))).to eq(predicates_for(scope.where(['(field_tests.datetime_field >= ?)', Time.utc(2012, 3, 1)]))) - expect(predicates_for(abstract_model.send(:filter_scope, scope, 'datetime_field' => {'1' => {v: ['', '', 'February 01, 2012 12:00'], o: 'between'}}))).to eq(predicates_for(scope.where(['(field_tests.datetime_field <= ?)', Time.utc(2012, 2, 1).end_of_day]))) - expect(predicates_for(abstract_model.send(:filter_scope, scope, 'datetime_field' => {'1' => {v: ['February 01, 2012 12:00'], o: 'default'}}))).to eq(predicates_for(scope.where(['(field_tests.datetime_field BETWEEN ? AND ?)', Time.utc(2012, 2, 1), Time.utc(2012, 2, 1).end_of_day]))) + expect(predicates_for(abstract_model.send(:filter_scope, scope, 'datetime_field' => {'1' => {v: ['', '2012-02-01T12:00:00', '2012-03-01T12:00:00'], o: 'between'}}))).to eq(["(field_tests.datetime_field BETWEEN '2012-02-01 12:00:00' AND '2012-03-01 12:00:00')"]) + expect(predicates_for(abstract_model.send(:filter_scope, scope, 'datetime_field' => {'1' => {v: ['', '2012-03-01T12:00:00', ''], o: 'between'}}))).to eq(["(field_tests.datetime_field >= '2012-03-01 12:00:00')"]) + expect(predicates_for(abstract_model.send(:filter_scope, scope, 'datetime_field' => {'1' => {v: ['', '', '2012-02-01T12:00:00'], o: 'between'}}))).to eq(["(field_tests.datetime_field <= '2012-02-01 12:00:00')"]) + expect(predicates_for(abstract_model.send(:filter_scope, scope, 'datetime_field' => {'1' => {v: ['2012-02-01T12:00:00'], o: 'default'}}))).to eq(["(field_tests.datetime_field = '2012-02-01 12:00:00')"]) + expect(predicates_for(abstract_model.send(:filter_scope, scope, 'datetime_field' => {'1' => {v: [], o: 'today'}}))).to eq(["(field_tests.datetime_field BETWEEN '#{Date.today} 00:00:00' AND '#{Date.today} 23:59:59.999999')"]) + expect(predicates_for(abstract_model.send(:filter_scope, scope, 'datetime_field' => {'1' => {v: [], o: 'yesterday'}}))).to eq(["(field_tests.datetime_field BETWEEN '#{Date.yesterday} 00:00:00' AND '#{Date.yesterday} 23:59:59.999999')"]) + expect(predicates_for(abstract_model.send(:filter_scope, scope, 'datetime_field' => {'1' => {v: [], o: 'this_week'}}))).to eq(["(field_tests.datetime_field BETWEEN '#{Date.today.beginning_of_week} 00:00:00' AND '#{Date.today.end_of_week} 23:59:59.999999')"]) + expect(predicates_for(abstract_model.send(:filter_scope, scope, 'datetime_field' => {'1' => {v: [], o: 'last_week'}}))).to eq(["(field_tests.datetime_field BETWEEN '#{1.week.ago.to_date.beginning_of_week} 00:00:00' AND '#{1.week.ago.to_date.end_of_week} 23:59:59.999999')"]) + end + + it 'supports time type query' do + expect(predicates_for(abstract_model.send(:filter_scope, scope, 'time_field' => {'1' => {v: ['', '2000-01-01T12:00:00', '2000-01-01T14:00:00'], o: 'between'}}))).to eq(["(field_tests.time_field BETWEEN '2000-01-01 12:00:00' AND '2000-01-01 14:00:00')"]) + expect(predicates_for(abstract_model.send(:filter_scope, scope, 'time_field' => {'1' => {v: ['', '2000-01-01T14:00:00', ''], o: 'between'}}))).to eq(["(field_tests.time_field >= '2000-01-01 14:00:00')"]) + expect(predicates_for(abstract_model.send(:filter_scope, scope, 'time_field' => {'1' => {v: ['', '', '2000-01-01T12:00:00'], o: 'between'}}))).to eq(["(field_tests.time_field <= '2000-01-01 12:00:00')"]) + expect(predicates_for(abstract_model.send(:filter_scope, scope, 'time_field' => {'1' => {v: ['2000-01-01T12:00:00'], o: 'default'}}))).to eq(["(field_tests.time_field = '2000-01-01 12:00:00')"]) + expect(predicates_for(abstract_model.send(:filter_scope, scope, 'time_field' => {'1' => {v: ['2021-02-03T12:00:00'], o: 'default'}}))).to eq(["(field_tests.time_field = '2000-01-01 12:00:00')"]) end end diff --git a/spec/rails_admin/adapters/mongoid_spec.rb b/spec/rails_admin/adapters/mongoid_spec.rb index f9f9fe8518..3a72e00085 100644 --- a/spec/rails_admin/adapters/mongoid_spec.rb +++ b/spec/rails_admin/adapters/mongoid_spec.rb @@ -127,7 +127,7 @@ RailsAdmin.config Team do field :players do queryable true - searchable :all + searchable :name end end @teams = FactoryBot.create_list(:team, 3) @@ -151,7 +151,7 @@ RailsAdmin.config Team do field :fans do queryable true - searchable :all + searchable :name end end @teams = FactoryBot.create_list(:team, 3) @@ -383,17 +383,25 @@ def parse_value(value) end it 'supports date type query' do - expect(@abstract_model.send(:filter_scope, FieldTest, 'date_field' => {'1' => {v: ['', 'January 02, 2012', 'January 03, 2012'], o: 'between'}}).selector).to eq('$and' => [{'date_field' => {'$gte' => Date.new(2012, 1, 2), '$lte' => Date.new(2012, 1, 3)}}]) - expect(@abstract_model.send(:filter_scope, FieldTest, 'date_field' => {'1' => {v: ['', 'January 03, 2012', ''], o: 'between'}}).selector).to eq('$and' => [{'date_field' => {'$gte' => Date.new(2012, 1, 3)}}]) - expect(@abstract_model.send(:filter_scope, FieldTest, 'date_field' => {'1' => {v: ['', '', 'January 02, 2012'], o: 'between'}}).selector).to eq('$and' => [{'date_field' => {'$lte' => Date.new(2012, 1, 2)}}]) - expect(@abstract_model.send(:filter_scope, FieldTest, 'date_field' => {'1' => {v: ['January 02, 2012'], o: 'default'}}).selector).to eq('$and' => [{'date_field' => {'$gte' => Date.new(2012, 1, 2), '$lte' => Date.new(2012, 1, 2)}}]) + expect(@abstract_model.send(:filter_scope, FieldTest, 'date_field' => {'1' => {v: ['', '2012-01-02', '2012-01-03'], o: 'between'}}).selector).to eq('$and' => [{'date_field' => {'$gte' => Date.new(2012, 1, 2), '$lte' => Date.new(2012, 1, 3)}}]) + expect(@abstract_model.send(:filter_scope, FieldTest, 'date_field' => {'1' => {v: ['', '2012-01-03', ''], o: 'between'}}).selector).to eq('$and' => [{'date_field' => {'$gte' => Date.new(2012, 1, 3)}}]) + expect(@abstract_model.send(:filter_scope, FieldTest, 'date_field' => {'1' => {v: ['', '', '2012-01-02'], o: 'between'}}).selector).to eq('$and' => [{'date_field' => {'$lte' => Date.new(2012, 1, 2)}}]) + expect(@abstract_model.send(:filter_scope, FieldTest, 'date_field' => {'1' => {v: ['2012-01-02'], o: 'default'}}).selector).to eq('$and' => [{'date_field' => Date.new(2012, 1, 2)}]) + expect(@abstract_model.send(:filter_scope, FieldTest, 'date_field' => {'1' => {v: [], o: 'today'}}).selector).to eq('$and' => [{'date_field' => Date.today}]) + expect(@abstract_model.send(:filter_scope, FieldTest, 'date_field' => {'1' => {v: [], o: 'yesterday'}}).selector).to eq('$and' => [{'date_field' => Date.yesterday}]) + expect(@abstract_model.send(:filter_scope, FieldTest, 'date_field' => {'1' => {v: [], o: 'this_week'}}).selector).to eq('$and' => [{'date_field' => {'$gte' => Date.today.beginning_of_week, '$lte' => Date.today.end_of_week}}]) + expect(@abstract_model.send(:filter_scope, FieldTest, 'date_field' => {'1' => {v: [], o: 'last_week'}}).selector).to eq('$and' => [{'date_field' => {'$gte' => 1.week.ago.to_date.beginning_of_week, '$lte' => 1.week.ago.to_date.end_of_week}}]) end it 'supports datetime type query' do - expect(@abstract_model.send(:filter_scope, FieldTest, 'datetime_field' => {'1' => {v: ['', 'January 02, 2012 00:00', 'January 03, 2012 00:00'], o: 'between'}}).selector).to eq('$and' => [{'datetime_field' => {'$gte' => Time.zone.local(2012, 1, 2), '$lte' => Time.zone.local(2012, 1, 3).end_of_day}}]) - expect(@abstract_model.send(:filter_scope, FieldTest, 'datetime_field' => {'1' => {v: ['', 'January 03, 2012 00:00', ''], o: 'between'}}).selector).to eq('$and' => [{'datetime_field' => {'$gte' => Time.zone.local(2012, 1, 3)}}]) - expect(@abstract_model.send(:filter_scope, FieldTest, 'datetime_field' => {'1' => {v: ['', '', 'January 02, 2012 00:00'], o: 'between'}}).selector).to eq('$and' => [{'datetime_field' => {'$lte' => Time.zone.local(2012, 1, 2).end_of_day}}]) - expect(@abstract_model.send(:filter_scope, FieldTest, 'datetime_field' => {'1' => {v: ['January 02, 2012 00:00'], o: 'default'}}).selector).to eq('$and' => [{'datetime_field' => {'$gte' => Time.zone.local(2012, 1, 2), '$lte' => Time.zone.local(2012, 1, 2).end_of_day}}]) + expect(@abstract_model.send(:filter_scope, FieldTest, 'datetime_field' => {'1' => {v: ['', '2012-01-02T12:00:00', '2012-01-03T12:00:00'], o: 'between'}}).selector).to eq('$and' => [{'datetime_field' => {'$gte' => Time.zone.local(2012, 1, 2, 12), '$lte' => Time.zone.local(2012, 1, 3, 12)}}]) + expect(@abstract_model.send(:filter_scope, FieldTest, 'datetime_field' => {'1' => {v: ['', '2012-01-03T12:00:00', ''], o: 'between'}}).selector).to eq('$and' => [{'datetime_field' => {'$gte' => Time.zone.local(2012, 1, 3, 12)}}]) + expect(@abstract_model.send(:filter_scope, FieldTest, 'datetime_field' => {'1' => {v: ['', '', '2012-01-02T12:00:00'], o: 'between'}}).selector).to eq('$and' => [{'datetime_field' => {'$lte' => Time.zone.local(2012, 1, 2, 12)}}]) + expect(@abstract_model.send(:filter_scope, FieldTest, 'datetime_field' => {'1' => {v: ['2012-01-02T12:00:00'], o: 'default'}}).selector).to eq('$and' => [{'datetime_field' => Time.zone.local(2012, 1, 2, 12)}]) + expect(@abstract_model.send(:filter_scope, FieldTest, 'datetime_field' => {'1' => {v: [], o: 'today'}}).selector).to eq('$and' => [{'datetime_field' => {'$gte' => Date.today.beginning_of_day, '$lte' => Date.today.end_of_day}}]) + expect(@abstract_model.send(:filter_scope, FieldTest, 'datetime_field' => {'1' => {v: [], o: 'yesterday'}}).selector).to eq('$and' => [{'datetime_field' => {'$gte' => Date.yesterday.beginning_of_day, '$lte' => Date.yesterday.end_of_day}}]) + expect(@abstract_model.send(:filter_scope, FieldTest, 'datetime_field' => {'1' => {v: [], o: 'this_week'}}).selector).to eq('$and' => [{'datetime_field' => {'$gte' => Date.today.beginning_of_week.beginning_of_day, '$lte' => Date.today.end_of_week.end_of_day}}]) + expect(@abstract_model.send(:filter_scope, FieldTest, 'datetime_field' => {'1' => {v: [], o: 'last_week'}}).selector).to eq('$and' => [{'datetime_field' => {'$gte' => 1.week.ago.to_date.beginning_of_week.beginning_of_day, '$lte' => 1.week.ago.to_date.end_of_week.end_of_day}}]) end it 'supports enum type query' do diff --git a/spec/rails_admin/config/fields/types/time_spec.rb b/spec/rails_admin/config/fields/types/time_spec.rb index ceb0ec5746..3ba6a6420e 100644 --- a/spec/rails_admin/config/fields/types/time_spec.rb +++ b/spec/rails_admin/config/fields/types/time_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -RSpec.describe RailsAdmin::Config::Fields::Types::Time do +RSpec.describe RailsAdmin::Config::Fields::Types::Time, active_record: true do it_behaves_like 'a generic field type', :time_field, :time describe '#parse_input' do @@ -18,7 +18,7 @@ before :each do @object = FactoryBot.create(:field_test) - @time = ::Time.now.getutc + @time = ::Time.new(2000, 1, 1, 3, 45) end after :each do @@ -30,10 +30,10 @@ expect(@object.time_field.strftime('%H:%M')).to eq(@time.strftime('%H:%M')) end - it 'interprets time value as UTC when timezone is specified' do + it 'interprets time value as local time when timezone is specified' do Time.zone = 'Eastern Time (US & Canada)' # -05:00 - @object.time_field = field.parse_input(time_field: @time.strftime('%H:%M')) - expect(@object.time_field.utc.strftime('%H:%M')).to eq(@time.strftime('%H:%M')) + @object.time_field = field.parse_input(time_field: '2000-01-01T03:45:00') + expect(@object.time_field.strftime('%H:%M')).to eq('03:45') end context 'with a custom strftime_format' do diff --git a/spec/rails_admin/support/datetime_spec.rb b/spec/rails_admin/support/datetime_spec.rb index cc404d74f3..c36ce3ef38 100644 --- a/spec/rails_admin/support/datetime_spec.rb +++ b/spec/rails_admin/support/datetime_spec.rb @@ -11,8 +11,7 @@ '%-d %B %Y' => 'D MMMM YYYY', }.each do |strftime_format, momentjs_format| it "convert strftime_format to momentjs_format - example #{strftime_format}" do - strftime_format = RailsAdmin::Support::Datetime.new(strftime_format) - expect(strftime_format.to_momentjs).to eq momentjs_format + expect(RailsAdmin::Support::Datetime.to_momentjs(strftime_format)).to eq momentjs_format end end end