-
-
Save sgoedecke/c5c9806b2747f2566db161d0f4f0513b to your computer and use it in GitHub Desktop.
# frozen_string_literal: true | |
module AbcHelper | |
def bare_a() end | |
end |
# frozen_string_literal: true | |
require "abstract_unit" | |
require "set" | |
module AbstractController | |
module Testing | |
# Test basic dispatching. | |
# ==== | |
# * Call process | |
# * Test that the response_body is set correctly | |
class SimpleController < AbstractController::Base | |
end | |
class Me < SimpleController | |
def index | |
self.response_body = "Hello world" | |
"Something else" | |
end | |
end | |
class TestBasic < ActiveSupport::TestCase | |
test "dispatching works" do | |
controller = Me.new | |
controller.process(:index) | |
assert_equal "Hello world", controller.response_body | |
end | |
end | |
# Test Render mixin | |
# ==== | |
class RenderingController < AbstractController::Base | |
include AbstractController::Rendering | |
include ActionView::Rendering | |
def _prefixes | |
[] | |
end | |
def render(options = {}) | |
if options.is_a?(String) | |
options = { _template_name: options } | |
end | |
super | |
end | |
append_view_path File.expand_path("views", __dir__) | |
end | |
class Me2 < RenderingController | |
def index | |
render "index.erb" | |
end | |
def with_final_newline | |
render "with_final_newline.erb" | |
end | |
def index_to_string | |
self.response_body = render_to_string "index" | |
end | |
def action_with_ivars | |
@my_ivar = "Hello" | |
render "action_with_ivars.erb" | |
end | |
def naked_render | |
render | |
end | |
def rendering_to_body | |
self.response_body = render_to_body template: "naked_render" | |
end | |
def rendering_to_string | |
self.response_body = render_to_string template: "naked_render" | |
end | |
end | |
class TestRenderingController < ActiveSupport::TestCase | |
def setup | |
@controller = Me2.new | |
end | |
test "rendering templates works" do | |
@controller.process(:index) | |
assert_equal "Hello from index.erb", @controller.response_body | |
end | |
test "stripping final newline works" do | |
ActionView::Template::Handlers::ERB.strip_trailing_newlines = true | |
@controller.process(:with_final_newline) | |
assert_equal "Hello from with_final_newline.erb", @controller.response_body | |
ensure | |
ActionView::Template::Handlers::ERB.strip_trailing_newlines = false | |
end | |
test "render_to_string works with a String as an argument" do | |
@controller.process(:index_to_string) | |
assert_equal "Hello from index.erb", @controller.response_body | |
end | |
test "rendering passes ivars to the view" do | |
@controller.process(:action_with_ivars) | |
assert_equal "Hello from index_with_ivars.erb", @controller.response_body | |
end | |
test "rendering with no template name" do | |
@controller.process(:naked_render) | |
assert_equal "Hello from naked_render.erb", @controller.response_body | |
end | |
test "rendering to a rack body" do | |
@controller.process(:rendering_to_body) | |
assert_equal "Hello from naked_render.erb", @controller.response_body | |
end | |
test "rendering to a string" do | |
@controller.process(:rendering_to_string) | |
assert_equal "Hello from naked_render.erb", @controller.response_body | |
end | |
end | |
# Test rendering with prefixes | |
# ==== | |
# * self._prefix is used when defined | |
class PrefixedViews < RenderingController | |
private | |
def self.prefix | |
name.underscore | |
end | |
def _prefixes | |
[self.class.prefix] | |
end | |
end | |
class Me3 < PrefixedViews | |
def index | |
render | |
end | |
def formatted | |
self.formats = [:html] | |
render | |
end | |
end | |
class TestPrefixedViews < ActiveSupport::TestCase | |
def setup | |
@controller = Me3.new | |
end | |
test "templates are located inside their 'prefix' folder" do | |
@controller.process(:index) | |
assert_equal "Hello from me3/index.erb", @controller.response_body | |
end | |
test "templates included their format" do | |
@controller.process(:formatted) | |
assert_equal "Hello from me3/formatted.html.erb", @controller.response_body | |
end | |
end | |
class OverridingLocalPrefixes < AbstractController::Base | |
include AbstractController::Rendering | |
include ActionView::Rendering | |
append_view_path File.expand_path("views", __dir__) | |
def index | |
render | |
end | |
def self.local_prefixes | |
# this would usually return "abstract_controller/testing/overriding_local_prefixes" | |
super + ["abstract_controller/testing/me3"] | |
end | |
class Inheriting < self | |
end | |
end | |
class OverridingLocalPrefixesTest < ActiveSupport::TestCase | |
test "overriding .local_prefixes adds prefix" do | |
@controller = OverridingLocalPrefixes.new | |
@controller.process(:index) | |
assert_equal "Hello from me3/index.erb", @controller.response_body | |
end | |
test ".local_prefixes is inherited" do | |
@controller = OverridingLocalPrefixes::Inheriting.new | |
@controller.process(:index) | |
assert_equal "Hello from me3/index.erb", @controller.response_body | |
end | |
end | |
# Test rendering with layouts | |
# ==== | |
# self._layout is used when defined | |
class WithLayouts < PrefixedViews | |
include ActionView::Layouts | |
private | |
def self.layout(formats) | |
find_template(name.underscore, { formats: formats }, { _prefixes: ["layouts"] }) | |
rescue ActionView::MissingTemplate | |
begin | |
find_template("application", { formats: formats }, { _prefixes: ["layouts"] }) | |
rescue ActionView::MissingTemplate | |
end | |
end | |
def render_to_body(options = {}) | |
options[:_layout] = options[:layout] || _default_layout({}) | |
super | |
end | |
end | |
class Me4 < WithLayouts | |
def index | |
render | |
end | |
end | |
class TestLayouts < ActiveSupport::TestCase | |
test "layouts are included" do | |
controller = Me4.new | |
controller.process(:index) | |
assert_equal "Me4 Enter : Hello from me4/index.erb : Exit", controller.response_body | |
end | |
end | |
# respond_to_action?(action_name) | |
# ==== | |
# * A method can be used as an action only if this method | |
# returns true when passed the method name as an argument | |
# * Defaults to true in AbstractController | |
class DefaultRespondToActionController < AbstractController::Base | |
def index() self.response_body = "success" end | |
end | |
class ActionMissingRespondToActionController < AbstractController::Base | |
# No actions | |
private | |
def action_missing(action_name) | |
self.response_body = "success" | |
end | |
end | |
class RespondToActionController < AbstractController::Base | |
def index() self.response_body = "success" end | |
def fail() self.response_body = "fail" end | |
private | |
def method_for_action(action_name) | |
action_name.to_s != "fail" && action_name | |
end | |
end | |
class TestRespondToAction < ActiveSupport::TestCase | |
def assert_dispatch(klass, body = "success", action = :index) | |
controller = klass.new | |
controller.process(action) | |
assert_equal body, controller.response_body | |
end | |
test "an arbitrary method is available as an action by default" do | |
assert_dispatch DefaultRespondToActionController, "success", :index | |
end | |
test "raises ActionNotFound when method does not exist and action_missing is not defined" do | |
assert_raise(ActionNotFound) { DefaultRespondToActionController.new.process(:fail) } | |
end | |
test "dispatches to action_missing when method does not exist and action_missing is defined" do | |
assert_dispatch ActionMissingRespondToActionController, "success", :ohai | |
end | |
test "a method is available as an action if method_for_action returns true" do | |
assert_dispatch RespondToActionController, "success", :index | |
end | |
test "raises ActionNotFound if method is defined but method_for_action returns false" do | |
assert_raise(ActionNotFound) { RespondToActionController.new.process(:fail) } | |
end | |
end | |
class Me6 < AbstractController::Base | |
action_methods | |
def index | |
end | |
end | |
class TestActionMethodsReloading < ActiveSupport::TestCase | |
test "action_methods should be reloaded after defining a new method" do | |
assert_equal Set.new(["index"]), Me6.action_methods | |
end | |
end | |
end | |
end |
# frozen_string_literal: true | |
require "concurrent/map" | |
module ActionView | |
# This class defines the interface for a renderer. Each class that | |
# subclasses +AbstractRenderer+ is used by the base +Renderer+ class to | |
# render a specific type of object. | |
# | |
# The base +Renderer+ class uses its +render+ method to delegate to the | |
# renderers. These currently consist of | |
# | |
# PartialRenderer - Used for rendering partials | |
# TemplateRenderer - Used for rendering other types of templates | |
# StreamingTemplateRenderer - Used for streaming | |
# | |
# Whenever the +render+ method is called on the base +Renderer+ class, a new | |
# renderer object of the correct type is created, and the +render+ method on | |
# that new object is called in turn. This abstracts the set up and rendering | |
# into a separate classes for partials and templates. | |
class AbstractRenderer # :nodoc: | |
delegate :template_exists?, :any_templates?, :formats, to: :@lookup_context | |
def initialize(lookup_context) | |
@lookup_context = lookup_context | |
end | |
def render | |
raise NotImplementedError | |
end | |
module ObjectRendering # :nodoc: | |
PREFIXED_PARTIAL_NAMES = Concurrent::Map.new do |h, k| | |
h[k] = Concurrent::Map.new | |
end | |
def initialize(lookup_context, options) | |
super | |
@context_prefix = lookup_context.prefixes.first | |
end | |
private | |
def local_variable(path) | |
if as = @options[:as] | |
raise_invalid_option_as(as) unless /\A[a-z_]\w*\z/.match?(as.to_s) | |
as.to_sym | |
else | |
base = path.end_with?("/") ? "" : File.basename(path) | |
raise_invalid_identifier(path) unless base =~ /\A_?(.*?)(?:\.\w+)*\z/ | |
$1.to_sym | |
end | |
end | |
IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " \ | |
"make sure your partial name starts with underscore." | |
OPTION_AS_ERROR_MESSAGE = "The value (%s) of the option `as` is not a valid Ruby identifier; " \ | |
"make sure it starts with lowercase letter, " \ | |
"and is followed by any combination of letters, numbers and underscores." | |
def raise_invalid_identifier(path) | |
raise ArgumentError, IDENTIFIER_ERROR_MESSAGE % path | |
end | |
def raise_invalid_option_as(as) | |
raise ArgumentError, OPTION_AS_ERROR_MESSAGE % as | |
end | |
# Obtains the path to where the object's partial is located. If the object | |
# responds to +to_partial_path+, then +to_partial_path+ will be called and | |
# will provide the path. If the object does not respond to +to_partial_path+, | |
# then an +ArgumentError+ is raised. | |
# | |
# If +prefix_partial_path_with_controller_namespace+ is true, then this | |
# method will prefix the partial paths with a namespace. | |
def partial_path(object, view) | |
object = object.to_model if object.respond_to?(:to_model) | |
path = if object.respond_to?(:to_partial_path) | |
object.to_partial_path | |
else | |
raise ArgumentError.new("'#{object.inspect}' is not an ActiveModel-compatible object. It must implement :to_partial_path.") | |
end | |
if view.prefix_partial_path_with_controller_namespace | |
PREFIXED_PARTIAL_NAMES[@context_prefix][path] ||= merge_prefix_into_object_path(@context_prefix, path.dup) | |
else | |
path | |
end | |
end | |
def merge_prefix_into_object_path(prefix, object_path) | |
if prefix.include?(?/) && object_path.include?(?/) | |
prefixes = [] | |
prefix_array = File.dirname(prefix).split("/") | |
object_path_array = object_path.split("/")[0..-3] # skip model dir & partial | |
prefix_array.each_with_index do |dir, index| | |
break if dir == object_path_array[index] | |
prefixes << dir | |
end | |
(prefixes << object_path).join("/") | |
else | |
object_path | |
end | |
end | |
end | |
class RenderedCollection # :nodoc: | |
def self.empty(format) | |
EmptyCollection.new format | |
end | |
attr_reader :rendered_templates | |
def initialize(rendered_templates, spacer) | |
@rendered_templates = rendered_templates | |
@spacer = spacer | |
end | |
def body | |
@rendered_templates.map(&:body).join(@spacer.body).html_safe | |
end | |
def format | |
rendered_templates.first.format | |
end | |
class EmptyCollection | |
attr_reader :format | |
def initialize(format) | |
@format = format | |
end | |
def body; nil; end | |
end | |
end | |
class RenderedTemplate # :nodoc: | |
attr_reader :body, :template | |
def initialize(body, template) | |
@body = body | |
@template = template | |
end | |
def format | |
template.format | |
end | |
EMPTY_SPACER = Struct.new(:body).new | |
end | |
private | |
NO_DETAILS = {}.freeze | |
def extract_details(options) # :doc: | |
details = nil | |
LookupContext.registered_details.each do |key| | |
value = options[key] | |
if value | |
(details ||= {})[key] = Array(value) | |
end | |
end | |
details || NO_DETAILS | |
end | |
def prepend_formats(formats) # :doc: | |
formats = Array(formats) | |
return if formats.empty? || @lookup_context.html_fallback_for_js | |
@lookup_context.formats = formats | @lookup_context.formats | |
end | |
def build_rendered_template(content, template) | |
RenderedTemplate.new content, template | |
end | |
def build_rendered_collection(templates, spacer) | |
RenderedCollection.new templates, spacer | |
end | |
end | |
end |
# frozen_string_literal: true | |
$:.unshift File.expand_path("lib", __dir__) | |
ENV["TMPDIR"] = File.expand_path("tmp", __dir__) | |
require "active_support/core_ext/kernel/reporting" | |
# These are the normal settings that will be set up by Railties | |
# TODO: Have these tests support other combinations of these values | |
silence_warnings do | |
Encoding.default_internal = Encoding::UTF_8 | |
Encoding.default_external = Encoding::UTF_8 | |
end | |
require "active_support/testing/autorun" | |
require "active_support/testing/method_call_assertions" | |
require "action_controller" | |
require "action_view" | |
require "action_view/testing/resolvers" | |
require "active_support/dependencies" | |
require "active_model" | |
module ActionViewTestSuiteUtils | |
def self.require_helpers(helpers_dirs) | |
Array(helpers_dirs).each do |helpers_dir| | |
Dir.glob("#{helpers_dir}/**/*_helper.rb") do |helper_file| | |
require helper_file | |
end | |
end | |
end | |
end | |
ActionViewTestSuiteUtils.require_helpers("#{__dir__}/fixtures/helpers") | |
ActionViewTestSuiteUtils.require_helpers("#{__dir__}/fixtures/alternate_helpers") | |
Thread.abort_on_exception = true | |
# Show backtraces for deprecated behavior for quicker cleanup. | |
ActiveSupport::Deprecation.debug = true | |
# Disable available locale checks to avoid warnings running the test suite. | |
I18n.enforce_available_locales = false | |
ORIGINAL_LOCALES = I18n.available_locales.map(&:to_s).sort | |
FIXTURE_LOAD_PATH = File.expand_path("fixtures", __dir__) | |
module RenderERBUtils | |
def view | |
@view ||= begin | |
path = ActionView::FileSystemResolver.new(FIXTURE_LOAD_PATH) | |
view_paths = ActionView::PathSet.new([path]) | |
view = ActionView::Base.with_empty_template_cache | |
view.with_view_paths(view_paths) | |
end | |
end | |
def render_erb(string) | |
@virtual_path = nil | |
template = ActionView::Template.new( | |
string.strip, | |
"test template", | |
ActionView::Template.handler_for_extension(:erb), | |
format: :html, locals: []) | |
view = ActionView::Base.with_empty_template_cache | |
template.render(view.empty, {}).strip | |
end | |
end | |
class RoutedRackApp | |
attr_reader :routes | |
def initialize(routes, &blk) | |
@routes = routes | |
@stack = ActionDispatch::MiddlewareStack.new(&blk).build(@routes) | |
end | |
def call(env) | |
@stack.call(env) | |
end | |
end | |
class BasicController | |
attr_accessor :request, :response | |
def config | |
@config ||= ActiveSupport::InheritableOptions.new(ActionController::Base.config).tap do |config| | |
# VIEW TODO: View tests should not require a controller | |
public_dir = File.expand_path("fixtures/public", __dir__) | |
config.assets_dir = public_dir | |
config.javascripts_dir = "#{public_dir}/javascripts" | |
config.stylesheets_dir = "#{public_dir}/stylesheets" | |
config.assets = ActiveSupport::InheritableOptions.new(prefix: "assets") | |
config | |
end | |
end | |
end | |
class ActionDispatch::IntegrationTest < ActiveSupport::TestCase | |
def self.build_app(routes = nil) | |
routes ||= ActionDispatch::Routing::RouteSet.new.tap { |rs| | |
rs.draw { } | |
} | |
RoutedRackApp.new(routes) do |middleware| | |
middleware.use ActionDispatch::ShowExceptions, ActionDispatch::PublicExceptions.new("#{FIXTURE_LOAD_PATH}/public") | |
middleware.use ActionDispatch::DebugExceptions | |
middleware.use ActionDispatch::Callbacks | |
middleware.use ActionDispatch::Cookies | |
middleware.use ActionDispatch::Flash | |
middleware.use Rack::Head | |
yield(middleware) if block_given? | |
end | |
end | |
self.app = build_app | |
def with_routing(&block) | |
temporary_routes = ActionDispatch::Routing::RouteSet.new | |
old_app, self.class.app = self.class.app, self.class.build_app(temporary_routes) | |
yield temporary_routes | |
ensure | |
self.class.app = old_app | |
end | |
end | |
ActionView::RoutingUrlFor.include(ActionDispatch::Routing::UrlFor) | |
module ActionController | |
class Base | |
self.view_paths = FIXTURE_LOAD_PATH | |
def self.test_routes(&block) | |
routes = ActionDispatch::Routing::RouteSet.new | |
routes.draw(&block) | |
include routes.url_helpers | |
routes | |
end | |
end | |
class TestCase | |
include ActionDispatch::TestProcess | |
def self.with_routes(&block) | |
routes = ActionDispatch::Routing::RouteSet.new | |
routes.draw(&block) | |
include Module.new { | |
define_method(:setup) do | |
super() | |
@routes = routes | |
@controller.singleton_class.include @routes.url_helpers if @controller | |
end | |
} | |
routes | |
end | |
def with_routes(&block) | |
@routes = ActionDispatch::Routing::RouteSet.new | |
@routes.draw(&block) | |
@routes | |
end | |
end | |
end | |
module ActionDispatch | |
class DebugExceptions | |
private | |
remove_method :stderr_logger | |
# Silence logger | |
def stderr_logger | |
nil | |
end | |
end | |
end | |
class ActiveSupport::TestCase | |
if Process.respond_to?(:fork) && !Gem.win_platform? | |
parallelize | |
else | |
parallelize(with: :threads) | |
end | |
include ActiveSupport::Testing::MethodCallAssertions | |
private | |
# Skips the current run on Rubinius using Minitest::Assertions#skip | |
def rubinius_skip(message = "") | |
skip message if RUBY_ENGINE == "rbx" | |
end | |
# Skips the current run on JRuby using Minitest::Assertions#skip | |
def jruby_skip(message = "") | |
skip message if defined?(JRUBY_VERSION) | |
end | |
end | |
require_relative "../../tools/test_common" |
# frozen_string_literal: true | |
#-- | |
# Copyright (c) 2004-2022 David Heinemeier Hansson | |
# | |
# Permission is hereby granted, free of charge, to any person obtaining | |
# a copy of this software and associated documentation files (the | |
# "Software"), to deal in the Software without restriction, including | |
# without limitation the rights to use, copy, modify, merge, publish, | |
# distribute, sublicense, and/or sell copies of the Software, and to | |
# permit persons to whom the Software is furnished to do so, subject to | |
# the following conditions: | |
# | |
# The above copyright notice and this permission notice shall be | |
# included in all copies or substantial portions of the Software. | |
# | |
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | |
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | |
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
#++ | |
require "active_support" | |
require "active_support/rails" | |
require "action_view/version" | |
module ActionView | |
extend ActiveSupport::Autoload | |
ENCODING_FLAG = '#.*coding[:=]\s*(\S+)[ \t]*' | |
eager_autoload do | |
autoload :Base | |
autoload :Context | |
autoload :Digestor | |
autoload :Helpers | |
autoload :LookupContext | |
autoload :Layouts | |
autoload :PathSet | |
autoload :RecordIdentifier | |
autoload :Rendering | |
autoload :RoutingUrlFor | |
autoload :Template | |
autoload :TemplateDetails | |
autoload :TemplatePath | |
autoload :UnboundTemplate | |
autoload :ViewPaths | |
autoload_under "renderer" do | |
autoload :Renderer | |
autoload :AbstractRenderer | |
autoload :PartialRenderer | |
autoload :CollectionRenderer | |
autoload :ObjectRenderer | |
autoload :TemplateRenderer | |
autoload :StreamingTemplateRenderer | |
end | |
autoload_at "action_view/template/resolver" do | |
autoload :Resolver | |
autoload :FileSystemResolver | |
end | |
autoload_at "action_view/buffers" do | |
autoload :OutputBuffer | |
autoload :StreamingBuffer | |
end | |
autoload_at "action_view/flows" do | |
autoload :OutputFlow | |
autoload :StreamingFlow | |
end | |
autoload_at "action_view/template/error" do | |
autoload :MissingTemplate | |
autoload :ActionViewError | |
autoload :EncodingError | |
autoload :TemplateError | |
autoload :SyntaxErrorInTemplate | |
autoload :WrongEncodingError | |
end | |
end | |
autoload :CacheExpiry | |
autoload :TestCase | |
def self.eager_load! | |
super | |
ActionView::Helpers.eager_load! | |
ActionView::Template.eager_load! | |
end | |
end | |
require "active_support/core_ext/string/output_safety" | |
ActiveSupport.on_load(:i18n) do | |
I18n.load_path << File.expand_path("action_view/locale/en.yml", __dir__) | |
end |
# frozen_string_literal: true | |
require "active_support/core_ext/module/attribute_accessors" | |
require "active_support/core_ext/enumerable" | |
module ActionView | |
# = Active Model Helpers | |
module Helpers # :nodoc: | |
module ActiveModelHelper | |
end | |
module ActiveModelInstanceTag | |
def object | |
@active_model_object ||= begin | |
object = super | |
object.respond_to?(:to_model) ? object.to_model : object | |
end | |
end | |
def content_tag(type, options, *) | |
select_markup_helper?(type) ? super : error_wrapping(super) | |
end | |
def tag(type, options, *) | |
tag_generate_errors?(options) ? error_wrapping(super) : super | |
end | |
def error_wrapping(html_tag) | |
if object_has_errors? | |
@template_object.instance_exec(html_tag, self, &Base.field_error_proc) | |
else | |
html_tag | |
end | |
end | |
def error_message | |
object.errors[@method_name] | |
end | |
private | |
def object_has_errors? | |
object.respond_to?(:errors) && object.errors.respond_to?(:[]) && error_message.present? | |
end | |
def select_markup_helper?(type) | |
["optgroup", "option"].include?(type) | |
end | |
def tag_generate_errors?(options) | |
options["type"] != "hidden" | |
end | |
end | |
end | |
end |
# frozen_string_literal: true | |
require "abstract_unit" | |
class ActiveModelHelperTest < ActionView::TestCase | |
tests ActionView::Helpers::ActiveModelHelper | |
silence_warnings do | |
Post = Struct.new(:author_name, :body, :category, :published, :updated_at) do | |
include ActiveModel::Conversion | |
include ActiveModel::Validations | |
def persisted? | |
false | |
end | |
end | |
end | |
def setup | |
super | |
@post = Post.new | |
@post.errors.add(:author_name, "can't be empty") | |
@post.errors.add(:body, "foo") | |
@post.errors.add(:category, "must exist") | |
@post.errors.add(:published, "must be accepted") | |
@post.errors.add(:updated_at, "bar") | |
@post.author_name = "" | |
@post.body = "Back to the hill and over it again!" | |
@post.category = "rails" | |
@post.published = false | |
@post.updated_at = Date.new(2004, 6, 15) | |
end | |
def test_text_area_with_errors | |
assert_dom_equal( | |
%(<div class="field_with_errors"><textarea id="post_body" name="post[body]">\nBack to the hill and over it again!</textarea></div>), | |
text_area("post", "body") | |
) | |
end | |
def test_text_field_with_errors | |
assert_dom_equal( | |
%(<div class="field_with_errors"><input id="post_author_name" name="post[author_name]" type="text" value="" /></div>), | |
text_field("post", "author_name") | |
) | |
end | |
def test_select_with_errors | |
assert_dom_equal( | |
%(<div class="field_with_errors"><select name="post[author_name]" id="post_author_name"><option value="a">a</option>\n<option value="b">b</option></select></div>), | |
select("post", "author_name", [:a, :b]) | |
) | |
end | |
def test_select_with_errors_and_blank_option | |
expected_dom = %(<div class="field_with_errors"><select name="post[author_name]" id="post_author_name"><option value="">Choose one...</option>\n<option value="a">a</option>\n<option value="b">b</option></select></div>) | |
assert_dom_equal(expected_dom, select("post", "author_name", [:a, :b], include_blank: "Choose one...")) | |
assert_dom_equal(expected_dom, select("post", "author_name", [:a, :b], prompt: "Choose one...")) | |
end | |
def test_select_grouped_options_with_errors | |
grouped_options = [ | |
["A", [["A1"], ["A2"]]], | |
["B", [["B1"], ["B2"]]], | |
] | |
assert_dom_equal( | |
%(<div class="field_with_errors"><select name="post[category]" id="post_category"><optgroup label="A"><option value="A1">A1</option>\n<option value="A2">A2</option></optgroup><optgroup label="B"><option value="B1">B1</option>\n<option value="B2">B2</option></optgroup></select></div>), | |
select("post", "category", grouped_options) | |
) | |
end | |
def test_collection_select_with_errors | |
assert_dom_equal( | |
%(<div class="field_with_errors"><select name="post[author_name]" id="post_author_name"><option value="a">a</option>\n<option value="b">b</option></select></div>), | |
collection_select("post", "author_name", [:a, :b], :to_s, :to_s) | |
) | |
end | |
def test_date_select_with_errors | |
assert_dom_equal( | |
%(<div class="field_with_errors"><select id="post_updated_at_1i" name="post[updated_at(1i)]">\n<option selected="selected" value="2004">2004</option>\n<option value="2005">2005</option>\n</select>\n<input id="post_updated_at_2i" name="post[updated_at(2i)]" type="hidden" value="6" autocomplete="off" />\n<input id="post_updated_at_3i" name="post[updated_at(3i)]" type="hidden" value="1" autocomplete="off" />\n</div>), | |
date_select("post", "updated_at", discard_month: true, discard_day: true, start_year: 2004, end_year: 2005) | |
) | |
end | |
def test_datetime_select_with_errors | |
assert_dom_equal( | |
%(<div class="field_with_errors"><input id="post_updated_at_1i" name="post[updated_at(1i)]" type="hidden" value="2004" autocomplete="off" />\n<input id="post_updated_at_2i" name="post[updated_at(2i)]" type="hidden" value="6" autocomplete="off" />\n<input id="post_updated_at_3i" name="post[updated_at(3i)]" type="hidden" value="1" autocomplete="off" />\n<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n<option selected="selected" value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n</select>\n : <select id="post_updated_at_5i" name="post[updated_at(5i)]">\n<option selected="selected" value="00">00</option>\n</select>\n</div>), | |
datetime_select("post", "updated_at", discard_year: true, discard_month: true, discard_day: true, minute_step: 60) | |
) | |
end | |
def test_time_select_with_errors | |
assert_dom_equal( | |
%(<div class="field_with_errors"><input id="post_updated_at_1i" name="post[updated_at(1i)]" type="hidden" value="2004" autocomplete="off" />\n<input id="post_updated_at_2i" name="post[updated_at(2i)]" type="hidden" value="6" autocomplete="off" />\n<input id="post_updated_at_3i" name="post[updated_at(3i)]" type="hidden" value="15" autocomplete="off" />\n<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n<option selected="selected" value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n</select>\n : <select id="post_updated_at_5i" name="post[updated_at(5i)]">\n<option selected="selected" value="00">00</option>\n</select>\n</div>), | |
time_select("post", "updated_at", minute_step: 60) | |
) | |
end | |
def test_label_with_errors | |
assert_dom_equal( | |
%(<div class="field_with_errors"><label for="post_body">Body</label></div>), | |
label("post", "body") | |
) | |
end | |
def test_check_box_with_errors | |
assert_dom_equal( | |
%(<input name="post[published]" type="hidden" value="0" autocomplete="off" /><div class="field_with_errors"><input type="checkbox" value="1" name="post[published]" id="post_published" /></div>), | |
check_box("post", "published") | |
) | |
end | |
def test_check_boxes_with_errors | |
assert_dom_equal( | |
%(<input name="post[published]" type="hidden" value="0" autocomplete="off" /><div class="field_with_errors"><input type="checkbox" value="1" name="post[published]" id="post_published" /></div><input name="post[published]" type="hidden" value="0" autocomplete="off" /><div class="field_with_errors"><input type="checkbox" value="1" name="post[published]" id="post_published" /></div>), | |
check_box("post", "published") + check_box("post", "published") | |
) | |
end | |
def test_radio_button_with_errors | |
assert_dom_equal( | |
%(<div class="field_with_errors"><input type="radio" value="rails" checked="checked" name="post[category]" id="post_category_rails" /></div>), | |
radio_button("post", "category", "rails") | |
) | |
end | |
def test_radio_buttons_with_errors | |
assert_dom_equal( | |
%(<div class="field_with_errors"><input type="radio" value="rails" checked="checked" name="post[category]" id="post_category_rails" /></div><div class="field_with_errors"><input type="radio" value="java" name="post[category]" id="post_category_java" /></div>), | |
radio_button("post", "category", "rails") + radio_button("post", "category", "java") | |
) | |
end | |
def test_collection_check_boxes_with_errors | |
assert_dom_equal( | |
%(<input type="hidden" name="post[category][]" value="" autocomplete="off" /><div class="field_with_errors"><input type="checkbox" value="ruby" name="post[category][]" id="post_category_ruby" /></div><label for="post_category_ruby">ruby</label><div class="field_with_errors"><input type="checkbox" value="java" name="post[category][]" id="post_category_java" /></div><label for="post_category_java">java</label>), | |
collection_check_boxes("post", "category", [:ruby, :java], :to_s, :to_s) | |
) | |
end | |
def test_collection_radio_buttons_with_errors | |
assert_dom_equal( | |
%(<input type="hidden" name="post[category]" value="" autocomplete="off" /><div class="field_with_errors"><input type="radio" value="ruby" name="post[category]" id="post_category_ruby" /></div><label for="post_category_ruby">ruby</label><div class="field_with_errors"><input type="radio" value="java" name="post[category]" id="post_category_java" /></div><label for="post_category_java">java</label>), | |
collection_radio_buttons("post", "category", [:ruby, :java], :to_s, :to_s) | |
) | |
end | |
def test_hidden_field_does_not_render_errors | |
assert_dom_equal( | |
%(<input id="post_author_name" name="post[author_name]" type="hidden" value="" autocomplete="off" />), | |
hidden_field("post", "author_name") | |
) | |
end | |
def test_field_error_proc | |
old_proc = ActionView::Base.field_error_proc | |
ActionView::Base.field_error_proc = Proc.new do |html_tag, instance| | |
raw(%(<div class=\"field_with_errors\">#{html_tag} <span class="error">#{[instance.error_message].join(', ')}</span></div>)) | |
end | |
assert_dom_equal( | |
%(<div class="field_with_errors"><input id="post_author_name" name="post[author_name]" type="text" value="" /> <span class="error">can't be empty</span></div>), | |
text_field("post", "author_name") | |
) | |
ensure | |
ActionView::Base.field_error_proc = old_proc if old_proc | |
end | |
end |
# frozen_string_literal: true | |
require "abstract_unit" | |
# Define the essentials | |
class ActiveRecordTestConnector | |
cattr_accessor :able_to_connect | |
cattr_accessor :connected | |
# Set our defaults | |
self.connected = false | |
self.able_to_connect = true | |
end | |
# Try to grab AR | |
unless defined?(ActiveRecord) && defined?(FixtureSet) | |
begin | |
PATH_TO_AR = File.expand_path("../../activerecord/lib", __dir__) | |
raise LoadError, "#{PATH_TO_AR} doesn't exist" unless File.directory?(PATH_TO_AR) | |
$LOAD_PATH.unshift PATH_TO_AR | |
require "active_record" | |
rescue LoadError => e | |
$stderr.print "Failed to load Active Record. Skipping Active Record assertion tests: #{e}" | |
ActiveRecordTestConnector.able_to_connect = false | |
end | |
end | |
$stderr.flush | |
# Define the rest of the connector | |
class ActiveRecordTestConnector | |
class << self | |
def setup | |
unless connected || !able_to_connect | |
setup_connection | |
load_schema | |
require_fixture_models | |
self.connected = true | |
end | |
rescue Exception => e # errors from ActiveRecord setup | |
$stderr.puts "\nSkipping ActiveRecord assertion tests: #{e}" | |
# $stderr.puts " #{e.backtrace.join("\n ")}\n" | |
self.able_to_connect = false | |
end | |
def reconnect | |
return unless able_to_connect | |
ActiveRecord::Base.connection.reconnect! | |
load_schema | |
end | |
private | |
def setup_connection | |
if Object.const_defined?(:ActiveRecord) | |
defaults = { database: ":memory:" } | |
adapter = defined?(JRUBY_VERSION) ? "jdbcsqlite3" : "sqlite3" | |
options = defaults.merge adapter: adapter, timeout: 500 | |
ActiveRecord::Base.establish_connection(options) | |
ActiveRecord::Base.configurations = { "sqlite3_ar_integration" => options } | |
ActiveRecord::Base.connection | |
Object.const_set :QUOTED_TYPE, ActiveRecord::Base.connection.quote_column_name("type") unless Object.const_defined?(:QUOTED_TYPE) | |
else | |
raise "Can't setup connection since ActiveRecord isn't loaded." | |
end | |
end | |
# Load actionpack sqlite3 tables | |
def load_schema | |
File.read(File.expand_path("fixtures/db_definitions/sqlite.sql", __dir__)).split(";").each do |sql| | |
ActiveRecord::Base.connection.execute(sql) unless sql.blank? | |
end | |
end | |
def require_fixture_models | |
Dir.glob(File.expand_path("fixtures/*.rb", __dir__)).each { |f| require f } | |
end | |
end | |
end | |
class ActiveRecordTestCase < ActionController::TestCase | |
include ActiveRecord::TestFixtures | |
def self.tests(controller) | |
super | |
if defined? controller::ROUTES | |
include Module.new { | |
define_method(:setup) do | |
super() | |
@routes = controller::ROUTES | |
end | |
} | |
end | |
end | |
# Set our fixture path | |
if ActiveRecordTestConnector.able_to_connect | |
self.fixture_path = [FIXTURE_LOAD_PATH] | |
self.use_transactional_tests = false | |
end | |
def self.fixtures(*args) | |
super if ActiveRecordTestConnector.connected | |
end | |
def run(*args) | |
super if ActiveRecordTestConnector.connected | |
end | |
end | |
ActiveRecordTestConnector.setup | |
ActiveSupport::Testing::Parallelization.after_fork_hook do | |
ActiveRecordTestConnector.reconnect | |
end |
# frozen_string_literal: true | |
require "active_support/core_ext/array/extract_options" | |
require "active_support/core_ext/hash/keys" | |
require "active_support/core_ext/object/inclusion" | |
require "action_view/helpers/asset_url_helper" | |
require "action_view/helpers/tag_helper" | |
module ActionView | |
# = Action View Asset Tag Helpers | |
module Helpers # :nodoc: | |
# This module provides methods for generating HTML that links views to assets such | |
# as images, JavaScripts, stylesheets, and feeds. These methods do not verify | |
# the assets exist before linking to them: | |
# | |
# image_tag("rails.png") | |
# # => <img src="/assets/rails.png" /> | |
# stylesheet_link_tag("application") | |
# # => <link href="/assets/application.css?body=1" rel="stylesheet" /> | |
module AssetTagHelper | |
include AssetUrlHelper | |
include TagHelper | |
mattr_accessor :image_loading | |
mattr_accessor :image_decoding | |
mattr_accessor :preload_links_header | |
mattr_accessor :apply_stylesheet_media_default | |
# Returns an HTML script tag for each of the +sources+ provided. | |
# | |
# Sources may be paths to JavaScript files. Relative paths are assumed to be relative | |
# to <tt>assets/javascripts</tt>, full paths are assumed to be relative to the document | |
# root. Relative paths are idiomatic, use absolute paths only when needed. | |
# | |
# When passing paths, the ".js" extension is optional. If you do not want ".js" | |
# appended to the path <tt>extname: false</tt> can be set on the options. | |
# | |
# You can modify the HTML attributes of the script tag by passing a hash as the | |
# last argument. | |
# | |
# When the Asset Pipeline is enabled, you can pass the name of your manifest as | |
# source, and include other JavaScript or CoffeeScript files inside the manifest. | |
# | |
# If the server supports Early Hints header links for these assets will be | |
# automatically pushed. | |
# | |
# ==== Options | |
# | |
# When the last parameter is a hash you can add HTML attributes using that | |
# parameter. The following options are supported: | |
# | |
# * <tt>:extname</tt> - Append an extension to the generated URL unless the extension | |
# already exists. This only applies for relative URLs. | |
# * <tt>:protocol</tt> - Sets the protocol of the generated URL. This option only | |
# applies when a relative URL and +host+ options are provided. | |
# * <tt>:host</tt> - When a relative URL is provided the host is added to the | |
# that path. | |
# * <tt>:skip_pipeline</tt> - This option is used to bypass the asset pipeline | |
# when it is set to true. | |
# * <tt>:nonce</tt> - When set to true, adds an automatic nonce value if | |
# you have Content Security Policy enabled. | |
# | |
# ==== Examples | |
# | |
# javascript_include_tag "xmlhr" | |
# # => <script src="/assets/xmlhr.debug-1284139606.js"></script> | |
# | |
# javascript_include_tag "xmlhr", host: "localhost", protocol: "https" | |
# # => <script src="https://localhost/assets/xmlhr.debug-1284139606.js"></script> | |
# | |
# javascript_include_tag "template.jst", extname: false | |
# # => <script src="/assets/template.debug-1284139606.jst"></script> | |
# | |
# javascript_include_tag "xmlhr.js" | |
# # => <script src="/assets/xmlhr.debug-1284139606.js"></script> | |
# | |
# javascript_include_tag "common.javascript", "/elsewhere/cools" | |
# # => <script src="/assets/common.javascript.debug-1284139606.js"></script> | |
# # <script src="/elsewhere/cools.debug-1284139606.js"></script> | |
# | |
# javascript_include_tag "http://www.example.com/xmlhr" | |
# # => <script src="http://www.example.com/xmlhr"></script> | |
# | |
# javascript_include_tag "http://www.example.com/xmlhr.js" | |
# # => <script src="http://www.example.com/xmlhr.js"></script> | |
# | |
# javascript_include_tag "http://www.example.com/xmlhr.js", nonce: true | |
# # => <script src="http://www.example.com/xmlhr.js" nonce="..."></script> | |
def javascript_include_tag(*sources) | |
options = sources.extract_options!.stringify_keys | |
path_options = options.extract!("protocol", "extname", "host", "skip_pipeline").symbolize_keys | |
preload_links = [] | |
nopush = options["nopush"].nil? ? true : options.delete("nopush") | |
crossorigin = options.delete("crossorigin") | |
crossorigin = "anonymous" if crossorigin == true | |
integrity = options["integrity"] | |
rel = options["type"] == "module" ? "modulepreload" : "preload" | |
sources_tags = sources.uniq.map { |source| | |
href = path_to_javascript(source, path_options) | |
if preload_links_header && !options["defer"] && href.present? && !href.start_with?("data:") | |
preload_link = "<#{href}>; rel=#{rel}; as=script" | |
preload_link += "; crossorigin=#{crossorigin}" unless crossorigin.nil? | |
preload_link += "; integrity=#{integrity}" unless integrity.nil? | |
preload_link += "; nopush" if nopush | |
preload_links << preload_link | |
end | |
tag_options = { | |
"src" => href, | |
"crossorigin" => crossorigin | |
}.merge!(options) | |
if tag_options["nonce"] == true | |
tag_options["nonce"] = content_security_policy_nonce | |
end | |
content_tag("script", "", tag_options) | |
}.join("\n").html_safe | |
if preload_links_header | |
send_preload_links_header(preload_links) | |
end | |
sources_tags | |
end | |
# Returns a stylesheet link tag for the sources specified as arguments. | |
# | |
# When passing paths, the <tt>.css</tt> extension is optional. | |
# If you don't specify an extension, <tt>.css</tt> will be appended automatically. | |
# If you do not want <tt>.css</tt> appended to the path, | |
# set <tt>extname: false</tt> in the options. | |
# You can modify the link attributes by passing a hash as the last argument. | |
# | |
# If the server supports Early Hints header links for these assets will be | |
# automatically pushed. | |
# | |
# ==== Options | |
# | |
# * <tt>:extname</tt> - Append an extension to the generated URL unless the extension | |
# already exists. This only applies for relative URLs. | |
# * <tt>:protocol</tt> - Sets the protocol of the generated URL. This option only | |
# applies when a relative URL and +host+ options are provided. | |
# * <tt>:host</tt> - When a relative URL is provided the host is added to the | |
# that path. | |
# * <tt>:skip_pipeline</tt> - This option is used to bypass the asset pipeline | |
# when it is set to true. | |
# | |
# ==== Examples | |
# | |
# stylesheet_link_tag "style" | |
# # => <link href="/assets/style.css" rel="stylesheet" /> | |
# | |
# stylesheet_link_tag "style.css" | |
# # => <link href="/assets/style.css" rel="stylesheet" /> | |
# | |
# stylesheet_link_tag "http://www.example.com/style.css" | |
# # => <link href="http://www.example.com/style.css" rel="stylesheet" /> | |
# | |
# stylesheet_link_tag "style.less", extname: false, skip_pipeline: true, rel: "stylesheet/less" | |
# # => <link href="/stylesheets/style.less" rel="stylesheet/less"> | |
# | |
# stylesheet_link_tag "style", media: "all" | |
# # => <link href="/assets/style.css" media="all" rel="stylesheet" /> | |
# | |
# stylesheet_link_tag "style", media: "print" | |
# # => <link href="/assets/style.css" media="print" rel="stylesheet" /> | |
# | |
# stylesheet_link_tag "random.styles", "/css/stylish" | |
# # => <link href="/assets/random.styles" rel="stylesheet" /> | |
# # <link href="/css/stylish.css" rel="stylesheet" /> | |
def stylesheet_link_tag(*sources) | |
options = sources.extract_options!.stringify_keys | |
path_options = options.extract!("protocol", "extname", "host", "skip_pipeline").symbolize_keys | |
preload_links = [] | |
crossorigin = options.delete("crossorigin") | |
crossorigin = "anonymous" if crossorigin == true | |
nopush = options["nopush"].nil? ? true : options.delete("nopush") | |
integrity = options["integrity"] | |
sources_tags = sources.uniq.map { |source| | |
href = path_to_stylesheet(source, path_options) | |
if preload_links_header && href.present? && !href.start_with?("data:") | |
preload_link = "<#{href}>; rel=preload; as=style" | |
preload_link += "; crossorigin=#{crossorigin}" unless crossorigin.nil? | |
preload_link += "; integrity=#{integrity}" unless integrity.nil? | |
preload_link += "; nopush" if nopush | |
preload_links << preload_link | |
end | |
tag_options = { | |
"rel" => "stylesheet", | |
"crossorigin" => crossorigin, | |
"href" => href | |
}.merge!(options) | |
if apply_stylesheet_media_default && tag_options["media"].blank? | |
tag_options["media"] = "screen" | |
end | |
tag(:link, tag_options) | |
}.join("\n").html_safe | |
if preload_links_header | |
send_preload_links_header(preload_links) | |
end | |
sources_tags | |
end | |
# Returns a link tag that browsers and feed readers can use to auto-detect | |
# an RSS, Atom, or JSON feed. The +type+ can be <tt>:rss</tt> (default), | |
# <tt>:atom</tt>, or <tt>:json</tt>. Control the link options in url_for format | |
# using the +url_options+. You can modify the LINK tag itself in +tag_options+. | |
# | |
# ==== Options | |
# | |
# * <tt>:rel</tt> - Specify the relation of this link, defaults to "alternate" | |
# * <tt>:type</tt> - Override the auto-generated mime type | |
# * <tt>:title</tt> - Specify the title of the link, defaults to the +type+ | |
# | |
# ==== Examples | |
# | |
# auto_discovery_link_tag | |
# # => <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/action" /> | |
# auto_discovery_link_tag(:atom) | |
# # => <link rel="alternate" type="application/atom+xml" title="ATOM" href="http://www.currenthost.com/controller/action" /> | |
# auto_discovery_link_tag(:json) | |
# # => <link rel="alternate" type="application/json" title="JSON" href="http://www.currenthost.com/controller/action" /> | |
# auto_discovery_link_tag(:rss, {action: "feed"}) | |
# # => <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/feed" /> | |
# auto_discovery_link_tag(:rss, {action: "feed"}, {title: "My RSS"}) | |
# # => <link rel="alternate" type="application/rss+xml" title="My RSS" href="http://www.currenthost.com/controller/feed" /> | |
# auto_discovery_link_tag(:rss, {controller: "news", action: "feed"}) | |
# # => <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/news/feed" /> | |
# auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", {title: "Example RSS"}) | |
# # => <link rel="alternate" type="application/rss+xml" title="Example RSS" href="http://www.example.com/feed.rss" /> | |
def auto_discovery_link_tag(type = :rss, url_options = {}, tag_options = {}) | |
if !(type == :rss || type == :atom || type == :json) && tag_options[:type].blank? | |
raise ArgumentError.new("You should pass :type tag_option key explicitly, because you have passed #{type} type other than :rss, :atom, or :json.") | |
end | |
tag( | |
"link", | |
"rel" => tag_options[:rel] || "alternate", | |
"type" => tag_options[:type] || Template::Types[type].to_s, | |
"title" => tag_options[:title] || type.to_s.upcase, | |
"href" => url_options.is_a?(Hash) ? url_for(url_options.merge(only_path: false)) : url_options | |
) | |
end | |
# Returns a link tag for a favicon managed by the asset pipeline. | |
# | |
# If a page has no link like the one generated by this helper, browsers | |
# ask for <tt>/favicon.ico</tt> automatically, and cache the file if the | |
# request succeeds. If the favicon changes it is hard to get it updated. | |
# | |
# To have better control applications may let the asset pipeline manage | |
# their favicon storing the file under <tt>app/assets/images</tt>, and | |
# using this helper to generate its corresponding link tag. | |
# | |
# The helper gets the name of the favicon file as first argument, which | |
# defaults to "favicon.ico", and also supports +:rel+ and +:type+ options | |
# to override their defaults, "icon" and "image/x-icon" | |
# respectively: | |
# | |
# favicon_link_tag | |
# # => <link href="/assets/favicon.ico" rel="icon" type="image/x-icon" /> | |
# | |
# favicon_link_tag 'myicon.ico' | |
# # => <link href="/assets/myicon.ico" rel="icon" type="image/x-icon" /> | |
# | |
# Mobile Safari looks for a different link tag, pointing to an image that | |
# will be used if you add the page to the home screen of an iOS device. | |
# The following call would generate such a tag: | |
# | |
# favicon_link_tag 'mb-icon.png', rel: 'apple-touch-icon', type: 'image/png' | |
# # => <link href="/assets/mb-icon.png" rel="apple-touch-icon" type="image/png" /> | |
def favicon_link_tag(source = "favicon.ico", options = {}) | |
tag("link", { | |
rel: "icon", | |
type: "image/x-icon", | |
href: path_to_image(source, skip_pipeline: options.delete(:skip_pipeline)) | |
}.merge!(options.symbolize_keys)) | |
end | |
# Returns a link tag that browsers can use to preload the +source+. | |
# The +source+ can be the path of a resource managed by asset pipeline, | |
# a full path, or an URI. | |
# | |
# ==== Options | |
# | |
# * <tt>:type</tt> - Override the auto-generated mime type, defaults to the mime type for +source+ extension. | |
# * <tt>:as</tt> - Override the auto-generated value for as attribute, calculated using +source+ extension and mime type. | |
# * <tt>:crossorigin</tt> - Specify the crossorigin attribute, required to load cross-origin resources. | |
# * <tt>:nopush</tt> - Specify if the use of server push is not desired for the resource. Defaults to +false+. | |
# * <tt>:integrity</tt> - Specify the integrity attribute. | |
# | |
# ==== Examples | |
# | |
# preload_link_tag("custom_theme.css") | |
# # => <link rel="preload" href="/assets/custom_theme.css" as="style" type="text/css" /> | |
# | |
# preload_link_tag("/videos/video.webm") | |
# # => <link rel="preload" href="/videos/video.mp4" as="video" type="video/webm" /> | |
# | |
# preload_link_tag(post_path(format: :json), as: "fetch") | |
# # => <link rel="preload" href="/posts.json" as="fetch" type="application/json" /> | |
# | |
# preload_link_tag("worker.js", as: "worker") | |
# # => <link rel="preload" href="/assets/worker.js" as="worker" type="text/javascript" /> | |
# | |
# preload_link_tag("//example.com/font.woff2") | |
# # => <link rel="preload" href="//example.com/font.woff2" as="font" type="font/woff2" crossorigin="anonymous"/> | |
# | |
# preload_link_tag("//example.com/font.woff2", crossorigin: "use-credentials") | |
# # => <link rel="preload" href="//example.com/font.woff2" as="font" type="font/woff2" crossorigin="use-credentials" /> | |
# | |
# preload_link_tag("/media/audio.ogg", nopush: true) | |
# # => <link rel="preload" href="/media/audio.ogg" as="audio" type="audio/ogg" /> | |
# | |
def preload_link_tag(source, options = {}) | |
href = path_to_asset(source, skip_pipeline: options.delete(:skip_pipeline)) | |
extname = File.extname(source).downcase.delete(".") | |
mime_type = options.delete(:type) || Template::Types[extname]&.to_s | |
as_type = options.delete(:as) || resolve_link_as(extname, mime_type) | |
crossorigin = options.delete(:crossorigin) | |
crossorigin = "anonymous" if crossorigin == true || (crossorigin.blank? && as_type == "font") | |
integrity = options[:integrity] | |
nopush = options.delete(:nopush) || false | |
rel = mime_type == "module" ? "modulepreload" : "preload" | |
link_tag = tag.link(**{ | |
rel: rel, | |
href: href, | |
as: as_type, | |
type: mime_type, | |
crossorigin: crossorigin | |
}.merge!(options.symbolize_keys)) | |
preload_link = "<#{href}>; rel=#{rel}; as=#{as_type}" | |
preload_link += "; type=#{mime_type}" if mime_type | |
preload_link += "; crossorigin=#{crossorigin}" if crossorigin | |
preload_link += "; integrity=#{integrity}" if integrity | |
preload_link += "; nopush" if nopush | |
send_preload_links_header([preload_link]) | |
link_tag | |
end | |
# Returns an HTML image tag for the +source+. The +source+ can be a full | |
# path, a file, or an Active Storage attachment. | |
# | |
# ==== Options | |
# | |
# You can add HTML attributes using the +options+. The +options+ supports | |
# additional keys for convenience and conformance: | |
# | |
# * <tt>:size</tt> - Supplied as "{Width}x{Height}" or "{Number}", so "30x45" becomes | |
# width="30" and height="45", and "50" becomes width="50" and height="50". | |
# <tt>:size</tt> will be ignored if the value is not in the correct format. | |
# * <tt>:srcset</tt> - If supplied as a hash or array of <tt>[source, descriptor]</tt> | |
# pairs, each image path will be expanded before the list is formatted as a string. | |
# | |
# ==== Examples | |
# | |
# Assets (images that are part of your app): | |
# | |
# image_tag("icon") | |
# # => <img src="/assets/icon" /> | |
# image_tag("icon.png") | |
# # => <img src="/assets/icon.png" /> | |
# image_tag("icon.png", size: "16x10", alt: "Edit Entry") | |
# # => <img src="/assets/icon.png" width="16" height="10" alt="Edit Entry" /> | |
# image_tag("/icons/icon.gif", size: "16") | |
# # => <img src="/icons/icon.gif" width="16" height="16" /> | |
# image_tag("/icons/icon.gif", height: '32', width: '32') | |
# # => <img height="32" src="/icons/icon.gif" width="32" /> | |
# image_tag("/icons/icon.gif", class: "menu_icon") | |
# # => <img class="menu_icon" src="/icons/icon.gif" /> | |
# image_tag("/icons/icon.gif", data: { title: 'Rails Application' }) | |
# # => <img data-title="Rails Application" src="/icons/icon.gif" /> | |
# image_tag("icon.png", srcset: { "icon_2x.png" => "2x", "icon_4x.png" => "4x" }) | |
# # => <img src="/assets/icon.png" srcset="/assets/icon_2x.png 2x, /assets/icon_4x.png 4x"> | |
# image_tag("pic.jpg", srcset: [["pic_1024.jpg", "1024w"], ["pic_1980.jpg", "1980w"]], sizes: "100vw") | |
# # => <img src="/assets/pic.jpg" srcset="/assets/pic_1024.jpg 1024w, /assets/pic_1980.jpg 1980w" sizes="100vw"> | |
# | |
# Active Storage blobs (images that are uploaded by the users of your app): | |
# | |
# image_tag(user.avatar) | |
# # => <img src="/rails/active_storage/blobs/.../tiger.jpg" /> | |
# image_tag(user.avatar.variant(resize_to_limit: [100, 100])) | |
# # => <img src="/rails/active_storage/representations/.../tiger.jpg" /> | |
# image_tag(user.avatar.variant(resize_to_limit: [100, 100]), size: '100') | |
# # => <img width="100" height="100" src="/rails/active_storage/representations/.../tiger.jpg" /> | |
def image_tag(source, options = {}) | |
options = options.symbolize_keys | |
check_for_image_tag_errors(options) | |
skip_pipeline = options.delete(:skip_pipeline) | |
options[:src] = resolve_image_source(source, skip_pipeline) | |
if options[:srcset] && !options[:srcset].is_a?(String) | |
options[:srcset] = options[:srcset].map do |src_path, size| | |
src_path = path_to_image(src_path, skip_pipeline: skip_pipeline) | |
"#{src_path} #{size}" | |
end.join(", ") | |
end | |
options[:width], options[:height] = extract_dimensions(options.delete(:size)) if options[:size] | |
options[:loading] ||= image_loading if image_loading | |
options[:decoding] ||= image_decoding if image_decoding | |
tag("img", options) | |
end | |
# Returns an HTML video tag for the +sources+. If +sources+ is a string, | |
# a single video tag will be returned. If +sources+ is an array, a video | |
# tag with nested source tags for each source will be returned. The | |
# +sources+ can be full paths or files that exist in your public videos | |
# directory. | |
# | |
# ==== Options | |
# | |
# When the last parameter is a hash you can add HTML attributes using that | |
# parameter. The following options are supported: | |
# | |
# * <tt>:poster</tt> - Set an image (like a screenshot) to be shown | |
# before the video loads. The path is calculated like the +src+ of +image_tag+. | |
# * <tt>:size</tt> - Supplied as "{Width}x{Height}" or "{Number}", so "30x45" becomes | |
# width="30" and height="45", and "50" becomes width="50" and height="50". | |
# <tt>:size</tt> will be ignored if the value is not in the correct format. | |
# * <tt>:poster_skip_pipeline</tt> will bypass the asset pipeline when using | |
# the <tt>:poster</tt> option instead using an asset in the public folder. | |
# | |
# ==== Examples | |
# | |
# video_tag("trailer") | |
# # => <video src="/videos/trailer"></video> | |
# video_tag("trailer.ogg") | |
# # => <video src="/videos/trailer.ogg"></video> | |
# video_tag("trailer.ogg", controls: true, preload: 'none') | |
# # => <video preload="none" controls="controls" src="/videos/trailer.ogg"></video> | |
# video_tag("trailer.m4v", size: "16x10", poster: "screenshot.png") | |
# # => <video src="/videos/trailer.m4v" width="16" height="10" poster="/assets/screenshot.png"></video> | |
# video_tag("trailer.m4v", size: "16x10", poster: "screenshot.png", poster_skip_pipeline: true) | |
# # => <video src="/videos/trailer.m4v" width="16" height="10" poster="screenshot.png"></video> | |
# video_tag("/trailers/hd.avi", size: "16x16") | |
# # => <video src="/trailers/hd.avi" width="16" height="16"></video> | |
# video_tag("/trailers/hd.avi", size: "16") | |
# # => <video height="16" src="/trailers/hd.avi" width="16"></video> | |
# video_tag("/trailers/hd.avi", height: '32', width: '32') | |
# # => <video height="32" src="/trailers/hd.avi" width="32"></video> | |
# video_tag("trailer.ogg", "trailer.flv") | |
# # => <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video> | |
# video_tag(["trailer.ogg", "trailer.flv"]) | |
# # => <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video> | |
# video_tag(["trailer.ogg", "trailer.flv"], size: "160x120") | |
# # => <video height="120" width="160"><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video> | |
def video_tag(*sources) | |
options = sources.extract_options!.symbolize_keys | |
public_poster_folder = options.delete(:poster_skip_pipeline) | |
sources << options | |
multiple_sources_tag_builder("video", sources) do |tag_options| | |
tag_options[:poster] = path_to_image(tag_options[:poster], skip_pipeline: public_poster_folder) if tag_options[:poster] | |
tag_options[:width], tag_options[:height] = extract_dimensions(tag_options.delete(:size)) if tag_options[:size] | |
end | |
end | |
# Returns an HTML audio tag for the +sources+. If +sources+ is a string, | |
# a single audio tag will be returned. If +sources+ is an array, an audio | |
# tag with nested source tags for each source will be returned. The | |
# +sources+ can be full paths or files that exist in your public audios | |
# directory. | |
# | |
# When the last parameter is a hash you can add HTML attributes using that | |
# parameter. | |
# | |
# audio_tag("sound") | |
# # => <audio src="/audios/sound"></audio> | |
# audio_tag("sound.wav") | |
# # => <audio src="/audios/sound.wav"></audio> | |
# audio_tag("sound.wav", autoplay: true, controls: true) | |
# # => <audio autoplay="autoplay" controls="controls" src="/audios/sound.wav"></audio> | |
# audio_tag("sound.wav", "sound.mid") | |
# # => <audio><source src="/audios/sound.wav" /><source src="/audios/sound.mid" /></audio> | |
def audio_tag(*sources) | |
multiple_sources_tag_builder("audio", sources) | |
end | |
private | |
def multiple_sources_tag_builder(type, sources) | |
options = sources.extract_options!.symbolize_keys | |
skip_pipeline = options.delete(:skip_pipeline) | |
sources.flatten! | |
yield options if block_given? | |
if sources.size > 1 | |
content_tag(type, options) do | |
safe_join sources.map { |source| tag("source", src: send("path_to_#{type}", source, skip_pipeline: skip_pipeline)) } | |
end | |
else | |
options[:src] = send("path_to_#{type}", sources.first, skip_pipeline: skip_pipeline) | |
content_tag(type, nil, options) | |
end | |
end | |
def resolve_image_source(source, skip_pipeline) | |
if source.is_a?(Symbol) || source.is_a?(String) | |
path_to_image(source, skip_pipeline: skip_pipeline) | |
else | |
polymorphic_url(source) | |
end | |
rescue NoMethodError => e | |
raise ArgumentError, "Can't resolve image into URL: #{e}" | |
end | |
def extract_dimensions(size) | |
size = size.to_s | |
if /\A\d+x\d+\z/.match?(size) | |
size.split("x") | |
elsif /\A\d+\z/.match?(size) | |
[size, size] | |
end | |
end | |
def check_for_image_tag_errors(options) | |
if options[:size] && (options[:height] || options[:width]) | |
raise ArgumentError, "Cannot pass a :size option with a :height or :width option" | |
end | |
end | |
def resolve_link_as(extname, mime_type) | |
case extname | |
when "js" then "script" | |
when "css" then "style" | |
when "vtt" then "track" | |
else | |
mime_type.to_s.split("/").first.presence_in(%w(audio video font image)) | |
end | |
end | |
MAX_HEADER_SIZE = 8_000 # Some HTTP client and proxies have a 8kiB header limit | |
def send_preload_links_header(preload_links, max_header_size: MAX_HEADER_SIZE) | |
return if preload_links.empty? | |
response_present = respond_to?(:response) && response | |
return if response_present && response.sending? | |
if respond_to?(:request) && request | |
request.send_early_hints("Link" => preload_links.join("\n")) | |
end | |
if response_present | |
header = response.headers["Link"] | |
header = header ? header.dup : +"" | |
# rindex count characters not bytes, but we assume non-ascii characters | |
# are rare in urls, and we have a 192 bytes margin. | |
last_line_offset = header.rindex("\n") | |
last_line_size = if last_line_offset | |
header.bytesize - last_line_offset | |
else | |
header.bytesize | |
end | |
preload_links.each do |link| | |
if link.bytesize + last_line_size + 1 < max_header_size | |
unless header.empty? | |
header << "," | |
last_line_size += 1 | |
end | |
else | |
header << "\n" | |
last_line_size = 0 | |
end | |
header << link | |
last_line_size += link.bytesize | |
end | |
response.headers["Link"] = header | |
end | |
end | |
end | |
end | |
end |
# frozen_string_literal: true | |
require "abstract_unit" | |
require "active_support/ordered_options" | |
require "action_dispatch" | |
ActionView::Template::Types.delegate_to Mime | |
module AssetTagHelperTestHelpers | |
def with_preload_links_header(new_preload_links_header = true) | |
original_preload_links_header = ActionView::Helpers::AssetTagHelper.preload_links_header | |
ActionView::Helpers::AssetTagHelper.preload_links_header = new_preload_links_header | |
yield | |
ensure | |
ActionView::Helpers::AssetTagHelper.preload_links_header = original_preload_links_header | |
end | |
end | |
class AssetTagHelperTest < ActionView::TestCase | |
tests ActionView::Helpers::AssetTagHelper | |
include AssetTagHelperTestHelpers | |
attr_reader :request, :response | |
class FakeRequest | |
attr_accessor :script_name | |
def protocol() "http://" end | |
def ssl?() false end | |
def host_with_port() "localhost" end | |
def base_url() "http://www.example.com" end | |
def send_early_hints(links) end | |
end | |
class FakeResponse | |
def headers | |
@headers ||= {} | |
end | |
def sending?; false; end | |
end | |
def setup | |
super | |
@controller = BasicController.new | |
@request = FakeRequest.new | |
@controller.request = @request | |
@response = FakeResponse.new | |
@controller.response = @response | |
end | |
def url_for(*args) | |
"http://www.example.com" | |
end | |
def content_security_policy_nonce | |
"iyhD0Yc0W+c=" | |
end | |
AssetPathToTag = { | |
%(asset_path("")) => %(), | |
%(asset_path(" ")) => %(), | |
%(asset_path("foo")) => %(/foo), | |
%(asset_path("style.css")) => %(/style.css), | |
%(asset_path("xmlhr.js")) => %(/xmlhr.js), | |
%(asset_path("xml.png")) => %(/xml.png), | |
%(asset_path("dir/xml.png")) => %(/dir/xml.png), | |
%(asset_path("/dir/xml.png")) => %(/dir/xml.png), | |
%(asset_path("script.min")) => %(/script.min), | |
%(asset_path("script.min.js")) => %(/script.min.js), | |
%(asset_path("style.min")) => %(/style.min), | |
%(asset_path("style.min.css")) => %(/style.min.css), | |
%(asset_path("http://www.outside.com/image.jpg")) => %(http://www.outside.com/image.jpg), | |
%(asset_path("HTTP://www.outside.com/image.jpg")) => %(HTTP://www.outside.com/image.jpg), | |
%(asset_path("style", type: :stylesheet)) => %(/stylesheets/style.css), | |
%(asset_path("xmlhr", type: :javascript)) => %(/javascripts/xmlhr.js), | |
%(asset_path("xml.png", type: :image)) => %(/images/xml.png) | |
} | |
AutoDiscoveryToTag = { | |
%(auto_discovery_link_tag) => %(<link href="http://www.example.com" rel="alternate" title="RSS" type="application/rss+xml" />), | |
%(auto_discovery_link_tag(:rss)) => %(<link href="http://www.example.com" rel="alternate" title="RSS" type="application/rss+xml" />), | |
%(auto_discovery_link_tag(:atom)) => %(<link href="http://www.example.com" rel="alternate" title="ATOM" type="application/atom+xml" />), | |
%(auto_discovery_link_tag(:json)) => %(<link href="http://www.example.com" rel="alternate" title="JSON" type="application/json" />), | |
%(auto_discovery_link_tag(:rss, :action => "feed")) => %(<link href="http://www.example.com" rel="alternate" title="RSS" type="application/rss+xml" />), | |
%(auto_discovery_link_tag(:rss, "http://localhost/feed")) => %(<link href="http://localhost/feed" rel="alternate" title="RSS" type="application/rss+xml" />), | |
%(auto_discovery_link_tag(:rss, "//localhost/feed")) => %(<link href="//localhost/feed" rel="alternate" title="RSS" type="application/rss+xml" />), | |
%(auto_discovery_link_tag(:rss, {:action => "feed"}, {:title => "My RSS"})) => %(<link href="http://www.example.com" rel="alternate" title="My RSS" type="application/rss+xml" />), | |
%(auto_discovery_link_tag(:rss, {}, {:title => "My RSS"})) => %(<link href="http://www.example.com" rel="alternate" title="My RSS" type="application/rss+xml" />), | |
%(auto_discovery_link_tag(nil, {}, {:type => "text/html"})) => %(<link href="http://www.example.com" rel="alternate" title="" type="text/html" />), | |
%(auto_discovery_link_tag(nil, {}, {:title => "No stream.. really", :type => "text/html"})) => %(<link href="http://www.example.com" rel="alternate" title="No stream.. really" type="text/html" />), | |
%(auto_discovery_link_tag(:rss, {}, {:title => "My RSS", :type => "text/html"})) => %(<link href="http://www.example.com" rel="alternate" title="My RSS" type="text/html" />), | |
%(auto_discovery_link_tag(:atom, {}, {:rel => "Not so alternate"})) => %(<link href="http://www.example.com" rel="Not so alternate" title="ATOM" type="application/atom+xml" />), | |
} | |
JavascriptPathToTag = { | |
%(javascript_path("xmlhr")) => %(/javascripts/xmlhr.js), | |
%(javascript_path("super/xmlhr")) => %(/javascripts/super/xmlhr.js), | |
%(javascript_path("/super/xmlhr.js")) => %(/super/xmlhr.js), | |
%(javascript_path("xmlhr.min")) => %(/javascripts/xmlhr.min.js), | |
%(javascript_path("xmlhr.min.js")) => %(/javascripts/xmlhr.min.js), | |
%(javascript_path("xmlhr.js?123")) => %(/javascripts/xmlhr.js?123), | |
%(javascript_path("xmlhr.js?body=1")) => %(/javascripts/xmlhr.js?body=1), | |
%(javascript_path("xmlhr.js#hash")) => %(/javascripts/xmlhr.js#hash), | |
%(javascript_path("xmlhr.js?123#hash")) => %(/javascripts/xmlhr.js?123#hash) | |
} | |
PathToJavascriptToTag = { | |
%(path_to_javascript("xmlhr")) => %(/javascripts/xmlhr.js), | |
%(path_to_javascript("super/xmlhr")) => %(/javascripts/super/xmlhr.js), | |
%(path_to_javascript("/super/xmlhr.js")) => %(/super/xmlhr.js) | |
} | |
JavascriptUrlToTag = { | |
%(javascript_url("xmlhr")) => %(http://www.example.com/javascripts/xmlhr.js), | |
%(javascript_url("super/xmlhr")) => %(http://www.example.com/javascripts/super/xmlhr.js), | |
%(javascript_url("/super/xmlhr.js")) => %(http://www.example.com/super/xmlhr.js) | |
} | |
UrlToJavascriptToTag = { | |
%(url_to_javascript("xmlhr")) => %(http://www.example.com/javascripts/xmlhr.js), | |
%(url_to_javascript("super/xmlhr")) => %(http://www.example.com/javascripts/super/xmlhr.js), | |
%(url_to_javascript("/super/xmlhr.js")) => %(http://www.example.com/super/xmlhr.js) | |
} | |
JavascriptIncludeToTag = { | |
%(javascript_include_tag("bank")) => %(<script src="/javascripts/bank.js" ></script>), | |
%(javascript_include_tag("bank.js")) => %(<script src="/javascripts/bank.js" ></script>), | |
%(javascript_include_tag("bank", :lang => "vbscript")) => %(<script lang="vbscript" src="/javascripts/bank.js" ></script>), | |
%(javascript_include_tag("bank", :host => "assets.example.com")) => %(<script src="http://assets.example.com/javascripts/bank.js"></script>), | |
%(javascript_include_tag("http://example.com/all")) => %(<script src="http://example.com/all"></script>), | |
%(javascript_include_tag("http://example.com/all.js")) => %(<script src="http://example.com/all.js"></script>), | |
%(javascript_include_tag("//example.com/all.js")) => %(<script src="//example.com/all.js"></script>), | |
} | |
StylePathToTag = { | |
%(stylesheet_path("bank")) => %(/stylesheets/bank.css), | |
%(stylesheet_path("bank.css")) => %(/stylesheets/bank.css), | |
%(stylesheet_path('subdir/subdir')) => %(/stylesheets/subdir/subdir.css), | |
%(stylesheet_path('/subdir/subdir.css')) => %(/subdir/subdir.css), | |
%(stylesheet_path("style.min")) => %(/stylesheets/style.min.css), | |
%(stylesheet_path("style.min.css")) => %(/stylesheets/style.min.css) | |
} | |
PathToStyleToTag = { | |
%(path_to_stylesheet("style")) => %(/stylesheets/style.css), | |
%(path_to_stylesheet("style.css")) => %(/stylesheets/style.css), | |
%(path_to_stylesheet('dir/file')) => %(/stylesheets/dir/file.css), | |
%(path_to_stylesheet('/dir/file.rcss', :extname => false)) => %(/dir/file.rcss), | |
%(path_to_stylesheet('/dir/file', :extname => '.rcss')) => %(/dir/file.rcss) | |
} | |
StyleUrlToTag = { | |
%(stylesheet_url("bank")) => %(http://www.example.com/stylesheets/bank.css), | |
%(stylesheet_url("bank.css")) => %(http://www.example.com/stylesheets/bank.css), | |
%(stylesheet_url('subdir/subdir')) => %(http://www.example.com/stylesheets/subdir/subdir.css), | |
%(stylesheet_url('/subdir/subdir.css')) => %(http://www.example.com/subdir/subdir.css) | |
} | |
UrlToStyleToTag = { | |
%(url_to_stylesheet("style")) => %(http://www.example.com/stylesheets/style.css), | |
%(url_to_stylesheet("style.css")) => %(http://www.example.com/stylesheets/style.css), | |
%(url_to_stylesheet('dir/file')) => %(http://www.example.com/stylesheets/dir/file.css), | |
%(url_to_stylesheet('/dir/file.rcss', :extname => false)) => %(http://www.example.com/dir/file.rcss), | |
%(url_to_stylesheet('/dir/file', :extname => '.rcss')) => %(http://www.example.com/dir/file.rcss) | |
} | |
StyleLinkToTag = { | |
%(stylesheet_link_tag("bank")) => %(<link href="/stylesheets/bank.css" rel="stylesheet" />), | |
%(stylesheet_link_tag("bank.css")) => %(<link href="/stylesheets/bank.css" rel="stylesheet" />), | |
%(stylesheet_link_tag("/elsewhere/file")) => %(<link href="/elsewhere/file.css" rel="stylesheet" />), | |
%(stylesheet_link_tag("subdir/subdir")) => %(<link href="/stylesheets/subdir/subdir.css" rel="stylesheet" />), | |
%(stylesheet_link_tag("bank", :media => "all")) => %(<link href="/stylesheets/bank.css" media="all" rel="stylesheet" />), | |
%(stylesheet_link_tag("bank", :host => "assets.example.com")) => %(<link href="http://assets.example.com/stylesheets/bank.css" rel="stylesheet" />), | |
%(stylesheet_link_tag("http://www.example.com/styles/style")) => %(<link href="http://www.example.com/styles/style" rel="stylesheet" />), | |
%(stylesheet_link_tag("http://www.example.com/styles/style.css")) => %(<link href="http://www.example.com/styles/style.css" rel="stylesheet" />), | |
%(stylesheet_link_tag("//www.example.com/styles/style.css")) => %(<link href="//www.example.com/styles/style.css" rel="stylesheet" />), | |
} | |
ImagePathToTag = { | |
%(image_path("xml")) => %(/images/xml), | |
%(image_path("xml.png")) => %(/images/xml.png), | |
%(image_path("dir/xml.png")) => %(/images/dir/xml.png), | |
%(image_path("/dir/xml.png")) => %(/dir/xml.png) | |
} | |
PathToImageToTag = { | |
%(path_to_image("xml")) => %(/images/xml), | |
%(path_to_image("xml.png")) => %(/images/xml.png), | |
%(path_to_image("dir/xml.png")) => %(/images/dir/xml.png), | |
%(path_to_image("/dir/xml.png")) => %(/dir/xml.png) | |
} | |
ImageUrlToTag = { | |
%(image_url("xml")) => %(http://www.example.com/images/xml), | |
%(image_url("xml.png")) => %(http://www.example.com/images/xml.png), | |
%(image_url("dir/xml.png")) => %(http://www.example.com/images/dir/xml.png), | |
%(image_url("/dir/xml.png")) => %(http://www.example.com/dir/xml.png) | |
} | |
UrlToImageToTag = { | |
%(url_to_image("xml")) => %(http://www.example.com/images/xml), | |
%(url_to_image("xml.png")) => %(http://www.example.com/images/xml.png), | |
%(url_to_image("dir/xml.png")) => %(http://www.example.com/images/dir/xml.png), | |
%(url_to_image("/dir/xml.png")) => %(http://www.example.com/dir/xml.png) | |
} | |
ImageLinkToTag = { | |
%(image_tag("xml.png")) => %(<img src="/images/xml.png" />), | |
%(image_tag("rss.gif", :alt => "rss syndication")) => %(<img alt="rss syndication" src="/images/rss.gif" />), | |
%(image_tag("gold.png", :size => "20")) => %(<img height="20" src="/images/gold.png" width="20" />), | |
%(image_tag("gold.png", :size => 20)) => %(<img height="20" src="/images/gold.png" width="20" />), | |
%(image_tag("gold.png", :size => "45x70")) => %(<img height="70" src="/images/gold.png" width="45" />), | |
%(image_tag("gold.png", "size" => "45x70")) => %(<img height="70" src="/images/gold.png" width="45" />), | |
%(image_tag("error.png", "size" => "45 x 70")) => %(<img src="/images/error.png" />), | |
%(image_tag("error.png", "size" => "x")) => %(<img src="/images/error.png" />), | |
%(image_tag("google.com.png")) => %(<img src="/images/google.com.png" />), | |
%(image_tag("slash..png")) => %(<img src="/images/slash..png" />), | |
%(image_tag(".pdf.png")) => %(<img src="/images/.pdf.png" />), | |
%(image_tag("http://www.rubyonrails.com/images/rails.png")) => %(<img src="http://www.rubyonrails.com/images/rails.png" />), | |
%(image_tag("//www.rubyonrails.com/images/rails.png")) => %(<img src="//www.rubyonrails.com/images/rails.png" />), | |
%(image_tag("mouse.png", :alt => nil)) => %(<img src="/images/mouse.png" />), | |
%(image_tag("data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==", :alt => nil)) => %(<img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" />), | |
%(image_tag("")) => %(<img src="" />), | |
%(image_tag("gold.png", data: { title: 'Rails Application' })) => %(<img data-title="Rails Application" src="/images/gold.png" />), | |
%(image_tag("rss.gif", srcset: "/assets/pic_640.jpg 640w, /assets/pic_1024.jpg 1024w")) => %(<img srcset="/assets/pic_640.jpg 640w, /assets/pic_1024.jpg 1024w" src="/images/rss.gif" />), | |
%(image_tag("rss.gif", srcset: { "pic_640.jpg" => "640w", "pic_1024.jpg" => "1024w" })) => %(<img srcset="/images/pic_640.jpg 640w, /images/pic_1024.jpg 1024w" src="/images/rss.gif" />), | |
%(image_tag("rss.gif", srcset: [["pic_640.jpg", "640w"], ["pic_1024.jpg", "1024w"]])) => %(<img srcset="/images/pic_640.jpg 640w, /images/pic_1024.jpg 1024w" src="/images/rss.gif" />) | |
} | |
FaviconLinkToTag = { | |
%(favicon_link_tag) => %(<link href="/images/favicon.ico" rel="icon" type="image/x-icon" />), | |
%(favicon_link_tag 'favicon.ico') => %(<link href="/images/favicon.ico" rel="icon" type="image/x-icon" />), | |
%(favicon_link_tag 'favicon.ico', :rel => 'foo') => %(<link href="/images/favicon.ico" rel="foo" type="image/x-icon" />), | |
%(favicon_link_tag 'favicon.ico', :rel => 'foo', :type => 'bar') => %(<link href="/images/favicon.ico" rel="foo" type="bar" />), | |
%(favicon_link_tag 'mb-icon.png', :rel => 'apple-touch-icon', :type => 'image/png') => %(<link href="/images/mb-icon.png" rel="apple-touch-icon" type="image/png" />) | |
} | |
PreloadLinkToTag = { | |
%(preload_link_tag '/application.js', type: 'module') => %(<link rel="modulepreload" href="/application.js" as="script" type="module" >), | |
%(preload_link_tag '/styles/custom_theme.css') => %(<link rel="preload" href="/styles/custom_theme.css" as="style" type="text/css" />), | |
%(preload_link_tag '/videos/video.webm') => %(<link rel="preload" href="/videos/video.webm" as="video" type="video/webm" />), | |
%(preload_link_tag '/posts.json', as: 'fetch') => %(<link rel="preload" href="/posts.json" as="fetch" type="application/json" />), | |
%(preload_link_tag '/users', as: 'fetch', type: 'application/json') => %(<link rel="preload" href="/users" as="fetch" type="application/json" />), | |
%(preload_link_tag '//example.com/map?callback=initMap', as: 'fetch', type: 'application/javascript') => %(<link rel="preload" href="//example.com/map?callback=initMap" as="fetch" type="application/javascript" />), | |
%(preload_link_tag '//example.com/font.woff2') => %(<link rel="preload" href="//example.com/font.woff2" as="font" type="font/woff2" crossorigin="anonymous"/>), | |
%(preload_link_tag '//example.com/font.woff2', crossorigin: 'use-credentials') => %(<link rel="preload" href="//example.com/font.woff2" as="font" type="font/woff2" crossorigin="use-credentials" />), | |
%(preload_link_tag '/media/audio.ogg', nopush: true) => %(<link rel="preload" href="/media/audio.ogg" as="audio" type="audio/ogg" />), | |
%(preload_link_tag '/style.css', integrity: 'sha256-AbpHGcgLb+kRsJGnwFEktk7uzpZOCcBY74+YBdrKVGs') => %(<link rel="preload" href="/style.css" as="style" type="text/css" integrity="sha256-AbpHGcgLb+kRsJGnwFEktk7uzpZOCcBY74+YBdrKVGs">), | |
%(preload_link_tag '/sprite.svg') => %(<link rel="preload" href="/sprite.svg" as="image" type="image/svg+xml">), | |
%(preload_link_tag '/mb-icon.png') => %(<link rel="preload" href="/mb-icon.png" as="image" type="image/png">) | |
} | |
VideoPathToTag = { | |
%(video_path("xml")) => %(/videos/xml), | |
%(video_path("xml.ogg")) => %(/videos/xml.ogg), | |
%(video_path("dir/xml.ogg")) => %(/videos/dir/xml.ogg), | |
%(video_path("/dir/xml.ogg")) => %(/dir/xml.ogg) | |
} | |
PathToVideoToTag = { | |
%(path_to_video("xml")) => %(/videos/xml), | |
%(path_to_video("xml.ogg")) => %(/videos/xml.ogg), | |
%(path_to_video("dir/xml.ogg")) => %(/videos/dir/xml.ogg), | |
%(path_to_video("/dir/xml.ogg")) => %(/dir/xml.ogg) | |
} | |
VideoUrlToTag = { | |
%(video_url("xml")) => %(http://www.example.com/videos/xml), | |
%(video_url("xml.ogg")) => %(http://www.example.com/videos/xml.ogg), | |
%(video_url("dir/xml.ogg")) => %(http://www.example.com/videos/dir/xml.ogg), | |
%(video_url("/dir/xml.ogg")) => %(http://www.example.com/dir/xml.ogg) | |
} | |
UrlToVideoToTag = { | |
%(url_to_video("xml")) => %(http://www.example.com/videos/xml), | |
%(url_to_video("xml.ogg")) => %(http://www.example.com/videos/xml.ogg), | |
%(url_to_video("dir/xml.ogg")) => %(http://www.example.com/videos/dir/xml.ogg), | |
%(url_to_video("/dir/xml.ogg")) => %(http://www.example.com/dir/xml.ogg) | |
} | |
VideoLinkToTag = { | |
%(video_tag("xml.ogg")) => %(<video src="/videos/xml.ogg"></video>), | |
%(video_tag("rss.m4v", :autoplay => true, :controls => true)) => %(<video autoplay="autoplay" controls="controls" src="/videos/rss.m4v"></video>), | |
%(video_tag("rss.m4v", :preload => 'none')) => %(<video preload="none" src="/videos/rss.m4v"></video>), | |
%(video_tag("gold.m4v", :size => "160x120")) => %(<video height="120" src="/videos/gold.m4v" width="160"></video>), | |
%(video_tag("gold.m4v", "size" => "320x240")) => %(<video height="240" src="/videos/gold.m4v" width="320"></video>), | |
%(video_tag("trailer.ogg", :poster => "screenshot.png")) => %(<video poster="/images/screenshot.png" src="/videos/trailer.ogg"></video>), | |
%(video_tag("error.avi", "size" => "100")) => %(<video height="100" src="/videos/error.avi" width="100"></video>), | |
%(video_tag("error.avi", "size" => 100)) => %(<video height="100" src="/videos/error.avi" width="100"></video>), | |
%(video_tag("error.avi", "size" => "100 x 100")) => %(<video src="/videos/error.avi"></video>), | |
%(video_tag("error.avi", "size" => "x")) => %(<video src="/videos/error.avi"></video>), | |
%(video_tag("http://media.rubyonrails.org/video/rails_blog_2.mov")) => %(<video src="http://media.rubyonrails.org/video/rails_blog_2.mov"></video>), | |
%(video_tag("//media.rubyonrails.org/video/rails_blog_2.mov")) => %(<video src="//media.rubyonrails.org/video/rails_blog_2.mov"></video>), | |
%(video_tag("multiple.ogg", "multiple.avi")) => %(<video><source src="/videos/multiple.ogg" /><source src="/videos/multiple.avi" /></video>), | |
%(video_tag(["multiple.ogg", "multiple.avi"])) => %(<video><source src="/videos/multiple.ogg" /><source src="/videos/multiple.avi" /></video>), | |
%(video_tag(["multiple.ogg", "multiple.avi"], :size => "160x120", :controls => true)) => %(<video controls="controls" height="120" width="160"><source src="/videos/multiple.ogg" /><source src="/videos/multiple.avi" /></video>) | |
} | |
AudioPathToTag = { | |
%(audio_path("xml")) => %(/audios/xml), | |
%(audio_path("xml.wav")) => %(/audios/xml.wav), | |
%(audio_path("dir/xml.wav")) => %(/audios/dir/xml.wav), | |
%(audio_path("/dir/xml.wav")) => %(/dir/xml.wav) | |
} | |
PathToAudioToTag = { | |
%(path_to_audio("xml")) => %(/audios/xml), | |
%(path_to_audio("xml.wav")) => %(/audios/xml.wav), | |
%(path_to_audio("dir/xml.wav")) => %(/audios/dir/xml.wav), | |
%(path_to_audio("/dir/xml.wav")) => %(/dir/xml.wav) | |
} | |
AudioUrlToTag = { | |
%(audio_url("xml")) => %(http://www.example.com/audios/xml), | |
%(audio_url("xml.wav")) => %(http://www.example.com/audios/xml.wav), | |
%(audio_url("dir/xml.wav")) => %(http://www.example.com/audios/dir/xml.wav), | |
%(audio_url("/dir/xml.wav")) => %(http://www.example.com/dir/xml.wav) | |
} | |
UrlToAudioToTag = { | |
%(url_to_audio("xml")) => %(http://www.example.com/audios/xml), | |
%(url_to_audio("xml.wav")) => %(http://www.example.com/audios/xml.wav), | |
%(url_to_audio("dir/xml.wav")) => %(http://www.example.com/audios/dir/xml.wav), | |
%(url_to_audio("/dir/xml.wav")) => %(http://www.example.com/dir/xml.wav) | |
} | |
AudioLinkToTag = { | |
%(audio_tag("xml.wav")) => %(<audio src="/audios/xml.wav"></audio>), | |
%(audio_tag("rss.wav", :autoplay => true, :controls => true)) => %(<audio autoplay="autoplay" controls="controls" src="/audios/rss.wav"></audio>), | |
%(audio_tag("http://media.rubyonrails.org/audio/rails_blog_2.mov")) => %(<audio src="http://media.rubyonrails.org/audio/rails_blog_2.mov"></audio>), | |
%(audio_tag("//media.rubyonrails.org/audio/rails_blog_2.mov")) => %(<audio src="//media.rubyonrails.org/audio/rails_blog_2.mov"></audio>), | |
%(audio_tag("audio.mp3", "audio.ogg")) => %(<audio><source src="/audios/audio.mp3" /><source src="/audios/audio.ogg" /></audio>), | |
%(audio_tag(["audio.mp3", "audio.ogg"])) => %(<audio><source src="/audios/audio.mp3" /><source src="/audios/audio.ogg" /></audio>), | |
%(audio_tag(["audio.mp3", "audio.ogg"], :preload => 'none', :controls => true)) => %(<audio preload="none" controls="controls"><source src="/audios/audio.mp3" /><source src="/audios/audio.ogg" /></audio>) | |
} | |
FontPathToTag = { | |
%(font_path("font.eot")) => %(/fonts/font.eot), | |
%(font_path("font.eot#iefix")) => %(/fonts/font.eot#iefix), | |
%(font_path("font.woff")) => %(/fonts/font.woff), | |
%(font_path("font.ttf")) => %(/fonts/font.ttf), | |
%(font_path("font.ttf?123")) => %(/fonts/font.ttf?123) | |
} | |
FontUrlToTag = { | |
%(font_url("font.eot")) => %(http://www.example.com/fonts/font.eot), | |
%(font_url("font.eot#iefix")) => %(http://www.example.com/fonts/font.eot#iefix), | |
%(font_url("font.woff")) => %(http://www.example.com/fonts/font.woff), | |
%(font_url("font.ttf")) => %(http://www.example.com/fonts/font.ttf), | |
%(font_url("font.ttf?123")) => %(http://www.example.com/fonts/font.ttf?123), | |
%(font_url("font.ttf", host: "http://assets.example.com")) => %(http://assets.example.com/fonts/font.ttf) | |
} | |
UrlToFontToTag = { | |
%(url_to_font("font.eot")) => %(http://www.example.com/fonts/font.eot), | |
%(url_to_font("font.eot#iefix")) => %(http://www.example.com/fonts/font.eot#iefix), | |
%(url_to_font("font.woff")) => %(http://www.example.com/fonts/font.woff), | |
%(url_to_font("font.ttf")) => %(http://www.example.com/fonts/font.ttf), | |
%(url_to_font("font.ttf?123")) => %(http://www.example.com/fonts/font.ttf?123), | |
%(url_to_font("font.ttf", host: "http://assets.example.com")) => %(http://assets.example.com/fonts/font.ttf) | |
} | |
def test_autodiscovery_link_tag_with_unknown_type_but_not_pass_type_option_key | |
assert_raise(ArgumentError) do | |
auto_discovery_link_tag(:xml) | |
end | |
end | |
def test_autodiscovery_link_tag_with_unknown_type | |
result = auto_discovery_link_tag(:xml, "/feed.xml", type: "application/xml") | |
expected = %(<link href="/feed.xml" rel="alternate" title="XML" type="application/xml" />) | |
assert_dom_equal expected, result | |
end | |
def test_asset_path_tag | |
AssetPathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } | |
end | |
def test_asset_path_tag_raises_an_error_for_nil_source | |
e = assert_raise(ArgumentError) { asset_path(nil) } | |
assert_equal("nil is not a valid asset source", e.message) | |
end | |
def test_asset_path_tag_to_not_create_duplicate_slashes | |
@controller.config.asset_host = "host/" | |
assert_dom_equal("http://host/foo", asset_path("foo")) | |
@controller.config.relative_url_root = "/some/root/" | |
assert_dom_equal("http://host/some/root/foo", asset_path("foo")) | |
end | |
def test_compute_asset_public_path | |
assert_equal "/robots.txt", compute_asset_path("robots.txt") | |
assert_equal "/robots.txt", compute_asset_path("/robots.txt") | |
assert_equal "/javascripts/foo.js", compute_asset_path("foo.js", type: :javascript) | |
assert_equal "/javascripts/foo.js", compute_asset_path("/foo.js", type: :javascript) | |
assert_equal "/stylesheets/foo.css", compute_asset_path("foo.css", type: :stylesheet) | |
end | |
def test_auto_discovery_link_tag | |
AutoDiscoveryToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } | |
end | |
def test_javascript_path | |
JavascriptPathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } | |
end | |
def test_path_to_javascript_alias_for_javascript_path | |
PathToJavascriptToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } | |
end | |
def test_javascript_url | |
JavascriptUrlToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } | |
end | |
def test_url_to_javascript_alias_for_javascript_url | |
UrlToJavascriptToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } | |
end | |
def test_javascript_include_tag | |
JavascriptIncludeToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } | |
end | |
def test_javascript_include_tag_with_missing_source | |
assert_nothing_raised { | |
javascript_include_tag("missing_security_guard") | |
} | |
assert_nothing_raised { | |
javascript_include_tag("http://example.com/css/missing_security_guard") | |
} | |
end | |
def test_javascript_include_tag_is_html_safe | |
assert_predicate javascript_include_tag("prototype"), :html_safe? | |
end | |
def test_javascript_include_tag_relative_protocol | |
@controller.config.asset_host = "assets.example.com" | |
assert_dom_equal %(<script src="//assets.example.com/javascripts/prototype.js"></script>), javascript_include_tag("prototype", protocol: :relative) | |
end | |
def test_javascript_include_tag_default_protocol | |
@controller.config.asset_host = "assets.example.com" | |
@controller.config.default_asset_host_protocol = :relative | |
assert_dom_equal %(<script src="//assets.example.com/javascripts/prototype.js"></script>), javascript_include_tag("prototype") | |
end | |
def test_javascript_include_tag_nonce | |
assert_dom_equal %(<script src="/javascripts/bank.js" nonce="iyhD0Yc0W+c="></script>), javascript_include_tag("bank", nonce: true) | |
end | |
def test_stylesheet_path | |
StylePathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } | |
end | |
def test_path_to_stylesheet_alias_for_stylesheet_path | |
PathToStyleToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } | |
end | |
def test_stylesheet_url | |
StyleUrlToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } | |
end | |
def test_url_to_stylesheet_alias_for_stylesheet_url | |
UrlToStyleToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } | |
end | |
def test_stylesheet_link_tag | |
StyleLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } | |
end | |
def test_stylesheet_link_tag_with_missing_source | |
assert_nothing_raised { | |
stylesheet_link_tag("missing_security_guard") | |
} | |
assert_nothing_raised { | |
stylesheet_link_tag("http://example.com/css/missing_security_guard") | |
} | |
end | |
def test_stylesheet_link_tag_without_request | |
@request = nil | |
assert_dom_equal( | |
%(<link rel="stylesheet" href="/stylesheets/foo.css" />), | |
stylesheet_link_tag("foo.css") | |
) | |
end | |
def test_stylesheet_link_tag_is_html_safe | |
assert_predicate stylesheet_link_tag("dir/file"), :html_safe? | |
assert_predicate stylesheet_link_tag("dir/other/file", "dir/file2"), :html_safe? | |
end | |
def test_stylesheet_link_tag_escapes_options | |
assert_dom_equal %(<link href="/file.css" media="<script>" rel="stylesheet" />), stylesheet_link_tag("/file", media: "<script>") | |
end | |
def test_stylesheet_link_tag_should_not_output_the_same_asset_twice | |
assert_dom_equal %(<link href="/stylesheets/wellington.css" rel="stylesheet" />\n<link href="/stylesheets/amsterdam.css" rel="stylesheet" />), stylesheet_link_tag("wellington", "wellington", "amsterdam") | |
end | |
def test_stylesheet_link_tag_with_relative_protocol | |
@controller.config.asset_host = "assets.example.com" | |
assert_dom_equal %(<link href="//assets.example.com/stylesheets/wellington.css" rel="stylesheet" />), stylesheet_link_tag("wellington", protocol: :relative) | |
end | |
def test_stylesheet_link_tag_with_default_protocol | |
@controller.config.asset_host = "assets.example.com" | |
@controller.config.default_asset_host_protocol = :relative | |
assert_dom_equal %(<link href="//assets.example.com/stylesheets/wellington.css" rel="stylesheet" />), stylesheet_link_tag("wellington") | |
end | |
def test_stylesheet_link_tag_with_configured_stylesheet_media_default | |
original_default_media = ActionView::Helpers::AssetTagHelper.apply_stylesheet_media_default | |
ActionView::Helpers::AssetTagHelper.apply_stylesheet_media_default = true | |
assert_dom_equal %(<link href="/file.css" media="screen" rel="stylesheet" />), stylesheet_link_tag("/file") | |
assert_dom_equal %(<link href="/file.css" media="all" rel="stylesheet" />), stylesheet_link_tag("/file", media: "all") | |
ensure | |
ActionView::Helpers::AssetTagHelper.apply_stylesheet_media_default = original_default_media | |
end | |
def test_stylesheet_link_tag_without_default_extension_applied | |
assert_dom_equal %(<link href="/stylesheets/wellington.less" rel="stylesheet" />), stylesheet_link_tag("wellington.less", extname: false) | |
end | |
def test_javascript_include_tag_without_default_extension_applied | |
assert_dom_equal %(<script src="/javascripts/foo.jsx"></script>), javascript_include_tag("foo.jsx", extname: false) | |
end | |
def test_javascript_include_tag_without_request | |
@request = nil | |
assert_dom_equal %(<script src="/javascripts/foo.js"></script>), javascript_include_tag("foo.js") | |
end | |
def test_should_set_preload_links | |
with_preload_links_header do | |
stylesheet_link_tag("http://example.com/style.css") | |
javascript_include_tag("http://example.com/all.js") | |
expected = "<http://example.com/style.css>; rel=preload; as=style; nopush,<http://example.com/all.js>; rel=preload; as=script; nopush" | |
assert_equal expected, @response.headers["Link"] | |
end | |
end | |
def test_should_not_set_preload_links_for_data_url | |
with_preload_links_header do | |
stylesheet_link_tag("data:text/css;base64,YWxlcnQoIkhlbGxvIik7") | |
javascript_include_tag("data:text/javascript;base64,YWxlcnQoIkhlbGxvIik7") | |
assert_nil @response.headers["Link"] | |
end | |
end | |
def test_should_generate_links_under_the_max_size | |
with_preload_links_header do | |
100.times do |i| | |
stylesheet_link_tag("http://example.com/style.css?#{i}") | |
javascript_include_tag("http://example.com/all.js?#{i}") | |
end | |
lines = @response.headers["Link"].split("\n") | |
assert_equal 2, lines.size | |
end | |
end | |
def test_should_not_preload_links_with_defer | |
with_preload_links_header do | |
javascript_include_tag("http://example.com/all.js", defer: true) | |
assert_nil @response.headers["Link"] | |
end | |
end | |
def test_should_allow_caller_to_remove_nopush | |
with_preload_links_header do | |
stylesheet_link_tag("http://example.com/style.css", nopush: false) | |
javascript_include_tag("http://example.com/all.js", nopush: false) | |
expected = "<http://example.com/style.css>; rel=preload; as=style,<http://example.com/all.js>; rel=preload; as=script" | |
assert_equal expected, @response.headers["Link"] | |
end | |
end | |
def test_should_set_preload_links_with_cross_origin | |
with_preload_links_header do | |
stylesheet_link_tag("http://example.com/style.css", crossorigin: "use-credentials") | |
javascript_include_tag("http://example.com/all.js", crossorigin: true) | |
expected = "<http://example.com/style.css>; rel=preload; as=style; crossorigin=use-credentials; nopush,<http://example.com/all.js>; rel=preload; as=script; crossorigin=anonymous; nopush" | |
assert_equal expected, @response.headers["Link"] | |
end | |
end | |
def test_should_set_preload_links_with_rel_modulepreload | |
with_preload_links_header do | |
javascript_include_tag("http://example.com/all.js", type: "module") | |
expected = "<http://example.com/all.js>; rel=modulepreload; as=script; nopush" | |
assert_equal expected, @response.headers["Link"] | |
end | |
end | |
def test_should_set_preload_early_hints_with_rel_modulepreload | |
with_preload_links_header do | |
preload_link_tag("http://example.com/all.js", type: "module") | |
expected = "<http://example.com/all.js>; rel=modulepreload; as=script; type=module" | |
assert_equal expected, @response.headers["Link"] | |
end | |
end | |
def test_should_set_preload_links_with_integrity_hashes | |
with_preload_links_header do | |
stylesheet_link_tag("http://example.com/style.css", integrity: "sha256-AbpHGcgLb+kRsJGnwFEktk7uzpZOCcBY74+YBdrKVGs") | |
javascript_include_tag("http://example.com/all.js", integrity: "sha256-AbpHGcgLb+kRsJGnwFEktk7uzpZOCcBY74+YBdrKVGs") | |
expected = "<http://example.com/style.css>; rel=preload; as=style; integrity=sha256-AbpHGcgLb+kRsJGnwFEktk7uzpZOCcBY74+YBdrKVGs; nopush,<http://example.com/all.js>; rel=preload; as=script; integrity=sha256-AbpHGcgLb+kRsJGnwFEktk7uzpZOCcBY74+YBdrKVGs; nopush" | |
assert_equal expected, @response.headers["Link"] | |
end | |
end | |
def test_should_not_preload_links_when_disabled | |
with_preload_links_header(false) do | |
stylesheet_link_tag("http://example.com/style.css") | |
javascript_include_tag("http://example.com/all.js") | |
assert_nil @response.headers["Link"] | |
end | |
end | |
def test_image_path | |
ImagePathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } | |
end | |
def test_path_to_image_alias_for_image_path | |
PathToImageToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } | |
end | |
def test_image_url | |
ImageUrlToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } | |
end | |
def test_url_to_image_alias_for_image_url | |
UrlToImageToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } | |
end | |
def test_image_tag | |
ImageLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } | |
end | |
def test_image_tag_does_not_modify_options | |
options = { size: "16x10" } | |
image_tag("icon", options) | |
assert_equal({ size: "16x10" }, options) | |
end | |
def test_image_tag_raises_an_error_for_competing_size_arguments | |
exception = assert_raise(ArgumentError) do | |
image_tag("gold.png", height: "100", width: "200", size: "45x70") | |
end | |
assert_equal("Cannot pass a :size option with a :height or :width option", exception.message) | |
end | |
def test_image_tag_loading_attribute_default_value | |
original_image_loading = ActionView::Helpers::AssetTagHelper.image_loading | |
ActionView::Helpers::AssetTagHelper.image_loading = "lazy" | |
assert_dom_equal %(<img src="" loading="lazy" />), image_tag("") | |
assert_dom_equal %(<img src="" loading="eager" />), image_tag("", loading: "eager") | |
ensure | |
ActionView::Helpers::AssetTagHelper.image_loading = original_image_loading | |
end | |
def test_image_tag_decoding_attribute_default_value | |
original_image_decoding = ActionView::Helpers::AssetTagHelper.image_decoding | |
ActionView::Helpers::AssetTagHelper.image_decoding = "async" | |
assert_dom_equal %(<img src="" decoding="async" />), image_tag("") | |
assert_dom_equal %(<img src="" decoding="sync" />), image_tag("", decoding: "sync") | |
ensure | |
ActionView::Helpers::AssetTagHelper.image_decoding = original_image_decoding | |
end | |
def test_favicon_link_tag | |
FaviconLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } | |
end | |
def test_preload_link_tag | |
PreloadLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } | |
end | |
def test_video_path | |
VideoPathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } | |
end | |
def test_path_to_video_alias_for_video_path | |
PathToVideoToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } | |
end | |
def test_video_url | |
VideoUrlToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } | |
end | |
def test_url_to_video_alias_for_video_url | |
UrlToVideoToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } | |
end | |
def test_video_tag | |
VideoLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } | |
end | |
def test_audio_path | |
AudioPathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } | |
end | |
def test_path_to_audio_alias_for_audio_path | |
PathToAudioToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } | |
end | |
def test_audio_url | |
AudioUrlToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } | |
end | |
def test_url_to_audio_alias_for_audio_url | |
UrlToAudioToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } | |
end | |
def test_audio_tag | |
AudioLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } | |
end | |
def test_font_path | |
FontPathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } | |
end | |
def test_font_url | |
FontUrlToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } | |
end | |
def test_url_to_font_alias_for_font_url | |
UrlToFontToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } | |
end | |
def test_video_audio_tag_does_not_modify_options | |
options = { autoplay: true } | |
video_tag("video", options) | |
assert_equal({ autoplay: true }, options) | |
audio_tag("audio", options) | |
assert_equal({ autoplay: true }, options) | |
end | |
def test_image_tag_interpreting_email_cid_correctly | |
# An inline image has no need for an alt tag to be automatically generated from the cid: | |
assert_equal '<img src="cid:thi%25%25sis@acontentid" />', image_tag("cid:thi%25%25sis@acontentid") | |
end | |
def test_image_tag_interpreting_email_adding_optional_alt_tag | |
assert_equal '<img alt="Image" src="cid:thi%25%25sis@acontentid" />', image_tag("cid:thi%25%25sis@acontentid", alt: "Image") | |
end | |
def test_should_not_modify_source_string | |
source = "/images/rails.png" | |
copy = source.dup | |
image_tag(source) | |
assert_equal copy, source | |
end | |
class PlaceholderImage | |
def blank?; true; end | |
def to_s; "no-image-yet.png"; end | |
end | |
def test_image_path_with_blank_placeholder | |
assert_equal "/images/no-image-yet.png", image_path(PlaceholderImage.new) | |
end | |
def test_image_path_with_asset_host_proc_returning_nil | |
@controller.config.asset_host = Proc.new do |source| | |
unless source.end_with?("tiff") | |
"cdn.example.com" | |
end | |
end | |
assert_equal "/images/file.tiff", image_path("file.tiff") | |
assert_equal "http://cdn.example.com/images/file.png", image_path("file.png") | |
end | |
def test_image_url_with_asset_host_proc_returning_nil | |
@controller.config.asset_host = Proc.new { nil } | |
@controller.request = Struct.new(:base_url, :script_name).new("http://www.example.com", nil) | |
assert_equal "/images/rails.png", image_path("rails.png") | |
assert_equal "http://www.example.com/images/rails.png", image_url("rails.png") | |
end | |
def test_caching_image_path_with_caching_and_proc_asset_host_using_request | |
@controller.config.asset_host = Proc.new do |source, request| | |
if request.ssl? | |
"#{request.protocol}#{request.host_with_port}" | |
else | |
"#{request.protocol}assets#{source.length}.example.com" | |
end | |
end | |
@controller.request.stub(:ssl?, false) do | |
assert_equal "http://assets15.example.com/images/xml.png", image_path("xml.png") | |
end | |
@controller.request.stub(:ssl?, true) do | |
assert_equal "http://localhost/images/xml.png", image_path("xml.png") | |
end | |
end | |
end | |
class AssetTagHelperNonVhostTest < ActionView::TestCase | |
tests ActionView::Helpers::AssetTagHelper | |
attr_reader :request | |
def setup | |
super | |
@controller = BasicController.new | |
@controller.config.relative_url_root = "/collaboration/hieraki" | |
@request = Struct.new(:protocol, :base_url) do | |
def send_early_hints(links); end | |
end.new("gopher://", "gopher://www.example.com") | |
@controller.request = @request | |
end | |
def url_for(options) | |
"http://www.example.com/collaboration/hieraki" | |
end | |
def test_should_compute_proper_path | |
assert_dom_equal(%(<link href="http://www.example.com/collaboration/hieraki" rel="alternate" title="RSS" type="application/rss+xml" />), auto_discovery_link_tag) | |
assert_dom_equal(%(/collaboration/hieraki/javascripts/xmlhr.js), javascript_path("xmlhr")) | |
assert_dom_equal(%(/collaboration/hieraki/stylesheets/style.css), stylesheet_path("style")) | |
assert_dom_equal(%(/collaboration/hieraki/images/xml.png), image_path("xml.png")) | |
end | |
def test_should_return_nothing_if_asset_host_isnt_configured | |
assert_nil compute_asset_host("foo") | |
end | |
def test_should_current_request_host_is_always_returned_for_request | |
assert_equal "gopher://www.example.com", compute_asset_host("foo", protocol: :request) | |
end | |
def test_should_return_custom_host_if_passed_in_options | |
assert_equal "http://custom.example.com", compute_asset_host("foo", host: "http://custom.example.com") | |
end | |
def test_should_ignore_relative_root_path_on_complete_url | |
assert_dom_equal(%(http://www.example.com/images/xml.png), image_path("http://www.example.com/images/xml.png")) | |
end | |
def test_should_return_simple_string_asset_host | |
@controller.config.asset_host = "assets.example.com" | |
assert_equal "gopher://assets.example.com", compute_asset_host("foo") | |
end | |
def test_should_return_relative_asset_host | |
@controller.config.asset_host = "assets.example.com" | |
assert_equal "//assets.example.com", compute_asset_host("foo", protocol: :relative) | |
end | |
def test_should_return_custom_protocol_asset_host | |
@controller.config.asset_host = "assets.example.com" | |
assert_equal "ftp://assets.example.com", compute_asset_host("foo", protocol: "ftp") | |
end | |
def test_should_compute_proper_path_with_asset_host | |
@controller.config.asset_host = "assets.example.com" | |
assert_dom_equal(%(<link href="http://www.example.com/collaboration/hieraki" rel="alternate" title="RSS" type="application/rss+xml" />), auto_discovery_link_tag) | |
assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/javascripts/xmlhr.js), javascript_path("xmlhr")) | |
assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/stylesheets/style.css), stylesheet_path("style")) | |
assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/images/xml.png), image_path("xml.png")) | |
end | |
def test_should_compute_proper_path_with_asset_host_and_default_protocol | |
@controller.config.asset_host = "assets.example.com" | |
@controller.config.default_asset_host_protocol = :request | |
assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/javascripts/xmlhr.js), javascript_path("xmlhr")) | |
assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/stylesheets/style.css), stylesheet_path("style")) | |
assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/images/xml.png), image_path("xml.png")) | |
end | |
def test_should_compute_proper_url_with_asset_host | |
@controller.config.asset_host = "assets.example.com" | |
assert_dom_equal(%(<link href="http://www.example.com/collaboration/hieraki" rel="alternate" title="RSS" type="application/rss+xml" />), auto_discovery_link_tag) | |
assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/javascripts/xmlhr.js), javascript_url("xmlhr")) | |
assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/stylesheets/style.css), stylesheet_url("style")) | |
assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/images/xml.png), image_url("xml.png")) | |
end | |
def test_should_compute_proper_url_with_asset_host_and_default_protocol | |
@controller.config.asset_host = "assets.example.com" | |
@controller.config.default_asset_host_protocol = :request | |
assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/javascripts/xmlhr.js), javascript_url("xmlhr")) | |
assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/stylesheets/style.css), stylesheet_url("style")) | |
assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/images/xml.png), image_url("xml.png")) | |
end | |
def test_should_return_asset_host_with_protocol | |
@controller.config.asset_host = "http://assets.example.com" | |
assert_equal "http://assets.example.com", compute_asset_host("foo") | |
end | |
def test_should_ignore_asset_host_on_complete_url | |
@controller.config.asset_host = "http://assets.example.com" | |
assert_dom_equal(%(<link href="http://bar.example.com/stylesheets/style.css" rel="stylesheet" />), stylesheet_link_tag("http://bar.example.com/stylesheets/style.css")) | |
end | |
def test_should_ignore_asset_host_on_scheme_relative_url | |
@controller.config.asset_host = "http://assets.example.com" | |
assert_dom_equal(%(<link href="//bar.example.com/stylesheets/style.css" rel="stylesheet" />), stylesheet_link_tag("//bar.example.com/stylesheets/style.css")) | |
end | |
def test_should_wildcard_asset_host | |
@controller.config.asset_host = "http://a%d.example.com" | |
assert_match(%r(http://a[0123]\.example\.com), compute_asset_host("foo")) | |
end | |
def test_should_wildcard_asset_host_between_zero_and_four | |
@controller.config.asset_host = "http://a%d.example.com" | |
assert_match(%r(http://a[0123]\.example\.com/collaboration/hieraki/images/xml\.png), image_path("xml.png")) | |
assert_match(%r(http://a[0123]\.example\.com/collaboration/hieraki/images/xml\.png), image_url("xml.png")) | |
end | |
def test_asset_host_without_protocol_should_be_protocol_relative | |
@controller.config.asset_host = "a.example.com" | |
assert_equal "gopher://a.example.com/collaboration/hieraki/images/xml.png", image_path("xml.png") | |
assert_equal "gopher://a.example.com/collaboration/hieraki/images/xml.png", image_url("xml.png") | |
end | |
def test_asset_host_without_protocol_should_be_protocol_relative_even_if_path_present | |
@controller.config.asset_host = "a.example.com/files/go/here" | |
assert_equal "gopher://a.example.com/files/go/here/collaboration/hieraki/images/xml.png", image_path("xml.png") | |
assert_equal "gopher://a.example.com/files/go/here/collaboration/hieraki/images/xml.png", image_url("xml.png") | |
end | |
def test_assert_css_and_js_of_the_same_name_return_correct_extension | |
assert_dom_equal(%(/collaboration/hieraki/javascripts/foo.js), javascript_path("foo")) | |
assert_dom_equal(%(/collaboration/hieraki/stylesheets/foo.css), stylesheet_path("foo")) | |
end | |
end | |
class AssetTagHelperWithoutRequestTest < ActionView::TestCase | |
tests ActionView::Helpers::AssetTagHelper | |
undef :request | |
def test_stylesheet_link_tag_without_request | |
assert_dom_equal( | |
%(<link rel="stylesheet" href="/stylesheets/foo.css" />), | |
stylesheet_link_tag("foo.css") | |
) | |
end | |
def test_javascript_include_tag_without_request | |
assert_dom_equal %(<script src="/javascripts/foo.js"></script>), javascript_include_tag("foo.js") | |
end | |
end | |
class AssetTagHelperWithStreamingRequest < ActionView::TestCase | |
tests ActionView::Helpers::AssetTagHelper | |
include AssetTagHelperTestHelpers | |
def setup | |
super | |
response.sending! | |
end | |
def test_stylesheet_link_tag_with_streaming | |
with_preload_links_header do | |
assert_dom_equal( | |
%(<link rel="stylesheet" href="/stylesheets/foo.css" />), | |
stylesheet_link_tag("foo.css") | |
) | |
end | |
end | |
def test_javascript_include_tag_with_streaming | |
with_preload_links_header do | |
assert_dom_equal %(<script src="/javascripts/foo.js"></script>), javascript_include_tag("foo.js") | |
end | |
end | |
end | |
class AssetUrlHelperControllerTest < ActionView::TestCase | |
tests ActionView::Helpers::AssetUrlHelper | |
def setup | |
super | |
@controller = BasicController.new | |
@controller.extend ActionView::Helpers::AssetUrlHelper | |
@request = Class.new do | |
attr_accessor :script_name | |
def protocol() "http://" end | |
def ssl?() false end | |
def host_with_port() "www.example.com" end | |
def base_url() "http://www.example.com" end | |
end.new | |
@controller.request = @request | |
end | |
def test_asset_path | |
assert_equal "/foo", @controller.asset_path("foo") | |
end | |
def test_asset_url | |
assert_equal "http://www.example.com/foo", @controller.asset_url("foo") | |
end | |
end | |
class AssetUrlHelperEmptyModuleTest < ActionView::TestCase | |
tests ActionView::Helpers::AssetUrlHelper | |
def setup | |
super | |
@module = Module.new | |
@module.extend ActionView::Helpers::AssetUrlHelper | |
end | |
def test_asset_path | |
assert_equal "/foo", @module.asset_path("foo") | |
end | |
def test_asset_url | |
assert_equal "/foo", @module.asset_url("foo") | |
end | |
def test_asset_url_with_request | |
@module.instance_eval do | |
def request | |
Struct.new(:base_url, :script_name).new("http://www.example.com", nil) | |
end | |
end | |
assert @module.request | |
assert_equal "http://www.example.com/foo", @module.asset_url("foo") | |
end | |
def test_asset_url_with_config_asset_host | |
@module.instance_eval do | |
def config | |
Struct.new(:asset_host).new("http://www.example.com") | |
end | |
end | |
assert @module.config.asset_host | |
assert_equal "http://www.example.com/foo", @module.asset_url("foo") | |
end | |
def test_asset_url_with_custom_asset_host | |
@module.instance_eval do | |
def config | |
Struct.new(:asset_host).new("http://www.example.com") | |
end | |
end | |
assert @module.config.asset_host | |
assert_equal "http://custom.example.com/foo", @module.asset_url("foo", host: "http://custom.example.com") | |
end | |
end |
# frozen_string_literal: true | |
require "zlib" | |
module ActionView | |
# = Action View Asset URL Helpers | |
module Helpers # :nodoc: | |
# This module provides methods for generating asset paths and | |
# URLs. | |
# | |
# image_path("rails.png") | |
# # => "/assets/rails.png" | |
# | |
# image_url("rails.png") | |
# # => "http://www.example.com/assets/rails.png" | |
# | |
# === Using asset hosts | |
# | |
# By default, Rails links to these assets on the current host in the public | |
# folder, but you can direct Rails to link to assets from a dedicated asset | |
# server by setting <tt>ActionController::Base.asset_host</tt> in the application | |
# configuration, typically in <tt>config/environments/production.rb</tt>. | |
# For example, you'd define <tt>assets.example.com</tt> to be your asset | |
# host this way, inside the <tt>configure</tt> block of your environment-specific | |
# configuration files or <tt>config/application.rb</tt>: | |
# | |
# config.action_controller.asset_host = "assets.example.com" | |
# | |
# Helpers take that into account: | |
# | |
# image_tag("rails.png") | |
# # => <img src="http://assets.example.com/assets/rails.png" /> | |
# stylesheet_link_tag("application") | |
# # => <link href="http://assets.example.com/assets/application.css" rel="stylesheet" /> | |
# | |
# Browsers open a limited number of simultaneous connections to a single | |
# host. The exact number varies by browser and version. This limit may cause | |
# some asset downloads to wait for previous assets to finish before they can | |
# begin. You can use the <tt>%d</tt> wildcard in the +asset_host+ to | |
# distribute the requests over four hosts. For example, | |
# <tt>assets%d.example.com</tt> will spread the asset requests over | |
# "assets0.example.com", ..., "assets3.example.com". | |
# | |
# image_tag("rails.png") | |
# # => <img src="http://assets0.example.com/assets/rails.png" /> | |
# stylesheet_link_tag("application") | |
# # => <link href="http://assets2.example.com/assets/application.css" rel="stylesheet" /> | |
# | |
# This may improve the asset loading performance of your application. | |
# It is also possible the combination of additional connection overhead | |
# (DNS, SSL) and the overall browser connection limits may result in this | |
# solution being slower. You should be sure to measure your actual | |
# performance across targeted browsers both before and after this change. | |
# | |
# To implement the corresponding hosts you can either set up four actual | |
# hosts or use wildcard DNS to CNAME the wildcard to a single asset host. | |
# You can read more about setting up your DNS CNAME records from your ISP. | |
# | |
# Note: This is purely a browser performance optimization and is not meant | |
# for server load balancing. See https://www.die.net/musings/page_load_time/ | |
# for background and https://www.browserscope.org/?category=network for | |
# connection limit data. | |
# | |
# Alternatively, you can exert more control over the asset host by setting | |
# +asset_host+ to a proc like this: | |
# | |
# ActionController::Base.asset_host = Proc.new { |source| | |
# "http://assets#{OpenSSL::Digest::SHA256.hexdigest(source).to_i(16) % 2 + 1}.example.com" | |
# } | |
# image_tag("rails.png") | |
# # => <img src="http://assets1.example.com/assets/rails.png" /> | |
# stylesheet_link_tag("application") | |
# # => <link href="http://assets2.example.com/assets/application.css" rel="stylesheet" /> | |
# | |
# The example above generates "http://assets1.example.com" and | |
# "http://assets2.example.com". This option is useful for example if | |
# you need fewer/more than four hosts, custom host names, etc. | |
# | |
# As you see the proc takes a +source+ parameter. That's a string with the | |
# absolute path of the asset, for example "/assets/rails.png". | |
# | |
# ActionController::Base.asset_host = Proc.new { |source| | |
# if source.end_with?('.css') | |
# "http://stylesheets.example.com" | |
# else | |
# "http://assets.example.com" | |
# end | |
# } | |
# image_tag("rails.png") | |
# # => <img src="http://assets.example.com/assets/rails.png" /> | |
# stylesheet_link_tag("application") | |
# # => <link href="http://stylesheets.example.com/assets/application.css" rel="stylesheet" /> | |
# | |
# Alternatively you may ask for a second parameter +request+. That one is | |
# particularly useful for serving assets from an SSL-protected page. The | |
# example proc below disables asset hosting for HTTPS connections, while | |
# still sending assets for plain HTTP requests from asset hosts. If you don't | |
# have SSL certificates for each of the asset hosts this technique allows you | |
# to avoid warnings in the client about mixed media. | |
# Note that the +request+ parameter might not be supplied, e.g. when the assets | |
# are precompiled with the command <tt>bin/rails assets:precompile</tt>. Make sure to use a | |
# +Proc+ instead of a lambda, since a +Proc+ allows missing parameters and sets them | |
# to +nil+. | |
# | |
# config.action_controller.asset_host = Proc.new { |source, request| | |
# if request && request.ssl? | |
# "#{request.protocol}#{request.host_with_port}" | |
# else | |
# "#{request.protocol}assets.example.com" | |
# end | |
# } | |
# | |
# You can also implement a custom asset host object that responds to +call+ | |
# and takes either one or two parameters just like the proc. | |
# | |
# config.action_controller.asset_host = AssetHostingWithMinimumSsl.new( | |
# "http://asset%d.example.com", "https://asset1.example.com" | |
# ) | |
# | |
module AssetUrlHelper | |
URI_REGEXP = %r{^[-a-z]+://|^(?:cid|data):|^//}i | |
# This is the entry point for all assets. | |
# When using an asset pipeline gem (e.g. propshaft or sprockets-rails), the | |
# behavior is "enhanced". You can bypass the asset pipeline by passing in | |
# <tt>skip_pipeline: true</tt> to the options. | |
# | |
# All other asset *_path helpers delegate through this method. | |
# | |
# === With the asset pipeline | |
# | |
# All options passed to +asset_path+ will be passed to +compute_asset_path+ | |
# which is implemented by asset pipeline gems. | |
# | |
# asset_path("application.js") # => "/assets/application-60aa4fdc5cea14baf5400fba1abf4f2a46a5166bad4772b1effe341570f07de9.js" | |
# asset_path('application.js', host: 'example.com') # => "//example.com/assets/application.js" | |
# asset_path("application.js", host: 'example.com', protocol: 'https') # => "https://example.com/assets/application.js" | |
# | |
# === Without the asset pipeline (<tt>skip_pipeline: true</tt>) | |
# | |
# Accepts a <tt>type</tt> option that can specify the asset's extension. No error | |
# checking is done to verify the source passed into +asset_path+ is valid | |
# and that the file exists on disk. | |
# | |
# asset_path("application.js", skip_pipeline: true) # => "application.js" | |
# asset_path("filedoesnotexist.png", skip_pipeline: true) # => "filedoesnotexist.png" | |
# asset_path("application", type: :javascript, skip_pipeline: true) # => "/javascripts/application.js" | |
# asset_path("application", type: :stylesheet, skip_pipeline: true) # => "/stylesheets/application.css" | |
# | |
# === Options applying to all assets | |
# | |
# Below lists scenarios that apply to +asset_path+ whether or not you're | |
# using the asset pipeline. | |
# | |
# - All fully qualified URLs are returned immediately. This bypasses the | |
# asset pipeline and all other behavior described. | |
# | |
# asset_path("http://www.example.com/js/xmlhr.js") # => "http://www.example.com/js/xmlhr.js" | |
# | |
# - All assets that begin with a forward slash are assumed to be full | |
# URLs and will not be expanded. This will bypass the asset pipeline. | |
# | |
# asset_path("/foo.png") # => "/foo.png" | |
# | |
# - All blank strings will be returned immediately. This bypasses the | |
# asset pipeline and all other behavior described. | |
# | |
# asset_path("") # => "" | |
# | |
# - If <tt>config.relative_url_root</tt> is specified, all assets will have that | |
# root prepended. | |
# | |
# Rails.application.config.relative_url_root = "bar" | |
# asset_path("foo.js", skip_pipeline: true) # => "bar/foo.js" | |
# | |
# - A different asset host can be specified via <tt>config.action_controller.asset_host</tt> | |
# this is commonly used in conjunction with a CDN. | |
# | |
# Rails.application.config.action_controller.asset_host = "assets.example.com" | |
# asset_path("foo.js", skip_pipeline: true) # => "http://assets.example.com/foo.js" | |
# | |
# - An extension name can be specified manually with <tt>extname</tt>. | |
# | |
# asset_path("foo", skip_pipeline: true, extname: ".js") # => "/foo.js" | |
# asset_path("foo.css", skip_pipeline: true, extname: ".js") # => "/foo.css.js" | |
def asset_path(source, options = {}) | |
raise ArgumentError, "nil is not a valid asset source" if source.nil? | |
source = source.to_s | |
return "" if source.blank? | |
return source if URI_REGEXP.match?(source) | |
tail, source = source[/([?#].+)$/], source.sub(/([?#].+)$/, "") | |
if extname = compute_asset_extname(source, options) | |
source = "#{source}#{extname}" | |
end | |
if source[0] != ?/ | |
if options[:skip_pipeline] | |
source = public_compute_asset_path(source, options) | |
else | |
source = compute_asset_path(source, options) | |
end | |
end | |
relative_url_root = defined?(config.relative_url_root) && config.relative_url_root | |
if relative_url_root | |
source = File.join(relative_url_root, source) unless source.start_with?("#{relative_url_root}/") | |
end | |
if host = compute_asset_host(source, options) | |
source = File.join(host, source) | |
end | |
"#{source}#{tail}" | |
end | |
alias_method :path_to_asset, :asset_path # aliased to avoid conflicts with an asset_path named route | |
# Computes the full URL to an asset in the public directory. This | |
# will use +asset_path+ internally, so most of their behaviors | |
# will be the same. If +:host+ options is set, it overwrites global | |
# +config.action_controller.asset_host+ setting. | |
# | |
# All other options provided are forwarded to +asset_path+ call. | |
# | |
# asset_url "application.js" # => http://example.com/assets/application.js | |
# asset_url "application.js", host: "http://cdn.example.com" # => http://cdn.example.com/assets/application.js | |
# | |
def asset_url(source, options = {}) | |
path_to_asset(source, options.merge(protocol: :request)) | |
end | |
alias_method :url_to_asset, :asset_url # aliased to avoid conflicts with an asset_url named route | |
ASSET_EXTENSIONS = { | |
javascript: ".js", | |
stylesheet: ".css" | |
} | |
# Compute extname to append to asset path. Returns +nil+ if | |
# nothing should be added. | |
def compute_asset_extname(source, options = {}) | |
return if options[:extname] == false | |
extname = options[:extname] || ASSET_EXTENSIONS[options[:type]] | |
if extname && File.extname(source) != extname | |
extname | |
else | |
nil | |
end | |
end | |
# Maps asset types to public directory. | |
ASSET_PUBLIC_DIRECTORIES = { | |
audio: "/audios", | |
font: "/fonts", | |
image: "/images", | |
javascript: "/javascripts", | |
stylesheet: "/stylesheets", | |
video: "/videos" | |
} | |
# Computes asset path to public directory. Plugins and | |
# extensions can override this method to point to custom assets | |
# or generate digested paths or query strings. | |
def compute_asset_path(source, options = {}) | |
dir = ASSET_PUBLIC_DIRECTORIES[options[:type]] || "" | |
File.join(dir, source) | |
end | |
alias :public_compute_asset_path :compute_asset_path | |
# Pick an asset host for this source. Returns +nil+ if no host is set, | |
# the host if no wildcard is set, the host interpolated with the | |
# numbers 0-3 if it contains <tt>%d</tt> (the number is the source hash mod 4), | |
# or the value returned from invoking call on an object responding to call | |
# (proc or otherwise). | |
def compute_asset_host(source = "", options = {}) | |
request = self.request if respond_to?(:request) | |
host = options[:host] | |
host ||= config.asset_host if defined? config.asset_host | |
if host | |
if host.respond_to?(:call) | |
arity = host.respond_to?(:arity) ? host.arity : host.method(:call).arity | |
args = [source] | |
args << request if request && (arity > 1 || arity < 0) | |
host = host.call(*args) | |
elsif host.include?("%d") | |
host = host % (Zlib.crc32(source) % 4) | |
end | |
end | |
host ||= request.base_url if request && options[:protocol] == :request | |
return unless host | |
if URI_REGEXP.match?(host) | |
host | |
else | |
protocol = options[:protocol] || config.default_asset_host_protocol || (request ? :request : :relative) | |
case protocol | |
when :relative | |
"//#{host}" | |
when :request | |
"#{request.protocol}#{host}" | |
else | |
"#{protocol}://#{host}" | |
end | |
end | |
end | |
# Computes the path to a JavaScript asset in the public javascripts directory. | |
# If the +source+ filename has no extension, .js will be appended (except for explicit URIs) | |
# Full paths from the document root will be passed through. | |
# Used internally by +javascript_include_tag+ to build the script path. | |
# | |
# javascript_path "xmlhr" # => /assets/xmlhr.js | |
# javascript_path "dir/xmlhr.js" # => /assets/dir/xmlhr.js | |
# javascript_path "/dir/xmlhr" # => /dir/xmlhr.js | |
# javascript_path "http://www.example.com/js/xmlhr" # => http://www.example.com/js/xmlhr | |
# javascript_path "http://www.example.com/js/xmlhr.js" # => http://www.example.com/js/xmlhr.js | |
def javascript_path(source, options = {}) | |
path_to_asset(source, { type: :javascript }.merge!(options)) | |
end | |
alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route | |
# Computes the full URL to a JavaScript asset in the public javascripts directory. | |
# This will use +javascript_path+ internally, so most of their behaviors will be the same. | |
# Since +javascript_url+ is based on +asset_url+ method you can set +:host+ options. If +:host+ | |
# options is set, it overwrites global +config.action_controller.asset_host+ setting. | |
# | |
# javascript_url "js/xmlhr.js", host: "http://stage.example.com" # => http://stage.example.com/assets/js/xmlhr.js | |
# | |
def javascript_url(source, options = {}) | |
url_to_asset(source, { type: :javascript }.merge!(options)) | |
end | |
alias_method :url_to_javascript, :javascript_url # aliased to avoid conflicts with a javascript_url named route | |
# Computes the path to a stylesheet asset in the public stylesheets directory. | |
# If the +source+ filename has no extension, .css will be appended (except for explicit URIs). | |
# Full paths from the document root will be passed through. | |
# Used internally by +stylesheet_link_tag+ to build the stylesheet path. | |
# | |
# stylesheet_path "style" # => /assets/style.css | |
# stylesheet_path "dir/style.css" # => /assets/dir/style.css | |
# stylesheet_path "/dir/style.css" # => /dir/style.css | |
# stylesheet_path "http://www.example.com/css/style" # => http://www.example.com/css/style | |
# stylesheet_path "http://www.example.com/css/style.css" # => http://www.example.com/css/style.css | |
def stylesheet_path(source, options = {}) | |
path_to_asset(source, { type: :stylesheet }.merge!(options)) | |
end | |
alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with a stylesheet_path named route | |
# Computes the full URL to a stylesheet asset in the public stylesheets directory. | |
# This will use +stylesheet_path+ internally, so most of their behaviors will be the same. | |
# Since +stylesheet_url+ is based on +asset_url+ method you can set +:host+ options. If +:host+ | |
# options is set, it overwrites global +config.action_controller.asset_host+ setting. | |
# | |
# stylesheet_url "css/style.css", host: "http://stage.example.com" # => http://stage.example.com/assets/css/style.css | |
# | |
def stylesheet_url(source, options = {}) | |
url_to_asset(source, { type: :stylesheet }.merge!(options)) | |
end | |
alias_method :url_to_stylesheet, :stylesheet_url # aliased to avoid conflicts with a stylesheet_url named route | |
# Computes the path to an image asset. | |
# Full paths from the document root will be passed through. | |
# Used internally by +image_tag+ to build the image path: | |
# | |
# image_path("edit") # => "/assets/edit" | |
# image_path("edit.png") # => "/assets/edit.png" | |
# image_path("icons/edit.png") # => "/assets/icons/edit.png" | |
# image_path("/icons/edit.png") # => "/icons/edit.png" | |
# image_path("http://www.example.com/img/edit.png") # => "http://www.example.com/img/edit.png" | |
# | |
# If you have images as application resources this method may conflict with their named routes. | |
# The alias +path_to_image+ is provided to avoid that. Rails uses the alias internally, and | |
# plugin authors are encouraged to do so. | |
def image_path(source, options = {}) | |
path_to_asset(source, { type: :image }.merge!(options)) | |
end | |
alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route | |
# Computes the full URL to an image asset. | |
# This will use +image_path+ internally, so most of their behaviors will be the same. | |
# Since +image_url+ is based on +asset_url+ method you can set +:host+ options. If +:host+ | |
# options is set, it overwrites global +config.action_controller.asset_host+ setting. | |
# | |
# image_url "edit.png", host: "http://stage.example.com" # => http://stage.example.com/assets/edit.png | |
# | |
def image_url(source, options = {}) | |
url_to_asset(source, { type: :image }.merge!(options)) | |
end | |
alias_method :url_to_image, :image_url # aliased to avoid conflicts with an image_url named route | |
# Computes the path to a video asset in the public videos directory. | |
# Full paths from the document root will be passed through. | |
# Used internally by +video_tag+ to build the video path. | |
# | |
# video_path("hd") # => /videos/hd | |
# video_path("hd.avi") # => /videos/hd.avi | |
# video_path("trailers/hd.avi") # => /videos/trailers/hd.avi | |
# video_path("/trailers/hd.avi") # => /trailers/hd.avi | |
# video_path("http://www.example.com/vid/hd.avi") # => http://www.example.com/vid/hd.avi | |
def video_path(source, options = {}) | |
path_to_asset(source, { type: :video }.merge!(options)) | |
end | |
alias_method :path_to_video, :video_path # aliased to avoid conflicts with a video_path named route | |
# Computes the full URL to a video asset in the public videos directory. | |
# This will use +video_path+ internally, so most of their behaviors will be the same. | |
# Since +video_url+ is based on +asset_url+ method you can set +:host+ options. If +:host+ | |
# options is set, it overwrites global +config.action_controller.asset_host+ setting. | |
# | |
# video_url "hd.avi", host: "http://stage.example.com" # => http://stage.example.com/videos/hd.avi | |
# | |
def video_url(source, options = {}) | |
url_to_asset(source, { type: :video }.merge!(options)) | |
end | |
alias_method :url_to_video, :video_url # aliased to avoid conflicts with a video_url named route | |
# Computes the path to an audio asset in the public audios directory. | |
# Full paths from the document root will be passed through. | |
# Used internally by +audio_tag+ to build the audio path. | |
# | |
# audio_path("horse") # => /audios/horse | |
# audio_path("horse.wav") # => /audios/horse.wav | |
# audio_path("sounds/horse.wav") # => /audios/sounds/horse.wav | |
# audio_path("/sounds/horse.wav") # => /sounds/horse.wav | |
# audio_path("http://www.example.com/sounds/horse.wav") # => http://www.example.com/sounds/horse.wav | |
def audio_path(source, options = {}) | |
path_to_asset(source, { type: :audio }.merge!(options)) | |
end | |
alias_method :path_to_audio, :audio_path # aliased to avoid conflicts with an audio_path named route | |
# Computes the full URL to an audio asset in the public audios directory. | |
# This will use +audio_path+ internally, so most of their behaviors will be the same. | |
# Since +audio_url+ is based on +asset_url+ method you can set +:host+ options. If +:host+ | |
# options is set, it overwrites global +config.action_controller.asset_host+ setting. | |
# | |
# audio_url "horse.wav", host: "http://stage.example.com" # => http://stage.example.com/audios/horse.wav | |
# | |
def audio_url(source, options = {}) | |
url_to_asset(source, { type: :audio }.merge!(options)) | |
end | |
alias_method :url_to_audio, :audio_url # aliased to avoid conflicts with an audio_url named route | |
# Computes the path to a font asset. | |
# Full paths from the document root will be passed through. | |
# | |
# font_path("font") # => /fonts/font | |
# font_path("font.ttf") # => /fonts/font.ttf | |
# font_path("dir/font.ttf") # => /fonts/dir/font.ttf | |
# font_path("/dir/font.ttf") # => /dir/font.ttf | |
# font_path("http://www.example.com/dir/font.ttf") # => http://www.example.com/dir/font.ttf | |
def font_path(source, options = {}) | |
path_to_asset(source, { type: :font }.merge!(options)) | |
end | |
alias_method :path_to_font, :font_path # aliased to avoid conflicts with a font_path named route | |
# Computes the full URL to a font asset. | |
# This will use +font_path+ internally, so most of their behaviors will be the same. | |
# Since +font_url+ is based on +asset_url+ method you can set +:host+ options. If +:host+ | |
# options is set, it overwrites global +config.action_controller.asset_host+ setting. | |
# | |
# font_url "font.ttf", host: "http://stage.example.com" # => http://stage.example.com/fonts/font.ttf | |
# | |
def font_url(source, options = {}) | |
url_to_asset(source, { type: :font }.merge!(options)) | |
end | |
alias_method :url_to_font, :font_url # aliased to avoid conflicts with a font_url named route | |
end | |
end | |
end |
# frozen_string_literal: true | |
require "set" | |
module ActionView | |
# = Action View Atom Feed Helpers | |
module Helpers # :nodoc: | |
module AtomFeedHelper | |
# Adds easy defaults to writing Atom feeds with the Builder template engine (this does not work on ERB or any other | |
# template languages). | |
# | |
# Full usage example: | |
# | |
# config/routes.rb: | |
# Rails.application.routes.draw do | |
# resources :posts | |
# root to: "posts#index" | |
# end | |
# | |
# app/controllers/posts_controller.rb: | |
# class PostsController < ApplicationController | |
# # GET /posts.html | |
# # GET /posts.atom | |
# def index | |
# @posts = Post.all | |
# | |
# respond_to do |format| | |
# format.html | |
# format.atom | |
# end | |
# end | |
# end | |
# | |
# app/views/posts/index.atom.builder: | |
# atom_feed do |feed| | |
# feed.title("My great blog!") | |
# feed.updated(@posts[0].created_at) if @posts.length > 0 | |
# | |
# @posts.each do |post| | |
# feed.entry(post) do |entry| | |
# entry.title(post.title) | |
# entry.content(post.body, type: 'html') | |
# | |
# entry.author do |author| | |
# author.name("DHH") | |
# end | |
# end | |
# end | |
# end | |
# | |
# The options for atom_feed are: | |
# | |
# * <tt>:language</tt>: Defaults to "en-US". | |
# * <tt>:root_url</tt>: The HTML alternative that this feed is doubling for. Defaults to / on the current host. | |
# * <tt>:url</tt>: The URL for this feed. Defaults to the current URL. | |
# * <tt>:id</tt>: The id for this feed. Defaults to "tag:localhost,2005:/posts", in this case. | |
# * <tt>:schema_date</tt>: The date at which the tag scheme for the feed was first used. A good default is the year you | |
# created the feed. See http://feedvalidator.org/docs/error/InvalidTAG.html for more information. If not specified, | |
# 2005 is used (as an "I don't care" value). | |
# * <tt>:instruct</tt>: Hash of XML processing instructions in the form {target => {attribute => value, }} or {target => [{attribute => value, }, ]} | |
# | |
# Other namespaces can be added to the root element: | |
# | |
# app/views/posts/index.atom.builder: | |
# atom_feed({'xmlns:app' => 'http://www.w3.org/2007/app', | |
# 'xmlns:openSearch' => 'http://a9.com/-/spec/opensearch/1.1/'}) do |feed| | |
# feed.title("My great blog!") | |
# feed.updated((@posts.first.created_at)) | |
# feed.tag!('openSearch:totalResults', 10) | |
# | |
# @posts.each do |post| | |
# feed.entry(post) do |entry| | |
# entry.title(post.title) | |
# entry.content(post.body, type: 'html') | |
# entry.tag!('app:edited', Time.now) | |
# | |
# entry.author do |author| | |
# author.name("DHH") | |
# end | |
# end | |
# end | |
# end | |
# | |
# The Atom spec defines five elements (content rights title subtitle | |
# summary) which may directly contain xhtml content if type: 'xhtml' | |
# is specified as an attribute. If so, this helper will take care of | |
# the enclosing div and xhtml namespace declaration. Example usage: | |
# | |
# entry.summary type: 'xhtml' do |xhtml| | |
# xhtml.p pluralize(order.line_items.count, "line item") | |
# xhtml.p "Shipped to #{order.address}" | |
# xhtml.p "Paid by #{order.pay_type}" | |
# end | |
# | |
# | |
# <tt>atom_feed</tt> yields an +AtomFeedBuilder+ instance. Nested elements yield | |
# an +AtomBuilder+ instance. | |
def atom_feed(options = {}, &block) | |
if options[:schema_date] | |
options[:schema_date] = options[:schema_date].strftime("%Y-%m-%d") if options[:schema_date].respond_to?(:strftime) | |
else | |
options[:schema_date] = "2005" # The Atom spec copyright date | |
end | |
xml = options.delete(:xml) || eval("xml", block.binding) | |
xml.instruct! | |
if options[:instruct] | |
options[:instruct].each do |target, attrs| | |
if attrs.respond_to?(:keys) | |
xml.instruct!(target, attrs) | |
elsif attrs.respond_to?(:each) | |
attrs.each { |attr_group| xml.instruct!(target, attr_group) } | |
end | |
end | |
end | |
feed_opts = { "xml:lang" => options[:language] || "en-US", "xmlns" => "http://www.w3.org/2005/Atom" } | |
feed_opts.merge!(options).select! { |k, _| k.start_with?("xml") } | |
xml.feed(feed_opts) do | |
xml.id(options[:id] || "tag:#{request.host},#{options[:schema_date]}:#{request.fullpath.split(".")[0]}") | |
xml.link(rel: "alternate", type: "text/html", href: options[:root_url] || (request.protocol + request.host_with_port)) | |
xml.link(rel: "self", type: "application/atom+xml", href: options[:url] || request.url) | |
yield AtomFeedBuilder.new(xml, self, options) | |
end | |
end | |
class AtomBuilder # :nodoc: | |
XHTML_TAG_NAMES = %w(content rights title subtitle summary).to_set | |
def initialize(xml) | |
@xml = xml | |
end | |
private | |
# Delegate to xml builder, first wrapping the element in an xhtml | |
# namespaced div element if the method and arguments indicate | |
# that an xhtml_block? is desired. | |
def method_missing(method, *arguments, &block) | |
if xhtml_block?(method, arguments) | |
@xml.__send__(method, *arguments) do | |
@xml.div(xmlns: "http://www.w3.org/1999/xhtml") do |xhtml| | |
block.call(xhtml) | |
end | |
end | |
else | |
@xml.__send__(method, *arguments, &block) | |
end | |
end | |
# True if the method name matches one of the five elements defined | |
# in the Atom spec as potentially containing XHTML content and | |
# if type: 'xhtml' is, in fact, specified. | |
def xhtml_block?(method, arguments) | |
if XHTML_TAG_NAMES.include?(method.to_s) | |
last = arguments.last | |
last.is_a?(Hash) && last[:type].to_s == "xhtml" | |
end | |
end | |
end | |
class AtomFeedBuilder < AtomBuilder # :nodoc: | |
def initialize(xml, view, feed_options = {}) | |
@xml, @view, @feed_options = xml, view, feed_options | |
end | |
# Accepts a Date or Time object and inserts it in the proper format. If +nil+ is passed, current time in UTC is used. | |
def updated(date_or_time = nil) | |
@xml.updated((date_or_time || Time.now.utc).xmlschema) | |
end | |
# Creates an entry tag for a specific record and prefills the id using class and id. | |
# | |
# Options: | |
# | |
# * <tt>:published</tt>: Time first published. Defaults to the created_at attribute on the record if one such exists. | |
# * <tt>:updated</tt>: Time of update. Defaults to the updated_at attribute on the record if one such exists. | |
# * <tt>:url</tt>: The URL for this entry or +false+ or +nil+ for not having a link tag. Defaults to the +polymorphic_url+ for the record. | |
# * <tt>:id</tt>: The ID for this entry. Defaults to "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}" | |
# * <tt>:type</tt>: The TYPE for this entry. Defaults to "text/html". | |
def entry(record, options = {}) | |
@xml.entry do | |
@xml.id(options[:id] || "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}") | |
if options[:published] || (record.respond_to?(:created_at) && record.created_at) | |
@xml.published((options[:published] || record.created_at).xmlschema) | |
end | |
if options[:updated] || (record.respond_to?(:updated_at) && record.updated_at) | |
@xml.updated((options[:updated] || record.updated_at).xmlschema) | |
end | |
type = options.fetch(:type, "text/html") | |
url = options.fetch(:url) { @view.polymorphic_url(record) } | |
@xml.link(rel: "alternate", type: type, href: url) if url | |
yield AtomBuilder.new(@xml) | |
end | |
end | |
end | |
end | |
end | |
end |
# frozen_string_literal: true | |
require "abstract_unit" | |
Scroll = Struct.new(:id, :to_param, :title, :body, :updated_at, :created_at) do | |
extend ActiveModel::Naming | |
include ActiveModel::Conversion | |
def persisted? | |
false | |
end | |
end | |
class ScrollsController < ActionController::Base | |
FEEDS = {} | |
FEEDS["defaults"] = <<-EOT | |
atom_feed(:schema_date => '2008') do |feed| | |
feed.title("My great blog!") | |
feed.updated(@scrolls.first.created_at) | |
@scrolls.each do |scroll| | |
feed.entry(scroll) do |entry| | |
entry.title(scroll.title) | |
entry.content(scroll.body, :type => 'html') | |
entry.author do |author| | |
author.name("DHH") | |
end | |
end | |
end | |
end | |
EOT | |
FEEDS["entry_options"] = <<-EOT | |
atom_feed do |feed| | |
feed.title("My great blog!") | |
feed.updated(@scrolls.first.created_at) | |
@scrolls.each do |scroll| | |
feed.entry(scroll, :url => "/otherstuff/" + scroll.to_param.to_s, :updated => Time.utc(2007, 1, scroll.id)) do |entry| | |
entry.title(scroll.title) | |
entry.content(scroll.body, :type => 'html') | |
entry.author do |author| | |
author.name("DHH") | |
end | |
end | |
end | |
end | |
EOT | |
FEEDS["entry_type_options"] = <<-EOT | |
atom_feed(:schema_date => '2008') do |feed| | |
feed.title("My great blog!") | |
feed.updated(@scrolls.first.created_at) | |
@scrolls.each do |scroll| | |
feed.entry(scroll, :type => 'text/xml') do |entry| | |
entry.title(scroll.title) | |
entry.content(scroll.body, :type => 'html') | |
entry.author do |author| | |
author.name("DHH") | |
end | |
end | |
end | |
end | |
EOT | |
FEEDS["entry_url_false_option"] = <<-EOT | |
atom_feed do |feed| | |
feed.title("My great blog!") | |
feed.updated(@scrolls.first.created_at) | |
@scrolls.each do |scroll| | |
feed.entry(scroll, :url => false) do |entry| | |
entry.title(scroll.title) | |
entry.content(scroll.body, :type => 'html') | |
entry.author do |author| | |
author.name("DHH") | |
end | |
end | |
end | |
end | |
EOT | |
FEEDS["xml_block"] = <<-EOT | |
atom_feed do |feed| | |
feed.title("My great blog!") | |
feed.updated(@scrolls.first.created_at) | |
feed.author do |author| | |
author.name("DHH") | |
end | |
@scrolls.each do |scroll| | |
feed.entry(scroll, :url => "/otherstuff/" + scroll.to_param.to_s, :updated => Time.utc(2007, 1, scroll.id)) do |entry| | |
entry.title(scroll.title) | |
entry.content(scroll.body, :type => 'html') | |
end | |
end | |
end | |
EOT | |
FEEDS["feed_with_atomPub_namespace"] = <<-EOT | |
atom_feed({'xmlns:app' => 'http://www.w3.org/2007/app', | |
'xmlns:openSearch' => 'http://a9.com/-/spec/opensearch/1.1/'}) do |feed| | |
feed.title("My great blog!") | |
feed.updated(@scrolls.first.created_at) | |
@scrolls.each do |scroll| | |
feed.entry(scroll) do |entry| | |
entry.title(scroll.title) | |
entry.content(scroll.body, :type => 'html') | |
entry.tag!('app:edited', Time.now) | |
entry.author do |author| | |
author.name("DHH") | |
end | |
end | |
end | |
end | |
EOT | |
FEEDS["feed_with_overridden_ids"] = <<-EOT | |
atom_feed({:id => 'tag:test.rubyonrails.org,2008:test/'}) do |feed| | |
feed.title("My great blog!") | |
feed.updated(@scrolls.first.created_at) | |
@scrolls.each do |scroll| | |
feed.entry(scroll, :id => "tag:test.rubyonrails.org,2008:"+scroll.id.to_s) do |entry| | |
entry.title(scroll.title) | |
entry.content(scroll.body, :type => 'html') | |
entry.tag!('app:edited', Time.now) | |
entry.author do |author| | |
author.name("DHH") | |
end | |
end | |
end | |
end | |
EOT | |
FEEDS["feed_with_xml_processing_instructions"] = <<-EOT | |
atom_feed(:schema_date => '2008', | |
:instruct => {'xml-stylesheet' => { :href=> 't.css', :type => 'text/css' }}) do |feed| | |
feed.title("My great blog!") | |
feed.updated(@scrolls.first.created_at) | |
@scrolls.each do |scroll| | |
feed.entry(scroll) do |entry| | |
entry.title(scroll.title) | |
entry.content(scroll.body, :type => 'html') | |
entry.author do |author| | |
author.name("DHH") | |
end | |
end | |
end | |
end | |
EOT | |
FEEDS["feed_with_xml_processing_instructions_duplicate_targets"] = <<-EOT | |
atom_feed(:schema_date => '2008', | |
:instruct => {'target1' => [{ :a => '1', :b => '2' }, { :c => '3', :d => '4' }]}) do |feed| | |
feed.title("My great blog!") | |
feed.updated(@scrolls.first.created_at) | |
@scrolls.each do |scroll| | |
feed.entry(scroll) do |entry| | |
entry.title(scroll.title) | |
entry.content(scroll.body, :type => 'html') | |
entry.author do |author| | |
author.name("DHH") | |
end | |
end | |
end | |
end | |
EOT | |
FEEDS["feed_with_xhtml_content"] = <<-'EOT' | |
atom_feed do |feed| | |
feed.title("My great blog!") | |
feed.updated(@scrolls.first.created_at) | |
@scrolls.each do |scroll| | |
feed.entry(scroll) do |entry| | |
entry.title(scroll.title) | |
entry.summary(:type => 'xhtml') do |xhtml| | |
xhtml.p "before #{scroll.id}" | |
xhtml.p {xhtml << scroll.body} | |
xhtml.p "after #{scroll.id}" | |
end | |
entry.tag!('app:edited', Time.now) | |
entry.author do |author| | |
author.name("DHH") | |
end | |
end | |
end | |
end | |
EOT | |
FEEDS["provide_builder"] = <<-'EOT' | |
# we pass in the new_xml to the helper so it doesn't | |
# call anything on the original builder | |
new_xml = Builder::XmlMarkup.new(:target=>''.dup) | |
atom_feed(:xml => new_xml) do |feed| | |
feed.title("My great blog!") | |
feed.updated(@scrolls.first.created_at) | |
@scrolls.each do |scroll| | |
feed.entry(scroll) do |entry| | |
entry.title(scroll.title) | |
entry.content(scroll.body, :type => 'html') | |
entry.author do |author| | |
author.name("DHH") | |
end | |
end | |
end | |
end | |
EOT | |
def index | |
@scrolls = [ | |
Scroll.new(1, "1", "Hello One", "Something <i>COOL!</i>", Time.utc(2007, 12, 12, 15), Time.utc(2007, 12, 12, 15)), | |
Scroll.new(2, "2", "Hello Two", "Something Boring", Time.utc(2007, 12, 12, 15)), | |
] | |
render inline: FEEDS[params[:id]], type: :builder | |
end | |
end | |
class AtomFeedTest < ActionController::TestCase | |
tests ScrollsController | |
def setup | |
super | |
@request.host = "www.nextangle.com" | |
end | |
def test_feed_should_use_default_language_if_none_is_given | |
with_restful_routing(:scrolls) do | |
get :index, params: { id: "defaults" } | |
assert_match(%r{xml:lang="en-US"}, @response.body) | |
end | |
end | |
def test_feed_should_include_two_entries | |
with_restful_routing(:scrolls) do | |
get :index, params: { id: "defaults" } | |
assert_select "entry", 2 | |
end | |
end | |
def test_entry_should_only_use_published_if_created_at_is_present | |
with_restful_routing(:scrolls) do | |
get :index, params: { id: "defaults" } | |
assert_select "published", 1 | |
end | |
end | |
def test_providing_builder_to_atom_feed | |
with_restful_routing(:scrolls) do | |
get :index, params: { id: "provide_builder" } | |
# because we pass in the non-default builder, the content generated by the | |
# helper should go 'nowhere'. Leaving the response body blank. | |
assert_predicate @response.body, :blank? | |
end | |
end | |
def test_entry_with_prefilled_options_should_use_those_instead_of_querying_the_record | |
with_restful_routing(:scrolls) do | |
get :index, params: { id: "entry_options" } | |
assert_select "updated", Time.utc(2007, 1, 1).xmlschema | |
assert_select "updated", Time.utc(2007, 1, 2).xmlschema | |
end | |
end | |
def test_self_url_should_default_to_current_request_url | |
with_restful_routing(:scrolls) do | |
get :index, params: { id: "defaults" } | |
assert_select "link[rel=self][href=\"http://www.nextangle.com/scrolls?id=defaults\"]" | |
end | |
end | |
def test_feed_id_should_be_a_valid_tag | |
with_restful_routing(:scrolls) do | |
get :index, params: { id: "defaults" } | |
assert_select "id", text: "tag:www.nextangle.com,2008:/scrolls?id=defaults" | |
end | |
end | |
def test_entry_id_should_be_a_valid_tag | |
with_restful_routing(:scrolls) do | |
get :index, params: { id: "defaults" } | |
assert_select "entry id", text: "tag:www.nextangle.com,2008:Scroll/1" | |
assert_select "entry id", text: "tag:www.nextangle.com,2008:Scroll/2" | |
end | |
end | |
def test_feed_should_allow_nested_xml_blocks | |
with_restful_routing(:scrolls) do | |
get :index, params: { id: "xml_block" } | |
assert_select "author name", text: "DHH" | |
end | |
end | |
def test_feed_should_include_atomPub_namespace | |
with_restful_routing(:scrolls) do | |
get :index, params: { id: "feed_with_atomPub_namespace" } | |
assert_match %r{xml:lang="en-US"}, @response.body | |
assert_match %r{xmlns="http://www\.w3\.org/2005/Atom"}, @response.body | |
assert_match %r{xmlns:app="http://www\.w3\.org/2007/app"}, @response.body | |
end | |
end | |
def test_feed_should_allow_overriding_ids | |
with_restful_routing(:scrolls) do | |
get :index, params: { id: "feed_with_overridden_ids" } | |
assert_select "id", text: "tag:test.rubyonrails.org,2008:test/" | |
assert_select "entry id", text: "tag:test.rubyonrails.org,2008:1" | |
assert_select "entry id", text: "tag:test.rubyonrails.org,2008:2" | |
end | |
end | |
def test_feed_xml_processing_instructions | |
with_restful_routing(:scrolls) do | |
get :index, params: { id: "feed_with_xml_processing_instructions" } | |
assert_match %r{<\?xml-stylesheet [^?]*type="text/css"}, @response.body | |
assert_match %r{<\?xml-stylesheet [^?]*href="t\.css"}, @response.body | |
end | |
end | |
def test_feed_xml_processing_instructions_duplicate_targets | |
with_restful_routing(:scrolls) do | |
get :index, params: { id: "feed_with_xml_processing_instructions_duplicate_targets" } | |
assert_match %r{<\?target1 (a="1" b="2"|b="2" a="1")\?>}, @response.body | |
assert_match %r{<\?target1 (c="3" d="4"|d="4" c="3")\?>}, @response.body | |
end | |
end | |
def test_feed_xhtml | |
with_restful_routing(:scrolls) do | |
get :index, params: { id: "feed_with_xhtml_content" } | |
assert_match %r{xmlns="http://www\.w3\.org/1999/xhtml"}, @response.body | |
assert_select "summary", text: /Something Boring/ | |
assert_select "summary", text: /after 2/ | |
end | |
end | |
def test_feed_entry_type_option_default_to_text_html | |
with_restful_routing(:scrolls) do | |
get :index, params: { id: "defaults" } | |
assert_select "entry link[rel=alternate][type=\"text/html\"]" | |
end | |
end | |
def test_feed_entry_type_option_specified | |
with_restful_routing(:scrolls) do | |
get :index, params: { id: "entry_type_options" } | |
assert_select "entry link[rel=alternate][type=\"text/xml\"]" | |
end | |
end | |
def test_feed_entry_url_false_option_adds_no_link | |
with_restful_routing(:scrolls) do | |
get :index, params: { id: "entry_url_false_option" } | |
assert_select "entry link", false | |
end | |
end | |
private | |
def with_restful_routing(resources) | |
with_routing do |set| | |
set.draw do | |
resources(resources) | |
end | |
yield | |
end | |
end | |
end |
# frozen_string_literal: true | |
module ActionView | |
module Helpers | |
module Tags # :nodoc: | |
class Base # :nodoc: | |
include Helpers::ActiveModelInstanceTag, Helpers::TagHelper, Helpers::FormTagHelper | |
include FormOptionsHelper | |
attr_reader :object | |
def initialize(object_name, method_name, template_object, options = {}) | |
@object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup | |
@template_object = template_object | |
@object_name.sub!(/\[\]$/, "") || @object_name.sub!(/\[\]\]$/, "]") | |
@object = retrieve_object(options.delete(:object)) | |
@skip_default_ids = options.delete(:skip_default_ids) | |
@allow_method_names_outside_object = options.delete(:allow_method_names_outside_object) | |
@options = options | |
if Regexp.last_match | |
@generate_indexed_names = true | |
@auto_index = retrieve_autoindex(Regexp.last_match.pre_match) | |
else | |
@generate_indexed_names = false | |
@auto_index = nil | |
end | |
end | |
# This is what child classes implement. | |
def render | |
raise NotImplementedError, "Subclasses must implement a render method" | |
end | |
private | |
def value | |
if @allow_method_names_outside_object | |
object.public_send @method_name if object && object.respond_to?(@method_name) | |
else | |
object.public_send @method_name if object | |
end | |
end | |
def value_before_type_cast | |
unless object.nil? | |
method_before_type_cast = @method_name + "_before_type_cast" | |
if value_came_from_user? && object.respond_to?(method_before_type_cast) | |
object.public_send(method_before_type_cast) | |
else | |
value | |
end | |
end | |
end | |
def value_came_from_user? | |
method_name = "#{@method_name}_came_from_user?" | |
!object.respond_to?(method_name) || object.public_send(method_name) | |
end | |
def retrieve_object(object) | |
if object | |
object | |
elsif @template_object.instance_variable_defined?("@#{@object_name}") | |
@template_object.instance_variable_get("@#{@object_name}") | |
end | |
rescue NameError | |
# As @object_name may contain the nested syntax (item[subobject]) we need to fallback to nil. | |
nil | |
end | |
def retrieve_autoindex(pre_match) | |
object = self.object || @template_object.instance_variable_get("@#{pre_match}") | |
if object && object.respond_to?(:to_param) | |
object.to_param | |
else | |
raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}" | |
end | |
end | |
def add_default_name_and_id_for_value(tag_value, options) | |
if tag_value.nil? | |
add_default_name_and_id(options) | |
else | |
specified_id = options["id"] | |
add_default_name_and_id(options) | |
if specified_id.blank? && options["id"].present? | |
options["id"] += "_#{sanitized_value(tag_value)}" | |
end | |
end | |
end | |
def add_default_name_and_id(options) | |
index = name_and_id_index(options) | |
options["name"] = options.fetch("name") { tag_name(options["multiple"], index) } | |
if generate_ids? | |
options["id"] = options.fetch("id") { tag_id(index, options.delete("namespace")) } | |
if namespace = options.delete("namespace") | |
options["id"] = options["id"] ? "#{namespace}_#{options['id']}" : namespace | |
end | |
end | |
end | |
def tag_name(multiple = false, index = nil) | |
@template_object.field_name(@object_name, sanitized_method_name, multiple: multiple, index: index) | |
end | |
def tag_id(index = nil, namespace = nil) | |
@template_object.field_id(@object_name, @method_name, index: index, namespace: namespace) | |
end | |
def sanitized_method_name | |
@sanitized_method_name ||= @method_name.delete_suffix("?") | |
end | |
def sanitized_value(value) | |
value.to_s.gsub(/[\s.]/, "_").gsub(/[^-[[:word:]]]/, "").downcase | |
end | |
def select_content_tag(option_tags, options, html_options) | |
html_options = html_options.stringify_keys | |
add_default_name_and_id(html_options) | |
if placeholder_required?(html_options) | |
raise ArgumentError, "include_blank cannot be false for a required field." if options[:include_blank] == false | |
options[:include_blank] ||= true unless options[:prompt] | |
end | |
value = options.fetch(:selected) { value() } | |
select = content_tag("select", add_options(option_tags, options, value), html_options) | |
if html_options["multiple"] && options.fetch(:include_hidden, true) | |
tag("input", disabled: html_options["disabled"], name: html_options["name"], type: "hidden", value: "", autocomplete: "off") + select | |
else | |
select | |
end | |
end | |
def placeholder_required?(html_options) | |
# See https://html.spec.whatwg.org/multipage/forms.html#attr-select-required | |
html_options["required"] && !html_options["multiple"] && html_options.fetch("size", 1).to_i == 1 | |
end | |
def add_options(option_tags, options, value = nil) | |
if options[:include_blank] | |
content = (options[:include_blank] if options[:include_blank].is_a?(String)) | |
label = (" " unless content) | |
option_tags = tag_builder.content_tag_string("option", content, value: "", label: label) + "\n" + option_tags | |
end | |
if value.blank? && options[:prompt] | |
tag_options = { value: "" }.tap do |prompt_opts| | |
prompt_opts[:disabled] = true if options[:disabled] == "" | |
prompt_opts[:selected] = true if options[:selected] == "" | |
end | |
option_tags = tag_builder.content_tag_string("option", prompt_text(options[:prompt]), tag_options) + "\n" + option_tags | |
end | |
option_tags | |
end | |
def name_and_id_index(options) | |
if options.key?("index") | |
options.delete("index") || "" | |
elsif @generate_indexed_names | |
@auto_index || "" | |
end | |
end | |
def generate_ids? | |
!@skip_default_ids | |
end | |
end | |
end | |
end | |
end |
# frozen_string_literal: true | |
require "active_support/core_ext/string/output_safety" | |
module ActionView | |
# Used as a buffer for views | |
# | |
# The main difference between this and ActiveSupport::SafeBuffer | |
# is for the methods `<<` and `safe_expr_append=` the inputs are | |
# checked for nil before they are assigned and `to_s` is called on | |
# the input. For example: | |
# | |
# obuf = ActionView::OutputBuffer.new "hello" | |
# obuf << 5 | |
# puts obuf # => "hello5" | |
# | |
# sbuf = ActiveSupport::SafeBuffer.new "hello" | |
# sbuf << 5 | |
# puts sbuf # => "hello\u0005" | |
# | |
class OutputBuffer < ActiveSupport::SafeBuffer # :nodoc: | |
def initialize(*) | |
super | |
encode! | |
end | |
def <<(value) | |
return self if value.nil? | |
super(value.to_s) | |
end | |
alias :append= :<< | |
def safe_expr_append=(val) | |
return self if val.nil? | |
safe_concat val.to_s | |
end | |
alias :safe_append= :safe_concat | |
end | |
class StreamingBuffer # :nodoc: | |
def initialize(block) | |
@block = block | |
end | |
def <<(value) | |
value = value.to_s | |
value = ERB::Util.h(value) unless value.html_safe? | |
@block.call(value) | |
end | |
alias :concat :<< | |
alias :append= :<< | |
def safe_concat(value) | |
@block.call(value.to_s) | |
end | |
alias :safe_append= :safe_concat | |
def html_safe? | |
true | |
end | |
def html_safe | |
self | |
end | |
end | |
end |
# frozen_string_literal: true | |
module ActionView | |
module Template::Handlers | |
class Builder | |
class_attribute :default_format, default: :xml | |
def call(template, source) | |
require_engine | |
"xml = ::Builder::XmlMarkup.new(:indent => 2);" \ | |
"self.output_buffer = xml.target!;" + | |
source + | |
";xml.target!;" | |
end | |
private | |
def require_engine # :doc: | |
@required ||= begin | |
require "builder" | |
true | |
end | |
end | |
end | |
end | |
end |
# frozen_string_literal: true | |
module ActionView | |
class CacheExpiry | |
class Executor | |
def initialize(watcher:) | |
@execution_lock = Concurrent::ReentrantReadWriteLock.new | |
@cache_expiry = ViewModificationWatcher.new(watcher: watcher) do | |
clear_cache | |
end | |
end | |
def run | |
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do | |
@cache_expiry.execute_if_updated | |
@execution_lock.acquire_read_lock | |
end | |
end | |
def complete(_) | |
@execution_lock.release_read_lock | |
end | |
private | |
def clear_cache | |
@execution_lock.with_write_lock do | |
ActionView::LookupContext::DetailsKey.clear | |
end | |
end | |
end | |
class ViewModificationWatcher | |
def initialize(watcher:, &block) | |
@watched_dirs = nil | |
@watcher_class = watcher | |
@watcher = nil | |
@mutex = Mutex.new | |
@block = block | |
end | |
def execute_if_updated | |
@mutex.synchronize do | |
watched_dirs = dirs_to_watch | |
return if watched_dirs.empty? | |
if watched_dirs != @watched_dirs | |
@watched_dirs = watched_dirs | |
@watcher = @watcher_class.new([], watched_dirs, &@block) | |
@watcher.execute | |
else | |
@watcher.execute_if_updated | |
end | |
end | |
end | |
private | |
def dirs_to_watch | |
all_view_paths.grep(FileSystemResolver).map!(&:path).tap(&:uniq!).sort! | |
end | |
def all_view_paths | |
ActionView::ViewPaths.all_view_paths.flat_map(&:paths) | |
end | |
end | |
end | |
end |
# frozen_string_literal: true | |
module ActionView | |
# = Action View Cache Helper | |
module Helpers # :nodoc: | |
module CacheHelper | |
class UncacheableFragmentError < StandardError; end | |
# This helper exposes a method for caching fragments of a view | |
# rather than an entire action or page. This technique is useful | |
# caching pieces like menus, lists of new topics, static HTML | |
# fragments, and so on. This method takes a block that contains | |
# the content you wish to cache. | |
# | |
# The best way to use this is by doing recyclable key-based cache expiration | |
# on top of a cache store like Memcached or Redis that'll automatically | |
# kick out old entries. | |
# | |
# When using this method, you list the cache dependency as the name of the cache, like so: | |
# | |
# <% cache project do %> | |
# <b>All the topics on this project</b> | |
# <%= render project.topics %> | |
# <% end %> | |
# | |
# This approach will assume that when a new topic is added, you'll touch | |
# the project. The cache key generated from this call will be something like: | |
# | |
# views/template/action:7a1156131a6928cb0026877f8b749ac9/projects/123 | |
# ^template path ^template tree digest ^class ^id | |
# | |
# This cache key is stable, but it's combined with a cache version derived from the project | |
# record. When the project updated_at is touched, the #cache_version changes, even | |
# if the key stays stable. This means that unlike a traditional key-based cache expiration | |
# approach, you won't be generating cache trash, unused keys, simply because the dependent | |
# record is updated. | |
# | |
# If your template cache depends on multiple sources (try to avoid this to keep things simple), | |
# you can name all these dependencies as part of an array: | |
# | |
# <% cache [ project, current_user ] do %> | |
# <b>All the topics on this project</b> | |
# <%= render project.topics %> | |
# <% end %> | |
# | |
# This will include both records as part of the cache key and updating either of them will | |
# expire the cache. | |
# | |
# ==== \Template digest | |
# | |
# The template digest that's added to the cache key is computed by taking an MD5 of the | |
# contents of the entire template file. This ensures that your caches will automatically | |
# expire when you change the template file. | |
# | |
# Note that the MD5 is taken of the entire template file, not just what's within the | |
# cache do/end call. So it's possible that changing something outside of that call will | |
# still expire the cache. | |
# | |
# Additionally, the digestor will automatically look through your template file for | |
# explicit and implicit dependencies, and include those as part of the digest. | |
# | |
# The digestor can be bypassed by passing skip_digest: true as an option to the cache call: | |
# | |
# <% cache project, skip_digest: true do %> | |
# <b>All the topics on this project</b> | |
# <%= render project.topics %> | |
# <% end %> | |
# | |
# ==== Implicit dependencies | |
# | |
# Most template dependencies can be derived from calls to render in the template itself. | |
# Here are some examples of render calls that Cache Digests knows how to decode: | |
# | |
# render partial: "comments/comment", collection: commentable.comments | |
# render "comments/comments" | |
# render 'comments/comments' | |
# render('comments/comments') | |
# | |
# render "header" translates to render("comments/header") | |
# | |
# render(@topic) translates to render("topics/topic") | |
# render(topics) translates to render("topics/topic") | |
# render(message.topics) translates to render("topics/topic") | |
# | |
# It's not possible to derive all render calls like that, though. | |
# Here are a few examples of things that can't be derived: | |
# | |
# render group_of_attachments | |
# render @project.documents.where(published: true).order('created_at') | |
# | |
# You will have to rewrite those to the explicit form: | |
# | |
# render partial: 'attachments/attachment', collection: group_of_attachments | |
# render partial: 'documents/document', collection: @project.documents.where(published: true).order('created_at') | |
# | |
# === Explicit dependencies | |
# | |
# Sometimes you'll have template dependencies that can't be derived at all. This is typically | |
# the case when you have template rendering that happens in helpers. Here's an example: | |
# | |
# <%= render_sortable_todolists @project.todolists %> | |
# | |
# You'll need to use a special comment format to call those out: | |
# | |
# <%# Template Dependency: todolists/todolist %> | |
# <%= render_sortable_todolists @project.todolists %> | |
# | |
# In some cases, like a single table inheritance setup, you might have | |
# a bunch of explicit dependencies. Instead of writing every template out, | |
# you can use a wildcard to match any template in a directory: | |
# | |
# <%# Template Dependency: events/* %> | |
# <%= render_categorizable_events @person.events %> | |
# | |
# This marks every template in the directory as a dependency. To find those | |
# templates, the wildcard path must be absolutely defined from <tt>app/views</tt> or paths | |
# otherwise added with +prepend_view_path+ or +append_view_path+. | |
# This way the wildcard for <tt>app/views/recordings/events</tt> would be <tt>recordings/events/*</tt> etc. | |
# | |
# The pattern used to match explicit dependencies is <tt>/# Template Dependency: (\S+)/</tt>, | |
# so it's important that you type it out just so. | |
# You can only declare one template dependency per line. | |
# | |
# === External dependencies | |
# | |
# If you use a helper method, for example, inside a cached block and | |
# you then update that helper, you'll have to bump the cache as well. | |
# It doesn't really matter how you do it, but the MD5 of the template file | |
# must change. One recommendation is to simply be explicit in a comment, like: | |
# | |
# <%# Helper Dependency Updated: May 6, 2012 at 6pm %> | |
# <%= some_helper_method(person) %> | |
# | |
# Now all you have to do is change that timestamp when the helper method changes. | |
# | |
# === Collection Caching | |
# | |
# When rendering a collection of objects that each use the same partial, a <tt>:cached</tt> | |
# option can be passed. | |
# | |
# For collections rendered such: | |
# | |
# <%= render partial: 'projects/project', collection: @projects, cached: true %> | |
# | |
# The <tt>cached: true</tt> will make Action View's rendering read several templates | |
# from cache at once instead of one call per template. | |
# | |
# Templates in the collection not already cached are written to cache. | |
# | |
# Works great alongside individual template fragment caching. | |
# For instance if the template the collection renders is cached like: | |
# | |
# # projects/_project.html.erb | |
# <% cache project do %> | |
# <%# ... %> | |
# <% end %> | |
# | |
# Any collection renders will find those cached templates when attempting | |
# to read multiple templates at once. | |
# | |
# If your collection cache depends on multiple sources (try to avoid this to keep things simple), | |
# you can name all these dependencies as part of a block that returns an array: | |
# | |
# <%= render partial: 'projects/project', collection: @projects, cached: -> project { [ project, current_user ] } %> | |
# | |
# This will include both records as part of the cache key and updating either of them will | |
# expire the cache. | |
def cache(name = {}, options = {}, &block) | |
if controller.respond_to?(:perform_caching) && controller.perform_caching | |
CachingRegistry.track_caching do | |
name_options = options.slice(:skip_digest) | |
safe_concat(fragment_for(cache_fragment_name(name, **name_options), options, &block)) | |
end | |
else | |
yield | |
end | |
nil | |
end | |
# Returns whether the current view fragment is within a +cache+ block. | |
# | |
# Useful when certain fragments aren't cacheable: | |
# | |
# <% cache project do %> | |
# <% raise StandardError, "Caching private data!" if caching? %> | |
# <% end %> | |
def caching? | |
CachingRegistry.caching? | |
end | |
# Raises +UncacheableFragmentError+ when called from within a +cache+ block. | |
# | |
# Useful to denote helper methods that can't participate in fragment caching: | |
# | |
# def project_name_with_time(project) | |
# uncacheable! | |
# "#{project.name} - #{Time.now}" | |
# end | |
# | |
# # Which will then raise if used within a +cache+ block: | |
# <% cache project do %> | |
# <%= project_name_with_time(project) %> | |
# <% end %> | |
def uncacheable! | |
raise UncacheableFragmentError, "can't be fragment cached" if caching? | |
end | |
# Cache fragments of a view if +condition+ is true | |
# | |
# <% cache_if admin?, project do %> | |
# <b>All the topics on this project</b> | |
# <%= render project.topics %> | |
# <% end %> | |
def cache_if(condition, name = {}, options = {}, &block) | |
if condition | |
cache(name, options, &block) | |
else | |
yield | |
end | |
nil | |
end | |
# Cache fragments of a view unless +condition+ is true | |
# | |
# <% cache_unless admin?, project do %> | |
# <b>All the topics on this project</b> | |
# <%= render project.topics %> | |
# <% end %> | |
def cache_unless(condition, name = {}, options = {}, &block) | |
cache_if !condition, name, options, &block | |
end | |
# This helper returns the name of a cache key for a given fragment cache | |
# call. By supplying <tt>skip_digest: true</tt> to cache, the digestion of cache | |
# fragments can be manually bypassed. This is useful when cache fragments | |
# cannot be manually expired unless you know the exact key which is the | |
# case when using memcached. | |
def cache_fragment_name(name = {}, skip_digest: nil, digest_path: nil) | |
if skip_digest | |
name | |
else | |
fragment_name_with_digest(name, digest_path) | |
end | |
end | |
def digest_path_from_template(template) # :nodoc: | |
digest = Digestor.digest(name: template.virtual_path, format: template.format, finder: lookup_context, dependencies: view_cache_dependencies) | |
if digest.present? | |
"#{template.virtual_path}:#{digest}" | |
else | |
template.virtual_path | |
end | |
end | |
private | |
def fragment_name_with_digest(name, digest_path) | |
name = controller.url_for(name).split("://").last if name.is_a?(Hash) | |
if @current_template&.virtual_path || digest_path | |
digest_path ||= digest_path_from_template(@current_template) | |
[ digest_path, name ] | |
else | |
name | |
end | |
end | |
def fragment_for(name = {}, options = nil, &block) | |
if content = read_fragment_for(name, options) | |
@view_renderer.cache_hits[@current_template&.virtual_path] = :hit if defined?(@view_renderer) | |
content | |
else | |
@view_renderer.cache_hits[@current_template&.virtual_path] = :miss if defined?(@view_renderer) | |
write_fragment_for(name, options, &block) | |
end | |
end | |
def read_fragment_for(name, options) | |
controller.read_fragment(name, options) | |
end | |
def write_fragment_for(name, options) | |
pos = output_buffer.length | |
yield | |
output_safe = output_buffer.html_safe? | |
fragment = output_buffer.slice!(pos..-1) | |
if output_safe | |
self.output_buffer = output_buffer.class.new(output_buffer) | |
end | |
controller.write_fragment(name, fragment, options) | |
end | |
module CachingRegistry # :nodoc: | |
extend self | |
def caching? | |
ActiveSupport::IsolatedExecutionState[:action_view_caching] ||= false | |
end | |
def track_caching | |
caching_was = ActiveSupport::IsolatedExecutionState[:action_view_caching] | |
ActiveSupport::IsolatedExecutionState[:action_view_caching] = true | |
yield | |
ensure | |
ActiveSupport::IsolatedExecutionState[:action_view_caching] = caching_was | |
end | |
end | |
end | |
end | |
end |
# frozen_string_literal: true | |
require "active_support/core_ext/string/output_safety" | |
module ActionView | |
# = Action View Capture Helper | |
module Helpers # :nodoc: | |
# CaptureHelper exposes methods to let you extract generated markup which | |
# can be used in other parts of a template or layout file. | |
# | |
# It provides a method to capture blocks into variables through capture and | |
# a way to capture a block of markup for use in a layout through {content_for}[rdoc-ref:ActionView::Helpers::CaptureHelper#content_for]. | |
module CaptureHelper | |
# The capture method extracts part of a template as a String object. | |
# You can then use this object anywhere in your templates, layout, or helpers. | |
# | |
# The capture method can be used in ERB templates... | |
# | |
# <% @greeting = capture do %> | |
# Welcome to my shiny new web page! The date and time is | |
# <%= Time.now %> | |
# <% end %> | |
# | |
# ...and Builder (RXML) templates. | |
# | |
# @timestamp = capture do | |
# "The current timestamp is #{Time.now}." | |
# end | |
# | |
# You can then use that variable anywhere else. For example: | |
# | |
# <html> | |
# <head><title><%= @greeting %></title></head> | |
# <body> | |
# <b><%= @greeting %></b> | |
# </body> | |
# </html> | |
# | |
# The return of capture is the string generated by the block. For Example: | |
# | |
# @greeting # => "Welcome to my shiny new web page! The date and time is 2018-09-06 11:09:16 -0500" | |
# | |
def capture(*args) | |
value = nil | |
buffer = with_output_buffer { value = yield(*args) } | |
if (string = buffer.presence || value) && string.is_a?(String) | |
ERB::Util.html_escape string | |
end | |
end | |
# Calling <tt>content_for</tt> stores a block of markup in an identifier for later use. | |
# In order to access this stored content in other templates, helper modules | |
# or the layout, you would pass the identifier as an argument to <tt>content_for</tt>. | |
# | |
# Note: <tt>yield</tt> can still be used to retrieve the stored content, but calling | |
# <tt>yield</tt> doesn't work in helper modules, while <tt>content_for</tt> does. | |
# | |
# <% content_for :not_authorized do %> | |
# alert('You are not authorized to do that!') | |
# <% end %> | |
# | |
# You can then use <tt>content_for :not_authorized</tt> anywhere in your templates. | |
# | |
# <%= content_for :not_authorized if current_user.nil? %> | |
# | |
# This is equivalent to: | |
# | |
# <%= yield :not_authorized if current_user.nil? %> | |
# | |
# <tt>content_for</tt>, however, can also be used in helper modules. | |
# | |
# module StorageHelper | |
# def stored_content | |
# content_for(:storage) || "Your storage is empty" | |
# end | |
# end | |
# | |
# This helper works just like normal helpers. | |
# | |
# <%= stored_content %> | |
# | |
# You can also use the <tt>yield</tt> syntax alongside an existing call to | |
# <tt>yield</tt> in a layout. For example: | |
# | |
# <%# This is the layout %> | |
# <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> | |
# <head> | |
# <title>My Website</title> | |
# <%= yield :script %> | |
# </head> | |
# <body> | |
# <%= yield %> | |
# </body> | |
# </html> | |
# | |
# And now, we'll create a view that has a <tt>content_for</tt> call that | |
# creates the <tt>script</tt> identifier. | |
# | |
# <%# This is our view %> | |
# Please login! | |
# | |
# <% content_for :script do %> | |
# <script>alert('You are not authorized to view this page!')</script> | |
# <% end %> | |
# | |
# Then, in another view, you could to do something like this: | |
# | |
# <%= link_to 'Logout', action: 'logout', remote: true %> | |
# | |
# <% content_for :script do %> | |
# <%= javascript_include_tag :defaults %> | |
# <% end %> | |
# | |
# That will place +script+ tags for your default set of JavaScript files on the page; | |
# this technique is useful if you'll only be using these scripts in a few views. | |
# | |
# Note that <tt>content_for</tt> concatenates (default) the blocks it is given for a particular | |
# identifier in order. For example: | |
# | |
# <% content_for :navigation do %> | |
# <li><%= link_to 'Home', action: 'index' %></li> | |
# <% end %> | |
# | |
# And in another place: | |
# | |
# <% content_for :navigation do %> | |
# <li><%= link_to 'Login', action: 'login' %></li> | |
# <% end %> | |
# | |
# Then, in another template or layout, this code would render both links in order: | |
# | |
# <ul><%= content_for :navigation %></ul> | |
# | |
# If the flush parameter is +true+ <tt>content_for</tt> replaces the blocks it is given. For example: | |
# | |
# <% content_for :navigation do %> | |
# <li><%= link_to 'Home', action: 'index' %></li> | |
# <% end %> | |
# | |
# <%# Add some other content, or use a different template: %> | |
# | |
# <% content_for :navigation, flush: true do %> | |
# <li><%= link_to 'Login', action: 'login' %></li> | |
# <% end %> | |
# | |
# Then, in another template or layout, this code would render only the last link: | |
# | |
# <ul><%= content_for :navigation %></ul> | |
# | |
# Lastly, simple content can be passed as a parameter: | |
# | |
# <% content_for :script, javascript_include_tag(:defaults) %> | |
# | |
# WARNING: <tt>content_for</tt> is ignored in caches. So you shouldn't use it for elements that will be fragment cached. | |
def content_for(name, content = nil, options = {}, &block) | |
if content || block_given? | |
if block_given? | |
options = content if content | |
content = capture(&block) | |
end | |
if content | |
options[:flush] ? @view_flow.set(name, content) : @view_flow.append(name, content) | |
end | |
nil | |
else | |
@view_flow.get(name).presence | |
end | |
end | |
# The same as +content_for+ but when used with streaming flushes | |
# straight back to the layout. In other words, if you want to | |
# concatenate several times to the same buffer when rendering a given | |
# template, you should use +content_for+, if not, use +provide+ to tell | |
# the layout to stop looking for more contents. | |
def provide(name, content = nil, &block) | |
content = capture(&block) if block_given? | |
result = @view_flow.append!(name, content) if content | |
result unless content | |
end | |
# <tt>content_for?</tt> checks whether any content has been captured yet using <tt>content_for</tt>. | |
# Useful to render parts of your layout differently based on what is in your views. | |
# | |
# <%# This is the layout %> | |
# <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> | |
# <head> | |
# <title>My Website</title> | |
# <%= yield :script %> | |
# </head> | |
# <body class="<%= content_for?(:right_col) ? 'two-column' : 'one-column' %>"> | |
# <%= yield %> | |
# <%= yield :right_col %> | |
# </body> | |
# </html> | |
def content_for?(name) | |
@view_flow.get(name).present? | |
end | |
# Use an alternate output buffer for the duration of the block. | |
# Defaults to a new empty string. | |
def with_output_buffer(buf = nil) # :nodoc: | |
unless buf | |
buf = ActionView::OutputBuffer.new | |
if output_buffer && output_buffer.respond_to?(:encoding) | |
buf.force_encoding(output_buffer.encoding) | |
end | |
end | |
self.output_buffer, old_buffer = buf, output_buffer | |
yield | |
output_buffer | |
ensure | |
self.output_buffer = old_buffer | |
end | |
end | |
end | |
end |
# frozen_string_literal: true | |
require "abstract_unit" | |
class CaptureHelperTest < ActionView::TestCase | |
def setup | |
super | |
@av = ActionView::Base.empty | |
@view_flow = ActionView::OutputFlow.new | |
end | |
def test_capture_captures_the_temporary_output_buffer_in_its_block | |
assert_nil @av.output_buffer | |
string = @av.capture do | |
@av.output_buffer << "foo" | |
@av.output_buffer << "bar" | |
end | |
assert_nil @av.output_buffer | |
assert_equal "foobar", string | |
end | |
def test_capture_captures_the_value_returned_by_the_block_if_the_temporary_buffer_is_blank | |
string = @av.capture("foo", "bar") do |a, b| | |
a + b | |
end | |
assert_equal "foobar", string | |
end | |
def test_capture_returns_nil_if_the_returned_value_is_not_a_string | |
assert_nil @av.capture { 1 } | |
end | |
def test_capture_escapes_html | |
string = @av.capture { "<em>bar</em>" } | |
assert_equal "<em>bar</em>", string | |
end | |
def test_capture_doesnt_escape_twice | |
string = @av.capture { raw("<em>bar</em>") } | |
assert_equal "<em>bar</em>", string | |
end | |
def test_content_for_used_for_read | |
content_for :foo, "foo" | |
assert_equal "foo", content_for(:foo) | |
content_for(:bar) { "bar" } | |
assert_equal "bar", content_for(:bar) | |
end | |
def test_content_for_with_multiple_calls | |
assert_not content_for?(:title) | |
content_for :title, "foo" | |
content_for :title, :bar | |
assert_equal "foobar", content_for(:title) | |
end | |
def test_content_for_with_multiple_calls_and_flush | |
assert_not content_for?(:title) | |
content_for :title, "foo" | |
content_for :title, :bar, flush: true | |
assert_equal "bar", content_for(:title) | |
end | |
def test_content_for_with_block | |
assert_not content_for?(:title) | |
content_for :title do | |
output_buffer << "foo" | |
output_buffer << "bar" | |
nil | |
end | |
assert_equal "foobar", content_for(:title) | |
end | |
def test_content_for_with_block_and_multiple_calls_with_flush | |
assert_not content_for?(:title) | |
content_for :title do | |
"foo" | |
end | |
content_for :title, flush: true do | |
"bar" | |
end | |
assert_equal "bar", content_for(:title) | |
end | |
def test_content_for_with_block_and_multiple_calls_with_flush_nil_content | |
assert_not content_for?(:title) | |
content_for :title do | |
"foo" | |
end | |
content_for :title, nil, flush: true do | |
"bar" | |
end | |
assert_equal "bar", content_for(:title) | |
end | |
def test_content_for_with_block_and_multiple_calls_without_flush | |
assert_not content_for?(:title) | |
content_for :title do | |
"foo" | |
end | |
content_for :title, flush: false do | |
"bar" | |
end | |
assert_equal "foobar", content_for(:title) | |
end | |
def test_content_for_with_whitespace_block | |
assert_not content_for?(:title) | |
content_for :title, "foo" | |
content_for :title do | |
output_buffer << " \n " | |
nil | |
end | |
content_for :title, "bar" | |
assert_equal "foobar", content_for(:title) | |
end | |
def test_content_for_with_whitespace_block_and_flush | |
assert_not content_for?(:title) | |
content_for :title, "foo" | |
content_for :title, flush: true do | |
output_buffer << " \n " | |
nil | |
end | |
content_for :title, "bar", flush: true | |
assert_equal "bar", content_for(:title) | |
end | |
def test_content_for_returns_nil_when_writing | |
assert_not content_for?(:title) | |
assert_nil content_for(:title, "foo") | |
assert_nil content_for(:title) { output_buffer << "bar"; nil } | |
assert_nil content_for(:title) { output_buffer << " \n "; nil } | |
assert_equal "foobar", content_for(:title) | |
assert_nil content_for(:title, "foo", flush: true) | |
assert_nil content_for(:title, flush: true) { output_buffer << "bar"; nil } | |
assert_nil content_for(:title, flush: true) { output_buffer << " \n "; nil } | |
assert_equal "bar", content_for(:title) | |
end | |
def test_content_for_returns_nil_when_content_missing | |
assert_nil content_for(:some_missing_key) | |
end | |
def test_content_for_question_mark | |
assert_not content_for?(:title) | |
content_for :title, "title" | |
assert content_for?(:title) | |
assert_not content_for?(:something_else) | |
end | |
def test_content_for_should_be_html_safe_after_flush_empty | |
assert_not content_for?(:title) | |
content_for :title do | |
content_tag(:p, "title") | |
end | |
assert_predicate content_for(:title), :html_safe? | |
content_for :title, "", flush: true | |
content_for(:title) do | |
content_tag(:p, "title") | |
end | |
assert_predicate content_for(:title), :html_safe? | |
end | |
def test_provide | |
assert_not content_for?(:title) | |
provide :title, "hi" | |
assert content_for?(:title) | |
assert_equal "hi", content_for(:title) | |
provide :title, "<p>title</p>" | |
assert_equal "hi<p>title</p>", content_for(:title) | |
@view_flow = ActionView::OutputFlow.new | |
provide :title, :hi | |
provide :title, raw("<p>title</p>") | |
assert_equal "hi<p>title</p>", content_for(:title) | |
end | |
def test_with_output_buffer_swaps_the_output_buffer_given_no_argument | |
assert_nil @av.output_buffer | |
buffer = @av.with_output_buffer do | |
@av.output_buffer << "." | |
end | |
assert_equal ".", buffer | |
assert_nil @av.output_buffer | |
end | |
def test_with_output_buffer_swaps_the_output_buffer_with_an_argument | |
assert_nil @av.output_buffer | |
buffer = ActionView::OutputBuffer.new(".") | |
@av.with_output_buffer(buffer) do | |
@av.output_buffer << "." | |
end | |
assert_equal "..", buffer | |
assert_nil @av.output_buffer | |
end | |
def test_with_output_buffer_restores_the_output_buffer | |
buffer = ActionView::OutputBuffer.new | |
@av.output_buffer = buffer | |
@av.with_output_buffer do | |
@av.output_buffer << "." | |
end | |
assert buffer.equal?(@av.output_buffer) | |
end | |
def test_with_output_buffer_sets_proper_encoding | |
@av.output_buffer = ActionView::OutputBuffer.new | |
# Ensure we set the output buffer to an encoding different than the default one. | |
alt_encoding = alt_encoding(@av.output_buffer) | |
@av.output_buffer.force_encoding(alt_encoding) | |
@av.with_output_buffer do | |
assert_equal alt_encoding, @av.output_buffer.encoding | |
end | |
end | |
def test_with_output_buffer_does_not_assume_there_is_an_output_buffer | |
assert_nil @av.output_buffer | |
assert_equal "", @av.with_output_buffer { } | |
end | |
def alt_encoding(output_buffer) | |
output_buffer.encoding == Encoding::US_ASCII ? Encoding::UTF_8 : Encoding::US_ASCII | |
end | |
end |
# frozen_string_literal: true | |
require "abstract_unit" | |
require "active_support/logger" | |
class CaptureController < ActionController::Base | |
self.view_paths = [ File.expand_path("../../fixtures/actionpack", __dir__) ] | |
def self.controller_path; "test"; end | |
def content_for | |
@title = nil | |
render layout: "talk_from_action" | |
end | |
def content_for_with_parameter | |
@title = nil | |
render layout: "talk_from_action" | |
end | |
def content_for_concatenated | |
@title = nil | |
render layout: "talk_from_action" | |
end | |
def non_erb_block_content_for | |
@title = nil | |
render layout: "talk_from_action" | |
end | |
def proper_block_detection | |
@todo = "some todo" | |
end | |
end | |
class CaptureTest < ActionController::TestCase | |
tests CaptureController | |
with_routes do | |
get :content_for, to: "test#content_for" | |
get :capturing, to: "test#capturing" | |
get :proper_block_detection, to: "test#proper_block_detection" | |
get :non_erb_block_content_for, to: "test#non_erb_block_content_for" | |
get :content_for_concatenated, to: "test#content_for_concatenated" | |
get :content_for_with_parameter, to: "test#content_for_with_parameter" | |
end | |
def setup | |
super | |
# enable a logger so that (e.g.) the benchmarking stuff runs, so we can get | |
# a more accurate simulation of what happens in "real life". | |
@controller.logger = ActiveSupport::Logger.new(nil) | |
@request.host = "www.nextangle.com" | |
end | |
def test_simple_capture | |
get :capturing | |
assert_equal "Dreamy days", @response.body.strip | |
end | |
def test_content_for | |
get :content_for | |
assert_equal expected_content_for_output, @response.body | |
end | |
def test_should_concatenate_content_for | |
get :content_for_concatenated | |
assert_equal expected_content_for_output, @response.body | |
end | |
def test_should_set_content_for_with_parameter | |
get :content_for_with_parameter | |
assert_equal expected_content_for_output, @response.body | |
end | |
def test_non_erb_block_content_for | |
get :non_erb_block_content_for | |
assert_equal expected_content_for_output, @response.body | |
end | |
def test_proper_block_detection | |
get :proper_block_detection | |
assert_equal "some todo", @response.body | |
end | |
private | |
def expected_content_for_output | |
"<title>Putting stuff in the title!</title>\nGreat stuff!" | |
end | |
end |
# frozen_string_literal: true | |
require "action_view/helpers/tags/checkable" | |
module ActionView | |
module Helpers | |
module Tags # :nodoc: | |
class CheckBox < Base # :nodoc: | |
include Checkable | |
def initialize(object_name, method_name, template_object, checked_value, unchecked_value, options) | |
@checked_value = checked_value | |
@unchecked_value = unchecked_value | |
super(object_name, method_name, template_object, options) | |
end | |
def render | |
options = @options.stringify_keys | |
options["type"] = "checkbox" | |
options["value"] = @checked_value | |
options["checked"] = "checked" if input_checked?(options) | |
if options["multiple"] | |
add_default_name_and_id_for_value(@checked_value, options) | |
options.delete("multiple") | |
else | |
add_default_name_and_id(options) | |
end | |
include_hidden = options.delete("include_hidden") { true } | |
checkbox = tag("input", options) | |
if include_hidden | |
hidden = hidden_field_for_checkbox(options) | |
hidden + checkbox | |
else | |
checkbox | |
end | |
end | |
private | |
def checked?(value) | |
case value | |
when TrueClass, FalseClass | |
value == !!@checked_value | |
when NilClass | |
false | |
when String | |
value == @checked_value | |
else | |
if value.respond_to?(:include?) | |
value.include?(@checked_value) | |
else | |
value.to_i == @checked_value.to_i | |
end | |
end | |
end | |
def hidden_field_for_checkbox(options) | |
@unchecked_value ? tag("input", options.slice("name", "disabled", "form").merge!("type" => "hidden", "value" => @unchecked_value, "autocomplete" => "off")) : "".html_safe | |
end | |
end | |
end | |
end | |
end |
# frozen_string_literal: true | |
module ActionView | |
module Helpers | |
module Tags # :nodoc: | |
module Checkable # :nodoc: | |
def input_checked?(options) | |
if options.has_key?("checked") | |
checked = options.delete "checked" | |
checked == true || checked == "checked" | |
else | |
checked?(value) | |
end | |
end | |
end | |
end | |
end | |
end |
# frozen_string_literal: true | |
require "active_support/core_ext/enumerable" | |
module ActionView | |
module CollectionCaching # :nodoc: | |
extend ActiveSupport::Concern | |
included do | |
# Fallback cache store if Action View is used without Rails. | |
# Otherwise overridden in Railtie to use Rails.cache. | |
mattr_accessor :collection_cache, default: ActiveSupport::Cache::MemoryStore.new | |
end | |
private | |
def will_cache?(options, view) | |
options[:cached] && view.controller.respond_to?(:perform_caching) && view.controller.perform_caching | |
end | |
def cache_collection_render(instrumentation_payload, view, template, collection) | |
return yield(collection) unless will_cache?(@options, view) | |
collection_iterator = collection | |
# Result is a hash with the key represents the | |
# key used for cache lookup and the value is the item | |
# on which the partial is being rendered | |
keyed_collection, ordered_keys = collection_by_cache_keys(view, template, collection) | |
# Pull all partials from cache | |
# Result is a hash, key matches the entry in | |
# `keyed_collection` where the cache was retrieved and the | |
# value is the value that was present in the cache | |
cached_partials = collection_cache.read_multi(*keyed_collection.keys) | |
instrumentation_payload[:cache_hits] = cached_partials.size | |
# Extract the items for the keys that are not found | |
collection = keyed_collection.reject { |key, _| cached_partials.key?(key) }.values | |
rendered_partials = collection.empty? ? [] : yield(collection_iterator.from_collection(collection)) | |
index = 0 | |
keyed_partials = fetch_or_cache_partial(cached_partials, template, order_by: keyed_collection.each_key) do | |
# This block is called once | |
# for every cache miss while preserving order. | |
rendered_partials[index].tap { index += 1 } | |
end | |
ordered_keys.map do |key| | |
keyed_partials[key] | |
end | |
end | |
def callable_cache_key? | |
@options[:cached].respond_to?(:call) | |
end | |
def collection_by_cache_keys(view, template, collection) | |
seed = callable_cache_key? ? @options[:cached] : ->(i) { i } | |
digest_path = view.digest_path_from_template(template) | |
collection.each_with_object([{}, []]) do |item, (hash, ordered_keys)| | |
key = expanded_cache_key(seed.call(item), view, template, digest_path) | |
ordered_keys << key | |
hash[key] = item | |
end | |
end | |
def expanded_cache_key(key, view, template, digest_path) | |
key = view.combined_fragment_cache_key(view.cache_fragment_name(key, digest_path: digest_path)) | |
key.frozen? ? key.dup : key # #read_multi & #write may require mutability, Dalli 2.6.0. | |
end | |
# `order_by` is an enumerable object containing keys of the cache, | |
# all keys are passed in whether found already or not. | |
# | |
# `cached_partials` is a hash. If the value exists | |
# it represents the rendered partial from the cache | |
# otherwise `Hash#fetch` will take the value of its block. | |
# | |
# This method expects a block that will return the rendered | |
# partial. An example is to render all results | |
# for each element that was not found in the cache and store it as an array. | |
# Order it so that the first empty cache element in `cached_partials` | |
# corresponds to the first element in `rendered_partials`. | |
# | |
# If the partial is not already cached it will also be | |
# written back to the underlying cache store. | |
def fetch_or_cache_partial(cached_partials, template, order_by:) | |
order_by.index_with do |cache_key| | |
if content = cached_partials[cache_key] | |
build_rendered_template(content, template) | |
else | |
yield.tap do |rendered_partial| | |
collection_cache.write(cache_key, rendered_partial.body) | |
end | |
end | |
end | |
end | |
end | |
end |
# frozen_string_literal: true | |
require "action_view/helpers/tags/collection_helpers" | |
module ActionView | |
module Helpers | |
module Tags # :nodoc: | |
class CollectionCheckBoxes < Base # :nodoc: | |
include CollectionHelpers | |
class CheckBoxBuilder < Builder # :nodoc: | |
def check_box(extra_html_options = {}) | |
html_options = extra_html_options.merge(@input_html_options) | |
html_options[:multiple] = true | |
html_options[:skip_default_ids] = false | |
@template_object.check_box(@object_name, @method_name, html_options, @value, nil) | |
end | |
end | |
def render(&block) | |
render_collection_for(CheckBoxBuilder, &block) | |
end | |
private | |
def render_component(builder) | |
builder.check_box + builder.label | |
end | |
def hidden_field_name | |
"#{super}[]" | |
end | |
end | |
end | |
end | |
end |
# frozen_string_literal: true | |
module ActionView | |
module Helpers | |
module Tags # :nodoc: | |
module CollectionHelpers # :nodoc: | |
class Builder # :nodoc: | |
attr_reader :object, :text, :value | |
def initialize(template_object, object_name, method_name, object, | |
sanitized_attribute_name, text, value, input_html_options) | |
@template_object = template_object | |
@object_name = object_name | |
@method_name = method_name | |
@object = object | |
@sanitized_attribute_name = sanitized_attribute_name | |
@text = text | |
@value = value | |
@input_html_options = input_html_options | |
end | |
def label(label_html_options = {}, &block) | |
html_options = @input_html_options.slice(:index, :namespace).merge(label_html_options) | |
html_options[:for] ||= @input_html_options[:id] if @input_html_options[:id] | |
@template_object.label(@object_name, @sanitized_attribute_name, @text, html_options, &block) | |
end | |
end | |
def initialize(object_name, method_name, template_object, collection, value_method, text_method, options, html_options) | |
@collection = collection | |
@value_method = value_method | |
@text_method = text_method | |
@html_options = html_options | |
super(object_name, method_name, template_object, options) | |
end | |
private | |
def instantiate_builder(builder_class, item, value, text, html_options) | |
builder_class.new(@template_object, @object_name, @method_name, item, | |
sanitize_attribute_name(value), text, value, html_options) | |
end | |
# Generate default options for collection helpers, such as :checked and | |
# :disabled. | |
def default_html_options_for_collection(item, value) | |
html_options = @html_options.dup | |
[:checked, :selected, :disabled, :readonly].each do |option| | |
current_value = @options[option] | |
next if current_value.nil? | |
accept = if current_value.respond_to?(:call) | |
current_value.call(item) | |
else | |
Array(current_value).map(&:to_s).include?(value.to_s) | |
end | |
if accept | |
html_options[option] = true | |
elsif option == :checked | |
html_options[option] = false | |
end | |
end | |
html_options[:object] = @object | |
html_options | |
end | |
def sanitize_attribute_name(value) | |
"#{sanitized_method_name}_#{sanitized_value(value)}" | |
end | |
def render_collection | |
@collection.map do |item| | |
value = value_for_collection(item, @value_method) | |
text = value_for_collection(item, @text_method) | |
default_html_options = default_html_options_for_collection(item, value) | |
additional_html_options = option_html_attributes(item) | |
yield item, value, text, default_html_options.merge(additional_html_options) | |
end.join.html_safe | |
end | |
def render_collection_for(builder_class, &block) | |
options = @options.stringify_keys | |
rendered_collection = render_collection do |item, value, text, default_html_options| | |
builder = instantiate_builder(builder_class, item, value, text, default_html_options) | |
if block_given? | |
@template_object.capture(builder, &block) | |
else | |
render_component(builder) | |
end | |
end | |
# Prepend a hidden field to make sure something will be sent back to the | |
# server if all radio buttons are unchecked. | |
if options.fetch("include_hidden", true) | |
hidden_field + rendered_collection | |
else | |
rendered_collection | |
end | |
end | |
def hidden_field | |
hidden_name = @html_options[:name] || hidden_field_name | |
@template_object.hidden_field_tag(hidden_name, "", id: nil) | |
end | |
def hidden_field_name | |
"#{tag_name(false, @options[:index])}" | |
end | |
end | |
end | |
end | |
end |
# frozen_string_literal: true | |
require "action_view/renderer/partial_renderer" | |
module ActionView | |
class PartialIteration | |
# The number of iterations that will be done by the partial. | |
attr_reader :size | |
# The current iteration of the partial. | |
attr_reader :index | |
def initialize(size) | |
@size = size | |
@index = 0 | |
end | |
# Check if this is the first iteration of the partial. | |
def first? | |
index == 0 | |
end | |
# Check if this is the last iteration of the partial. | |
def last? | |
index == size - 1 | |
end | |
def iterate! # :nodoc: | |
@index += 1 | |
end | |
end | |
class CollectionRenderer < PartialRenderer # :nodoc: | |
include ObjectRendering | |
class CollectionIterator # :nodoc: | |
include Enumerable | |
def initialize(collection) | |
@collection = collection | |
end | |
def each(&blk) | |
@collection.each(&blk) | |
end | |
def size | |
@collection.size | |
end | |
def length | |
@collection.respond_to?(:length) ? @collection.length : size | |
end | |
end | |
class SameCollectionIterator < CollectionIterator # :nodoc: | |
def initialize(collection, path, variables) | |
super(collection) | |
@path = path | |
@variables = variables | |
end | |
def from_collection(collection) | |
self.class.new(collection, @path, @variables) | |
end | |
def each_with_info | |
return enum_for(:each_with_info) unless block_given? | |
variables = [@path] + @variables | |
@collection.each { |o| yield(o, variables) } | |
end | |
end | |
class PreloadCollectionIterator < SameCollectionIterator # :nodoc: | |
def initialize(collection, path, variables, relation) | |
super(collection, path, variables) | |
relation.skip_preloading! unless relation.loaded? | |
@relation = relation | |
end | |
def from_collection(collection) | |
self.class.new(collection, @path, @variables, @relation) | |
end | |
def each_with_info | |
return super unless block_given? | |
@relation.preload_associations(@collection) | |
super | |
end | |
end | |
class MixedCollectionIterator < CollectionIterator # :nodoc: | |
def initialize(collection, paths) | |
super(collection) | |
@paths = paths | |
end | |
def each_with_info | |
return enum_for(:each_with_info) unless block_given? | |
@collection.each_with_index { |o, i| yield(o, @paths[i]) } | |
end | |
end | |
def render_collection_with_partial(collection, partial, context, block) | |
iter_vars = retrieve_variable(partial) | |
collection = if collection.respond_to?(:preload_associations) | |
PreloadCollectionIterator.new(collection, partial, iter_vars, collection) | |
else | |
SameCollectionIterator.new(collection, partial, iter_vars) | |
end | |
template = find_template(partial, @locals.keys + iter_vars) | |
layout = if !block && (layout = @options[:layout]) | |
find_template(layout.to_s, @locals.keys + iter_vars) | |
end | |
render_collection(collection, context, partial, template, layout, block) | |
end | |
def render_collection_derive_partial(collection, context, block) | |
paths = collection.map { |o| partial_path(o, context) } | |
if paths.uniq.length == 1 | |
# Homogeneous | |
render_collection_with_partial(collection, paths.first, context, block) | |
else | |
if @options[:cached] | |
raise NotImplementedError, "render caching requires a template. Please specify a partial when rendering" | |
end | |
paths.map! { |path| retrieve_variable(path).unshift(path) } | |
collection = MixedCollectionIterator.new(collection, paths) | |
render_collection(collection, context, nil, nil, nil, block) | |
end | |
end | |
private | |
def retrieve_variable(path) | |
variable = local_variable(path) | |
[variable, :"#{variable}_counter", :"#{variable}_iteration"] | |
end | |
def render_collection(collection, view, path, template, layout, block) | |
identifier = (template && template.identifier) || path | |
ActiveSupport::Notifications.instrument( | |
"render_collection.action_view", | |
identifier: identifier, | |
layout: layout && layout.virtual_path, | |
count: collection.length | |
) do |payload| | |
spacer = if @options.key?(:spacer_template) | |
spacer_template = find_template(@options[:spacer_template], @locals.keys) | |
build_rendered_template(spacer_template.render(view, @locals), spacer_template) | |
else | |
RenderedTemplate::EMPTY_SPACER | |
end | |
collection_body = if template | |
cache_collection_render(payload, view, template, collection) do |filtered_collection| | |
collection_with_template(view, template, layout, filtered_collection) | |
end | |
else | |
collection_with_template(view, nil, layout, collection) | |
end | |
return RenderedCollection.empty(@lookup_context.formats.first) if collection_body.empty? | |
build_rendered_collection(collection_body, spacer) | |
end | |
end | |
def collection_with_template(view, template, layout, collection) | |
locals = @locals | |
cache = {} | |
partial_iteration = PartialIteration.new(collection.size) | |
collection.each_with_info.map do |object, (path, as, counter, iteration)| | |
index = partial_iteration.index | |
locals[as] = object | |
locals[counter] = index | |
locals[iteration] = partial_iteration | |
_template = (cache[path] ||= (template || find_template(path, @locals.keys + [as, counter, iteration]))) | |
content = _template.render(view, locals) | |
content = layout.render(view, locals) { content } if layout | |
partial_iteration.iterate! | |
build_rendered_template(content, _template) | |
end | |
end | |
end | |
end |
# frozen_string_literal: true | |
module ActionView | |
module Helpers | |
module Tags # :nodoc: | |
class CollectionSelect < Base # :nodoc: | |
def initialize(object_name, method_name, template_object, collection, value_method, text_method, options, html_options) | |
@collection = collection | |
@value_method = value_method | |
@text_method = text_method | |
@html_options = html_options | |
super(object_name, method_name, template_object, options) | |
end | |
def render | |
option_tags_options = { | |
selected: @options.fetch(:selected) { value }, | |
disabled: @options[:disabled] | |
} | |
select_content_tag( | |
options_from_collection_for_select(@collection, @value_method, @text_method, option_tags_options), | |
@options, @html_options | |
) | |
end | |
end | |
end | |
end | |
end |
# frozen_string_literal: true | |
module ActionView | |
module Helpers | |
module Tags # :nodoc: | |
class ColorField < TextField # :nodoc: | |
def render | |
options = @options.stringify_keys | |
options["value"] ||= validate_color_string(value) | |
@options = options | |
super | |
end | |
private | |
def validate_color_string(string) | |
regex = /#[0-9a-fA-F]{6}/ | |
if regex.match?(string) | |
string.downcase | |
else | |
"#000000" | |
end | |
end | |
end | |
end | |
end | |
end |
# frozen_string_literal: true | |
class Company < ActiveRecord::Base | |
has_one :mascot | |
self.sequence_name = :companies_nonstd_seq | |
validates_presence_of :name | |
def validate | |
errors.add("rating", "rating should not be 2") if rating == 2 | |
end | |
end |
# frozen_string_literal: true | |
require "abstract_unit" | |
class CompiledTemplatesTest < ActiveSupport::TestCase | |
attr_reader :view_class | |
def setup | |
super | |
view_paths = ActionController::Base.view_paths | |
view_paths.each(&:clear_cache) | |
@view_class = ActionView::Base.with_empty_template_cache | |
end | |
def teardown | |
super | |
ActionView::LookupContext::DetailsKey.clear | |
end | |
def test_template_with_nil_erb_return | |
assert_equal "This is nil: \n", render(template: "test/nil_return") | |
end | |
def test_template_with_ruby_keyword_locals | |
assert_equal "The class is foo", | |
render(template: "test/render_file_with_ruby_keyword_locals", locals: { class: "foo" }) | |
end | |
def test_template_with_invalid_identifier_locals | |
locals = { | |
foo: "bar", | |
Foo: "bar", | |
"d-a-s-h-e-s": "", | |
"white space": "", | |
} | |
assert_equal locals.inspect, render(template: "test/render_file_inspect_local_assigns", locals: locals) | |
end | |
def test_template_with_delegation_reserved_keywords | |
locals = { | |
_: "one", | |
arg: "two", | |
args: "three", | |
block: "four", | |
} | |
assert_equal "one two three four", render(template: "test/test_template_with_delegation_reserved_keywords", locals: locals) | |
end | |
def test_template_with_unicode_identifier | |
assert_equal "🎂", render(template: "test/render_file_unicode_local", locals: { 🎃: "🎂" }) | |
end | |
def test_template_with_instance_variable_identifier | |
expected_deprecation = "In Rails 7.1, @foo will be ignored." | |
assert_deprecated(expected_deprecation) do | |
assert_equal "bar", render(template: "test/render_file_instance_variable", locals: { "@foo": "bar" }) | |
end | |
end | |
def test_template_gets_recompiled_when_using_different_keys_in_local_assigns | |
assert_equal "one", render(template: "test/render_file_with_locals_and_default") | |
assert_equal "two", render(template: "test/render_file_with_locals_and_default", locals: { secret: "two" }) | |
end | |
private | |
def render(*args) | |
ActionController::Base.render(*args) | |
end | |
end |
# frozen_string_literal: true | |
module ActionView | |
# = Action View Context | |
# | |
# Action View contexts are supplied to Action Controller to render a template. | |
# The default Action View context is ActionView::Base. | |
# | |
# In order to work with Action Controller, a Context must just include this | |
# module. The initialization of the variables used by the context | |
# (@output_buffer, @view_flow, and @virtual_path) is responsibility of the | |
# object that includes this module (although you can call _prepare_context | |
# defined below). | |
module Context | |
attr_accessor :output_buffer, :view_flow | |
# Prepares the context by setting the appropriate instance variables. | |
def _prepare_context | |
@view_flow = OutputFlow.new | |
@output_buffer = nil | |
@virtual_path = nil | |
end | |
# Encapsulates the interaction with the view flow so it | |
# returns the correct buffer on +yield+. This is usually | |
# overwritten by helpers to add more behavior. | |
def _layout_for(name = nil) | |
name ||= :layout | |
view_flow.get(name).html_safe | |
end | |
end | |
end |
# frozen_string_literal: true | |
require "active_support/core_ext/module/attr_internal" | |
module ActionView | |
module Helpers # :nodoc: | |
# This module keeps all methods and behavior in ActionView | |
# that simply delegates to the controller. | |
module ControllerHelper # :nodoc: | |
attr_internal :controller, :request | |
CONTROLLER_DELEGATES = [:request_forgery_protection_token, :params, | |
:session, :cookies, :response, :headers, :flash, :action_name, | |
:controller_name, :controller_path] | |
delegate(*CONTROLLER_DELEGATES, to: :controller) | |
def assign_controller(controller) | |
if @_controller = controller | |
@_request = controller.request if controller.respond_to?(:request) | |
@_config = controller.config.inheritable_copy if controller.respond_to?(:config) | |
@_default_form_builder = controller.default_form_builder if controller.respond_to?(:default_form_builder) | |
end | |
end | |
def logger | |
controller.logger if controller.respond_to?(:logger) | |
end | |
def respond_to?(method_name, include_private = false) | |
return controller.respond_to?(method_name) if CONTROLLER_DELEGATES.include?(method_name.to_sym) | |
super | |
end | |
end | |
end | |
end |
# frozen_string_literal: true | |
require "abstract_unit" | |
class ControllerHelperTest < ActionView::TestCase | |
tests ActionView::Helpers::ControllerHelper | |
class SpecializedFormBuilder < ActionView::Helpers::FormBuilder ; end | |
def test_assign_controller_sets_default_form_builder | |
@controller = OpenStruct.new(default_form_builder: SpecializedFormBuilder) | |
assign_controller(@controller) | |
assert_equal SpecializedFormBuilder, default_form_builder | |
end | |
def test_assign_controller_skips_default_form_builder | |
@controller = OpenStruct.new | |
assign_controller(@controller) | |
assert_nil default_form_builder | |
end | |
def test_respond_to | |
@controller = OpenStruct.new | |
assign_controller(@controller) | |
assert_not respond_to?(:params) | |
assert respond_to?(:assign_controller) | |
@controller.params = {} | |
assert respond_to?(:params) | |
assert respond_to?(:assign_controller) | |
end | |
end |
# frozen_string_literal: true | |
require "active_record_unit" | |
require "active_record/railties/controller_runtime" | |
require "fixtures/project" | |
require "active_support/log_subscriber/test_helper" | |
require "action_controller/log_subscriber" | |
ActionController::Base.include(ActiveRecord::Railties::ControllerRuntime) | |
class ControllerRuntimeLogSubscriberTest < ActionController::TestCase | |
class LogSubscriberController < ActionController::Base | |
def show | |
render inline: "<%= Project.all %>" | |
end | |
def zero | |
render inline: "Zero DB runtime" | |
end | |
def create | |
ActiveRecord::LogSubscriber.runtime += 100 | |
Project.last | |
redirect_to "/" | |
end | |
def redirect | |
Project.all | |
redirect_to action: "show" | |
end | |
def db_after_render | |
render inline: "Hello world" | |
Project.all | |
ActiveRecord::LogSubscriber.runtime += 100 | |
end | |
end | |
include ActiveSupport::LogSubscriber::TestHelper | |
tests LogSubscriberController | |
with_routes do | |
get :show, to: "#{LogSubscriberController.controller_path}#show" | |
get :zero, to: "#{LogSubscriberController.controller_path}#zero" | |
get :db_after_render, to: "#{LogSubscriberController.controller_path}#db_after_render" | |
get :redirect, to: "#{LogSubscriberController.controller_path}#redirect" | |
post :create, to: "#{LogSubscriberController.controller_path}#create" | |
end | |
def setup | |
@old_logger = ActionController::Base.logger | |
super | |
ActionController::LogSubscriber.attach_to :action_controller | |
end | |
def teardown | |
super | |
ActiveSupport::LogSubscriber.log_subscribers.clear | |
ActionController::Base.logger = @old_logger | |
end | |
def set_logger(logger) | |
ActionController::Base.logger = logger | |
end | |
def test_log_with_active_record | |
get :show | |
wait | |
assert_equal 2, @logger.logged(:info).size | |
assert_match(/\(Views: [\d.]+ms \| ActiveRecord: [\d.]+ms \| Allocations: [\d.]+\)/, @logger.logged(:info)[1]) | |
end | |
def test_runtime_reset_before_requests | |
ActiveRecord::LogSubscriber.runtime += 12345 | |
get :zero | |
wait | |
assert_equal 2, @logger.logged(:info).size | |
assert_match(/\(Views: [\d.]+ms \| ActiveRecord: [\d.]+ms \| Allocations: [\d.]+\)/, @logger.logged(:info)[1]) | |
end | |
def test_log_with_active_record_when_post | |
post :create | |
wait | |
assert_match(/ActiveRecord: ([1-9][\d.]+)ms \| Allocations: [\d.]+\)/, @logger.logged(:info)[2]) | |
end | |
def test_log_with_active_record_when_redirecting | |
get :redirect | |
wait | |
assert_equal 3, @logger.logged(:info).size | |
assert_match(/\(ActiveRecord: [\d.]+ms \| Allocations: [\d.]+\)/, @logger.logged(:info)[2]) | |
end | |
def test_include_time_query_time_after_rendering | |
get :db_after_render | |
wait | |
assert_equal 2, @logger.logged(:info).size | |
assert_match(/\(Views: [\d.]+ms \| ActiveRecord: ([1-9][\d.]+)ms \| Allocations: [\d.]+\)/, @logger.logged(:info)[1]) | |
end | |
end |
# frozen_string_literal: true | |
module ActionView | |
# = Action View CSP Helper | |
module Helpers # :nodoc: | |
module CspHelper | |
# Returns a meta tag "csp-nonce" with the per-session nonce value | |
# for allowing inline <script> tags. | |
# | |
# <head> | |
# <%= csp_meta_tag %> | |
# </head> | |
# | |
# This is used by the Rails UJS helper to create dynamically | |
# loaded inline <script> elements. | |
# | |
def csp_meta_tag(**options) | |
if content_security_policy? | |
options[:name] = "csp-nonce" | |
options[:content] = content_security_policy_nonce | |
tag("meta", options) | |
end | |
end | |
end | |
end | |
end |
# frozen_string_literal: true | |
require "abstract_unit" | |
class CspHelperWithCspEnabledTest < ActionView::TestCase | |
tests ActionView::Helpers::CspHelper | |
def content_security_policy_nonce | |
"iyhD0Yc0W+c=" | |
end | |
def content_security_policy? | |
true | |
end | |
def test_csp_meta_tag | |
assert_equal "<meta name=\"csp-nonce\" content=\"iyhD0Yc0W+c=\" />", csp_meta_tag | |
end | |
def test_csp_meta_tag_with_options | |
assert_equal "<meta property=\"csp-nonce\" name=\"csp-nonce\" content=\"iyhD0Yc0W+c=\" />", csp_meta_tag(property: "csp-nonce") | |
end | |
end | |
class CspHelperWithCspDisabledTest < ActionView::TestCase | |
tests ActionView::Helpers::CspHelper | |
def content_security_policy? | |
false | |
end | |
def test_csp_meta_tag | |
assert_nil csp_meta_tag | |
end | |
end |
# frozen_string_literal: true | |
module ActionView | |
# = Action View CSRF Helper | |
module Helpers # :nodoc: | |
module CsrfHelper | |
# Returns meta tags "csrf-param" and "csrf-token" with the name of the cross-site | |
# request forgery protection parameter and token, respectively. | |
# | |
# <head> | |
# <%= csrf_meta_tags %> | |
# </head> | |
# | |
# These are used to generate the dynamic forms that implement non-remote links with | |
# <tt>:method</tt>. | |
# | |
# You don't need to use these tags for regular forms as they generate their own hidden fields. | |
# | |
# For AJAX requests other than GETs, extract the "csrf-token" from the meta-tag and send as the | |
# "X-CSRF-Token" HTTP header. If you are using rails-ujs this happens automatically. | |
# | |
def csrf_meta_tags | |
if defined?(protect_against_forgery?) && protect_against_forgery? | |
[ | |
tag("meta", name: "csrf-param", content: request_forgery_protection_token), | |
tag("meta", name: "csrf-token", content: form_authenticity_token) | |
].join("\n").html_safe | |
end | |
end | |
# For backwards compatibility. | |
alias csrf_meta_tag csrf_meta_tags | |
end | |
end | |
end |
# frozen_string_literal: true | |
require "abstract_unit" | |
class CsrfHelperTest < ActiveSupport::TestCase | |
cattr_accessor :request_forgery, default: false | |
include ActionView::Helpers::CsrfHelper | |
include ActionView::Helpers::TagHelper | |
include Rails::Dom::Testing::Assertions::DomAssertions | |
def test_csrf_meta_tags_without_request_forgery_protection | |
assert_dom_equal "", csrf_meta_tags | |
end | |
def test_csrf_meta_tags_with_request_forgery_protection | |
self.request_forgery = true | |
assert_dom_equal <<~DOM.chomp, csrf_meta_tags | |
<meta name="csrf-param" content="form_token" /> | |
<meta name="csrf-token" content="secret" /> | |
DOM | |
ensure | |
self.request_forgery = false | |
end | |
def test_csrf_meta_tags_without_protect_against_forgery_method | |
self.class.undef_method(:protect_against_forgery?) | |
assert_dom_equal "", csrf_meta_tags | |
ensure | |
self.class.define_method(:protect_against_forgery?) { request_forgery } | |
end | |
def protect_against_forgery? | |
request_forgery | |
end | |
def form_authenticity_token(**) | |
"secret" | |
end | |
def request_forgery_protection_token | |
"form_token" | |
end | |
end |
# frozen_string_literal: true | |
module ActionView | |
module Helpers | |
module Tags # :nodoc: | |
class DateField < DatetimeField # :nodoc: | |
private | |
def format_date(value) | |
value&.strftime("%Y-%m-%d") | |
end | |
end | |
end | |
end | |
end |
# frozen_string_literal: true | |
require "date" | |
require "action_view/helpers/tag_helper" | |
require "active_support/core_ext/array/extract_options" | |
require "active_support/core_ext/date/conversions" | |
require "active_support/core_ext/hash/slice" | |
require "active_support/core_ext/object/acts_like" | |
require "active_support/core_ext/object/with_options" | |
module ActionView | |
module Helpers # :nodoc: | |
# = Action View Date Helpers | |
# | |
# The Date Helper primarily creates select/option tags for different kinds of dates and times or date and time | |
# elements. All of the select-type methods share a number of common options that are as follows: | |
# | |
# * <tt>:prefix</tt> - overwrites the default prefix of "date" used for the select names. So specifying "birthday" | |
# would give \birthday[month] instead of \date[month] if passed to the <tt>select_month</tt> method. | |
# * <tt>:include_blank</tt> - set to true if it should be possible to set an empty date. | |
# * <tt>:discard_type</tt> - set to true if you want to discard the type part of the select name. If set to true, | |
# the <tt>select_month</tt> method would use simply "date" (which can be overwritten using <tt>:prefix</tt>) instead | |
# of \date[month]. | |
module DateHelper | |
MINUTES_IN_YEAR = 525600 | |
MINUTES_IN_QUARTER_YEAR = 131400 | |
MINUTES_IN_THREE_QUARTERS_YEAR = 394200 | |
# Reports the approximate distance in time between two Time, Date, or DateTime objects or integers as seconds. | |
# Pass <tt>include_seconds: true</tt> if you want more detailed approximations when distance < 1 min, 29 secs. | |
# Distances are reported based on the following table: | |
# | |
# 0 <-> 29 secs # => less than a minute | |
# 30 secs <-> 1 min, 29 secs # => 1 minute | |
# 1 min, 30 secs <-> 44 mins, 29 secs # => [2..44] minutes | |
# 44 mins, 30 secs <-> 89 mins, 29 secs # => about 1 hour | |
# 89 mins, 30 secs <-> 23 hrs, 59 mins, 29 secs # => about [2..24] hours | |
# 23 hrs, 59 mins, 30 secs <-> 41 hrs, 59 mins, 29 secs # => 1 day | |
# 41 hrs, 59 mins, 30 secs <-> 29 days, 23 hrs, 59 mins, 29 secs # => [2..29] days | |
# 29 days, 23 hrs, 59 mins, 30 secs <-> 44 days, 23 hrs, 59 mins, 29 secs # => about 1 month | |
# 44 days, 23 hrs, 59 mins, 30 secs <-> 59 days, 23 hrs, 59 mins, 29 secs # => about 2 months | |
# 59 days, 23 hrs, 59 mins, 30 secs <-> 1 yr minus 1 sec # => [2..12] months | |
# 1 yr <-> 1 yr, 3 months # => about 1 year | |
# 1 yr, 3 months <-> 1 yr, 9 months # => over 1 year | |
# 1 yr, 9 months <-> 2 yr minus 1 sec # => almost 2 years | |
# 2 yrs <-> max time or date # => (same rules as 1 yr) | |
# | |
# With <tt>include_seconds: true</tt> and the difference < 1 minute 29 seconds: | |
# 0-4 secs # => less than 5 seconds | |
# 5-9 secs # => less than 10 seconds | |
# 10-19 secs # => less than 20 seconds | |
# 20-39 secs # => half a minute | |
# 40-59 secs # => less than a minute | |
# 60-89 secs # => 1 minute | |
# | |
# from_time = Time.now | |
# distance_of_time_in_words(from_time, from_time + 50.minutes) # => about 1 hour | |
# distance_of_time_in_words(from_time, 50.minutes.from_now) # => about 1 hour | |
# distance_of_time_in_words(from_time, from_time + 15.seconds) # => less than a minute | |
# distance_of_time_in_words(from_time, from_time + 15.seconds, include_seconds: true) # => less than 20 seconds | |
# distance_of_time_in_words(from_time, 3.years.from_now) # => about 3 years | |
# distance_of_time_in_words(from_time, from_time + 60.hours) # => 3 days | |
# distance_of_time_in_words(from_time, from_time + 45.seconds, include_seconds: true) # => less than a minute | |
# distance_of_time_in_words(from_time, from_time - 45.seconds, include_seconds: true) # => less than a minute | |
# distance_of_time_in_words(from_time, 76.seconds.from_now) # => 1 minute | |
# distance_of_time_in_words(from_time, from_time + 1.year + 3.days) # => about 1 year | |
# distance_of_time_in_words(from_time, from_time + 3.years + 6.months) # => over 3 years | |
# distance_of_time_in_words(from_time, from_time + 4.years + 9.days + 30.minutes + 5.seconds) # => about 4 years | |
# | |
# to_time = Time.now + 6.years + 19.days | |
# distance_of_time_in_words(from_time, to_time, include_seconds: true) # => about 6 years | |
# distance_of_time_in_words(to_time, from_time, include_seconds: true) # => about 6 years | |
# distance_of_time_in_words(Time.now, Time.now) # => less than a minute | |
# | |
# With the <tt>scope</tt> option, you can define a custom scope for Rails | |
# to look up the translation. | |
# | |
# For example you can define the following in your locale (e.g. en.yml). | |
# | |
# datetime: | |
# distance_in_words: | |
# short: | |
# about_x_hours: | |
# one: 'an hour' | |
# other: '%{count} hours' | |
# | |
# See https://github.com/svenfuchs/rails-i18n/blob/master/rails/locale/en.yml | |
# for more examples. | |
# | |
# Which will then result in the following: | |
# | |
# from_time = Time.now | |
# distance_of_time_in_words(from_time, from_time + 50.minutes, scope: 'datetime.distance_in_words.short') # => "an hour" | |
# distance_of_time_in_words(from_time, from_time + 3.hours, scope: 'datetime.distance_in_words.short') # => "3 hours" | |
def distance_of_time_in_words(from_time, to_time = 0, options = {}) | |
options = { | |
scope: :'datetime.distance_in_words' | |
}.merge!(options) | |
from_time = normalize_distance_of_time_argument_to_time(from_time) | |
to_time = normalize_distance_of_time_argument_to_time(to_time) | |
from_time, to_time = to_time, from_time if from_time > to_time | |
distance_in_minutes = ((to_time - from_time) / 60.0).round | |
distance_in_seconds = (to_time - from_time).round | |
I18n.with_options locale: options[:locale], scope: options[:scope] do |locale| | |
case distance_in_minutes | |
when 0..1 | |
return distance_in_minutes == 0 ? | |
locale.t(:less_than_x_minutes, count: 1) : | |
locale.t(:x_minutes, count: distance_in_minutes) unless options[:include_seconds] | |
case distance_in_seconds | |
when 0..4 then locale.t :less_than_x_seconds, count: 5 | |
when 5..9 then locale.t :less_than_x_seconds, count: 10 | |
when 10..19 then locale.t :less_than_x_seconds, count: 20 | |
when 20..39 then locale.t :half_a_minute | |
when 40..59 then locale.t :less_than_x_minutes, count: 1 | |
else locale.t :x_minutes, count: 1 | |
end | |
when 2...45 then locale.t :x_minutes, count: distance_in_minutes | |
when 45...90 then locale.t :about_x_hours, count: 1 | |
# 90 mins up to 24 hours | |
when 90...1440 then locale.t :about_x_hours, count: (distance_in_minutes.to_f / 60.0).round | |
# 24 hours up to 42 hours | |
when 1440...2520 then locale.t :x_days, count: 1 | |
# 42 hours up to 30 days | |
when 2520...43200 then locale.t :x_days, count: (distance_in_minutes.to_f / 1440.0).round | |
# 30 days up to 60 days | |
when 43200...86400 then locale.t :about_x_months, count: (distance_in_minutes.to_f / 43200.0).round | |
# 60 days up to 365 days | |
when 86400...525600 then locale.t :x_months, count: (distance_in_minutes.to_f / 43200.0).round | |
else | |
from_year = from_time.year | |
from_year += 1 if from_time.month >= 3 | |
to_year = to_time.year | |
to_year -= 1 if to_time.month < 3 | |
leap_years = (from_year > to_year) ? 0 : (from_year..to_year).count { |x| Date.leap?(x) } | |
minute_offset_for_leap_year = leap_years * 1440 | |
# Discount the leap year days when calculating year distance. | |
# e.g. if there are 20 leap year days between 2 dates having the same day | |
# and month then based on 365 days calculation | |
# the distance in years will come out to over 80 years when in written | |
# English it would read better as about 80 years. | |
minutes_with_offset = distance_in_minutes - minute_offset_for_leap_year | |
remainder = (minutes_with_offset % MINUTES_IN_YEAR) | |
distance_in_years = (minutes_with_offset.div MINUTES_IN_YEAR) | |
if remainder < MINUTES_IN_QUARTER_YEAR | |
locale.t(:about_x_years, count: distance_in_years) | |
elsif remainder < MINUTES_IN_THREE_QUARTERS_YEAR | |
locale.t(:over_x_years, count: distance_in_years) | |
else | |
locale.t(:almost_x_years, count: distance_in_years + 1) | |
end | |
end | |
end | |
end | |
# Like <tt>distance_of_time_in_words</tt>, but where <tt>to_time</tt> is fixed to <tt>Time.now</tt>. | |
# | |
# time_ago_in_words(3.minutes.from_now) # => 3 minutes | |
# time_ago_in_words(3.minutes.ago) # => 3 minutes | |
# time_ago_in_words(Time.now - 15.hours) # => about 15 hours | |
# time_ago_in_words(Time.now) # => less than a minute | |
# time_ago_in_words(Time.now, include_seconds: true) # => less than 5 seconds | |
# | |
# from_time = Time.now - 3.days - 14.minutes - 25.seconds | |
# time_ago_in_words(from_time) # => 3 days | |
# | |
# from_time = (3.days + 14.minutes + 25.seconds).ago | |
# time_ago_in_words(from_time) # => 3 days | |
# | |
# Note that you cannot pass a <tt>Numeric</tt> value to <tt>time_ago_in_words</tt>. | |
# | |
def time_ago_in_words(from_time, options = {}) | |
distance_of_time_in_words(from_time, Time.now, options) | |
end | |
alias_method :distance_of_time_in_words_to_now, :time_ago_in_words | |
# Returns a set of select tags (one for year, month, and day) pre-selected for accessing a specified date-based | |
# attribute (identified by +method+) on an object assigned to the template (identified by +object+). | |
# | |
# ==== Options | |
# * <tt>:use_month_numbers</tt> - Set to true if you want to use month numbers rather than month names (e.g. | |
# "2" instead of "February"). | |
# * <tt>:use_two_digit_numbers</tt> - Set to true if you want to display two digit month and day numbers (e.g. | |
# "02" instead of "February" and "08" instead of "8"). | |
# * <tt>:use_short_month</tt> - Set to true if you want to use abbreviated month names instead of full | |
# month names (e.g. "Feb" instead of "February"). | |
# * <tt>:add_month_numbers</tt> - Set to true if you want to use both month numbers and month names (e.g. | |
# "2 - February" instead of "February"). | |
# * <tt>:use_month_names</tt> - Set to an array with 12 month names if you want to customize month names. | |
# Note: You can also use Rails' i18n functionality for this. | |
# * <tt>:month_format_string</tt> - Set to a format string. The string gets passed keys +:number+ (integer) | |
# and +:name+ (string). A format string would be something like "%{name} (%<number>02d)" for example. | |
# See <tt>Kernel.sprintf</tt> for documentation on format sequences. | |
# * <tt>:date_separator</tt> - Specifies a string to separate the date fields. Default is "" (i.e. nothing). | |
# * <tt>:time_separator</tt> - Specifies a string to separate the time fields. Default is " : ". | |
# * <tt>:datetime_separator</tt>- Specifies a string to separate the date and time fields. Default is " — ". | |
# * <tt>:start_year</tt> - Set the start year for the year select. Default is <tt>Date.today.year - 5</tt> if | |
# you are creating new record. While editing existing record, <tt>:start_year</tt> defaults to | |
# the current selected year minus 5. | |
# * <tt>:end_year</tt> - Set the end year for the year select. Default is <tt>Date.today.year + 5</tt> if | |
# you are creating new record. While editing existing record, <tt>:end_year</tt> defaults to | |
# the current selected year plus 5. | |
# * <tt>:year_format</tt> - Set format of years for year select. Lambda should be passed. | |
# * <tt>:day_format</tt> - Set format of days for day select. Lambda should be passed. | |
# * <tt>:discard_day</tt> - Set to true if you don't want to show a day select. This includes the day | |
# as a hidden field instead of showing a select field. Also note that this implicitly sets the day to be the | |
# first of the given month in order to not create invalid dates like 31 February. | |
# * <tt>:discard_month</tt> - Set to true if you don't want to show a month select. This includes the month | |
# as a hidden field instead of showing a select field. Also note that this implicitly sets :discard_day to true. | |
# * <tt>:discard_year</tt> - Set to true if you don't want to show a year select. This includes the year | |
# as a hidden field instead of showing a select field. | |
# * <tt>:order</tt> - Set to an array containing <tt>:day</tt>, <tt>:month</tt> and <tt>:year</tt> to | |
# customize the order in which the select fields are shown. If you leave out any of the symbols, the respective | |
# select will not be shown (like when you set <tt>discard_xxx: true</tt>. Defaults to the order defined in | |
# the respective locale (e.g. [:year, :month, :day] in the en locale that ships with Rails). | |
# * <tt>:include_blank</tt> - Include a blank option in every select field so it's possible to set empty | |
# dates. | |
# * <tt>:default</tt> - Set a default date if the affected date isn't set or is +nil+. | |
# * <tt>:selected</tt> - Set a date that overrides the actual value. | |
# * <tt>:disabled</tt> - Set to true if you want show the select fields as disabled. | |
# * <tt>:prompt</tt> - Set to true (for a generic prompt), a prompt string or a hash of prompt strings | |
# for <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>, <tt>:minute</tt> and <tt>:second</tt>. | |
# Setting this option prepends a select option with a generic prompt (Day, Month, Year, Hour, Minute, Seconds) | |
# or the given prompt string. | |
# * <tt>:with_css_classes</tt> - Set to true or a hash of strings. Use true if you want to assign generic styles for | |
# select tags. This automatically set classes 'year', 'month', 'day', 'hour', 'minute' and 'second'. A hash of | |
# strings for <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>, <tt>:minute</tt>, <tt>:second</tt> | |
# will extend the select type with the given value. Use +html_options+ to modify every select tag in the set. | |
# * <tt>:use_hidden</tt> - Set to true if you only want to generate hidden input tags. | |
# | |
# If anything is passed in the +html_options+ hash it will be applied to every select tag in the set. | |
# | |
# NOTE: Discarded selects will default to 1. So if no month select is available, January will be assumed. | |
# | |
# # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute. | |
# date_select("article", "written_on") | |
# | |
# # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute, | |
# # with the year in the year drop down box starting at 1995. | |
# date_select("article", "written_on", start_year: 1995) | |
# | |
# # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute, | |
# # with the year in the year drop down box starting at 1995, numbers used for months instead of words, | |
# # and without a day select box. | |
# date_select("article", "written_on", start_year: 1995, use_month_numbers: true, | |
# discard_day: true, include_blank: true) | |
# | |
# # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute, | |
# # with two digit numbers used for months and days. | |
# date_select("article", "written_on", use_two_digit_numbers: true) | |
# | |
# # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute | |
# # with the fields ordered as day, month, year rather than month, day, year. | |
# date_select("article", "written_on", order: [:day, :month, :year]) | |
# | |
# # Generates a date select that when POSTed is stored in the user variable, in the birthday attribute | |
# # lacking a year field. | |
# date_select("user", "birthday", order: [:month, :day]) | |
# | |
# # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute | |
# # which is initially set to the date 3 days from the current date | |
# date_select("article", "written_on", default: 3.days.from_now) | |
# | |
# # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute | |
# # which is set in the form with today's date, regardless of the value in the Active Record object. | |
# date_select("article", "written_on", selected: Date.today) | |
# | |
# # Generates a date select that when POSTed is stored in the credit_card variable, in the bill_due attribute | |
# # that will have a default day of 20. | |
# date_select("credit_card", "bill_due", default: { day: 20 }) | |
# | |
# # Generates a date select with custom prompts. | |
# date_select("article", "written_on", prompt: { day: 'Select day', month: 'Select month', year: 'Select year' }) | |
# | |
# # Generates a date select with custom year format. | |
# date_select("article", "written_on", year_format: ->(year) { "Heisei #{year - 1988}" }) | |
# | |
# # Generates a date select with custom day format. | |
# date_select("article", "written_on", day_format: ->(day) { day.ordinalize }) | |
# | |
# The selects are prepared for multi-parameter assignment to an Active Record object. | |
# | |
# Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that | |
# all month choices are valid. | |
def date_select(object_name, method, options = {}, html_options = {}) | |
Tags::DateSelect.new(object_name, method, self, options, html_options).render | |
end | |
# Returns a set of select tags (one for hour, minute, and optionally second) pre-selected for accessing a | |
# specified time-based attribute (identified by +method+) on an object assigned to the template (identified by | |
# +object+). You can include the seconds with <tt>:include_seconds</tt>. You can get hours in the AM/PM format | |
# with <tt>:ampm</tt> option. | |
# | |
# This method will also generate 3 input hidden tags, for the actual year, month, and day unless the option | |
# <tt>:ignore_date</tt> is set to +true+. If you set the <tt>:ignore_date</tt> to +true+, you must have a | |
# +date_select+ on the same method within the form otherwise an exception will be raised. | |
# | |
# If anything is passed in the html_options hash it will be applied to every select tag in the set. | |
# | |
# # Creates a time select tag that, when POSTed, will be stored in the article variable in the sunrise attribute. | |
# time_select("article", "sunrise") | |
# | |
# # Creates a time select tag with a seconds field that, when POSTed, will be stored in the article variables in | |
# # the sunrise attribute. | |
# time_select("article", "start_time", include_seconds: true) | |
# | |
# # You can set the <tt>:minute_step</tt> to 15 which will give you: 00, 15, 30, and 45. | |
# time_select 'game', 'game_time', { minute_step: 15 } | |
# | |
# # Creates a time select tag with a custom prompt. Use <tt>prompt: true</tt> for generic prompts. | |
# time_select("article", "written_on", prompt: { hour: 'Choose hour', minute: 'Choose minute', second: 'Choose seconds' }) | |
# time_select("article", "written_on", prompt: { hour: true }) # generic prompt for hours | |
# time_select("article", "written_on", prompt: true) # generic prompts for all | |
# | |
# # You can set :ampm option to true which will show the hours as: 12 PM, 01 AM .. 11 PM. | |
# time_select 'game', 'game_time', { ampm: true } | |
# | |
# The selects are prepared for multi-parameter assignment to an Active Record object. | |
# | |
# Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that | |
# all month choices are valid. | |
def time_select(object_name, method, options = {}, html_options = {}) | |
Tags::TimeSelect.new(object_name, method, self, options, html_options).render | |
end | |
# Returns a set of select tags (one for year, month, day, hour, and minute) pre-selected for accessing a | |
# specified datetime-based attribute (identified by +method+) on an object assigned to the template (identified | |
# by +object+). | |
# | |
# If anything is passed in the html_options hash it will be applied to every select tag in the set. | |
# | |
# # Generates a datetime select that, when POSTed, will be stored in the article variable in the written_on | |
# # attribute. | |
# datetime_select("article", "written_on") | |
# | |
# # Generates a datetime select with a year select that starts at 1995 that, when POSTed, will be stored in the | |
# # article variable in the written_on attribute. | |
# datetime_select("article", "written_on", start_year: 1995) | |
# | |
# # Generates a datetime select with a default value of 3 days from the current time that, when POSTed, will | |
# # be stored in the trip variable in the departing attribute. | |
# datetime_select("trip", "departing", default: 3.days.from_now) | |
# | |
# # Generate a datetime select with hours in the AM/PM format | |
# datetime_select("article", "written_on", ampm: true) | |
# | |
# # Generates a datetime select that discards the type that, when POSTed, will be stored in the article variable | |
# # as the written_on attribute. | |
# datetime_select("article", "written_on", discard_type: true) | |
# | |
# # Generates a datetime select with a custom prompt. Use <tt>prompt: true</tt> for generic prompts. | |
# datetime_select("article", "written_on", prompt: { day: 'Choose day', month: 'Choose month', year: 'Choose year' }) | |
# datetime_select("article", "written_on", prompt: { hour: true }) # generic prompt for hours | |
# datetime_select("article", "written_on", prompt: true) # generic prompts for all | |
# | |
# The selects are prepared for multi-parameter assignment to an Active Record object. | |
def datetime_select(object_name, method, options = {}, html_options = {}) | |
Tags::DatetimeSelect.new(object_name, method, self, options, html_options).render | |
end | |
# Returns a set of HTML select-tags (one for year, month, day, hour, minute, and second) pre-selected with the | |
# +datetime+. It's also possible to explicitly set the order of the tags using the <tt>:order</tt> option with | |
# an array of symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. If you do not | |
# supply a Symbol, it will be appended onto the <tt>:order</tt> passed in. You can also add | |
# <tt>:date_separator</tt>, <tt>:datetime_separator</tt> and <tt>:time_separator</tt> keys to the +options+ to | |
# control visual display of the elements. | |
# | |
# If anything is passed in the html_options hash it will be applied to every select tag in the set. | |
# | |
# my_date_time = Time.now + 4.days | |
# | |
# # Generates a datetime select that defaults to the datetime in my_date_time (four days after today). | |
# select_datetime(my_date_time) | |
# | |
# # Generates a datetime select that defaults to today (no specified datetime) | |
# select_datetime() | |
# | |
# # Generates a datetime select that defaults to the datetime in my_date_time (four days after today) | |
# # with the fields ordered year, month, day rather than month, day, year. | |
# select_datetime(my_date_time, order: [:year, :month, :day]) | |
# | |
# # Generates a datetime select that defaults to the datetime in my_date_time (four days after today) | |
# # with a '/' between each date field. | |
# select_datetime(my_date_time, date_separator: '/') | |
# | |
# # Generates a datetime select that defaults to the datetime in my_date_time (four days after today) | |
# # with a date fields separated by '/', time fields separated by '' and the date and time fields | |
# # separated by a comma (','). | |
# select_datetime(my_date_time, date_separator: '/', time_separator: '', datetime_separator: ',') | |
# | |
# # Generates a datetime select that discards the type of the field and defaults to the datetime in | |
# # my_date_time (four days after today) | |
# select_datetime(my_date_time, discard_type: true) | |
# | |
# # Generate a datetime field with hours in the AM/PM format | |
# select_datetime(my_date_time, ampm: true) | |
# | |
# # Generates a datetime select that defaults to the datetime in my_date_time (four days after today) | |
# # prefixed with 'payday' rather than 'date' | |
# select_datetime(my_date_time, prefix: 'payday') | |
# | |
# # Generates a datetime select with a custom prompt. Use <tt>prompt: true</tt> for generic prompts. | |
# select_datetime(my_date_time, prompt: { day: 'Choose day', month: 'Choose month', year: 'Choose year' }) | |
# select_datetime(my_date_time, prompt: { hour: true }) # generic prompt for hours | |
# select_datetime(my_date_time, prompt: true) # generic prompts for all | |
def select_datetime(datetime = Time.current, options = {}, html_options = {}) | |
DateTimeSelector.new(datetime, options, html_options).select_datetime | |
end | |
# Returns a set of HTML select-tags (one for year, month, and day) pre-selected with the +date+. | |
# It's possible to explicitly set the order of the tags using the <tt>:order</tt> option with an array of | |
# symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. | |
# If the array passed to the <tt>:order</tt> option does not contain all the three symbols, all tags will be hidden. | |
# | |
# If anything is passed in the html_options hash it will be applied to every select tag in the set. | |
# | |
# my_date = Time.now + 6.days | |
# | |
# # Generates a date select that defaults to the date in my_date (six days after today). | |
# select_date(my_date) | |
# | |
# # Generates a date select that defaults to today (no specified date). | |
# select_date() | |
# | |
# # Generates a date select that defaults to the date in my_date (six days after today) | |
# # with the fields ordered year, month, day rather than month, day, year. | |
# select_date(my_date, order: [:year, :month, :day]) | |
# | |
# # Generates a date select that discards the type of the field and defaults to the date in | |
# # my_date (six days after today). | |
# select_date(my_date, discard_type: true) | |
# | |
# # Generates a date select that defaults to the date in my_date, | |
# # which has fields separated by '/'. | |
# select_date(my_date, date_separator: '/') | |
# | |
# # Generates a date select that defaults to the datetime in my_date (six days after today) | |
# # prefixed with 'payday' rather than 'date'. | |
# select_date(my_date, prefix: 'payday') | |
# | |
# # Generates a date select with a custom prompt. Use <tt>prompt: true</tt> for generic prompts. | |
# select_date(my_date, prompt: { day: 'Choose day', month: 'Choose month', year: 'Choose year' }) | |
# select_date(my_date, prompt: { hour: true }) # generic prompt for hours | |
# select_date(my_date, prompt: true) # generic prompts for all | |
def select_date(date = Date.current, options = {}, html_options = {}) | |
DateTimeSelector.new(date, options, html_options).select_date | |
end | |
# Returns a set of HTML select-tags (one for hour and minute). | |
# You can set <tt>:time_separator</tt> key to format the output, and | |
# the <tt>:include_seconds</tt> option to include an input for seconds. | |
# | |
# If anything is passed in the html_options hash it will be applied to every select tag in the set. | |
# | |
# my_time = Time.now + 5.days + 7.hours + 3.minutes + 14.seconds | |
# | |
# # Generates a time select that defaults to the time in my_time. | |
# select_time(my_time) | |
# | |
# # Generates a time select that defaults to the current time (no specified time). | |
# select_time() | |
# | |
# # Generates a time select that defaults to the time in my_time, | |
# # which has fields separated by ':'. | |
# select_time(my_time, time_separator: ':') | |
# | |
# # Generates a time select that defaults to the time in my_time, | |
# # that also includes an input for seconds. | |
# select_time(my_time, include_seconds: true) | |
# | |
# # Generates a time select that defaults to the time in my_time, that has fields | |
# # separated by ':' and includes an input for seconds. | |
# select_time(my_time, time_separator: ':', include_seconds: true) | |
# | |
# # Generate a time select field with hours in the AM/PM format | |
# select_time(my_time, ampm: true) | |
# | |
# # Generates a time select field with hours that range from 2 to 14 | |
# select_time(my_time, start_hour: 2, end_hour: 14) | |
# | |
# # Generates a time select with a custom prompt. Use <tt>:prompt</tt> to true for generic prompts. | |
# select_time(my_time, prompt: { day: 'Choose day', month: 'Choose month', year: 'Choose year' }) | |
# select_time(my_time, prompt: { hour: true }) # generic prompt for hours | |
# select_time(my_time, prompt: true) # generic prompts for all | |
def select_time(datetime = Time.current, options = {}, html_options = {}) | |
DateTimeSelector.new(datetime, options, html_options).select_time | |
end | |
# Returns a select tag with options for each of the seconds 0 through 59 with the current second selected. | |
# The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer. | |
# Override the field name using the <tt>:field_name</tt> option, 'second' by default. | |
# | |
# my_time = Time.now + 16.seconds | |
# | |
# # Generates a select field for seconds that defaults to the seconds for the time in my_time. | |
# select_second(my_time) | |
# | |
# # Generates a select field for seconds that defaults to the number given. | |
# select_second(33) | |
# | |
# # Generates a select field for seconds that defaults to the seconds for the time in my_time | |
# # that is named 'interval' rather than 'second'. | |
# select_second(my_time, field_name: 'interval') | |
# | |
# # Generates a select field for seconds with a custom prompt. Use <tt>prompt: true</tt> for a | |
# # generic prompt. | |
# select_second(14, prompt: 'Choose seconds') | |
def select_second(datetime, options = {}, html_options = {}) | |
DateTimeSelector.new(datetime, options, html_options).select_second | |
end | |
# Returns a select tag with options for each of the minutes 0 through 59 with the current minute selected. | |
# Also can return a select tag with options by <tt>minute_step</tt> from 0 through 59 with the 00 minute | |
# selected. The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer. | |
# Override the field name using the <tt>:field_name</tt> option, 'minute' by default. | |
# | |
# my_time = Time.now + 10.minutes | |
# | |
# # Generates a select field for minutes that defaults to the minutes for the time in my_time. | |
# select_minute(my_time) | |
# | |
# # Generates a select field for minutes that defaults to the number given. | |
# select_minute(14) | |
# | |
# # Generates a select field for minutes that defaults to the minutes for the time in my_time | |
# # that is named 'moment' rather than 'minute'. | |
# select_minute(my_time, field_name: 'moment') | |
# | |
# # Generates a select field for minutes with a custom prompt. Use <tt>prompt: true</tt> for a | |
# # generic prompt. | |
# select_minute(14, prompt: 'Choose minutes') | |
def select_minute(datetime, options = {}, html_options = {}) | |
DateTimeSelector.new(datetime, options, html_options).select_minute | |
end | |
# Returns a select tag with options for each of the hours 0 through 23 with the current hour selected. | |
# The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer. | |
# Override the field name using the <tt>:field_name</tt> option, 'hour' by default. | |
# | |
# my_time = Time.now + 6.hours | |
# | |
# # Generates a select field for hours that defaults to the hour for the time in my_time. | |
# select_hour(my_time) | |
# | |
# # Generates a select field for hours that defaults to the number given. | |
# select_hour(13) | |
# | |
# # Generates a select field for hours that defaults to the hour for the time in my_time | |
# # that is named 'stride' rather than 'hour'. | |
# select_hour(my_time, field_name: 'stride') | |
# | |
# # Generates a select field for hours with a custom prompt. Use <tt>prompt: true</tt> for a | |
# # generic prompt. | |
# select_hour(13, prompt: 'Choose hour') | |
# | |
# # Generate a select field for hours in the AM/PM format | |
# select_hour(my_time, ampm: true) | |
# | |
# # Generates a select field that includes options for hours from 2 to 14. | |
# select_hour(my_time, start_hour: 2, end_hour: 14) | |
def select_hour(datetime, options = {}, html_options = {}) | |
DateTimeSelector.new(datetime, options, html_options).select_hour | |
end | |
# Returns a select tag with options for each of the days 1 through 31 with the current day selected. | |
# The <tt>date</tt> can also be substituted for a day number. | |
# If you want to display days with a leading zero set the <tt>:use_two_digit_numbers</tt> key in +options+ to true. | |
# Override the field name using the <tt>:field_name</tt> option, 'day' by default. | |
# | |
# my_date = Time.now + 2.days | |
# | |
# # Generates a select field for days that defaults to the day for the date in my_date. | |
# select_day(my_date) | |
# | |
# # Generates a select field for days that defaults to the number given. | |
# select_day(5) | |
# | |
# # Generates a select field for days that defaults to the number given, but displays it with two digits. | |
# select_day(5, use_two_digit_numbers: true) | |
# | |
# # Generates a select field for days that defaults to the day for the date in my_date | |
# # that is named 'due' rather than 'day'. | |
# select_day(my_date, field_name: 'due') | |
# | |
# # Generates a select field for days with a custom prompt. Use <tt>prompt: true</tt> for a | |
# # generic prompt. | |
# select_day(5, prompt: 'Choose day') | |
def select_day(date, options = {}, html_options = {}) | |
DateTimeSelector.new(date, options, html_options).select_day | |
end | |
# Returns a select tag with options for each of the months January through December with the current month | |
# selected. The month names are presented as keys (what's shown to the user) and the month numbers (1-12) are | |
# used as values (what's submitted to the server). It's also possible to use month numbers for the presentation | |
# instead of names -- set the <tt>:use_month_numbers</tt> key in +options+ to true for this to happen. If you | |
# want both numbers and names, set the <tt>:add_month_numbers</tt> key in +options+ to true. If you would prefer | |
# to show month names as abbreviations, set the <tt>:use_short_month</tt> key in +options+ to true. If you want | |
# to use your own month names, set the <tt>:use_month_names</tt> key in +options+ to an array of 12 month names. | |
# If you want to display months with a leading zero set the <tt>:use_two_digit_numbers</tt> key in +options+ to true. | |
# Override the field name using the <tt>:field_name</tt> option, 'month' by default. | |
# | |
# # Generates a select field for months that defaults to the current month that | |
# # will use keys like "January", "March". | |
# select_month(Date.today) | |
# | |
# # Generates a select field for months that defaults to the current month that | |
# # is named "start" rather than "month". | |
# select_month(Date.today, field_name: 'start') | |
# | |
# # Generates a select field for months that defaults to the current month that | |
# # will use keys like "1", "3". | |
# select_month(Date.today, use_month_numbers: true) | |
# | |
# # Generates a select field for months that defaults to the current month that | |
# # will use keys like "1 - January", "3 - March". | |
# select_month(Date.today, add_month_numbers: true) | |
# | |
# # Generates a select field for months that defaults to the current month that | |
# # will use keys like "Jan", "Mar". | |
# select_month(Date.today, use_short_month: true) | |
# | |
# # Generates a select field for months that defaults to the current month that | |
# # will use keys like "Januar", "Marts." | |
# select_month(Date.today, use_month_names: %w(Januar Februar Marts ...)) | |
# | |
# # Generates a select field for months that defaults to the current month that | |
# # will use keys with two digit numbers like "01", "03". | |
# select_month(Date.today, use_two_digit_numbers: true) | |
# | |
# # Generates a select field for months with a custom prompt. Use <tt>prompt: true</tt> for a | |
# # generic prompt. | |
# select_month(14, prompt: 'Choose month') | |
def select_month(date, options = {}, html_options = {}) | |
DateTimeSelector.new(date, options, html_options).select_month | |
end | |
# Returns a select tag with options for each of the five years on each side of the current, which is selected. | |
# The five year radius can be changed using the <tt>:start_year</tt> and <tt>:end_year</tt> keys in the | |
# +options+. Both ascending and descending year lists are supported by making <tt>:start_year</tt> less than or | |
# greater than <tt>:end_year</tt>. The <tt>date</tt> can also be substituted for a year given as a number. | |
# Override the field name using the <tt>:field_name</tt> option, 'year' by default. | |
# | |
# # Generates a select field for years that defaults to the current year that | |
# # has ascending year values. | |
# select_year(Date.today, start_year: 1992, end_year: 2007) | |
# | |
# # Generates a select field for years that defaults to the current year that | |
# # is named 'birth' rather than 'year'. | |
# select_year(Date.today, field_name: 'birth') | |
# | |
# # Generates a select field for years that defaults to the current year that | |
# # has descending year values. | |
# select_year(Date.today, start_year: 2005, end_year: 1900) | |
# | |
# # Generates a select field for years that defaults to the year 2006 that | |
# # has ascending year values. | |
# select_year(2006, start_year: 2000, end_year: 2010) | |
# | |
# # Generates a select field for years with a custom prompt. Use <tt>prompt: true</tt> for a | |
# # generic prompt. | |
# select_year(14, prompt: 'Choose year') | |
def select_year(date, options = {}, html_options = {}) | |
DateTimeSelector.new(date, options, html_options).select_year | |
end | |
# Returns an HTML time tag for the given date or time. | |
# | |
# time_tag Date.today # => | |
# <time datetime="2010-11-04">November 04, 2010</time> | |
# time_tag Time.now # => | |
# <time datetime="2010-11-04T17:55:45+01:00">November 04, 2010 17:55</time> | |
# time_tag Date.yesterday, 'Yesterday' # => | |
# <time datetime="2010-11-03">Yesterday</time> | |
# time_tag Date.today, datetime: Date.today.strftime('%G-W%V') # => | |
# <time datetime="2010-W44">November 04, 2010</time> | |
# | |
# <%= time_tag Time.now do %> | |
# <span>Right now</span> | |
# <% end %> | |
# # => <time datetime="2010-11-04T17:55:45+01:00"><span>Right now</span></time> | |
def time_tag(date_or_time, *args, &block) | |
options = args.extract_options! | |
format = options.delete(:format) || :long | |
content = args.first || I18n.l(date_or_time, format: format) | |
content_tag("time", content, options.reverse_merge(datetime: date_or_time.iso8601), &block) | |
end | |
private | |
def normalize_distance_of_time_argument_to_time(value) | |
if value.is_a?(Numeric) | |
Time.at(value) | |
elsif value.respond_to?(:to_time) | |
value.to_time | |
else | |
raise ArgumentError, "#{value.inspect} can't be converted to a Time value" | |
end | |
end | |
end | |
class DateTimeSelector # :nodoc: | |
include ActionView::Helpers::TagHelper | |
DEFAULT_PREFIX = "date" | |
POSITION = { | |
year: 1, month: 2, day: 3, hour: 4, minute: 5, second: 6 | |
}.freeze | |
AMPM_TRANSLATION = Hash[ | |
[[0, "12 AM"], [1, "01 AM"], [2, "02 AM"], [3, "03 AM"], | |
[4, "04 AM"], [5, "05 AM"], [6, "06 AM"], [7, "07 AM"], | |
[8, "08 AM"], [9, "09 AM"], [10, "10 AM"], [11, "11 AM"], | |
[12, "12 PM"], [13, "01 PM"], [14, "02 PM"], [15, "03 PM"], | |
[16, "04 PM"], [17, "05 PM"], [18, "06 PM"], [19, "07 PM"], | |
[20, "08 PM"], [21, "09 PM"], [22, "10 PM"], [23, "11 PM"]] | |
].freeze | |
def initialize(datetime, options = {}, html_options = {}) | |
@options = options.dup | |
@html_options = html_options.dup | |
@datetime = datetime | |
@options[:datetime_separator] ||= " — " | |
@options[:time_separator] ||= " : " | |
end | |
def select_datetime | |
order = date_order.dup | |
order -= [:hour, :minute, :second] | |
@options[:discard_year] ||= true unless order.include?(:year) | |
@options[:discard_month] ||= true unless order.include?(:month) | |
@options[:discard_day] ||= true if @options[:discard_month] || !order.include?(:day) | |
@options[:discard_minute] ||= true if @options[:discard_hour] | |
@options[:discard_second] ||= true unless @options[:include_seconds] && !@options[:discard_minute] | |
set_day_if_discarded | |
if @options[:tag] && @options[:ignore_date] | |
select_time | |
else | |
[:day, :month, :year].each { |o| order.unshift(o) unless order.include?(o) } | |
order += [:hour, :minute, :second] unless @options[:discard_hour] | |
build_selects_from_types(order) | |
end | |
end | |
def select_date | |
order = date_order.dup | |
@options[:discard_hour] = true | |
@options[:discard_minute] = true | |
@options[:discard_second] = true | |
@options[:discard_year] ||= true unless order.include?(:year) | |
@options[:discard_month] ||= true unless order.include?(:month) | |
@options[:discard_day] ||= true if @options[:discard_month] || !order.include?(:day) | |
set_day_if_discarded | |
[:day, :month, :year].each { |o| order.unshift(o) unless order.include?(o) } | |
build_selects_from_types(order) | |
end | |
def select_time | |
order = [] | |
@options[:discard_month] = true | |
@options[:discard_year] = true | |
@options[:discard_day] = true | |
@options[:discard_second] ||= true unless @options[:include_seconds] | |
order += [:year, :month, :day] unless @options[:ignore_date] | |
order += [:hour, :minute] | |
order << :second if @options[:include_seconds] | |
build_selects_from_types(order) | |
end | |
def select_second | |
if @options[:use_hidden] || @options[:discard_second] | |
build_hidden(:second, sec) if @options[:include_seconds] | |
else | |
build_options_and_select(:second, sec) | |
end | |
end | |
def select_minute | |
if @options[:use_hidden] || @options[:discard_minute] | |
build_hidden(:minute, min) | |
else | |
build_options_and_select(:minute, min, step: @options[:minute_step]) | |
end | |
end | |
def select_hour | |
if @options[:use_hidden] || @options[:discard_hour] | |
build_hidden(:hour, hour) | |
else | |
options = {} | |
options[:ampm] = @options[:ampm] || false | |
options[:start] = @options[:start_hour] || 0 | |
options[:end] = @options[:end_hour] || 23 | |
build_options_and_select(:hour, hour, options) | |
end | |
end | |
def select_day | |
if @options[:use_hidden] || @options[:discard_day] | |
build_hidden(:day, day || 1) | |
else | |
build_select(:day, build_day_options(day)) | |
end | |
end | |
def select_month | |
if @options[:use_hidden] || @options[:discard_month] | |
build_hidden(:month, month || 1) | |
else | |
month_options = [] | |
1.upto(12) do |month_number| | |
options = { value: month_number } | |
options[:selected] = "selected" if month == month_number | |
month_options << content_tag("option", month_name(month_number), options) + "\n" | |
end | |
build_select(:month, month_options.join) | |
end | |
end | |
def select_year | |
if !year || @datetime == 0 | |
val = "1" | |
middle_year = Date.today.year | |
else | |
val = middle_year = year | |
end | |
if @options[:use_hidden] || @options[:discard_year] | |
build_hidden(:year, val) | |
else | |
options = {} | |
options[:start] = @options[:start_year] || middle_year - 5 | |
options[:end] = @options[:end_year] || middle_year + 5 | |
options[:step] = options[:start] < options[:end] ? 1 : -1 | |
max_years_allowed = @options[:max_years_allowed] || 1000 | |
if (options[:end] - options[:start]).abs > max_years_allowed | |
raise ArgumentError, "There are too many years options to be built. Are you sure you haven't mistyped something? You can provide the :max_years_allowed parameter." | |
end | |
build_select(:year, build_year_options(val, options)) | |
end | |
end | |
private | |
%w( sec min hour day month year ).each do |method| | |
define_method(method) do | |
case @datetime | |
when Hash then @datetime[method.to_sym] | |
when Numeric then @datetime | |
when nil then nil | |
else @datetime.send(method) | |
end | |
end | |
end | |
# If the day is hidden, the day should be set to the 1st so all month and year choices are | |
# valid. Otherwise, February 31st or February 29th, 2011 can be selected, which are invalid. | |
def set_day_if_discarded | |
if @datetime && @options[:discard_day] | |
@datetime = @datetime.change(day: 1) | |
end | |
end | |
# Returns translated month names, but also ensures that a custom month | |
# name array has a leading +nil+ element. | |
def month_names | |
@month_names ||= begin | |
month_names = @options[:use_month_names] || translated_month_names | |
month_names.unshift(nil) if month_names.size < 13 | |
month_names | |
end | |
end | |
# Returns translated month names. | |
# => [nil, "January", "February", "March", | |
# "April", "May", "June", "July", | |
# "August", "September", "October", | |
# "November", "December"] | |
# | |
# If <tt>:use_short_month</tt> option is set | |
# => [nil, "Jan", "Feb", "Mar", "Apr", "May", "Jun", | |
# "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] | |
def translated_month_names | |
key = @options[:use_short_month] ? :'date.abbr_month_names' : :'date.month_names' | |
I18n.translate(key, locale: @options[:locale]) | |
end | |
# Looks up day names by number. | |
# | |
# day_name(1) # => 1 | |
# | |
# If the <tt>use_two_digit_numbers: true</tt> option is passed to DateTimeSelector: | |
# | |
# day_name(1) # => "01" | |
# | |
# If the <tt>day_format: ->(day) { day.ordinalize }</tt> option is passed to DateTimeSelector: | |
# | |
# day_name(1) # => "1st" | |
def day_name(number) | |
if day_format_lambda = @options[:day_format] | |
day_format_lambda.call(number) | |
elsif @options[:use_two_digit_numbers] | |
"%02d" % number | |
else | |
number | |
end | |
end | |
# Looks up month names by number (1-based): | |
# | |
# month_name(1) # => "January" | |
# | |
# If the <tt>:use_month_numbers</tt> option is passed: | |
# | |
# month_name(1) # => 1 | |
# | |
# If the <tt>:use_two_digit_numbers</tt> option is passed: | |
# | |
# month_name(1) # => '01' | |
# | |
# If the <tt>:add_month_numbers</tt> option is passed: | |
# | |
# month_name(1) # => "1 - January" | |
# | |
# If the <tt>:month_format_string</tt> option is passed: | |
# | |
# month_name(1) # => "January (01)" | |
# | |
# depending on the format string. | |
def month_name(number) | |
if @options[:use_month_numbers] | |
number | |
elsif @options[:use_two_digit_numbers] | |
"%02d" % number | |
elsif @options[:add_month_numbers] | |
"#{number} - #{month_names[number]}" | |
elsif format_string = @options[:month_format_string] | |
format_string % { number: number, name: month_names[number] } | |
else | |
month_names[number] | |
end | |
end | |
# Looks up year names by number. | |
# | |
# year_name(1998) # => 1998 | |
# | |
# If the <tt>:year_format</tt> option is passed: | |
# | |
# year_name(1998) # => "Heisei 10" | |
def year_name(number) | |
if year_format_lambda = @options[:year_format] | |
year_format_lambda.call(number) | |
else | |
number | |
end | |
end | |
def date_order | |
@date_order ||= @options[:order] || translated_date_order | |
end | |
def translated_date_order | |
date_order = I18n.translate(:'date.order', locale: @options[:locale], default: []) | |
date_order = date_order.map(&:to_sym) | |
forbidden_elements = date_order - [:year, :month, :day] | |
if forbidden_elements.any? | |
raise StandardError, | |
"#{@options[:locale]}.date.order only accepts :year, :month and :day" | |
end | |
date_order | |
end | |
# Build full select tag from date type and options. | |
def build_options_and_select(type, selected, options = {}) | |
build_select(type, build_options(selected, options)) | |
end | |
# Build select option HTML from date value and options. | |
# build_options(15, start: 1, end: 31) | |
# => "<option value="1">1</option> | |
# <option value="2">2</option> | |
# <option value="3">3</option>..." | |
# | |
# If <tt>use_two_digit_numbers: true</tt> option is passed | |
# build_options(15, start: 1, end: 31, use_two_digit_numbers: true) | |
# => "<option value="1">01</option> | |
# <option value="2">02</option> | |
# <option value="3">03</option>..." | |
# | |
# If <tt>:step</tt> options is passed | |
# build_options(15, start: 1, end: 31, step: 2) | |
# => "<option value="1">1</option> | |
# <option value="3">3</option> | |
# <option value="5">5</option>..." | |
def build_options(selected, options = {}) | |
options = { | |
leading_zeros: true, ampm: false, use_two_digit_numbers: false | |
}.merge!(options) | |
start = options.delete(:start) || 0 | |
stop = options.delete(:end) || 59 | |
step = options.delete(:step) || 1 | |
leading_zeros = options.delete(:leading_zeros) | |
select_options = [] | |
start.step(stop, step) do |i| | |
value = leading_zeros ? sprintf("%02d", i) : i | |
tag_options = { value: value } | |
tag_options[:selected] = "selected" if selected == i | |
text = options[:use_two_digit_numbers] ? sprintf("%02d", i) : value | |
text = options[:ampm] ? AMPM_TRANSLATION[i] : text | |
select_options << content_tag("option", text, tag_options) | |
end | |
(select_options.join("\n") + "\n").html_safe | |
end | |
# Build select option HTML for day. | |
# build_day_options(2) | |
# => "<option value="1">1</option> | |
# <option value="2" selected="selected">2</option> | |
# <option value="3">3</option>..." | |
# | |
# If <tt>day_format: ->(day) { day.ordinalize }</tt> option is passed to DateTimeSelector | |
# build_day_options(2) | |
# => "<option value="1">1st</option> | |
# <option value="2" selected="selected">2nd</option> | |
# <option value="3">3rd</option>..." | |
# | |
# If <tt>use_two_digit_numbers: true</tt> option is passed to DateTimeSelector | |
# build_day_options(2) | |
# => "<option value="1">01</option> | |
# <option value="2" selected="selected">02</option> | |
# <option value="3">03</option>..." | |
def build_day_options(selected) | |
select_options = [] | |
(1..31).each do |value| | |
tag_options = { value: value } | |
tag_options[:selected] = "selected" if selected == value | |
text = day_name(value) | |
select_options << content_tag("option", text, tag_options) | |
end | |
(select_options.join("\n") + "\n").html_safe | |
end | |
# Build select option HTML for year. | |
# build_year_options(1998, start: 1998, end: 2000) | |
# => "<option value="1998" selected="selected">1998</option> | |
# <option value="1999">1999</option> | |
# <option value="2000">2000</option>" | |
def build_year_options(selected, options = {}) | |
start = options.delete(:start) | |
stop = options.delete(:end) | |
step = options.delete(:step) | |
select_options = [] | |
start.step(stop, step) do |value| | |
tag_options = { value: value } | |
tag_options[:selected] = "selected" if selected == value | |
text = year_name(value) | |
select_options << content_tag("option", text, tag_options) | |
end | |
(select_options.join("\n") + "\n").html_safe | |
end | |
# Builds select tag from date type and HTML select options. | |
# build_select(:month, "<option value="1">January</option>...") | |
# => "<select id="post_written_on_2i" name="post[written_on(2i)]"> | |
# <option value="1">January</option>... | |
# </select>" | |
def build_select(type, select_options_as_html) | |
select_options = { | |
id: input_id_from_type(type), | |
name: input_name_from_type(type) | |
}.merge!(@html_options) | |
select_options[:disabled] = "disabled" if @options[:disabled] | |
select_options[:class] = css_class_attribute(type, select_options[:class], @options[:with_css_classes]) if @options[:with_css_classes] | |
select_html = +"\n" | |
select_html << content_tag("option", "", value: "", label: " ") + "\n" if @options[:include_blank] | |
select_html << prompt_option_tag(type, @options[:prompt]) + "\n" if @options[:prompt] | |
select_html << select_options_as_html | |
(content_tag("select", select_html.html_safe, select_options) + "\n").html_safe | |
end | |
# Builds the CSS class value for the select element | |
# css_class_attribute(:year, 'date optional', { year: 'my-year' }) | |
# => "date optional my-year" | |
def css_class_attribute(type, html_options_class, options) # :nodoc: | |
css_class = \ | |
case options | |
when Hash | |
options[type.to_sym] | |
else | |
type | |
end | |
[html_options_class, css_class].compact.join(" ") | |
end | |
# Builds a prompt option tag with supplied options or from default options. | |
# prompt_option_tag(:month, prompt: 'Select month') | |
# => "<option value="">Select month</option>" | |
def prompt_option_tag(type, options) | |
prompt = \ | |
case options | |
when Hash | |
default_options = { year: false, month: false, day: false, hour: false, minute: false, second: false } | |
default_options.merge!(options)[type.to_sym] | |
when String | |
options | |
else | |
I18n.translate(:"datetime.prompts.#{type}", locale: @options[:locale]) | |
end | |
prompt ? content_tag("option", prompt, value: "") : "" | |
end | |
# Builds hidden input tag for date part and value. | |
# build_hidden(:year, 2008) | |
# => "<input type="hidden" id="date_year" name="date[year]" value="2008" autocomplete="off" />" | |
def build_hidden(type, value) | |
select_options = { | |
type: "hidden", | |
id: input_id_from_type(type), | |
name: input_name_from_type(type), | |
value: value, | |
autocomplete: "off" | |
}.merge!(@html_options.slice(:disabled)) | |
select_options[:disabled] = "disabled" if @options[:disabled] | |
tag(:input, select_options) + "\n".html_safe | |
end | |
# Returns the name attribute for the input tag. | |
# => post[written_on(1i)] | |
def input_name_from_type(type) | |
prefix = @options[:prefix] || ActionView::Helpers::DateTimeSelector::DEFAULT_PREFIX | |
prefix += "[#{@options[:index]}]" if @options.has_key?(:index) | |
field_name = @options[:field_name] || type.to_s | |
if @options[:include_position] | |
field_name += "(#{ActionView::Helpers::DateTimeSelector::POSITION[type]}i)" | |
end | |
@options[:discard_type] ? prefix : "#{prefix}[#{field_name}]" | |
end | |
# Returns the id attribute for the input tag. | |
# => "post_written_on_1i" | |
def input_id_from_type(type) | |
id = input_name_from_type(type).gsub(/([\[(])|(\]\[)/, "_").gsub(/[\])]/, "") | |
id = @options[:namespace] + "_" + id if @options[:namespace] | |
id | |
end | |
# Given an ordering of datetime components, create the selection HTML | |
# and join them with their appropriate separators. | |
def build_selects_from_types(order) | |
select = +"" | |
first_visible = order.find { |type| !@options[:"discard_#{type}"] } | |
order.reverse_each do |type| | |
separator = separator(type) unless type == first_visible # don't add before first visible field | |
select.insert(0, separator.to_s + public_send("select_#{type}").to_s) | |
end | |
select.html_safe | |
end | |
# Returns the separator for a given datetime component. | |
def separator(type) | |
return "" if @options[:use_hidden] | |
case type | |
when :year, :month, :day | |
@options[:"discard_#{type}"] ? "" : @options[:date_separator] | |
when :hour | |
(@options[:discard_year] && @options[:discard_day]) ? "" : @options[:datetime_separator] | |
when :minute, :second | |
@options[:"discard_#{type}"] ? "" : @options[:time_separator] | |
end | |
end | |
end | |
class FormBuilder | |
# Wraps ActionView::Helpers::DateHelper#date_select for form builders: | |
# | |
# <%= form_for @person do |f| %> | |
# <%= f.date_select :birth_date %> | |
# <%= f.submit %> | |
# <% end %> | |
# | |
# Please refer to the documentation of the base helper for details. | |
def date_select(method, options = {}, html_options = {}) | |
@template.date_select(@object_name, method, objectify_options(options), html_options) | |
end | |
# Wraps ActionView::Helpers::DateHelper#time_select for form builders: | |
# | |
# <%= form_for @race do |f| %> | |
# <%= f.time_select :average_lap %> | |
# <%= f.submit %> | |
# <% end %> | |
# | |
# Please refer to the documentation of the base helper for details. | |
def time_select(method, options = {}, html_options = {}) | |
@template.time_select(@object_name, method, objectify_options(options), html_options) | |
end | |
# Wraps ActionView::Helpers::DateHelper#datetime_select for form builders: | |
# | |
# <%= form_for @person do |f| %> | |
# <%= f.datetime_select :last_request_at %> | |
# <%= f.submit %> | |
# <% end %> | |
# | |
# Please refer to the documentation of the base helper for details. | |
def datetime_select(method, options = {}, html_options = {}) | |
@template.datetime_select(@object_name, method, objectify_options(options), html_options) | |
end | |
end | |
end | |
end |
# frozen_string_literal: true | |
require "abstract_unit" | |
require "active_support/core_ext/integer/time" | |
class DateHelperDistanceOfTimeInWordsI18nTests < ActiveSupport::TestCase | |
include ActionView::Helpers::DateHelper | |
attr_reader :request | |
def setup | |
@from = Time.utc(2004, 6, 6, 21, 45, 0) | |
end | |
# distance_of_time_in_words | |
def test_distance_of_time_in_words_calls_i18n | |
{ # with include_seconds | |
[2.seconds, { include_seconds: true }] => [:'less_than_x_seconds', 5], | |
[9.seconds, { include_seconds: true }] => [:'less_than_x_seconds', 10], | |
[19.seconds, { include_seconds: true }] => [:'less_than_x_seconds', 20], | |
[30.seconds, { include_seconds: true }] => [:'half_a_minute', nil], | |
[59.seconds, { include_seconds: true }] => [:'less_than_x_minutes', 1], | |
[60.seconds, { include_seconds: true }] => [:'x_minutes', 1], | |
# without include_seconds | |
[29.seconds, { include_seconds: false }] => [:'less_than_x_minutes', 1], | |
[60.seconds, { include_seconds: false }] => [:'x_minutes', 1], | |
[44.minutes, { include_seconds: false }] => [:'x_minutes', 44], | |
[61.minutes, { include_seconds: false }] => [:'about_x_hours', 1], | |
[24.hours, { include_seconds: false }] => [:'x_days', 1], | |
[30.days, { include_seconds: false }] => [:'about_x_months', 1], | |
[60.days, { include_seconds: false }] => [:'x_months', 2], | |
[1.year, { include_seconds: false }] => [:'about_x_years', 1], | |
[3.years + 6.months, { include_seconds: false }] => [:'over_x_years', 3], | |
[3.years + 10.months, { include_seconds: false }] => [:'almost_x_years', 4] | |
}.each do |passed, expected| | |
assert_distance_of_time_in_words_translates_key passed, expected | |
end | |
end | |
def test_distance_of_time_in_words_calls_i18n_with_custom_scope | |
{ | |
[30.days, { scope: :'datetime.distance_in_words_ago' }] => [:'about_x_months', 1], | |
[60.days, { scope: :'datetime.distance_in_words_ago' }] => [:'x_months', 2], | |
}.each do |passed, expected| | |
assert_distance_of_time_in_words_translates_key(passed, expected, scope: :'datetime.distance_in_words_ago') | |
end | |
end | |
def test_time_ago_in_words_passes_locale | |
assert_called_with(I18n, :t, [:less_than_x_minutes, scope: :'datetime.distance_in_words', count: 1, locale: "ru"]) do | |
time_ago_in_words(15.seconds.ago, locale: "ru") | |
end | |
end | |
def test_distance_of_time_pluralizations | |
{ [:'less_than_x_seconds', 1] => "less than 1 second", | |
[:'less_than_x_seconds', 2] => "less than 2 seconds", | |
[:'less_than_x_minutes', 1] => "less than a minute", | |
[:'less_than_x_minutes', 2] => "less than 2 minutes", | |
[:'x_minutes', 1] => "1 minute", | |
[:'x_minutes', 2] => "2 minutes", | |
[:'about_x_hours', 1] => "about 1 hour", | |
[:'about_x_hours', 2] => "about 2 hours", | |
[:'x_days', 1] => "1 day", | |
[:'x_days', 2] => "2 days", | |
[:'about_x_years', 1] => "about 1 year", | |
[:'about_x_years', 2] => "about 2 years", | |
[:'over_x_years', 1] => "over 1 year", | |
[:'over_x_years', 2] => "over 2 years" | |
}.each do |args, expected| | |
key, count = *args | |
assert_equal expected, I18n.t(key, count: count, scope: "datetime.distance_in_words") | |
end | |
end | |
def assert_distance_of_time_in_words_translates_key(passed, expected, expected_options = {}) | |
diff, passed_options = *passed | |
key, count = *expected | |
to = @from + diff | |
options = { locale: "en", scope: :'datetime.distance_in_words' }.merge!(expected_options) | |
options[:count] = count if count | |
assert_called_with(I18n, :t, [key, options]) do | |
distance_of_time_in_words(@from, to, passed_options.merge(locale: "en")) | |
end | |
end | |
end | |
class DateHelperSelectTagsI18nTests < ActiveSupport::TestCase | |
include ActionView::Helpers::DateHelper | |
attr_reader :request | |
# select_month | |
def test_select_month_given_use_month_names_option_does_not_translate_monthnames | |
assert_not_called(I18n, :translate) do | |
select_month(8, locale: "en", use_month_names: Date::MONTHNAMES) | |
end | |
end | |
def test_select_month_translates_monthnames | |
assert_called_with(I18n, :translate, [:'date.month_names', locale: "en"], returns: Date::MONTHNAMES) do | |
select_month(8, locale: "en") | |
end | |
end | |
def test_select_month_given_use_short_month_option_translates_abbr_monthnames | |
assert_called_with(I18n, :translate, [:'date.abbr_month_names', locale: "en"], returns: Date::ABBR_MONTHNAMES) do | |
select_month(8, locale: "en", use_short_month: true) | |
end | |
end | |
def test_date_or_time_select_translates_prompts | |
prompt_defaults = { year: "Year", month: "Month", day: "Day", hour: "Hour", minute: "Minute", second: "Seconds" } | |
defaults = { [:'date.order', locale: "en", default: []] => %w(year month day) } | |
prompt_defaults.each do |key, prompt| | |
defaults[[("datetime.prompts." + key.to_s).to_sym, locale: "en"]] = prompt | |
end | |
prompts_check = -> (prompt, x) do | |
@prompt_called ||= 0 | |
return_value = defaults[[prompt, x]] | |
@prompt_called += 1 if return_value.present? | |
return_value | |
end | |
I18n.stub(:translate, prompts_check) do | |
datetime_select("post", "updated_at", locale: "en", include_seconds: true, prompt: true, use_month_names: Date::MONTHNAMES) | |
end | |
assert_equal defaults.count, @prompt_called | |
end | |
# date_or_time_select | |
def test_date_or_time_select_given_an_order_options_does_not_translate_order | |
assert_not_called(I18n, :translate) do | |
datetime_select("post", "updated_at", order: [:year, :month, :day], locale: "en", use_month_names: Date::MONTHNAMES) | |
end | |
end | |
def test_date_or_time_select_given_no_order_options_translates_order | |
assert_called_with(I18n, :translate, [ [:'date.order', locale: "en", default: []], [:"date.month_names", { locale: "en" }] ], returns: %w(year month day)) do | |
datetime_select("post", "updated_at", locale: "en") | |
end | |
end | |
def test_date_or_time_select_given_invalid_order | |
assert_called_with(I18n, :translate, [:'date.order', locale: "en", default: []], returns: %w(invalid month day)) do | |
assert_raise StandardError do | |
datetime_select("post", "updated_at", locale: "en") | |
end | |
end | |
end | |
def test_date_or_time_select_given_symbol_keys | |
assert_called_with(I18n, :translate, [ [:'date.order', locale: "en", default: []], [:"date.month_names", { locale: "en" }] ], returns: [:year, :month, :day]) do | |
datetime_select("post", "updated_at", locale: "en") | |
end | |
end | |
end |
# frozen_string_literal: true | |
require "abstract_unit" | |
require "active_support/core_ext/integer/time" | |
class DateHelperTest < ActionView::TestCase | |
tests ActionView::Helpers::DateHelper | |
silence_warnings do | |
Post = Struct.new("Post", :id, :written_on, :updated_at) | |
Post.class_eval do | |
def id | |
123 | |
end | |
def id_before_type_cast | |
123 | |
end | |
def to_param | |
"123" | |
end | |
end | |
ComposedDate = Struct.new("ComposedDate", :year, :month, :day) | |
end | |
def assert_distance_of_time_in_words(from, to = nil) | |
to ||= from | |
# 0..1 minute with :include_seconds => true | |
assert_equal "less than 5 seconds", distance_of_time_in_words(from, to + 0.seconds, include_seconds: true) | |
assert_equal "less than 5 seconds", distance_of_time_in_words(from, to + 4.seconds, include_seconds: true) | |
assert_equal "less than 10 seconds", distance_of_time_in_words(from, to + 5.seconds, include_seconds: true) | |
assert_equal "less than 10 seconds", distance_of_time_in_words(from, to + 9.seconds, include_seconds: true) | |
assert_equal "less than 20 seconds", distance_of_time_in_words(from, to + 10.seconds, include_seconds: true) | |
assert_equal "less than 20 seconds", distance_of_time_in_words(from, to + 19.seconds, include_seconds: true) | |
assert_equal "half a minute", distance_of_time_in_words(from, to + 20.seconds, include_seconds: true) | |
assert_equal "half a minute", distance_of_time_in_words(from, to + 39.seconds, include_seconds: true) | |
assert_equal "less than a minute", distance_of_time_in_words(from, to + 40.seconds, include_seconds: true) | |
assert_equal "less than a minute", distance_of_time_in_words(from, to + 59.seconds, include_seconds: true) | |
assert_equal "1 minute", distance_of_time_in_words(from, to + 60.seconds, include_seconds: true) | |
assert_equal "1 minute", distance_of_time_in_words(from, to + 89.seconds, include_seconds: true) | |
# 0..1 minute with :include_seconds => false | |
assert_equal "less than a minute", distance_of_time_in_words(from, to + 0.seconds, include_seconds: false) | |
assert_equal "less than a minute", distance_of_time_in_words(from, to + 4.seconds, include_seconds: false) | |
assert_equal "less than a minute", distance_of_time_in_words(from, to + 5.seconds, include_seconds: false) | |
assert_equal "less than a minute", distance_of_time_in_words(from, to + 9.seconds, include_seconds: false) | |
assert_equal "less than a minute", distance_of_time_in_words(from, to + 10.seconds, include_seconds: false) | |
assert_equal "less than a minute", distance_of_time_in_words(from, to + 19.seconds, include_seconds: false) | |
assert_equal "less than a minute", distance_of_time_in_words(from, to + 20.seconds, include_seconds: false) | |
assert_equal "1 minute", distance_of_time_in_words(from, to + 39.seconds, include_seconds: false) | |
assert_equal "1 minute", distance_of_time_in_words(from, to + 40.seconds, include_seconds: false) | |
assert_equal "1 minute", distance_of_time_in_words(from, to + 59.seconds, include_seconds: false) | |
assert_equal "1 minute", distance_of_time_in_words(from, to + 60.seconds, include_seconds: false) | |
assert_equal "1 minute", distance_of_time_in_words(from, to + 89.seconds, include_seconds: false) | |
# Note that we are including a 30-second boundary around the interval we | |
# want to test. For instance, "1 minute" is actually 30s to 1m29s. The | |
# reason for doing this is simple -- in `distance_of_time_to_words`, when we | |
# take the distance between our two Time objects in seconds and convert it | |
# to minutes, we round the number. So 29s gets rounded down to 0m, 30s gets | |
# rounded up to 1m, and 1m29s gets rounded down to 1m. A similar thing | |
# happens with the other cases. | |
# First case 0..1 minute | |
assert_equal "less than a minute", distance_of_time_in_words(from, to + 0.seconds) | |
assert_equal "less than a minute", distance_of_time_in_words(from, to + 29.seconds) | |
assert_equal "1 minute", distance_of_time_in_words(from, to + 30.seconds) | |
assert_equal "1 minute", distance_of_time_in_words(from, to + 1.minutes + 29.seconds) | |
# 2 minutes up to 45 minutes | |
assert_equal "2 minutes", distance_of_time_in_words(from, to + 1.minutes + 30.seconds) | |
assert_equal "44 minutes", distance_of_time_in_words(from, to + 44.minutes + 29.seconds) | |
# 45 minutes up to 90 minutes | |
assert_equal "about 1 hour", distance_of_time_in_words(from, to + 44.minutes + 30.seconds) | |
assert_equal "about 1 hour", distance_of_time_in_words(from, to + 89.minutes + 29.seconds) | |
# 90 minutes up to 24 hours | |
assert_equal "about 2 hours", distance_of_time_in_words(from, to + 89.minutes + 30.seconds) | |
assert_equal "about 24 hours", distance_of_time_in_words(from, to + 23.hours + 59.minutes + 29.seconds) | |
# 24 hours up to 42 hours | |
assert_equal "1 day", distance_of_time_in_words(from, to + 23.hours + 59.minutes + 30.seconds) | |
assert_equal "1 day", distance_of_time_in_words(from, to + 41.hours + 59.minutes + 29.seconds) | |
# 42 hours up to 30 days | |
assert_equal "2 days", distance_of_time_in_words(from, to + 41.hours + 59.minutes + 30.seconds) | |
assert_equal "3 days", distance_of_time_in_words(from, to + 2.days + 12.hours) | |
assert_equal "30 days", distance_of_time_in_words(from, to + 29.days + 23.hours + 59.minutes + 29.seconds) | |
# 30 days up to 60 days | |
assert_equal "about 1 month", distance_of_time_in_words(from, to + 29.days + 23.hours + 59.minutes + 30.seconds) | |
assert_equal "about 1 month", distance_of_time_in_words(from, to + 44.days + 23.hours + 59.minutes + 29.seconds) | |
assert_equal "about 2 months", distance_of_time_in_words(from, to + 44.days + 23.hours + 59.minutes + 30.seconds) | |
assert_equal "about 2 months", distance_of_time_in_words(from, to + 59.days + 23.hours + 59.minutes + 29.seconds) | |
# 60 days up to 365 days | |
assert_equal "2 months", distance_of_time_in_words(from, to + 59.days + 23.hours + 59.minutes + 30.seconds) | |
assert_equal "12 months", distance_of_time_in_words(from, to + 1.years - 31.seconds) | |
# >= 365 days | |
assert_equal "about 1 year", distance_of_time_in_words(from, to + 1.years - 30.seconds) | |
assert_equal "about 1 year", distance_of_time_in_words(from, to + 1.years + 3.months - 1.day) | |
assert_equal "over 1 year", distance_of_time_in_words(from, to + 1.years + 6.months) | |
assert_equal "almost 2 years", distance_of_time_in_words(from, to + 2.years - 3.months + 1.day) | |
assert_equal "about 2 years", distance_of_time_in_words(from, to + 2.years + 3.months - 1.day) | |
assert_equal "over 2 years", distance_of_time_in_words(from, to + 2.years + 3.months + 1.day) | |
assert_equal "over 2 years", distance_of_time_in_words(from, to + 2.years + 9.months - 1.day) | |
assert_equal "almost 3 years", distance_of_time_in_words(from, to + 2.years + 9.months + 1.day) | |
assert_equal "almost 5 years", distance_of_time_in_words(from, to + 5.years - 3.months + 1.day) | |
assert_equal "about 5 years", distance_of_time_in_words(from, to + 5.years + 3.months - 1.day) | |
assert_equal "over 5 years", distance_of_time_in_words(from, to + 5.years + 3.months + 1.day) | |
assert_equal "over 5 years", distance_of_time_in_words(from, to + 5.years + 9.months - 1.day) | |
assert_equal "almost 6 years", distance_of_time_in_words(from, to + 5.years + 9.months + 1.day) | |
assert_equal "almost 10 years", distance_of_time_in_words(from, to + 10.years - 3.months + 1.day) | |
assert_equal "about 10 years", distance_of_time_in_words(from, to + 10.years + 3.months - 1.day) | |
assert_equal "over 10 years", distance_of_time_in_words(from, to + 10.years + 3.months + 1.day) | |
assert_equal "over 10 years", distance_of_time_in_words(from, to + 10.years + 9.months - 1.day) | |
assert_equal "almost 11 years", distance_of_time_in_words(from, to + 10.years + 9.months + 1.day) | |
# test to < from | |
assert_equal "about 4 hours", distance_of_time_in_words(from + 4.hours, to) | |
assert_equal "less than 20 seconds", distance_of_time_in_words(from + 19.seconds, to, include_seconds: true) | |
assert_equal "less than a minute", distance_of_time_in_words(from + 19.seconds, to, include_seconds: false) | |
end | |
def test_distance_in_words | |
from = Time.utc(2004, 6, 6, 21, 45, 0) | |
assert_distance_of_time_in_words(from) | |
end | |
def test_distance_in_words_with_nil_input | |
assert_raises(ArgumentError) { distance_of_time_in_words(nil) } | |
assert_raises(ArgumentError) { distance_of_time_in_words(0, nil) } | |
end | |
def test_distance_in_words_with_mixed_argument_types | |
assert_equal "1 minute", distance_of_time_in_words(0, Time.at(60)) | |
assert_equal "10 minutes", distance_of_time_in_words(Time.at(600), 0) | |
end | |
def test_distance_in_words_doesnt_use_the_quotient_operator | |
rubinius_skip "Date is written in Ruby and relies on Integer#/" | |
jruby_skip "Date is written in Ruby and relies on Integer#/" | |
# Make sure that we avoid Integer#/ (redefined by mathn) | |
Integer.send :private, :/ | |
from = Time.utc(2004, 6, 6, 21, 45, 0) | |
assert_distance_of_time_in_words(from) | |
ensure | |
Integer.send :public, :/ | |
end | |
def test_time_ago_in_words_passes_include_seconds | |
assert_equal "less than 20 seconds", time_ago_in_words(15.seconds.ago, include_seconds: true) | |
assert_equal "less than a minute", time_ago_in_words(15.seconds.ago, include_seconds: false) | |
end | |
def test_distance_in_words_with_time_zones | |
from = Time.mktime(2004, 6, 6, 21, 45, 0) | |
assert_distance_of_time_in_words(from.in_time_zone("Alaska")) | |
assert_distance_of_time_in_words(from.in_time_zone("Hawaii")) | |
end | |
def test_distance_in_words_with_different_time_zones | |
from = Time.mktime(2004, 6, 6, 21, 45, 0) | |
assert_distance_of_time_in_words( | |
from.in_time_zone("Alaska"), | |
from.in_time_zone("Hawaii") | |
) | |
end | |
def test_distance_in_words_with_dates | |
start_date = Date.new 1975, 1, 31 | |
end_date = Date.new 1977, 1, 31 | |
assert_equal("about 2 years", distance_of_time_in_words(start_date, end_date)) | |
start_date = Date.new 1982, 12, 3 | |
end_date = Date.new 2010, 11, 30 | |
assert_equal("almost 28 years", distance_of_time_in_words(start_date, end_date)) | |
assert_equal("almost 28 years", distance_of_time_in_words(end_date, start_date)) | |
end | |
def test_distance_in_words_with_integers | |
assert_equal "1 minute", distance_of_time_in_words(59) | |
assert_equal "about 1 hour", distance_of_time_in_words(60 * 60) | |
assert_equal "1 minute", distance_of_time_in_words(0, 59) | |
assert_equal "about 1 hour", distance_of_time_in_words(60 * 60, 0) | |
assert_equal "about 3 years", distance_of_time_in_words(10**8) | |
assert_equal "about 3 years", distance_of_time_in_words(0, 10**8) | |
end | |
def test_distance_in_words_with_times | |
assert_equal "1 minute", distance_of_time_in_words(30.seconds) | |
assert_equal "1 minute", distance_of_time_in_words(59.seconds) | |
assert_equal "2 minutes", distance_of_time_in_words(119.seconds) | |
assert_equal "2 minutes", distance_of_time_in_words(1.minute + 59.seconds) | |
assert_equal "3 minutes", distance_of_time_in_words(2.minute + 30.seconds) | |
assert_equal "44 minutes", distance_of_time_in_words(44.minutes + 29.seconds) | |
assert_equal "about 1 hour", distance_of_time_in_words(44.minutes + 30.seconds) | |
assert_equal "about 1 hour", distance_of_time_in_words(60.minutes) | |
# include seconds | |
assert_equal "half a minute", distance_of_time_in_words(39.seconds, 0, include_seconds: true) | |
assert_equal "less than a minute", distance_of_time_in_words(40.seconds, 0, include_seconds: true) | |
assert_equal "less than a minute", distance_of_time_in_words(59.seconds, 0, include_seconds: true) | |
assert_equal "1 minute", distance_of_time_in_words(60.seconds, 0, include_seconds: true) | |
end | |
def test_time_ago_in_words | |
assert_equal "about 1 year", time_ago_in_words(1.year.ago - 1.day) | |
end | |
def test_select_day | |
expected = +%(<select id="date_day" name="date[day]">\n) | |
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_day(Time.mktime(2003, 8, 16)) | |
assert_dom_equal expected, select_day(16) | |
end | |
def test_select_day_with_blank | |
expected = +%(<select id="date_day" name="date[day]">\n) | |
expected << %(<option value="" label=" "></option>\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_day(Time.mktime(2003, 8, 16), include_blank: true) | |
assert_dom_equal expected, select_day(16, include_blank: true) | |
end | |
def test_select_day_nil_with_blank | |
expected = +%(<select id="date_day" name="date[day]">\n) | |
expected << %(<option value="" label=" "></option>\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_day(nil, include_blank: true) | |
end | |
def test_select_day_with_two_digit_numbers | |
expected = +%(<select id="date_day" name="date[day]">\n) | |
expected << %(<option value="1">01</option>\n<option selected="selected" value="2">02</option>\n<option value="3">03</option>\n<option value="4">04</option>\n<option value="5">05</option>\n<option value="6">06</option>\n<option value="7">07</option>\n<option value="8">08</option>\n<option value="9">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_day(Time.mktime(2011, 8, 2), use_two_digit_numbers: true) | |
assert_dom_equal expected, select_day(2, use_two_digit_numbers: true) | |
end | |
def test_select_day_with_day_fomat | |
expected = +%(<select id="date_day" name="date[day]">\n) | |
expected << %(<option value="1">1st</option>\n<option selected="selected" value="2">2nd</option>\n<option value="3">3rd</option>\n<option value="4">4th</option>\n<option value="5">5th</option>\n<option value="6">6th</option>\n<option value="7">7th</option>\n<option value="8">8th</option>\n<option value="9">9th</option>\n<option value="10">10th</option>\n<option value="11">11th</option>\n<option value="12">12th</option>\n<option value="13">13th</option>\n<option value="14">14th</option>\n<option value="15">15th</option>\n<option value="16">16th</option>\n<option value="17">17th</option>\n<option value="18">18th</option>\n<option value="19">19th</option>\n<option value="20">20th</option>\n<option value="21">21st</option>\n<option value="22">22nd</option>\n<option value="23">23rd</option>\n<option value="24">24th</option>\n<option value="25">25th</option>\n<option value="26">26th</option>\n<option value="27">27th</option>\n<option value="28">28th</option>\n<option value="29">29th</option>\n<option value="30">30th</option>\n<option value="31">31st</option>\n) | |
expected << "</select>\n" | |
day_format = ->(day) { ActiveSupport::Inflector.ordinalize(day) } | |
assert_dom_equal expected, select_day(Time.mktime(2011, 8, 2), day_format: day_format) | |
assert_dom_equal expected, select_day(2, day_format: day_format) | |
end | |
def test_select_day_with_html_options | |
expected = +%(<select id="date_day" name="date[day]" class="selector">\n) | |
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_day(Time.mktime(2003, 8, 16), {}, { class: "selector" }) | |
assert_dom_equal expected, select_day(16, {}, { class: "selector" }) | |
end | |
def test_select_day_with_default_prompt | |
expected = +%(<select id="date_day" name="date[day]">\n) | |
expected << %(<option value="">Day</option>\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_day(16, prompt: true) | |
end | |
def test_select_day_with_custom_prompt | |
expected = +%(<select id="date_day" name="date[day]">\n) | |
expected << %(<option value="">Choose day</option>\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_day(16, prompt: "Choose day") | |
end | |
def test_select_day_with_generic_with_css_classes | |
expected = +%(<select id="date_day" name="date[day]" class="day">\n) | |
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_day(16, with_css_classes: true) | |
end | |
def test_select_day_with_custom_with_css_classes | |
expected = +%(<select id="date_day" name="date[day]" class="my-day">\n) | |
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_day(16, with_css_classes: { day: "my-day" }) | |
end | |
def test_select_month | |
expected = +%(<select id="date_month" name="date[month]">\n) | |
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_month(Time.mktime(2003, 8, 16)) | |
assert_dom_equal expected, select_month(8) | |
end | |
def test_select_month_with_two_digit_numbers | |
expected = +%(<select id="date_month" name="date[month]">\n) | |
expected << %(<option value="1">01</option>\n<option value="2">02</option>\n<option value="3">03</option>\n<option value="4">04</option>\n<option value="5">05</option>\n<option value="6">06</option>\n<option value="7">07</option>\n<option value="8" selected="selected">08</option>\n<option value="9">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_month(Time.mktime(2011, 8, 16), use_two_digit_numbers: true) | |
assert_dom_equal expected, select_month(8, use_two_digit_numbers: true) | |
end | |
def test_select_month_with_disabled | |
expected = +%(<select id="date_month" name="date[month]" disabled="disabled">\n) | |
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_month(Time.mktime(2003, 8, 16), disabled: true) | |
assert_dom_equal expected, select_month(8, disabled: true) | |
end | |
def test_select_month_with_field_name_override | |
expected = +%(<select id="date_mois" name="date[mois]">\n) | |
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_month(Time.mktime(2003, 8, 16), field_name: "mois") | |
assert_dom_equal expected, select_month(8, field_name: "mois") | |
end | |
def test_select_month_with_blank | |
expected = +%(<select id="date_month" name="date[month]">\n) | |
expected << %(<option value="" label=" "></option>\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_month(Time.mktime(2003, 8, 16), include_blank: true) | |
assert_dom_equal expected, select_month(8, include_blank: true) | |
end | |
def test_select_month_nil_with_blank | |
expected = +%(<select id="date_month" name="date[month]">\n) | |
expected << %(<option value="" label=" "></option>\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_month(nil, include_blank: true) | |
end | |
def test_select_month_with_numbers | |
expected = +%(<select id="date_month" name="date[month]">\n) | |
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8" selected="selected">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_month(Time.mktime(2003, 8, 16), use_month_numbers: true) | |
assert_dom_equal expected, select_month(8, use_month_numbers: true) | |
end | |
def test_select_month_with_numbers_and_names | |
expected = +%(<select id="date_month" name="date[month]">\n) | |
expected << %(<option value="1">1 - January</option>\n<option value="2">2 - February</option>\n<option value="3">3 - March</option>\n<option value="4">4 - April</option>\n<option value="5">5 - May</option>\n<option value="6">6 - June</option>\n<option value="7">7 - July</option>\n<option value="8" selected="selected">8 - August</option>\n<option value="9">9 - September</option>\n<option value="10">10 - October</option>\n<option value="11">11 - November</option>\n<option value="12">12 - December</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_month(Time.mktime(2003, 8, 16), add_month_numbers: true) | |
assert_dom_equal expected, select_month(8, add_month_numbers: true) | |
end | |
def test_select_month_with_format_string | |
expected = +%(<select id="date_month" name="date[month]">\n) | |
expected << %(<option value="1">January (01)</option>\n<option value="2">February (02)</option>\n<option value="3">March (03)</option>\n<option value="4">April (04)</option>\n<option value="5">May (05)</option>\n<option value="6">June (06)</option>\n<option value="7">July (07)</option>\n<option value="8" selected="selected">August (08)</option>\n<option value="9">September (09)</option>\n<option value="10">October (10)</option>\n<option value="11">November (11)</option>\n<option value="12">December (12)</option>\n) | |
expected << "</select>\n" | |
format_string = "%{name} (%<number>02d)" | |
assert_dom_equal expected, select_month(Time.mktime(2003, 8, 16), month_format_string: format_string) | |
assert_dom_equal expected, select_month(8, month_format_string: format_string) | |
end | |
def test_select_month_with_numbers_and_names_with_abbv | |
expected = +%(<select id="date_month" name="date[month]">\n) | |
expected << %(<option value="1">1 - Jan</option>\n<option value="2">2 - Feb</option>\n<option value="3">3 - Mar</option>\n<option value="4">4 - Apr</option>\n<option value="5">5 - May</option>\n<option value="6">6 - Jun</option>\n<option value="7">7 - Jul</option>\n<option value="8" selected="selected">8 - Aug</option>\n<option value="9">9 - Sep</option>\n<option value="10">10 - Oct</option>\n<option value="11">11 - Nov</option>\n<option value="12">12 - Dec</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_month(Time.mktime(2003, 8, 16), add_month_numbers: true, use_short_month: true) | |
assert_dom_equal expected, select_month(8, add_month_numbers: true, use_short_month: true) | |
end | |
def test_select_month_with_abbv | |
expected = +%(<select id="date_month" name="date[month]">\n) | |
expected << %(<option value="1">Jan</option>\n<option value="2">Feb</option>\n<option value="3">Mar</option>\n<option value="4">Apr</option>\n<option value="5">May</option>\n<option value="6">Jun</option>\n<option value="7">Jul</option>\n<option value="8" selected="selected">Aug</option>\n<option value="9">Sep</option>\n<option value="10">Oct</option>\n<option value="11">Nov</option>\n<option value="12">Dec</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_month(Time.mktime(2003, 8, 16), use_short_month: true) | |
assert_dom_equal expected, select_month(8, use_short_month: true) | |
end | |
def test_select_month_with_custom_names | |
month_names = %w(nil Januar Februar Marts April Maj Juni Juli August September Oktober November December) | |
expected = +%(<select id="date_month" name="date[month]">\n) | |
1.upto(12) { |month| expected << %(<option value="#{month}"#{' selected="selected"' if month == 8}>#{month_names[month]}</option>\n) } | |
expected << "</select>\n" | |
assert_dom_equal expected, select_month(Time.mktime(2003, 8, 16), use_month_names: month_names) | |
assert_dom_equal expected, select_month(8, use_month_names: month_names) | |
end | |
def test_select_month_with_zero_indexed_custom_names | |
month_names = %w(Januar Februar Marts April Maj Juni Juli August September Oktober November December) | |
expected = +%(<select id="date_month" name="date[month]">\n) | |
1.upto(12) { |month| expected << %(<option value="#{month}"#{' selected="selected"' if month == 8}>#{month_names[month - 1]}</option>\n) } | |
expected << "</select>\n" | |
assert_dom_equal expected, select_month(Time.mktime(2003, 8, 16), use_month_names: month_names) | |
assert_dom_equal expected, select_month(8, use_month_names: month_names) | |
end | |
def test_select_month_with_hidden | |
assert_dom_equal "<input type=\"hidden\" id=\"date_month\" name=\"date[month]\" value=\"8\" autocomplete=\"off\" />\n", select_month(8, use_hidden: true) | |
end | |
def test_select_month_with_hidden_and_field_name | |
assert_dom_equal "<input type=\"hidden\" id=\"date_mois\" name=\"date[mois]\" value=\"8\" autocomplete=\"off\" />\n", select_month(8, use_hidden: true, field_name: "mois") | |
end | |
def test_select_month_with_html_options | |
expected = +%(<select id="date_month" name="date[month]" class="selector" accesskey="M">\n) | |
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_month(Time.mktime(2003, 8, 16), {}, { class: "selector", accesskey: "M" }) | |
end | |
def test_select_month_with_default_prompt | |
expected = +%(<select id="date_month" name="date[month]">\n) | |
expected << %(<option value="">Month</option>\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_month(8, prompt: true) | |
end | |
def test_select_month_with_custom_prompt | |
expected = +%(<select id="date_month" name="date[month]">\n) | |
expected << %(<option value="">Choose month</option>\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_month(8, prompt: "Choose month") | |
end | |
def test_select_month_with_generic_with_css_classes | |
expected = +%(<select id="date_month" name="date[month]" class="month">\n) | |
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_month(8, with_css_classes: true) | |
end | |
def test_select_month_with_custom_with_css_classes | |
expected = +%(<select id="date_month" name="date[month]" class="my-month">\n) | |
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_month(8, with_css_classes: { month: "my-month" }) | |
end | |
def test_select_year | |
expected = +%(<select id="date_year" name="date[year]">\n) | |
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_year(Time.mktime(2003, 8, 16), start_year: 2003, end_year: 2005) | |
assert_dom_equal expected, select_year(2003, start_year: 2003, end_year: 2005) | |
end | |
def test_select_year_with_empty_hash_value_and_no_start_year | |
travel_to Time.new(2019, 1, 1) do | |
expected = +%(<select id="date_year" name="date[year]">\n) | |
expected << %(<option value="2014">2014</option>\n<option value="2015">2015</option>\n<option value="2016">2016</option>\n<option value="2017">2017</option>\n<option value="2018">2018</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_year({ year: nil, month: 4, day: nil }, { end_year: 2018 }) | |
end | |
end | |
def test_select_year_with_empty_object_value_and_no_start_year | |
travel_to Time.new(2019, 1, 1) do | |
expected = +%(<select id="date_year" name="date[year]">\n) | |
expected << %(<option value="2014">2014</option>\n<option value="2015">2015</option>\n<option value="2016">2016</option>\n<option value="2017">2017</option>\n<option value="2018">2018</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_year(ComposedDate.new(nil, 4, nil), end_year: 2018) | |
end | |
end | |
def test_select_year_with_disabled | |
expected = +%(<select id="date_year" name="date[year]" disabled="disabled">\n) | |
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_year(Time.mktime(2003, 8, 16), disabled: true, start_year: 2003, end_year: 2005) | |
assert_dom_equal expected, select_year(2003, disabled: true, start_year: 2003, end_year: 2005) | |
end | |
def test_select_year_with_field_name_override | |
expected = +%(<select id="date_annee" name="date[annee]">\n) | |
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_year(Time.mktime(2003, 8, 16), start_year: 2003, end_year: 2005, field_name: "annee") | |
assert_dom_equal expected, select_year(2003, start_year: 2003, end_year: 2005, field_name: "annee") | |
end | |
def test_select_year_with_type_discarding | |
expected = +%(<select id="date_year" name="date_year">\n) | |
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_year( | |
Time.mktime(2003, 8, 16), prefix: "date_year", discard_type: true, start_year: 2003, end_year: 2005) | |
assert_dom_equal expected, select_year( | |
2003, prefix: "date_year", discard_type: true, start_year: 2003, end_year: 2005) | |
end | |
def test_select_year_descending | |
expected = +%(<select id="date_year" name="date[year]">\n) | |
expected << %(<option value="2005" selected="selected">2005</option>\n<option value="2004">2004</option>\n<option value="2003">2003</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_year(Time.mktime(2005, 8, 16), start_year: 2005, end_year: 2003) | |
assert_dom_equal expected, select_year(2005, start_year: 2005, end_year: 2003) | |
end | |
def test_select_year_with_hidden | |
assert_dom_equal "<input type=\"hidden\" id=\"date_year\" name=\"date[year]\" value=\"2007\" autocomplete=\"off\" />\n", select_year(2007, use_hidden: true) | |
end | |
def test_select_year_with_hidden_and_field_name | |
assert_dom_equal "<input type=\"hidden\" id=\"date_anno\" name=\"date[anno]\" value=\"2007\" autocomplete=\"off\" />\n", select_year(2007, use_hidden: true, field_name: "anno") | |
end | |
def test_select_year_with_html_options | |
expected = +%(<select id="date_year" name="date[year]" class="selector" accesskey="M">\n) | |
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_year(Time.mktime(2003, 8, 16), { start_year: 2003, end_year: 2005 }, { class: "selector", accesskey: "M" }) | |
end | |
def test_select_year_with_default_prompt | |
expected = +%(<select id="date_year" name="date[year]">\n) | |
expected << %(<option value="">Year</option>\n<option value="2003">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_year(nil, start_year: 2003, end_year: 2005, prompt: true) | |
end | |
def test_select_year_with_custom_prompt | |
expected = +%(<select id="date_year" name="date[year]">\n) | |
expected << %(<option value="">Choose year</option>\n<option value="2003">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_year(nil, start_year: 2003, end_year: 2005, prompt: "Choose year") | |
end | |
def test_select_year_with_generic_with_css_classes | |
expected = +%(<select id="date_year" name="date[year]" class="year">\n) | |
expected << %(<option value="2003">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_year(nil, start_year: 2003, end_year: 2005, with_css_classes: true) | |
end | |
def test_select_year_with_custom_with_css_classes | |
expected = +%(<select id="date_year" name="date[year]" class="my-year">\n) | |
expected << %(<option value="2003">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_year(nil, start_year: 2003, end_year: 2005, with_css_classes: { year: "my-year" }) | |
end | |
def test_select_year_with_position | |
expected = +%(<select id="date_year_1i" name="date[year(1i)]">\n) | |
expected << %(<option value="2003">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_year(Date.current, include_position: true, start_year: 2003, end_year: 2005) | |
end | |
def test_select_year_with_custom_names | |
year_format_lambda = ->year { "Heisei #{ year - 1988 }" } | |
expected = %(<select id="date_year" name="date[year]">\n).dup | |
expected << %(<option value="2003">Heisei 15</option>\n<option value="2004">Heisei 16</option>\n<option value="2005">Heisei 17</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_year(nil, start_year: 2003, end_year: 2005, year_format: year_format_lambda) | |
end | |
def test_select_hour | |
expected = +%(<select id="date_hour" name="date[hour]">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18)) | |
end | |
def test_select_hour_with_ampm | |
expected = +%(<select id="date_hour" name="date[hour]">\n) | |
expected << %(<option value="00">12 AM</option>\n<option value="01">01 AM</option>\n<option value="02">02 AM</option>\n<option value="03">03 AM</option>\n<option value="04">04 AM</option>\n<option value="05">05 AM</option>\n<option value="06">06 AM</option>\n<option value="07">07 AM</option>\n<option value="08" selected="selected">08 AM</option>\n<option value="09">09 AM</option>\n<option value="10">10 AM</option>\n<option value="11">11 AM</option>\n<option value="12">12 PM</option>\n<option value="13">01 PM</option>\n<option value="14">02 PM</option>\n<option value="15">03 PM</option>\n<option value="16">04 PM</option>\n<option value="17">05 PM</option>\n<option value="18">06 PM</option>\n<option value="19">07 PM</option>\n<option value="20">08 PM</option>\n<option value="21">09 PM</option>\n<option value="22">10 PM</option>\n<option value="23">11 PM</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), ampm: true) | |
end | |
def test_select_hour_with_disabled | |
expected = +%(<select id="date_hour" name="date[hour]" disabled="disabled">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), disabled: true) | |
end | |
def test_select_hour_with_field_name_override | |
expected = +%(<select id="date_heure" name="date[heure]">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), field_name: "heure") | |
end | |
def test_select_hour_with_blank | |
expected = +%(<select id="date_hour" name="date[hour]">\n) | |
expected << %(<option value="" label=" "></option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), include_blank: true) | |
end | |
def test_select_hour_nil_with_blank | |
expected = +%(<select id="date_hour" name="date[hour]">\n) | |
expected << %(<option value="" label=" "></option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_hour(nil, include_blank: true) | |
end | |
def test_select_hour_with_html_options | |
expected = +%(<select id="date_hour" name="date[hour]" class="selector" accesskey="M">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), {}, { class: "selector", accesskey: "M" }) | |
end | |
def test_select_hour_with_default_prompt | |
expected = +%(<select id="date_hour" name="date[hour]">\n) | |
expected << %(<option value="">Hour</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), prompt: true) | |
end | |
def test_select_hour_with_custom_prompt | |
expected = +%(<select id="date_hour" name="date[hour]">\n) | |
expected << %(<option value="">Choose hour</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), prompt: "Choose hour") | |
end | |
def test_select_hour_with_generic_with_css_classes | |
expected = +%(<select id="date_hour" name="date[hour]" class="hour">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), with_css_classes: true) | |
end | |
def test_select_hour_with_custom_with_css_classes | |
expected = +%(<select id="date_hour" name="date[hour]" class="my-hour">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), with_css_classes: { hour: "my-hour" }) | |
end | |
def test_select_minute | |
expected = +%(<select id="date_minute" name="date[minute]">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18)) | |
end | |
def test_select_minute_with_disabled | |
expected = +%(<select id="date_minute" name="date[minute]" disabled="disabled">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), disabled: true) | |
end | |
def test_select_minute_with_field_name_override | |
expected = +%(<select id="date_minuto" name="date[minuto]">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), field_name: "minuto") | |
end | |
def test_select_minute_with_blank | |
expected = +%(<select id="date_minute" name="date[minute]">\n) | |
expected << %(<option value="" label=" "></option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), include_blank: true) | |
end | |
def test_select_minute_with_blank_and_step | |
expected = +%(<select id="date_minute" name="date[minute]">\n) | |
expected << %(<option value="" label=" "></option>\n<option value="00">00</option>\n<option value="15">15</option>\n<option value="30">30</option>\n<option value="45">45</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), include_blank: true, minute_step: 15) | |
end | |
def test_select_minute_nil_with_blank | |
expected = +%(<select id="date_minute" name="date[minute]">\n) | |
expected << %(<option value="" label=" "></option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_minute(nil, include_blank: true) | |
end | |
def test_select_minute_nil_with_blank_and_step | |
expected = +%(<select id="date_minute" name="date[minute]">\n) | |
expected << %(<option value="" label=" "></option>\n<option value="00">00</option>\n<option value="15">15</option>\n<option value="30">30</option>\n<option value="45">45</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_minute(nil, include_blank: true, minute_step: 15) | |
end | |
def test_select_minute_with_hidden | |
assert_dom_equal "<input type=\"hidden\" id=\"date_minute\" name=\"date[minute]\" value=\"8\" autocomplete=\"off\" />\n", select_minute(8, use_hidden: true) | |
end | |
def test_select_minute_with_hidden_and_field_name | |
assert_dom_equal "<input type=\"hidden\" id=\"date_minuto\" name=\"date[minuto]\" value=\"8\" autocomplete=\"off\" />\n", select_minute(8, use_hidden: true, field_name: "minuto") | |
end | |
def test_select_minute_with_html_options | |
expected = +%(<select id="date_minute" name="date[minute]" class="selector" accesskey="M">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), {}, { class: "selector", accesskey: "M" }) | |
end | |
def test_select_minute_with_default_prompt | |
expected = +%(<select id="date_minute" name="date[minute]">\n) | |
expected << %(<option value="">Minute</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), prompt: true) | |
end | |
def test_select_minute_with_custom_prompt | |
expected = +%(<select id="date_minute" name="date[minute]">\n) | |
expected << %(<option value="">Choose minute</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), prompt: "Choose minute") | |
end | |
def test_select_minute_with_generic_with_css_classes | |
expected = +%(<select id="date_minute" name="date[minute]" class="minute">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), with_css_classes: true) | |
end | |
def test_select_minute_with_custom_with_css_classes | |
expected = +%(<select id="date_minute" name="date[minute]" class="my-minute">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), with_css_classes: { minute: "my-minute" }) | |
end | |
def test_select_second | |
expected = +%(<select id="date_second" name="date[second]">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18)) | |
end | |
def test_select_second_with_disabled | |
expected = +%(<select id="date_second" name="date[second]" disabled="disabled">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18), disabled: true) | |
end | |
def test_select_second_with_field_name_override | |
expected = +%(<select id="date_segundo" name="date[segundo]">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18), field_name: "segundo") | |
end | |
def test_select_second_with_blank | |
expected = +%(<select id="date_second" name="date[second]">\n) | |
expected << %(<option value="" label=" "></option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18), include_blank: true) | |
end | |
def test_select_second_nil_with_blank | |
expected = +%(<select id="date_second" name="date[second]">\n) | |
expected << %(<option value="" label=" "></option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_second(nil, include_blank: true) | |
end | |
def test_select_second_with_html_options | |
expected = +%(<select id="date_second" name="date[second]" class="selector" accesskey="M">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18), {}, { class: "selector", accesskey: "M" }) | |
end | |
def test_select_second_with_default_prompt | |
expected = +%(<select id="date_second" name="date[second]">\n) | |
expected << %(<option value="">Seconds</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18), prompt: true) | |
end | |
def test_select_second_with_custom_prompt | |
expected = +%(<select id="date_second" name="date[second]">\n) | |
expected << %(<option value="">Choose seconds</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18), prompt: "Choose seconds") | |
end | |
def test_select_second_with_generic_with_css_classes | |
expected = +%(<select id="date_second" name="date[second]" class="second">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18), with_css_classes: true) | |
end | |
def test_select_second_with_custom_with_css_classes | |
expected = +%(<select id="date_second" name="date[second]" class="my-second">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18), with_css_classes: { second: "my-second" }) | |
end | |
def test_select_date | |
expected = +%(<select id="date_first_year" name="date[first][year]">\n) | |
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_first_month" name="date[first][month]">\n) | |
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_first_day" name="date[first][day]">\n) | |
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), start_year: 2003, end_year: 2005, prefix: "date[first]") | |
end | |
def test_select_date_with_too_big_range_between_start_year_and_end_year | |
assert_raise(ArgumentError) { select_date(Time.mktime(2003, 8, 16), start_year: 2000, end_year: 20000, prefix: "date[first]", order: [:month, :day, :year]) } | |
assert_raise(ArgumentError) { select_date(Time.mktime(2003, 8, 16), start_year: 100, end_year: 2000, prefix: "date[first]", order: [:month, :day, :year]) } | |
end | |
def test_select_date_can_have_more_then_1000_years_interval_if_forced_via_parameter | |
assert_nothing_raised { select_date(Time.mktime(2003, 8, 16), start_year: 2000, end_year: 3100, max_years_allowed: 2000) } | |
end | |
def test_select_date_with_order | |
expected = +%(<select id="date_first_month" name="date[first][month]">\n) | |
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_first_day" name="date[first][day]">\n) | |
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_first_year" name="date[first][year]">\n) | |
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), start_year: 2003, end_year: 2005, prefix: "date[first]", order: [:month, :day, :year]) | |
end | |
def test_select_date_with_incomplete_order | |
# Since the order is incomplete nothing will be shown | |
expected = +%(<input id="date_first_year" name="date[first][year]" type="hidden" value="2003" autocomplete="off" />\n) | |
expected << %(<input id="date_first_month" name="date[first][month]" type="hidden" value="8" autocomplete="off" />\n) | |
expected << %(<input id="date_first_day" name="date[first][day]" type="hidden" value="1" autocomplete="off" />\n) | |
assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), start_year: 2003, end_year: 2005, prefix: "date[first]", order: [:day]) | |
end | |
def test_select_date_with_disabled | |
expected = +%(<select id="date_first_year" name="date[first][year]" disabled="disabled">\n) | |
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_first_month" name="date[first][month]" disabled="disabled">\n) | |
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_first_day" name="date[first][day]" disabled="disabled">\n) | |
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), start_year: 2003, end_year: 2005, prefix: "date[first]", disabled: true) | |
end | |
def test_select_date_with_no_start_year | |
expected = +%(<select id="date_first_year" name="date[first][year]">\n) | |
(Date.today.year - 5).upto(Date.today.year + 1) do |y| | |
if y == Date.today.year | |
expected << %(<option value="#{y}" selected="selected">#{y}</option>\n) | |
else | |
expected << %(<option value="#{y}">#{y}</option>\n) | |
end | |
end | |
expected << "</select>\n" | |
expected << %(<select id="date_first_month" name="date[first][month]">\n) | |
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_first_day" name="date[first][day]">\n) | |
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_date( | |
Time.mktime(Date.today.year, 8, 16), end_year: Date.today.year + 1, prefix: "date[first]" | |
) | |
end | |
def test_select_date_with_no_end_year | |
expected = +%(<select id="date_first_year" name="date[first][year]">\n) | |
2003.upto(2008) do |y| | |
if y == 2003 | |
expected << %(<option value="#{y}" selected="selected">#{y}</option>\n) | |
else | |
expected << %(<option value="#{y}">#{y}</option>\n) | |
end | |
end | |
expected << "</select>\n" | |
expected << %(<select id="date_first_month" name="date[first][month]">\n) | |
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_first_day" name="date[first][day]">\n) | |
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_date( | |
Time.mktime(2003, 8, 16), start_year: 2003, prefix: "date[first]" | |
) | |
end | |
def test_select_date_with_no_start_or_end_year | |
expected = +%(<select id="date_first_year" name="date[first][year]">\n) | |
(Date.today.year - 5).upto(Date.today.year + 5) do |y| | |
if y == Date.today.year | |
expected << %(<option value="#{y}" selected="selected">#{y}</option>\n) | |
else | |
expected << %(<option value="#{y}">#{y}</option>\n) | |
end | |
end | |
expected << "</select>\n" | |
expected << %(<select id="date_first_month" name="date[first][month]">\n) | |
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_first_day" name="date[first][day]">\n) | |
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_date( | |
Time.mktime(Date.today.year, 8, 16), prefix: "date[first]" | |
) | |
end | |
def test_select_date_with_zero_value | |
expected = +%(<select id="date_first_year" name="date[first][year]">\n) | |
expected << %(<option value="2003">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_first_month" name="date[first][month]">\n) | |
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_first_day" name="date[first][day]">\n) | |
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_date(0, start_year: 2003, end_year: 2005, prefix: "date[first]") | |
end | |
def test_select_date_with_zero_value_and_no_start_year | |
expected = +%(<select id="date_first_year" name="date[first][year]">\n) | |
(Date.today.year - 5).upto(Date.today.year + 1) { |y| expected << %(<option value="#{y}">#{y}</option>\n) } | |
expected << "</select>\n" | |
expected << %(<select id="date_first_month" name="date[first][month]">\n) | |
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_first_day" name="date[first][day]">\n) | |
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_date(0, end_year: Date.today.year + 1, prefix: "date[first]") | |
end | |
def test_select_date_with_zero_value_and_no_end_year | |
expected = +%(<select id="date_first_year" name="date[first][year]">\n) | |
last_year = Time.now.year + 5 | |
2003.upto(last_year) { |y| expected << %(<option value="#{y}">#{y}</option>\n) } | |
expected << "</select>\n" | |
expected << %(<select id="date_first_month" name="date[first][month]">\n) | |
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_first_day" name="date[first][day]">\n) | |
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_date(0, start_year: 2003, prefix: "date[first]") | |
end | |
def test_select_date_with_zero_value_and_no_start_and_end_year | |
expected = +%(<select id="date_first_year" name="date[first][year]">\n) | |
(Date.today.year - 5).upto(Date.today.year + 5) { |y| expected << %(<option value="#{y}">#{y}</option>\n) } | |
expected << "</select>\n" | |
expected << %(<select id="date_first_month" name="date[first][month]">\n) | |
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_first_day" name="date[first][day]">\n) | |
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_date(0, prefix: "date[first]") | |
end | |
def test_select_date_with_nil_value_and_no_start_and_end_year | |
expected = +%(<select id="date_first_year" name="date[first][year]">\n) | |
(Date.today.year - 5).upto(Date.today.year + 5) { |y| expected << %(<option value="#{y}">#{y}</option>\n) } | |
expected << "</select>\n" | |
expected << %(<select id="date_first_month" name="date[first][month]">\n) | |
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_first_day" name="date[first][day]">\n) | |
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_date(nil, prefix: "date[first]") | |
end | |
def test_select_date_with_html_options | |
expected = +%(<select id="date_first_year" name="date[first][year]" class="selector">\n) | |
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_first_month" name="date[first][month]" class="selector">\n) | |
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_first_day" name="date[first][day]" class="selector">\n) | |
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), { start_year: 2003, end_year: 2005, prefix: "date[first]" }, { class: "selector" }) | |
end | |
def test_select_date_with_separator | |
expected = +%(<select id="date_first_year" name="date[first][year]">\n) | |
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) | |
expected << "</select>\n" | |
expected << " / " | |
expected << %(<select id="date_first_month" name="date[first][month]">\n) | |
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
expected << " / " | |
expected << %(<select id="date_first_day" name="date[first][day]">\n) | |
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), date_separator: " / ", start_year: 2003, end_year: 2005, prefix: "date[first]") | |
end | |
def test_select_date_with_separator_and_discard_day | |
expected = +%(<select id="date_first_year" name="date[first][year]">\n) | |
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) | |
expected << "</select>\n" | |
expected << " / " | |
expected << %(<select id="date_first_month" name="date[first][month]">\n) | |
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
expected << %(<input type="hidden" id="date_first_day" name="date[first][day]" value="1" autocomplete="off" />\n) | |
assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), date_separator: " / ", discard_day: true, start_year: 2003, end_year: 2005, prefix: "date[first]") | |
end | |
def test_select_date_with_separator_discard_month_and_day | |
expected = +%(<select id="date_first_year" name="date[first][year]">\n) | |
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) | |
expected << "</select>\n" | |
expected << %(<input type="hidden" id="date_first_month" name="date[first][month]" value="8" autocomplete="off" />\n) | |
expected << %(<input type="hidden" id="date_first_day" name="date[first][day]" value="1" autocomplete="off" />\n) | |
assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), date_separator: " / ", discard_month: true, discard_day: true, start_year: 2003, end_year: 2005, prefix: "date[first]") | |
end | |
def test_select_date_with_hidden | |
expected = +%(<input id="date_first_year" name="date[first][year]" type="hidden" value="2003" autocomplete="off"/>\n) | |
expected << %(<input id="date_first_month" name="date[first][month]" type="hidden" value="8" autocomplete="off" />\n) | |
expected << %(<input id="date_first_day" name="date[first][day]" type="hidden" value="16" autocomplete="off" />\n) | |
assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), prefix: "date[first]", use_hidden: true) | |
assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), date_separator: " / ", prefix: "date[first]", use_hidden: true) | |
end | |
def test_select_date_with_css_classes_option | |
expected = +%(<select id="date_first_year" name="date[first][year]" class="year">\n) | |
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_first_month" name="date[first][month]" class="month">\n) | |
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_first_day" name="date[first][day]" class="day">\n) | |
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), start_year: 2003, end_year: 2005, prefix: "date[first]", with_css_classes: true) | |
end | |
def test_select_date_with_custom_with_css_classes | |
expected = +%(<select id="date_year" name="date[year]" class="my-year">\n) | |
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_month" name="date[month]" class="my-month">\n) | |
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_day" name="date[day]" class="my-day">\n) | |
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), start_year: 2003, end_year: 2005, with_css_classes: { year: "my-year", month: "my-month", day: "my-day" }) | |
end | |
def test_select_date_with_css_classes_option_and_html_class_option | |
expected = +%(<select id="date_first_year" name="date[first][year]" class="datetime optional year">\n) | |
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_first_month" name="date[first][month]" class="datetime optional month">\n) | |
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_first_day" name="date[first][day]" class="datetime optional day">\n) | |
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), { start_year: 2003, end_year: 2005, prefix: "date[first]", with_css_classes: true }, { class: "datetime optional" }) | |
end | |
def test_select_date_with_custom_with_css_classes_and_html_class_option | |
expected = +%(<select id="date_year" name="date[year]" class="date optional my-year">\n) | |
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_month" name="date[month]" class="date optional my-month">\n) | |
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_day" name="date[day]" class="date optional my-day">\n) | |
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), { start_year: 2003, end_year: 2005, with_css_classes: { year: "my-year", month: "my-month", day: "my-day" } }, { class: "date optional" }) | |
end | |
def test_select_date_with_partial_with_css_classes_and_html_class_option | |
expected = +%(<select id="date_year" name="date[year]" class="date optional">\n) | |
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_month" name="date[month]" class="date optional my-month custom-grid">\n) | |
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_day" name="date[day]" class="date optional">\n) | |
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), { start_year: 2003, end_year: 2005, with_css_classes: { month: "my-month custom-grid" } }, { class: "date optional" }) | |
end | |
def test_select_date_with_html_class_option | |
expected = +%(<select id="date_year" name="date[year]" class="date optional custom-grid">\n) | |
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_month" name="date[month]" class="date optional custom-grid">\n) | |
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_day" name="date[day]" class="date optional custom-grid">\n) | |
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), { start_year: 2003, end_year: 2005 }, { class: "date optional custom-grid" }) | |
end | |
def test_select_datetime | |
expected = +%(<select id="date_first_year" name="date[first][year]">\n) | |
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_first_month" name="date[first][month]">\n) | |
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_first_day" name="date[first][day]">\n) | |
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
expected << " — " | |
expected << %(<select id="date_first_hour" name="date[first][hour]">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) | |
expected << "</select>\n" | |
expected << " : " | |
expected << %(<select id="date_first_minute" name="date[first][minute]">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), start_year: 2003, end_year: 2005, prefix: "date[first]") | |
end | |
def test_select_datetime_with_ampm | |
expected = +%(<select id="date_first_year" name="date[first][year]">\n) | |
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_first_month" name="date[first][month]">\n) | |
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_first_day" name="date[first][day]">\n) | |
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
expected << " — " | |
expected << %(<select id="date_first_hour" name="date[first][hour]">\n) | |
expected << %(<option value="00">12 AM</option>\n<option value="01">01 AM</option>\n<option value="02">02 AM</option>\n<option value="03">03 AM</option>\n<option value="04">04 AM</option>\n<option value="05">05 AM</option>\n<option value="06">06 AM</option>\n<option value="07">07 AM</option>\n<option value="08" selected="selected">08 AM</option>\n<option value="09">09 AM</option>\n<option value="10">10 AM</option>\n<option value="11">11 AM</option>\n<option value="12">12 PM</option>\n<option value="13">01 PM</option>\n<option value="14">02 PM</option>\n<option value="15">03 PM</option>\n<option value="16">04 PM</option>\n<option value="17">05 PM</option>\n<option value="18">06 PM</option>\n<option value="19">07 PM</option>\n<option value="20">08 PM</option>\n<option value="21">09 PM</option>\n<option value="22">10 PM</option>\n<option value="23">11 PM</option>\n) | |
expected << "</select>\n" | |
expected << " : " | |
expected << %(<select id="date_first_minute" name="date[first][minute]">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), start_year: 2003, end_year: 2005, prefix: "date[first]", ampm: true) | |
end | |
def test_select_datetime_with_separators | |
expected = +%(<select id="date_first_year" name="date[first][year]">\n) | |
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_first_month" name="date[first][month]">\n) | |
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_first_day" name="date[first][day]">\n) | |
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
expected << " — " | |
expected << %(<select id="date_first_hour" name="date[first][hour]">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) | |
expected << "</select>\n" | |
expected << " : " | |
expected << %(<select id="date_first_minute" name="date[first][minute]">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), start_year: 2003, end_year: 2005, prefix: "date[first]", datetime_separator: " — ", time_separator: " : ") | |
end | |
def test_select_datetime_with_nil_value_and_no_start_and_end_year | |
expected = +%(<select id="date_first_year" name="date[first][year]">\n) | |
(Date.today.year - 5).upto(Date.today.year + 5) { |y| expected << %(<option value="#{y}">#{y}</option>\n) } | |
expected << "</select>\n" | |
expected << %(<select id="date_first_month" name="date[first][month]">\n) | |
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_first_day" name="date[first][day]">\n) | |
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
expected << " — " | |
expected << %(<select id="date_first_hour" name="date[first][hour]">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) | |
expected << "</select>\n" | |
expected << " : " | |
expected << %(<select id="date_first_minute" name="date[first][minute]">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_datetime(nil, prefix: "date[first]") | |
end | |
def test_select_datetime_with_html_options | |
expected = +%(<select id="date_first_year" name="date[first][year]" class="selector">\n) | |
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_first_month" name="date[first][month]" class="selector">\n) | |
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_first_day" name="date[first][day]" class="selector">\n) | |
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
expected << " — " | |
expected << %(<select id="date_first_hour" name="date[first][hour]" class="selector">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) | |
expected << "</select>\n" | |
expected << " : " | |
expected << %(<select id="date_first_minute" name="date[first][minute]" class="selector">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), { start_year: 2003, end_year: 2005, prefix: "date[first]" }, { class: "selector" }) | |
end | |
def test_select_datetime_with_all_separators | |
expected = +%(<select id="date_first_year" name="date[first][year]" class="selector">\n) | |
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) | |
expected << "</select>\n" | |
expected << "/" | |
expected << %(<select id="date_first_month" name="date[first][month]" class="selector">\n) | |
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
expected << "/" | |
expected << %(<select id="date_first_day" name="date[first][day]" class="selector">\n) | |
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
expected << "—" | |
expected << %(<select id="date_first_hour" name="date[first][hour]" class="selector">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) | |
expected << "</select>\n" | |
expected << ":" | |
expected << %(<select id="date_first_minute" name="date[first][minute]" class="selector">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), { datetime_separator: "—", date_separator: "/", time_separator: ":", start_year: 2003, end_year: 2005, prefix: "date[first]" }, { class: "selector" }) | |
end | |
def test_select_datetime_should_work_with_date | |
assert_nothing_raised { select_datetime(Date.today) } | |
end | |
def test_select_datetime_with_default_prompt | |
expected = +%(<select id="date_first_year" name="date[first][year]">\n) | |
expected << %(<option value="">Year</option>\n<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_first_month" name="date[first][month]">\n) | |
expected << %(<option value="">Month</option>\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_first_day" name="date[first][day]">\n) | |
expected << %(<option value="">Day</option>\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
expected << " — " | |
expected << %(<select id="date_first_hour" name="date[first][hour]">\n) | |
expected << %(<option value="">Hour</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) | |
expected << "</select>\n" | |
expected << " : " | |
expected << %(<select id="date_first_minute" name="date[first][minute]">\n) | |
expected << %(<option value="">Minute</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), start_year: 2003, end_year: 2005, | |
prefix: "date[first]", prompt: true) | |
end | |
def test_select_datetime_with_custom_prompt | |
expected = +%(<select id="date_first_year" name="date[first][year]">\n) | |
expected << %(<option value="">Choose year</option>\n<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_first_month" name="date[first][month]">\n) | |
expected << %(<option value="">Choose month</option>\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_first_day" name="date[first][day]">\n) | |
expected << %(<option value="">Choose day</option>\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
expected << " — " | |
expected << %(<select id="date_first_hour" name="date[first][hour]">\n) | |
expected << %(<option value="">Choose hour</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) | |
expected << "</select>\n" | |
expected << " : " | |
expected << %(<select id="date_first_minute" name="date[first][minute]">\n) | |
expected << %(<option value="">Choose minute</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), start_year: 2003, end_year: 2005, prefix: "date[first]", | |
prompt: { day: "Choose day", month: "Choose month", year: "Choose year", hour: "Choose hour", minute: "Choose minute" }) | |
end | |
def test_select_datetime_with_generic_with_css_classes | |
expected = +%(<select id="date_year" name="date[year]" class="year">\n) | |
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_month" name="date[month]" class="month">\n) | |
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_day" name="date[day]" class="day">\n) | |
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
expected << " — " | |
expected << %(<select id="date_hour" name="date[hour]" class="hour">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) | |
expected << "</select>\n" | |
expected << " : " | |
expected << %(<select id="date_minute" name="date[minute]" class="minute">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), start_year: 2003, end_year: 2005, with_css_classes: true) | |
end | |
def test_select_datetime_with_custom_with_css_classes | |
expected = +%(<select id="date_year" name="date[year]" class="my-year">\n) | |
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_month" name="date[month]" class="my-month">\n) | |
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_day" name="date[day]" class="my-day">\n) | |
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
expected << " — " | |
expected << %(<select id="date_hour" name="date[hour]" class="my-hour">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) | |
expected << "</select>\n" | |
expected << " : " | |
expected << %(<select id="date_minute" name="date[minute]" class="my-minute">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), start_year: 2003, end_year: 2005, with_css_classes: { day: "my-day", month: "my-month", year: "my-year", hour: "my-hour", minute: "my-minute" }) | |
end | |
def test_select_datetime_with_custom_hours | |
expected = +%(<select id="date_first_year" name="date[first][year]">\n) | |
expected << %(<option value="">Choose year</option>\n<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_first_month" name="date[first][month]">\n) | |
expected << %(<option value="">Choose month</option>\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_first_day" name="date[first][day]">\n) | |
expected << %(<option value="">Choose day</option>\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
expected << " — " | |
expected << %(<select id="date_first_hour" name="date[first][hour]">\n) | |
expected << %(<option value="">Choose hour</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n) | |
expected << "</select>\n" | |
expected << " : " | |
expected << %(<select id="date_first_minute" name="date[first][minute]">\n) | |
expected << %(<option value="">Choose minute</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), start_year: 2003, end_year: 2005, start_hour: 1, end_hour: 9, prefix: "date[first]", | |
prompt: { day: "Choose day", month: "Choose month", year: "Choose year", hour: "Choose hour", minute: "Choose minute" }) | |
end | |
def test_select_datetime_with_hidden | |
expected = +%(<input id="date_first_year" name="date[first][year]" type="hidden" value="2003" autocomplete="off" />\n) | |
expected << %(<input id="date_first_month" name="date[first][month]" type="hidden" value="8" autocomplete="off" />\n) | |
expected << %(<input id="date_first_day" name="date[first][day]" type="hidden" value="16" autocomplete="off" />\n) | |
expected << %(<input id="date_first_hour" name="date[first][hour]" type="hidden" value="8" autocomplete="off" />\n) | |
expected << %(<input id="date_first_minute" name="date[first][minute]" type="hidden" value="4" autocomplete="off" />\n) | |
assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), prefix: "date[first]", use_hidden: true) | |
assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), datetime_separator: "—", date_separator: "/", | |
time_separator: ":", prefix: "date[first]", use_hidden: true) | |
end | |
def test_select_time | |
expected = +%(<input name="date[year]" id="date_year" value="2003" type="hidden" autocomplete="off" />\n) | |
expected << %(<input name="date[month]" id="date_month" value="8" type="hidden" autocomplete="off" />\n) | |
expected << %(<input name="date[day]" id="date_day" value="16" type="hidden" autocomplete="off" />\n) | |
expected << %(<select id="date_hour" name="date[hour]">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) | |
expected << "</select>\n" | |
expected << " : " | |
expected << %(<select id="date_minute" name="date[minute]">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18)) | |
assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), include_seconds: false) | |
end | |
def test_select_time_with_ampm | |
expected = +%(<input name="date[year]" id="date_year" value="2003" type="hidden" autocomplete="off" />\n) | |
expected << %(<input name="date[month]" id="date_month" value="8" type="hidden" autocomplete="off" />\n) | |
expected << %(<input name="date[day]" id="date_day" value="16" type="hidden" autocomplete="off" />\n) | |
expected << %(<select id="date_hour" name="date[hour]">\n) | |
expected << %(<option value="00">12 AM</option>\n<option value="01">01 AM</option>\n<option value="02">02 AM</option>\n<option value="03">03 AM</option>\n<option value="04">04 AM</option>\n<option value="05">05 AM</option>\n<option value="06">06 AM</option>\n<option value="07">07 AM</option>\n<option value="08" selected="selected">08 AM</option>\n<option value="09">09 AM</option>\n<option value="10">10 AM</option>\n<option value="11">11 AM</option>\n<option value="12">12 PM</option>\n<option value="13">01 PM</option>\n<option value="14">02 PM</option>\n<option value="15">03 PM</option>\n<option value="16">04 PM</option>\n<option value="17">05 PM</option>\n<option value="18">06 PM</option>\n<option value="19">07 PM</option>\n<option value="20">08 PM</option>\n<option value="21">09 PM</option>\n<option value="22">10 PM</option>\n<option value="23">11 PM</option>\n) | |
expected << "</select>\n" | |
expected << " : " | |
expected << %(<select id="date_minute" name="date[minute]">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), include_seconds: false, ampm: true) | |
end | |
def test_select_time_with_separator | |
expected = +%(<input name="date[year]" id="date_year" value="2003" type="hidden" autocomplete="off" />\n) | |
expected << %(<input name="date[month]" id="date_month" value="8" type="hidden" autocomplete="off" />\n) | |
expected << %(<input name="date[day]" id="date_day" value="16" type="hidden" autocomplete="off" />\n) | |
expected << %(<select id="date_hour" name="date[hour]">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) | |
expected << "</select>\n" | |
expected << " : " | |
expected << %(<select id="date_minute" name="date[minute]">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), time_separator: " : ") | |
assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), time_separator: " : ", include_seconds: false) | |
end | |
def test_select_time_with_seconds | |
expected = +%(<input name="date[year]" id="date_year" value="2003" type="hidden" autocomplete="off" />\n) | |
expected << %(<input name="date[month]" id="date_month" value="8" type="hidden" autocomplete="off" />\n) | |
expected << %(<input name="date[day]" id="date_day" value="16" type="hidden" autocomplete="off" />\n) | |
expected << %(<select id="date_hour" name="date[hour]">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) | |
expected << "</select>\n" | |
expected << " : " | |
expected << %(<select id="date_minute" name="date[minute]">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
expected << " : " | |
expected << %(<select id="date_second" name="date[second]">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), include_seconds: true) | |
end | |
def test_select_time_with_seconds_and_separator | |
expected = +%(<input name="date[year]" id="date_year" value="2003" type="hidden" autocomplete="off" />\n) | |
expected << %(<input name="date[month]" id="date_month" value="8" type="hidden" autocomplete="off" />\n) | |
expected << %(<input name="date[day]" id="date_day" value="16" type="hidden" autocomplete="off" />\n) | |
expected << %(<select id="date_hour" name="date[hour]">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) | |
expected << "</select>\n" | |
expected << " : " | |
expected << %(<select id="date_minute" name="date[minute]">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
expected << " : " | |
expected << %(<select id="date_second" name="date[second]">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), include_seconds: true, time_separator: " : ") | |
end | |
def test_select_time_with_html_options | |
expected = +%(<input name="date[year]" id="date_year" value="2003" type="hidden" autocomplete="off" />\n) | |
expected << %(<input name="date[month]" id="date_month" value="8" type="hidden" autocomplete="off" />\n) | |
expected << %(<input name="date[day]" id="date_day" value="16" type="hidden" autocomplete="off" />\n) | |
expected << %(<select id="date_hour" name="date[hour]" class="selector">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) | |
expected << "</select>\n" | |
expected << " : " | |
expected << %(<select id="date_minute" name="date[minute]" class="selector">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), {}, { class: "selector" }) | |
assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), { include_seconds: false }, { class: "selector" }) | |
end | |
def test_select_time_should_work_with_date | |
assert_nothing_raised { select_time(Date.today) } | |
end | |
def test_select_time_with_default_prompt | |
expected = +%(<input name="date[year]" id="date_year" value="2003" type="hidden" autocomplete="off" />\n) | |
expected << %(<input name="date[month]" id="date_month" value="8" type="hidden" autocomplete="off" />\n) | |
expected << %(<input name="date[day]" id="date_day" value="16" type="hidden" autocomplete="off" />\n) | |
expected << %(<select id="date_hour" name="date[hour]">\n) | |
expected << %(<option value="">Hour</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) | |
expected << "</select>\n" | |
expected << " : " | |
expected << %(<select id="date_minute" name="date[minute]">\n) | |
expected << %(<option value="">Minute</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
expected << " : " | |
expected << %(<select id="date_second" name="date[second]">\n) | |
expected << %(<option value="">Seconds</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), include_seconds: true, prompt: true) | |
end | |
def test_select_time_with_custom_prompt | |
expected = +%(<input name="date[year]" id="date_year" value="2003" type="hidden" autocomplete="off" />\n) | |
expected << %(<input name="date[month]" id="date_month" value="8" type="hidden" autocomplete="off" />\n) | |
expected << %(<input name="date[day]" id="date_day" value="16" type="hidden" autocomplete="off" />\n) | |
expected << %(<select id="date_hour" name="date[hour]">\n) | |
expected << %(<option value="">Choose hour</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) | |
expected << "</select>\n" | |
expected << " : " | |
expected << %(<select id="date_minute" name="date[minute]">\n) | |
expected << %(<option value="">Choose minute</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
expected << " : " | |
expected << %(<select id="date_second" name="date[second]">\n) | |
expected << %(<option value="">Choose seconds</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), include_seconds: true, | |
prompt: { hour: "Choose hour", minute: "Choose minute", second: "Choose seconds" }) | |
end | |
def test_select_time_with_generic_with_css_classes | |
expected = +%(<input name="date[year]" id="date_year" value="2003" type="hidden" autocomplete="off" />\n) | |
expected << %(<input name="date[month]" id="date_month" value="8" type="hidden" autocomplete="off" />\n) | |
expected << %(<input name="date[day]" id="date_day" value="16" type="hidden" autocomplete="off" />\n) | |
expected << %(<select id="date_hour" name="date[hour]" class="hour">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) | |
expected << "</select>\n" | |
expected << " : " | |
expected << %(<select id="date_minute" name="date[minute]" class="minute">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
expected << " : " | |
expected << %(<select id="date_second" name="date[second]" class="second">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), include_seconds: true, with_css_classes: true) | |
end | |
def test_select_time_with_custom_with_css_classes | |
expected = +%(<input name="date[year]" id="date_year" value="2003" type="hidden" autocomplete="off" />\n) | |
expected << %(<input name="date[month]" id="date_month" value="8" type="hidden" autocomplete="off" />\n) | |
expected << %(<input name="date[day]" id="date_day" value="16" type="hidden" autocomplete="off" />\n) | |
expected << %(<select id="date_hour" name="date[hour]" class="my-hour">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) | |
expected << "</select>\n" | |
expected << " : " | |
expected << %(<select id="date_minute" name="date[minute]" class="my-minute">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
expected << " : " | |
expected << %(<select id="date_second" name="date[second]" class="my-second">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), include_seconds: true, with_css_classes: { hour: "my-hour", minute: "my-minute", second: "my-second" }) | |
end | |
def test_select_time_with_hidden | |
expected = +%(<input id="date_first_year" name="date[first][year]" type="hidden" value="2003" autocomplete="off" />\n) | |
expected << %(<input id="date_first_month" name="date[first][month]" type="hidden" value="8" autocomplete="off" />\n) | |
expected << %(<input id="date_first_day" name="date[first][day]" type="hidden" value="16" autocomplete="off" />\n) | |
expected << %(<input id="date_first_hour" name="date[first][hour]" type="hidden" value="8" autocomplete="off" />\n) | |
expected << %(<input id="date_first_minute" name="date[first][minute]" type="hidden" value="4" autocomplete="off" />\n) | |
assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), prefix: "date[first]", use_hidden: true) | |
assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), time_separator: ":", prefix: "date[first]", use_hidden: true) | |
end | |
def test_date_select | |
@post = Post.new | |
@post.written_on = Date.new(2004, 6, 15) | |
expected = +%{<select id="post_written_on_1i" name="post[written_on(1i)]">\n} | |
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n} | |
expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n} | |
expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n} | |
expected << "</select>\n" | |
assert_dom_equal expected, date_select("post", "written_on") | |
end | |
def test_date_select_with_selected | |
@post = Post.new | |
@post.written_on = Date.new(2004, 6, 15) | |
expected = +%{<select id="post_written_on_1i" name="post[written_on(1i)]">\n} | |
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option selected="selected" value="2004">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n} | |
expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7" selected="selected">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n} | |
expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10" selected="selected">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n} | |
expected << "</select>\n" | |
assert_dom_equal expected, date_select("post", "written_on", selected: Date.new(2004, 07, 10)) | |
end | |
def test_date_select_with_selected_in_hash | |
@post = Post.new | |
@post.written_on = Date.new(2004, 6, 15) | |
expected = +%{<select id="post_written_on_1i" name="post[written_on(1i)]">\n} | |
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option selected="selected" value="2004">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n} | |
expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7" selected="selected">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n} | |
expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10" selected="selected">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n} | |
expected << "</select>\n" | |
assert_dom_equal expected, date_select("post", "written_on", selected: { day: 10, month: 07, year: 2004 }) | |
end | |
def test_date_select_with_selected_nil | |
@post = Post.new | |
@post.written_on = Date.new(2004, 6, 15) | |
expected = '<input id="post_written_on_1i" name="post[written_on(1i)]" type="hidden" value="1" autocomplete="off"/>' + "\n" | |
expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n} | |
expected << %{<option value="" label=" "></option>\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n} | |
expected << %{<option value="" label=" "></option>\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n} | |
expected << "</select>\n" | |
assert_dom_equal expected, date_select("post", "written_on", include_blank: true, discard_year: true, selected: nil) | |
end | |
def test_date_select_without_day | |
@post = Post.new | |
@post.written_on = Date.new(2004, 6, 15) | |
expected = +"<input type=\"hidden\" id=\"post_written_on_3i\" name=\"post[written_on(3i)]\" value=\"1\" autocomplete=\"off\" />\n" | |
expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n} | |
expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n} | |
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} | |
expected << "</select>\n" | |
assert_dom_equal expected, date_select("post", "written_on", order: [ :month, :year ]) | |
end | |
def test_date_select_without_day_and_month | |
@post = Post.new | |
@post.written_on = Date.new(2004, 2, 29) | |
expected = +"<input type=\"hidden\" id=\"post_written_on_2i\" name=\"post[written_on(2i)]\" value=\"2\" autocomplete=\"off\" />\n" | |
expected << "<input type=\"hidden\" id=\"post_written_on_3i\" name=\"post[written_on(3i)]\" value=\"1\" autocomplete=\"off\" />\n" | |
expected << %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n} | |
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} | |
expected << "</select>\n" | |
assert_dom_equal expected, date_select("post", "written_on", order: [ :year ]) | |
end | |
def test_date_select_without_day_with_separator | |
@post = Post.new | |
@post.written_on = Date.new(2004, 6, 15) | |
expected = +"<input type=\"hidden\" id=\"post_written_on_3i\" name=\"post[written_on(3i)]\" value=\"1\" autocomplete=\"off\" />\n" | |
expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n} | |
expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} | |
expected << "</select>\n" | |
expected << "/" | |
expected << %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n} | |
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} | |
expected << "</select>\n" | |
assert_dom_equal expected, date_select("post", "written_on", date_separator: "/", order: [ :month, :year ]) | |
end | |
def test_date_select_without_day_and_with_disabled_html_option | |
@post = Post.new | |
@post.written_on = Date.new(2004, 6, 15) | |
expected = +"<input type=\"hidden\" id=\"post_written_on_3i\" disabled=\"disabled\" name=\"post[written_on(3i)]\" value=\"1\" autocomplete=\"off\" />\n" | |
expected << %{<select id="post_written_on_2i" disabled="disabled" name="post[written_on(2i)]">\n} | |
expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_written_on_1i" disabled="disabled" name="post[written_on(1i)]">\n} | |
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} | |
expected << "</select>\n" | |
assert_dom_equal expected, date_select("post", "written_on", { order: [ :month, :year ] }, { disabled: true }) | |
end | |
def test_date_select_within_fields_for | |
@post = Post.new | |
@post.written_on = Date.new(2004, 6, 15) | |
output_buffer = fields_for :post, @post do |f| | |
concat f.date_select(:written_on) | |
end | |
expected = +%{<select id="post_written_on_1i" name="post[written_on(1i)]">\n<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option selected="selected" value="2004">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n</select>\n} | |
expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option selected="selected" value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n</select>\n} | |
expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option selected="selected" value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n</select>\n} | |
assert_dom_equal(expected, output_buffer) | |
end | |
def test_date_select_within_fields_for_with_index | |
@post = Post.new | |
@post.written_on = Date.new(2004, 6, 15) | |
id = 27 | |
output_buffer = fields_for :post, @post, index: id do |f| | |
concat f.date_select(:written_on) | |
end | |
expected = +%{<select id="post_#{id}_written_on_1i" name="post[#{id}][written_on(1i)]">\n<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option selected="selected" value="2004">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n</select>\n} | |
expected << %{<select id="post_#{id}_written_on_2i" name="post[#{id}][written_on(2i)]">\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option selected="selected" value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n</select>\n} | |
expected << %{<select id="post_#{id}_written_on_3i" name="post[#{id}][written_on(3i)]">\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option selected="selected" value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n</select>\n} | |
assert_dom_equal(expected, output_buffer) | |
end | |
def test_date_select_within_fields_for_with_blank_index | |
@post = Post.new | |
@post.written_on = Date.new(2004, 6, 15) | |
id = nil | |
output_buffer = fields_for :post, @post, index: id do |f| | |
concat f.date_select(:written_on) | |
end | |
expected = +%{<select id="post_#{id}_written_on_1i" name="post[#{id}][written_on(1i)]">\n<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option selected="selected" value="2004">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n</select>\n} | |
expected << %{<select id="post_#{id}_written_on_2i" name="post[#{id}][written_on(2i)]">\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option selected="selected" value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n</select>\n} | |
expected << %{<select id="post_#{id}_written_on_3i" name="post[#{id}][written_on(3i)]">\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option selected="selected" value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n</select>\n} | |
assert_dom_equal(expected, output_buffer) | |
end | |
def test_date_select_with_index | |
@post = Post.new | |
@post.written_on = Date.new(2004, 6, 15) | |
id = 456 | |
expected = +%{<select id="post_456_written_on_1i" name="post[#{id}][written_on(1i)]">\n} | |
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_456_written_on_2i" name="post[#{id}][written_on(2i)]">\n} | |
expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_456_written_on_3i" name="post[#{id}][written_on(3i)]">\n} | |
expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n} | |
expected << "</select>\n" | |
assert_dom_equal expected, date_select("post", "written_on", index: id) | |
end | |
def test_date_select_with_auto_index | |
@post = Post.new | |
@post.written_on = Date.new(2004, 6, 15) | |
id = 123 | |
expected = +%{<select id="post_123_written_on_1i" name="post[#{id}][written_on(1i)]">\n} | |
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_123_written_on_2i" name="post[#{id}][written_on(2i)]">\n} | |
expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_123_written_on_3i" name="post[#{id}][written_on(3i)]">\n} | |
expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n} | |
expected << "</select>\n" | |
assert_dom_equal expected, date_select("post[]", "written_on") | |
end | |
def test_date_select_with_different_order | |
@post = Post.new | |
@post.written_on = Date.new(2004, 6, 15) | |
expected = +%{<select id="post_written_on_3i" name="post[written_on(3i)]">\n} | |
1.upto(31) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 15}>#{i}</option>\n) } | |
expected << "</select>\n" | |
expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n} | |
1.upto(12) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 6}>#{Date::MONTHNAMES[i]}</option>\n) } | |
expected << "</select>\n" | |
expected << %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n} | |
1999.upto(2009) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 2004}>#{i}</option>\n) } | |
expected << "</select>\n" | |
assert_dom_equal expected, date_select("post", "written_on", order: [:day, :month, :year]) | |
end | |
def test_date_select_with_nil | |
@post = Post.new | |
start_year = Time.now.year - 5 | |
end_year = Time.now.year + 5 | |
expected = +%{<select id="post_written_on_1i" name="post[written_on(1i)]">\n} | |
start_year.upto(end_year) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == Time.now.year}>#{i}</option>\n) } | |
expected << "</select>\n" | |
expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n} | |
1.upto(12) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == Time.now.month}>#{Date::MONTHNAMES[i]}</option>\n) } | |
expected << "</select>\n" | |
expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n} | |
1.upto(31) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == Time.now.day}>#{i}</option>\n) } | |
expected << "</select>\n" | |
assert_dom_equal expected, date_select("post", "written_on") | |
end | |
def test_date_select_with_nil_and_blank | |
@post = Post.new | |
start_year = Time.now.year - 5 | |
end_year = Time.now.year + 5 | |
expected = +%{<select id="post_written_on_1i" name="post[written_on(1i)]">\n} | |
expected << "<option value=\"\" label=\" \"></option>\n" | |
start_year.upto(end_year) { |i| expected << %(<option value="#{i}">#{i}</option>\n) } | |
expected << "</select>\n" | |
expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n} | |
expected << "<option value=\"\" label=\" \"></option>\n" | |
1.upto(12) { |i| expected << %(<option value="#{i}">#{Date::MONTHNAMES[i]}</option>\n) } | |
expected << "</select>\n" | |
expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n} | |
expected << "<option value=\"\" label=\" \"></option>\n" | |
1.upto(31) { |i| expected << %(<option value="#{i}">#{i}</option>\n) } | |
expected << "</select>\n" | |
assert_dom_equal expected, date_select("post", "written_on", include_blank: true) | |
end | |
def test_date_select_with_nil_and_blank_and_order | |
@post = Post.new | |
start_year = Time.now.year - 5 | |
end_year = Time.now.year + 5 | |
expected = '<input name="post[written_on(3i)]" type="hidden" id="post_written_on_3i" value="1" autocomplete="off"/>' + "\n" | |
expected << %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n} | |
expected << "<option value=\"\" label=\" \"></option>\n" | |
start_year.upto(end_year) { |i| expected << %(<option value="#{i}">#{i}</option>\n) } | |
expected << "</select>\n" | |
expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n} | |
expected << "<option value=\"\" label=\" \"></option>\n" | |
1.upto(12) { |i| expected << %(<option value="#{i}">#{Date::MONTHNAMES[i]}</option>\n) } | |
expected << "</select>\n" | |
assert_dom_equal expected, date_select("post", "written_on", order: [:year, :month], include_blank: true) | |
end | |
def test_date_select_with_nil_and_blank_and_discard_month | |
@post = Post.new | |
start_year = Time.now.year - 5 | |
end_year = Time.now.year + 5 | |
expected = +%{<select id="post_written_on_1i" name="post[written_on(1i)]">\n} | |
expected << "<option value=\"\" label=\" \"></option>\n" | |
start_year.upto(end_year) { |i| expected << %(<option value="#{i}">#{i}</option>\n) } | |
expected << "</select>\n" | |
expected << '<input name="post[written_on(2i)]" type="hidden" id="post_written_on_2i" value="1" autocomplete="off"/>' + "\n" | |
expected << '<input name="post[written_on(3i)]" type="hidden" id="post_written_on_3i" value="1" autocomplete="off"/>' + "\n" | |
assert_dom_equal expected, date_select("post", "written_on", discard_month: true, include_blank: true) | |
end | |
def test_date_select_with_nil_and_blank_and_discard_year | |
@post = Post.new | |
expected = '<input id="post_written_on_1i" name="post[written_on(1i)]" type="hidden" value="1" autocomplete="off" />' + "\n" | |
expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n} | |
expected << "<option value=\"\" label=\" \"></option>\n" | |
1.upto(12) { |i| expected << %(<option value="#{i}">#{Date::MONTHNAMES[i]}</option>\n) } | |
expected << "</select>\n" | |
expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n} | |
expected << "<option value=\"\" label=\" \"></option>\n" | |
1.upto(31) { |i| expected << %(<option value="#{i}">#{i}</option>\n) } | |
expected << "</select>\n" | |
assert_dom_equal expected, date_select("post", "written_on", discard_year: true, include_blank: true) | |
end | |
def test_date_select_cant_override_discard_hour | |
@post = Post.new | |
@post.written_on = Date.new(2004, 6, 15) | |
expected = +%{<select id="post_written_on_1i" name="post[written_on(1i)]">\n} | |
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n} | |
expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n} | |
expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n} | |
expected << "</select>\n" | |
assert_dom_equal expected, date_select("post", "written_on", discard_hour: false) | |
end | |
def test_date_select_with_html_options | |
@post = Post.new | |
@post.written_on = Date.new(2004, 6, 15) | |
expected = +%{<select id="post_written_on_1i" name="post[written_on(1i)]" class="selector">\n} | |
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]" class="selector">\n} | |
expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]" class="selector">\n} | |
expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n} | |
expected << "</select>\n" | |
assert_dom_equal expected, date_select("post", "written_on", {}, { class: "selector" }) | |
end | |
def test_date_select_with_html_options_within_fields_for | |
@post = Post.new | |
@post.written_on = Date.new(2004, 6, 15) | |
output_buffer = fields_for :post, @post do |f| | |
concat f.date_select(:written_on, {}, { class: "selector" }) | |
end | |
expected = +%{<select id="post_written_on_1i" name="post[written_on(1i)]" class="selector">\n} | |
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]" class="selector">\n} | |
expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]" class="selector">\n} | |
expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n} | |
expected << "</select>\n" | |
assert_dom_equal expected, output_buffer | |
end | |
def test_date_select_with_separator | |
@post = Post.new | |
@post.written_on = Date.new(2004, 6, 15) | |
expected = +%{<select id="post_written_on_1i" name="post[written_on(1i)]">\n} | |
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} | |
expected << "</select>\n" | |
expected << " / " | |
expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n} | |
expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} | |
expected << "</select>\n" | |
expected << " / " | |
expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n} | |
expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n} | |
expected << "</select>\n" | |
assert_dom_equal expected, date_select("post", "written_on", date_separator: " / ") | |
end | |
def test_date_select_with_separator_and_order | |
@post = Post.new | |
@post.written_on = Date.new(2004, 6, 15) | |
expected = +%{<select id="post_written_on_3i" name="post[written_on(3i)]">\n} | |
expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n} | |
expected << "</select>\n" | |
expected << " / " | |
expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n} | |
expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} | |
expected << "</select>\n" | |
expected << " / " | |
expected << %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n} | |
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} | |
expected << "</select>\n" | |
assert_dom_equal expected, date_select("post", "written_on", order: [:day, :month, :year], date_separator: " / ") | |
end | |
def test_date_select_with_separator_and_order_and_year_discarded | |
@post = Post.new | |
@post.written_on = Date.new(2004, 6, 15) | |
expected = +%{<select id="post_written_on_3i" name="post[written_on(3i)]">\n} | |
expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n} | |
expected << "</select>\n" | |
expected << " / " | |
expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n} | |
expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} | |
expected << "</select>\n" | |
expected << %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" autocomplete="off" />\n} | |
assert_dom_equal expected, date_select("post", "written_on", order: [:day, :month, :year], discard_year: true, date_separator: " / ") | |
end | |
def test_date_select_with_default_prompt | |
@post = Post.new | |
@post.written_on = Date.new(2004, 6, 15) | |
expected = +%{<select id="post_written_on_1i" name="post[written_on(1i)]">\n} | |
expected << %{<option value="">Year</option>\n<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n} | |
expected << %{<option value="">Month</option>\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n} | |
expected << %{<option value="">Day</option>\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n} | |
expected << "</select>\n" | |
assert_dom_equal expected, date_select("post", "written_on", prompt: true) | |
end | |
def test_date_select_with_custom_prompt | |
@post = Post.new | |
@post.written_on = Date.new(2004, 6, 15) | |
expected = +%{<select id="post_written_on_1i" name="post[written_on(1i)]">\n} | |
expected << %{<option value="">Choose year</option>\n<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n} | |
expected << %{<option value="">Choose month</option>\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n} | |
expected << %{<option value="">Choose day</option>\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n} | |
expected << "</select>\n" | |
assert_dom_equal expected, date_select("post", "written_on", prompt: { year: "Choose year", month: "Choose month", day: "Choose day" }) | |
end | |
def test_date_select_with_generic_with_css_classes | |
@post = Post.new | |
@post.written_on = Date.new(2004, 6, 15) | |
expected = +%{<select id="post_written_on_1i" name="post[written_on(1i)]" class="year">\n} | |
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]" class="month">\n} | |
expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]" class="day">\n} | |
expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n} | |
expected << "</select>\n" | |
assert_dom_equal expected, date_select("post", "written_on", with_css_classes: true) | |
end | |
def test_date_select_with_custom_with_css_classes | |
@post = Post.new | |
@post.written_on = Date.new(2004, 6, 15) | |
expected = +%{<select id="post_written_on_1i" name="post[written_on(1i)]" class="my-year">\n} | |
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]" class="my-month">\n} | |
expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]" class="my-day">\n} | |
expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n} | |
expected << "</select>\n" | |
assert_dom_equal expected, date_select("post", "written_on", with_css_classes: { year: "my-year", month: "my-month", day: "my-day" }) | |
end | |
def test_time_select | |
@post = Post.new | |
@post.written_on = Time.local(2004, 6, 15, 15, 16, 35) | |
expected = +%{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" autocomplete="off" />\n} | |
expected << %{<input type="hidden" id="post_written_on_2i" name="post[written_on(2i)]" value="6" autocomplete="off" />\n} | |
expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="15" autocomplete="off" />\n} | |
expected << %(<select id="post_written_on_4i" name="post[written_on(4i)]">\n) | |
0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
expected << " : " | |
expected << %(<select id="post_written_on_5i" name="post[written_on(5i)]">\n) | |
0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
assert_dom_equal expected, time_select("post", "written_on") | |
end | |
def test_time_select_with_selected | |
@post = Post.new | |
@post.written_on = Time.local(2004, 6, 15, 15, 16, 35) | |
expected = +%{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" autocomplete="off" />\n} | |
expected << %{<input type="hidden" id="post_written_on_2i" name="post[written_on(2i)]" value="6" autocomplete="off" />\n} | |
expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="15" autocomplete="off" />\n} | |
expected << %(<select id="post_written_on_4i" name="post[written_on(4i)]">\n) | |
0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 12}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
expected << " : " | |
expected << %(<select id="post_written_on_5i" name="post[written_on(5i)]">\n) | |
0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 20}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
assert_dom_equal expected, time_select("post", "written_on", selected: Time.local(2004, 6, 15, 12, 20, 30)) | |
end | |
def test_time_select_with_selected_nil | |
@post = Post.new | |
@post.written_on = Time.local(2004, 6, 15, 15, 16, 35) | |
expected = +%{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="1" autocomplete="off" />\n} | |
expected << %{<input type="hidden" id="post_written_on_2i" name="post[written_on(2i)]" value="1" autocomplete="off" />\n} | |
expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="1" autocomplete="off" />\n} | |
expected << %(<select id="post_written_on_4i" name="post[written_on(4i)]">\n) | |
0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}">#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
expected << " : " | |
expected << %(<select id="post_written_on_5i" name="post[written_on(5i)]">\n) | |
0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}">#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
assert_dom_equal expected, time_select("post", "written_on", discard_year: true, discard_month: true, discard_day: true, selected: nil) | |
end | |
def test_time_select_without_date_hidden_fields | |
@post = Post.new | |
@post.written_on = Time.local(2004, 6, 15, 15, 16, 35) | |
expected = +%(<select id="post_written_on_4i" name="post[written_on(4i)]">\n) | |
0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
expected << " : " | |
expected << %(<select id="post_written_on_5i" name="post[written_on(5i)]">\n) | |
0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
assert_dom_equal expected, time_select("post", "written_on", ignore_date: true) | |
end | |
def test_time_select_with_seconds | |
@post = Post.new | |
@post.written_on = Time.local(2004, 6, 15, 15, 16, 35) | |
expected = +%{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" autocomplete="off" />\n} | |
expected << %{<input type="hidden" id="post_written_on_2i" name="post[written_on(2i)]" value="6" autocomplete="off" />\n} | |
expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="15" autocomplete="off" />\n} | |
expected << %(<select id="post_written_on_4i" name="post[written_on(4i)]">\n) | |
0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
expected << " : " | |
expected << %(<select id="post_written_on_5i" name="post[written_on(5i)]">\n) | |
0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
expected << " : " | |
expected << %(<select id="post_written_on_6i" name="post[written_on(6i)]">\n) | |
0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 35}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
assert_dom_equal expected, time_select("post", "written_on", include_seconds: true) | |
end | |
def test_time_select_with_html_options | |
@post = Post.new | |
@post.written_on = Time.local(2004, 6, 15, 15, 16, 35) | |
expected = +%{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" autocomplete="off" />\n} | |
expected << %{<input type="hidden" id="post_written_on_2i" name="post[written_on(2i)]" value="6" autocomplete="off" />\n} | |
expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="15" autocomplete="off" />\n} | |
expected << %(<select id="post_written_on_4i" name="post[written_on(4i)]" class="selector">\n) | |
0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
expected << " : " | |
expected << %(<select id="post_written_on_5i" name="post[written_on(5i)]" class="selector">\n) | |
0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
assert_dom_equal expected, time_select("post", "written_on", {}, { class: "selector" }) | |
end | |
def test_time_select_with_html_options_within_fields_for | |
@post = Post.new | |
@post.written_on = Time.local(2004, 6, 15, 15, 16, 35) | |
output_buffer = fields_for :post, @post do |f| | |
concat f.time_select(:written_on, {}, { class: "selector" }) | |
end | |
expected = +%{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" autocomplete="off" />\n} | |
expected << %{<input type="hidden" id="post_written_on_2i" name="post[written_on(2i)]" value="6" autocomplete="off" />\n} | |
expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="15" autocomplete="off" />\n} | |
expected << %(<select id="post_written_on_4i" name="post[written_on(4i)]" class="selector">\n) | |
0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
expected << " : " | |
expected << %(<select id="post_written_on_5i" name="post[written_on(5i)]" class="selector">\n) | |
0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
assert_dom_equal expected, output_buffer | |
end | |
def test_time_select_with_separator | |
@post = Post.new | |
@post.written_on = Time.local(2004, 6, 15, 15, 16, 35) | |
expected = +%{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" autocomplete="off" />\n} | |
expected << %{<input type="hidden" id="post_written_on_2i" name="post[written_on(2i)]" value="6" autocomplete="off" />\n} | |
expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="15" autocomplete="off" />\n} | |
expected << %(<select id="post_written_on_4i" name="post[written_on(4i)]">\n) | |
0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
expected << " - " | |
expected << %(<select id="post_written_on_5i" name="post[written_on(5i)]">\n) | |
0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
expected << " - " | |
expected << %(<select id="post_written_on_6i" name="post[written_on(6i)]">\n) | |
0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 35}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
assert_dom_equal expected, time_select("post", "written_on", time_separator: " - ", include_seconds: true) | |
end | |
def test_time_select_with_default_prompt | |
@post = Post.new | |
@post.written_on = Time.local(2004, 6, 15, 15, 16, 35) | |
expected = +%{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" autocomplete="off" />\n} | |
expected << %{<input type="hidden" id="post_written_on_2i" name="post[written_on(2i)]" value="6" autocomplete="off" />\n} | |
expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="15" autocomplete="off" />\n} | |
expected << %(<select id="post_written_on_4i" name="post[written_on(4i)]">\n) | |
expected << %(<option value="">Hour</option>\n) | |
0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
expected << " : " | |
expected << %(<select id="post_written_on_5i" name="post[written_on(5i)]">\n) | |
expected << %(<option value="">Minute</option>\n) | |
0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
assert_dom_equal expected, time_select("post", "written_on", prompt: true) | |
end | |
def test_time_select_with_custom_prompt | |
@post = Post.new | |
@post.written_on = Time.local(2004, 6, 15, 15, 16, 35) | |
expected = +%{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" autocomplete="off" />\n} | |
expected << %{<input type="hidden" id="post_written_on_2i" name="post[written_on(2i)]" value="6" autocomplete="off" />\n} | |
expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="15" autocomplete="off" />\n} | |
expected << %(<select id="post_written_on_4i" name="post[written_on(4i)]">\n) | |
expected << %(<option value="">Choose hour</option>\n) | |
0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
expected << " : " | |
expected << %(<select id="post_written_on_5i" name="post[written_on(5i)]">\n) | |
expected << %(<option value="">Choose minute</option>\n) | |
0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
assert_dom_equal expected, time_select("post", "written_on", prompt: { hour: "Choose hour", minute: "Choose minute" }) | |
end | |
def test_time_select_with_generic_with_css_classes | |
@post = Post.new | |
@post.written_on = Time.local(2004, 6, 15, 15, 16, 35) | |
expected = +%{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" autocomplete="off" />\n} | |
expected << %{<input type="hidden" id="post_written_on_2i" name="post[written_on(2i)]" value="6" autocomplete="off" />\n} | |
expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="15" autocomplete="off" />\n} | |
expected << %(<select id="post_written_on_4i" name="post[written_on(4i)]" class="hour">\n) | |
0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
expected << " : " | |
expected << %(<select id="post_written_on_5i" name="post[written_on(5i)]" class="minute">\n) | |
0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
assert_dom_equal expected, time_select("post", "written_on", with_css_classes: true) | |
end | |
def test_time_select_with_custom_with_css_classes | |
@post = Post.new | |
@post.written_on = Time.local(2004, 6, 15, 15, 16, 35) | |
expected = +%{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" autocomplete="off" />\n} | |
expected << %{<input type="hidden" id="post_written_on_2i" name="post[written_on(2i)]" value="6" autocomplete="off" />\n} | |
expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="15" autocomplete="off" />\n} | |
expected << %(<select id="post_written_on_4i" name="post[written_on(4i)]" class="my-hour">\n) | |
0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
expected << " : " | |
expected << %(<select id="post_written_on_5i" name="post[written_on(5i)]" class="my-minute">\n) | |
0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
assert_dom_equal expected, time_select("post", "written_on", with_css_classes: { hour: "my-hour", minute: "my-minute" }) | |
end | |
def test_time_select_with_disabled_html_option | |
@post = Post.new | |
@post.written_on = Time.local(2004, 6, 15, 15, 16, 35) | |
expected = +%{<input type="hidden" id="post_written_on_1i" disabled="disabled" name="post[written_on(1i)]" value="2004" autocomplete="off" />\n} | |
expected << %{<input type="hidden" id="post_written_on_2i" disabled="disabled" name="post[written_on(2i)]" value="6" autocomplete="off" />\n} | |
expected << %{<input type="hidden" id="post_written_on_3i" disabled="disabled" name="post[written_on(3i)]" value="15" autocomplete="off" />\n} | |
expected << %(<select id="post_written_on_4i" disabled="disabled" name="post[written_on(4i)]">\n) | |
0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
expected << " : " | |
expected << %(<select id="post_written_on_5i" disabled="disabled" name="post[written_on(5i)]">\n) | |
0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
assert_dom_equal expected, time_select("post", "written_on", {}, { disabled: true }) | |
end | |
def test_datetime_select | |
@post = Post.new | |
@post.updated_at = Time.local(2004, 6, 15, 16, 35) | |
expected = +%{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n} | |
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]">\n} | |
expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_updated_at_3i" name="post[updated_at(3i)]">\n} | |
expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n} | |
expected << "</select>\n" | |
expected << " — " | |
expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n} | |
expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n} | |
expected << "</select>\n" | |
expected << " : " | |
expected << %{<select id="post_updated_at_5i" name="post[updated_at(5i)]">\n} | |
expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35" selected="selected">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n} | |
expected << "</select>\n" | |
assert_dom_equal expected, datetime_select("post", "updated_at") | |
end | |
def test_datetime_select_with_selected | |
@post = Post.new | |
@post.updated_at = Time.local(2004, 6, 15, 16, 35) | |
expected = +%{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n} | |
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]">\n} | |
expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3" selected="selected">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_updated_at_3i" name="post[updated_at(3i)]">\n} | |
expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10" selected="selected">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n} | |
expected << "</select>\n" | |
expected << " — " | |
expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n} | |
expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12" selected="selected">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n} | |
expected << "</select>\n" | |
expected << " : " | |
expected << %{<select id="post_updated_at_5i" name="post[updated_at(5i)]">\n} | |
expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30" selected="selected">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n} | |
expected << "</select>\n" | |
assert_dom_equal expected, datetime_select("post", "updated_at", selected: Time.local(2004, 3, 10, 12, 30)) | |
end | |
def test_datetime_select_with_selected_nil | |
@post = Post.new | |
@post.updated_at = Time.local(2004, 6, 15, 16, 35) | |
expected = '<input id="post_updated_at_1i" name="post[updated_at(1i)]" type="hidden" value="1" autocomplete="off" />' + "\n" | |
expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]">\n} | |
expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_updated_at_3i" name="post[updated_at(3i)]">\n} | |
expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n} | |
expected << "</select>\n" | |
expected << " — " | |
expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n} | |
expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n} | |
expected << "</select>\n" | |
expected << " : " | |
expected << %{<select id="post_updated_at_5i" name="post[updated_at(5i)]">\n} | |
expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n} | |
expected << "</select>\n" | |
assert_dom_equal expected, datetime_select("post", "updated_at", discard_year: true, selected: nil) | |
end | |
def test_datetime_select_defaults_to_time_zone_now_when_config_time_zone_is_set | |
# The love zone is UTC+0 | |
mytz = Class.new(ActiveSupport::TimeZone) { | |
attr_accessor :now | |
}.create("tenderlove", 0, ActiveSupport::TimeZone.find_tzinfo("UTC")) | |
now = Time.mktime(2004, 6, 15, 16, 35, 0) | |
mytz.now = now | |
Time.zone = mytz | |
assert_equal mytz, Time.zone | |
@post = Post.new | |
expected = +%{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n} | |
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]">\n} | |
expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_updated_at_3i" name="post[updated_at(3i)]">\n} | |
expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n} | |
expected << "</select>\n" | |
expected << " — " | |
expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n} | |
expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n} | |
expected << "</select>\n" | |
expected << " : " | |
expected << %{<select id="post_updated_at_5i" name="post[updated_at(5i)]">\n} | |
expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35" selected="selected">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n} | |
expected << "</select>\n" | |
assert_dom_equal expected, datetime_select("post", "updated_at") | |
ensure | |
Time.zone = nil | |
end | |
def test_datetime_select_with_html_options_within_fields_for | |
@post = Post.new | |
@post.updated_at = Time.local(2004, 6, 15, 16, 35) | |
output_buffer = fields_for :post, @post do |f| | |
concat f.datetime_select(:updated_at, {}, { class: "selector" }) | |
end | |
expected = +%{<select id="post_updated_at_1i" name="post[updated_at(1i)]" class="selector">\n<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option selected="selected" value="2004">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n</select>\n} | |
expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]" class="selector">\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option selected="selected" value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n</select>\n} | |
expected << %{<select id="post_updated_at_3i" name="post[updated_at(3i)]" class="selector">\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option selected="selected" value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n</select>\n} | |
expected << %{ — <select id="post_updated_at_4i" name="post[updated_at(4i)]" class="selector">\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option selected="selected" value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n</select>\n} | |
expected << %{ : <select id="post_updated_at_5i" name="post[updated_at(5i)]" class="selector">\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option selected="selected" value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n</select>\n} | |
assert_dom_equal expected, output_buffer | |
end | |
def test_datetime_select_with_separators | |
@post = Post.new | |
@post.updated_at = Time.local(2004, 6, 15, 15, 16, 35) | |
expected = +%{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n} | |
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} | |
expected << "</select>\n" | |
expected << " / " | |
expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]">\n} | |
expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} | |
expected << "</select>\n" | |
expected << " / " | |
expected << %{<select id="post_updated_at_3i" name="post[updated_at(3i)]">\n} | |
expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n} | |
expected << "</select>\n" | |
expected << " , " | |
expected << %(<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n) | |
0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
expected << " - " | |
expected << %(<select id="post_updated_at_5i" name="post[updated_at(5i)]">\n) | |
0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
expected << " - " | |
expected << %(<select id="post_updated_at_6i" name="post[updated_at(6i)]">\n) | |
0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 35}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
assert_dom_equal expected, datetime_select("post", "updated_at", date_separator: " / ", datetime_separator: " , ", time_separator: " - ", include_seconds: true) | |
end | |
def test_datetime_select_with_integer | |
@post = Post.new | |
@post.updated_at = 3 | |
datetime_select("post", "updated_at") | |
end | |
def test_datetime_select_with_infinity # Float | |
@post = Post.new | |
@post.updated_at = (-1.0 / 0) | |
datetime_select("post", "updated_at") | |
end | |
def test_datetime_select_with_default_prompt | |
@post = Post.new | |
@post.updated_at = nil | |
expected = +%{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n} | |
expected << %{<option value="">Year</option>\n<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]">\n} | |
expected << %{<option value="">Month</option>\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_updated_at_3i" name="post[updated_at(3i)]">\n} | |
expected << %{<option value="">Day</option>\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n} | |
expected << "</select>\n" | |
expected << " — " | |
expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n} | |
expected << %{<option value="">Hour</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n} | |
expected << "</select>\n" | |
expected << " : " | |
expected << %{<select id="post_updated_at_5i" name="post[updated_at(5i)]">\n} | |
expected << %{<option value="">Minute</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n} | |
expected << "</select>\n" | |
assert_dom_equal expected, datetime_select("post", "updated_at", start_year: 1999, end_year: 2009, prompt: true) | |
end | |
def test_datetime_select_with_custom_prompt | |
@post = Post.new | |
@post.updated_at = nil | |
expected = +%{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n} | |
expected << %{<option value="">Choose year</option>\n<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]">\n} | |
expected << %{<option value="">Choose month</option>\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_updated_at_3i" name="post[updated_at(3i)]">\n} | |
expected << %{<option value="">Choose day</option>\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n} | |
expected << "</select>\n" | |
expected << " — " | |
expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n} | |
expected << %{<option value="">Choose hour</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n} | |
expected << "</select>\n" | |
expected << " : " | |
expected << %{<select id="post_updated_at_5i" name="post[updated_at(5i)]">\n} | |
expected << %{<option value="">Choose minute</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n} | |
expected << "</select>\n" | |
assert_dom_equal expected, datetime_select("post", "updated_at", start_year: 1999, end_year: 2009, prompt: { year: "Choose year", month: "Choose month", day: "Choose day", hour: "Choose hour", minute: "Choose minute" }) | |
end | |
def test_datetime_select_with_generic_with_css_classes | |
@post = Post.new | |
@post.written_on = Time.local(2004, 6, 15, 15, 16, 35) | |
expected = +%{<select id="post_written_on_1i" name="post[written_on(1i)]" class="year">\n} | |
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]" class="month">\n} | |
expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]" class="day">\n} | |
expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n} | |
expected << "</select>\n" | |
expected << " — " | |
expected << %{<select id="post_written_on_4i" name="post[written_on(4i)]" class="hour">\n} | |
expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n} | |
expected << "</select>\n" | |
expected << " : " | |
expected << %{<select id="post_written_on_5i" name="post[written_on(5i)]" class="minute">\n} | |
expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n} | |
expected << "</select>\n" | |
assert_dom_equal expected, datetime_select("post", "written_on", start_year: 1999, end_year: 2009, with_css_classes: true) | |
end | |
def test_datetime_select_with_custom_with_css_classes | |
@post = Post.new | |
@post.written_on = Time.local(2004, 6, 15, 15, 16, 35) | |
expected = +%{<select id="post_written_on_1i" name="post[written_on(1i)]" class="my-year">\n} | |
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]" class="my-month">\n} | |
expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]" class="my-day">\n} | |
expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n} | |
expected << "</select>\n" | |
expected << " — " | |
expected << %{<select id="post_written_on_4i" name="post[written_on(4i)]" class="my-hour">\n} | |
expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n} | |
expected << "</select>\n" | |
expected << " : " | |
expected << %{<select id="post_written_on_5i" name="post[written_on(5i)]" class="my-minute">\n} | |
expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n} | |
expected << "</select>\n" | |
assert_dom_equal expected, datetime_select("post", "written_on", start_year: 1999, end_year: 2009, with_css_classes: { year: "my-year", month: "my-month", day: "my-day", hour: "my-hour", minute: "my-minute" }) | |
end | |
def test_date_select_with_zero_value_and_no_start_year | |
expected = +%(<select id="date_first_year" name="date[first][year]">\n) | |
(Date.today.year - 5).upto(Date.today.year + 1) { |y| expected << %(<option value="#{y}">#{y}</option>\n) } | |
expected << "</select>\n" | |
expected << %(<select id="date_first_month" name="date[first][month]">\n) | |
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_first_day" name="date[first][day]">\n) | |
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_date(0, end_year: Date.today.year + 1, prefix: "date[first]") | |
end | |
def test_date_select_with_zero_value_and_no_end_year | |
expected = +%(<select id="date_first_year" name="date[first][year]">\n) | |
last_year = Time.now.year + 5 | |
2003.upto(last_year) { |y| expected << %(<option value="#{y}">#{y}</option>\n) } | |
expected << "</select>\n" | |
expected << %(<select id="date_first_month" name="date[first][month]">\n) | |
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_first_day" name="date[first][day]">\n) | |
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_date(0, start_year: 2003, prefix: "date[first]") | |
end | |
def test_date_select_with_zero_value_and_no_start_and_end_year | |
expected = +%(<select id="date_first_year" name="date[first][year]">\n) | |
(Date.today.year - 5).upto(Date.today.year + 5) { |y| expected << %(<option value="#{y}">#{y}</option>\n) } | |
expected << "</select>\n" | |
expected << %(<select id="date_first_month" name="date[first][month]">\n) | |
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_first_day" name="date[first][day]">\n) | |
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_date(0, prefix: "date[first]") | |
end | |
def test_date_select_with_nil_value_and_no_start_and_end_year | |
expected = +%(<select id="date_first_year" name="date[first][year]">\n) | |
(Date.today.year - 5).upto(Date.today.year + 5) { |y| expected << %(<option value="#{y}">#{y}</option>\n) } | |
expected << "</select>\n" | |
expected << %(<select id="date_first_month" name="date[first][month]">\n) | |
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_first_day" name="date[first][day]">\n) | |
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_date(nil, prefix: "date[first]") | |
end | |
def test_datetime_select_with_nil_value_and_no_start_and_end_year | |
expected = +%(<select id="date_first_year" name="date[first][year]">\n) | |
(Date.today.year - 5).upto(Date.today.year + 5) { |y| expected << %(<option value="#{y}">#{y}</option>\n) } | |
expected << "</select>\n" | |
expected << %(<select id="date_first_month" name="date[first][month]">\n) | |
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) | |
expected << "</select>\n" | |
expected << %(<select id="date_first_day" name="date[first][day]">\n) | |
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) | |
expected << "</select>\n" | |
expected << " — " | |
expected << %(<select id="date_first_hour" name="date[first][hour]">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) | |
expected << "</select>\n" | |
expected << " : " | |
expected << %(<select id="date_first_minute" name="date[first][minute]">\n) | |
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) | |
expected << "</select>\n" | |
assert_dom_equal expected, select_datetime(nil, prefix: "date[first]") | |
end | |
def test_datetime_select_with_options_index | |
@post = Post.new | |
@post.updated_at = Time.local(2004, 6, 15, 16, 35) | |
id = 456 | |
expected = +%{<select id="post_456_updated_at_1i" name="post[#{id}][updated_at(1i)]">\n} | |
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_456_updated_at_2i" name="post[#{id}][updated_at(2i)]">\n} | |
expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_456_updated_at_3i" name="post[#{id}][updated_at(3i)]">\n} | |
expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n} | |
expected << "</select>\n" | |
expected << " — " | |
expected << %{<select id="post_456_updated_at_4i" name="post[#{id}][updated_at(4i)]">\n} | |
expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n} | |
expected << "</select>\n" | |
expected << " : " | |
expected << %{<select id="post_456_updated_at_5i" name="post[#{id}][updated_at(5i)]">\n} | |
expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35" selected="selected">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n} | |
expected << "</select>\n" | |
assert_dom_equal expected, datetime_select("post", "updated_at", index: id) | |
end | |
def test_datetime_select_within_fields_for_with_options_index | |
@post = Post.new | |
@post.updated_at = Time.local(2004, 6, 15, 16, 35) | |
id = 456 | |
output_buffer = fields_for :post, @post, index: id do |f| | |
concat f.datetime_select(:updated_at) | |
end | |
expected = +%{<select id="post_456_updated_at_1i" name="post[#{id}][updated_at(1i)]">\n} | |
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_456_updated_at_2i" name="post[#{id}][updated_at(2i)]">\n} | |
expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_456_updated_at_3i" name="post[#{id}][updated_at(3i)]">\n} | |
expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n} | |
expected << "</select>\n" | |
expected << " — " | |
expected << %{<select id="post_456_updated_at_4i" name="post[#{id}][updated_at(4i)]">\n} | |
expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n} | |
expected << "</select>\n" | |
expected << " : " | |
expected << %{<select id="post_456_updated_at_5i" name="post[#{id}][updated_at(5i)]">\n} | |
expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35" selected="selected">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n} | |
expected << "</select>\n" | |
assert_dom_equal expected, output_buffer | |
end | |
def test_datetime_select_with_auto_index | |
@post = Post.new | |
@post.updated_at = Time.local(2004, 6, 15, 16, 35) | |
id = @post.id | |
expected = +%{<select id="post_123_updated_at_1i" name="post[#{id}][updated_at(1i)]">\n} | |
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_123_updated_at_2i" name="post[#{id}][updated_at(2i)]">\n} | |
expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_123_updated_at_3i" name="post[#{id}][updated_at(3i)]">\n} | |
expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n} | |
expected << "</select>\n" | |
expected << " — " | |
expected << %{<select id="post_123_updated_at_4i" name="post[#{id}][updated_at(4i)]">\n} | |
expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n} | |
expected << "</select>\n" | |
expected << " : " | |
expected << %{<select id="post_123_updated_at_5i" name="post[#{id}][updated_at(5i)]">\n} | |
expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35" selected="selected">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n} | |
expected << "</select>\n" | |
assert_dom_equal expected, datetime_select("post[]", "updated_at") | |
end | |
def test_datetime_select_with_seconds | |
@post = Post.new | |
@post.updated_at = Time.local(2004, 6, 15, 15, 16, 35) | |
expected = +%{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n} | |
1999.upto(2009) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 2004}>#{i}</option>\n) } | |
expected << "</select>\n" | |
expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]">\n} | |
1.upto(12) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 6}>#{Date::MONTHNAMES[i]}</option>\n) } | |
expected << "</select>\n" | |
expected << %{<select id="post_updated_at_3i" name="post[updated_at(3i)]">\n} | |
1.upto(31) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 15}>#{i}</option>\n) } | |
expected << "</select>\n" | |
expected << " — " | |
expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n} | |
0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
expected << " : " | |
expected << %{<select id="post_updated_at_5i" name="post[updated_at(5i)]">\n} | |
0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
expected << " : " | |
expected << %{<select id="post_updated_at_6i" name="post[updated_at(6i)]">\n} | |
0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 35}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
assert_dom_equal expected, datetime_select("post", "updated_at", include_seconds: true) | |
end | |
def test_datetime_select_discard_year | |
@post = Post.new | |
@post.updated_at = Time.local(2004, 6, 15, 15, 16, 35) | |
expected = +%{<input type="hidden" id="post_updated_at_1i" name="post[updated_at(1i)]" value="2004" autocomplete="off" />\n} | |
expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]">\n} | |
1.upto(12) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 6}>#{Date::MONTHNAMES[i]}</option>\n) } | |
expected << "</select>\n" | |
expected << %{<select id="post_updated_at_3i" name="post[updated_at(3i)]">\n} | |
1.upto(31) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 15}>#{i}</option>\n) } | |
expected << "</select>\n" | |
expected << " — " | |
expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n} | |
0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
expected << " : " | |
expected << %{<select id="post_updated_at_5i" name="post[updated_at(5i)]">\n} | |
0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
assert_dom_equal expected, datetime_select("post", "updated_at", discard_year: true) | |
end | |
def test_datetime_select_discard_month | |
@post = Post.new | |
@post.updated_at = Time.local(2004, 6, 15, 15, 16, 35) | |
expected = +%{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n} | |
1999.upto(2009) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 2004}>#{i}</option>\n) } | |
expected << "</select>\n" | |
expected << %{<input type="hidden" id="post_updated_at_2i" name="post[updated_at(2i)]" value="6" autocomplete="off" />\n} | |
expected << %{<input type="hidden" id="post_updated_at_3i" name="post[updated_at(3i)]" value="1" autocomplete="off" />\n} | |
expected << " — " | |
expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n} | |
0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
expected << " : " | |
expected << %{<select id="post_updated_at_5i" name="post[updated_at(5i)]">\n} | |
0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
assert_dom_equal expected, datetime_select("post", "updated_at", discard_month: true) | |
end | |
def test_datetime_select_discard_year_and_month | |
@post = Post.new | |
@post.updated_at = Time.local(2004, 6, 15, 15, 16, 35) | |
expected = +%{<input type="hidden" id="post_updated_at_1i" name="post[updated_at(1i)]" value="2004" autocomplete="off" />\n} | |
expected << %{<input type="hidden" id="post_updated_at_2i" name="post[updated_at(2i)]" value="6" autocomplete="off" />\n} | |
expected << %{<input type="hidden" id="post_updated_at_3i" name="post[updated_at(3i)]" value="1" autocomplete="off" />\n} | |
expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n} | |
0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
expected << " : " | |
expected << %{<select id="post_updated_at_5i" name="post[updated_at(5i)]">\n} | |
0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
assert_dom_equal expected, datetime_select("post", "updated_at", discard_year: true, discard_month: true) | |
end | |
def test_datetime_select_discard_year_and_month_with_disabled_html_option | |
@post = Post.new | |
@post.updated_at = Time.local(2004, 6, 15, 15, 16, 35) | |
expected = +%{<input type="hidden" id="post_updated_at_1i" disabled="disabled" name="post[updated_at(1i)]" value="2004" autocomplete="off" />\n} | |
expected << %{<input type="hidden" id="post_updated_at_2i" disabled="disabled" name="post[updated_at(2i)]" value="6" autocomplete="off" />\n} | |
expected << %{<input type="hidden" id="post_updated_at_3i" disabled="disabled" name="post[updated_at(3i)]" value="1" autocomplete="off" />\n} | |
expected << %{<select id="post_updated_at_4i" disabled="disabled" name="post[updated_at(4i)]">\n} | |
0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
expected << " : " | |
expected << %{<select id="post_updated_at_5i" disabled="disabled" name="post[updated_at(5i)]">\n} | |
0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
assert_dom_equal expected, datetime_select("post", "updated_at", { discard_year: true, discard_month: true }, { disabled: true }) | |
end | |
def test_datetime_select_discard_hour | |
@post = Post.new | |
@post.updated_at = Time.local(2004, 6, 15, 15, 16, 35) | |
expected = +%{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n} | |
1999.upto(2009) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 2004}>#{i}</option>\n) } | |
expected << "</select>\n" | |
expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]">\n} | |
1.upto(12) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 6}>#{Date::MONTHNAMES[i]}</option>\n) } | |
expected << "</select>\n" | |
expected << %{<select id="post_updated_at_3i" name="post[updated_at(3i)]">\n} | |
1.upto(31) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 15}>#{i}</option>\n) } | |
expected << "</select>\n" | |
assert_dom_equal expected, datetime_select("post", "updated_at", discard_hour: true) | |
end | |
def test_datetime_select_discard_minute | |
@post = Post.new | |
@post.updated_at = Time.local(2004, 6, 15, 15, 16, 35) | |
expected = +%{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n} | |
1999.upto(2009) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 2004}>#{i}</option>\n) } | |
expected << "</select>\n" | |
expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]">\n} | |
1.upto(12) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 6}>#{Date::MONTHNAMES[i]}</option>\n) } | |
expected << "</select>\n" | |
expected << %{<select id="post_updated_at_3i" name="post[updated_at(3i)]">\n} | |
1.upto(31) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 15}>#{i}</option>\n) } | |
expected << "</select>\n" | |
expected << " — " | |
expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n} | |
0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
expected << %{<input type="hidden" id="post_updated_at_5i" name="post[updated_at(5i)]" value="16" autocomplete="off" />\n} | |
assert_dom_equal expected, datetime_select("post", "updated_at", discard_minute: true) | |
end | |
def test_datetime_select_disabled_and_discard_minute | |
@post = Post.new | |
@post.updated_at = Time.local(2004, 6, 15, 15, 16, 35) | |
expected = +%{<select id="post_updated_at_1i" disabled="disabled" name="post[updated_at(1i)]">\n} | |
1999.upto(2009) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 2004}>#{i}</option>\n) } | |
expected << "</select>\n" | |
expected << %{<select id="post_updated_at_2i" disabled="disabled" name="post[updated_at(2i)]">\n} | |
1.upto(12) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 6}>#{Date::MONTHNAMES[i]}</option>\n) } | |
expected << "</select>\n" | |
expected << %{<select id="post_updated_at_3i" disabled="disabled" name="post[updated_at(3i)]">\n} | |
1.upto(31) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 15}>#{i}</option>\n) } | |
expected << "</select>\n" | |
expected << " — " | |
expected << %{<select id="post_updated_at_4i" disabled="disabled" name="post[updated_at(4i)]">\n} | |
0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
expected << %{<input type="hidden" id="post_updated_at_5i" disabled="disabled" name="post[updated_at(5i)]" value="16" autocomplete="off" />\n} | |
assert_dom_equal expected, datetime_select("post", "updated_at", discard_minute: true, disabled: true) | |
end | |
def test_datetime_select_invalid_order | |
@post = Post.new | |
@post.updated_at = Time.local(2004, 6, 15, 15, 16, 35) | |
expected = +%{<select id="post_updated_at_3i" name="post[updated_at(3i)]">\n} | |
1.upto(31) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 15}>#{i}</option>\n) } | |
expected << "</select>\n" | |
expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]">\n} | |
1.upto(12) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 6}>#{Date::MONTHNAMES[i]}</option>\n) } | |
expected << "</select>\n" | |
expected << %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n} | |
1999.upto(2009) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 2004}>#{i}</option>\n) } | |
expected << "</select>\n" | |
expected << " — " | |
expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n} | |
0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
expected << " : " | |
expected << %{<select id="post_updated_at_5i" name="post[updated_at(5i)]">\n} | |
0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
assert_dom_equal expected, datetime_select("post", "updated_at", order: [:minute, :day, :hour, :month, :year, :second]) | |
end | |
def test_datetime_select_discard_with_order | |
@post = Post.new | |
@post.updated_at = Time.local(2004, 6, 15, 15, 16, 35) | |
expected = +%{<input type="hidden" id="post_updated_at_1i" name="post[updated_at(1i)]" value="2004" autocomplete="off" />\n} | |
expected << %{<select id="post_updated_at_3i" name="post[updated_at(3i)]">\n} | |
1.upto(31) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 15}>#{i}</option>\n) } | |
expected << "</select>\n" | |
expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]">\n} | |
1.upto(12) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 6}>#{Date::MONTHNAMES[i]}</option>\n) } | |
expected << "</select>\n" | |
expected << " — " | |
expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n} | |
0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
expected << " : " | |
expected << %{<select id="post_updated_at_5i" name="post[updated_at(5i)]">\n} | |
0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
assert_dom_equal expected, datetime_select("post", "updated_at", order: [:day, :month]) | |
end | |
def test_datetime_select_with_default_value_as_time | |
@post = Post.new | |
@post.updated_at = nil | |
expected = +%{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n} | |
2001.upto(2011) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 2006}>#{i}</option>\n) } | |
expected << "</select>\n" | |
expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]">\n} | |
1.upto(12) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 9}>#{Date::MONTHNAMES[i]}</option>\n) } | |
expected << "</select>\n" | |
expected << %{<select id="post_updated_at_3i" name="post[updated_at(3i)]">\n} | |
1.upto(31) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 19}>#{i}</option>\n) } | |
expected << "</select>\n" | |
expected << " — " | |
expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n} | |
0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
expected << " : " | |
expected << %{<select id="post_updated_at_5i" name="post[updated_at(5i)]">\n} | |
0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
assert_dom_equal expected, datetime_select("post", "updated_at", default: Time.local(2006, 9, 19, 15, 16, 35)) | |
end | |
def test_include_blank_overrides_default_option | |
@post = Post.new | |
@post.updated_at = nil | |
expected = +%{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n} | |
expected << %(<option value="" label=" "></option>\n) | |
(Time.now.year - 5).upto(Time.now.year + 5) { |i| expected << %(<option value="#{i}">#{i}</option>\n) } | |
expected << "</select>\n" | |
expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]">\n} | |
expected << %(<option value="" label=" "></option>\n) | |
1.upto(12) { |i| expected << %(<option value="#{i}">#{Date::MONTHNAMES[i]}</option>\n) } | |
expected << "</select>\n" | |
expected << %{<select id="post_updated_at_3i" name="post[updated_at(3i)]">\n} | |
expected << %(<option value="" label=" "></option>\n) | |
1.upto(31) { |i| expected << %(<option value="#{i}">#{i}</option>\n) } | |
expected << "</select>\n" | |
assert_dom_equal expected, date_select("post", "updated_at", default: Time.local(2006, 9, 19, 15, 16, 35), include_blank: true) | |
end | |
def test_datetime_select_with_default_value_as_hash | |
@post = Post.new | |
@post.updated_at = nil | |
expected = +%{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n} | |
(Time.now.year - 5).upto(Time.now.year + 5) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == Time.now.year}>#{i}</option>\n) } | |
expected << "</select>\n" | |
expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]">\n} | |
1.upto(12) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 10}>#{Date::MONTHNAMES[i]}</option>\n) } | |
expected << "</select>\n" | |
expected << %{<select id="post_updated_at_3i" name="post[updated_at(3i)]">\n} | |
1.upto(31) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == Time.now.day}>#{i}</option>\n) } | |
expected << "</select>\n" | |
expected << " — " | |
expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n} | |
0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 9}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
expected << " : " | |
expected << %{<select id="post_updated_at_5i" name="post[updated_at(5i)]">\n} | |
0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 42}>#{sprintf("%02d", i)}</option>\n) } | |
expected << "</select>\n" | |
assert_dom_equal expected, datetime_select("post", "updated_at", default: { month: 10, minute: 42, hour: 9 }) | |
end | |
def test_datetime_select_with_html_options | |
@post = Post.new | |
@post.updated_at = Time.local(2004, 6, 15, 16, 35) | |
expected = +%{<select id="post_updated_at_1i" name="post[updated_at(1i)]" class="selector">\n} | |
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]" class="selector">\n} | |
expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} | |
expected << "</select>\n" | |
expected << %{<select id="post_updated_at_3i" name="post[updated_at(3i)]" class="selector">\n} | |
expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n} | |
expected << "</select>\n" | |
expected << " — " | |
expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]" class="selector">\n} | |
expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n} | |
expected << "</select>\n" | |
expected << " : " | |
expected << %{<select id="post_updated_at_5i" name="post[updated_at(5i)]" class="selector">\n} | |
expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35" selected="selected">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n} | |
expected << "</select>\n" | |
assert_dom_equal expected, datetime_select("post", "updated_at", {}, { class: "selector" }) | |
end | |
def test_date_select_should_not_change_passed_options_hash | |
@post = Post.new | |
@post.updated_at = Time.local(2008, 7, 16, 23, 30) | |
options = { | |
order: [ :year, :month, :day ], | |
default: { year: 2008, month: 7, day: 16, hour: 23, minute: 30, second: 1 }, | |
discard_type: false, | |
include_blank: false, | |
ignore_date: false, | |
include_seconds: true | |
} | |
date_select(@post, :updated_at, options) | |
# note: the literal hash is intentional to show that the actual options hash isn't modified | |
# don't change this! | |
assert_equal({ | |
order: [ :year, :month, :day ], | |
default: { year: 2008, month: 7, day: 16, hour: 23, minute: 30, second: 1 }, | |
discard_type: false, | |
include_blank: false, | |
ignore_date: false, | |
include_seconds: true | |
}, options) | |
end | |
def test_datetime_select_should_not_change_passed_options_hash | |
@post = Post.new | |
@post.updated_at = Time.local(2008, 7, 16, 23, 30) | |
options = { | |
order: [ :year, :month, :day ], | |
default: { year: 2008, month: 7, day: 16, hour: 23, minute: 30, second: 1 }, | |
discard_type: false, | |
include_blank: false, | |
ignore_date: false, | |
include_seconds: true | |
} | |
datetime_select(@post, :updated_at, options) | |
# note: the literal hash is intentional to show that the actual options hash isn't modified | |
# don't change this! | |
assert_equal({ | |
order: [ :year, :month, :day ], | |
default: { year: 2008, month: 7, day: 16, hour: 23, minute: 30, second: 1 }, | |
discard_type: false, | |
include_blank: false, | |
ignore_date: false, | |
include_seconds: true | |
}, options) | |
end | |
def test_time_select_should_not_change_passed_options_hash | |
@post = Post.new | |
@post.updated_at = Time.local(2008, 7, 16, 23, 30) | |
options = { | |
order: [ :year, :month, :day ], | |
default: { year: 2008, month: 7, day: 16, hour: 23, minute: 30, second: 1 }, | |
discard_type: false, | |
include_blank: false, | |
ignore_date: false, | |
include_seconds: true | |
} | |
time_select(@post, :updated_at, options) | |
# note: the literal hash is intentional to show that the actual options hash isn't modified | |
# don't change this! | |
assert_equal({ | |
order: [ :year, :month, :day ], | |
default: { year: 2008, month: 7, day: 16, hour: 23, minute: 30, second: 1 }, | |
discard_type: false, | |
include_blank: false, | |
ignore_date: false, | |
include_seconds: true | |
}, options) | |
end | |
def test_select_date_should_not_change_passed_options_hash | |
options = { | |
order: [ :year, :month, :day ], | |
default: { year: 2008, month: 7, day: 16, hour: 23, minute: 30, second: 1 }, | |
discard_type: false, | |
include_blank: false, | |
ignore_date: false, | |
include_seconds: true | |
} | |
select_date(Date.today, options) | |
# note: the literal hash is intentional to show that the actual options hash isn't modified | |
# don't change this! | |
assert_equal({ | |
order: [ :year, :month, :day ], | |
default: { year: 2008, month: 7, day: 16, hour: 23, minute: 30, second: 1 }, | |
discard_type: false, | |
include_blank: false, | |
ignore_date: false, | |
include_seconds: true | |
}, options) | |
end | |
def test_select_datetime_should_not_change_passed_options_hash | |
options = { | |
order: [ :year, :month, :day ], | |
default: { year: 2008, month: 7, day: 16, hour: 23, minute: 30, second: 1 }, | |
discard_type: false, | |
include_blank: false, | |
ignore_date: false, | |
include_seconds: true | |
} | |
select_datetime(Time.now, options) | |
# note: the literal hash is intentional to show that the actual options hash isn't modified | |
# don't change this! | |
assert_equal({ | |
order: [ :year, :month, :day ], | |
default: { year: 2008, month: 7, day: 16, hour: 23, minute: 30, second: 1 }, | |
discard_type: false, | |
include_blank: false, | |
ignore_date: false, | |
include_seconds: true | |
}, options) | |
end | |
def test_select_time_should_not_change_passed_options_hash | |
options = { | |
order: [ :year, :month, :day ], | |
default: { year: 2008, month: 7, day: 16, hour: 23, minute: 30, second: 1 }, | |
discard_type: false, | |
include_blank: false, | |
ignore_date: false, | |
include_seconds: true | |
} | |
select_time(Time.now, options) | |
# note: the literal hash is intentional to show that the actual options hash isn't modified | |
# don't change this! | |
assert_equal({ | |
order: [ :year, :month, :day ], | |
default: { year: 2008, month: 7, day: 16, hour: 23, minute: 30, second: 1 }, | |
discard_type: false, | |
include_blank: false, | |
ignore_date: false, | |
include_seconds: true | |
}, options) | |
end | |
def test_select_html_safety | |
assert_predicate select_day(16), :html_safe? | |
assert_predicate select_month(8), :html_safe? | |
assert_predicate select_year(Time.mktime(2003, 8, 16, 8, 4, 18)), :html_safe? | |
assert_predicate select_minute(Time.mktime(2003, 8, 16, 8, 4, 18)), :html_safe? | |
assert_predicate select_second(Time.mktime(2003, 8, 16, 8, 4, 18)), :html_safe? | |
assert_predicate select_minute(8, use_hidden: true), :html_safe? | |
assert_predicate select_month(8, prompt: "Choose month"), :html_safe? | |
assert_predicate select_time(Time.mktime(2003, 8, 16, 8, 4, 18), {}, { class: "selector" }), :html_safe? | |
assert_predicate select_date(Time.mktime(2003, 8, 16), date_separator: " / ", start_year: 2003, end_year: 2005, prefix: "date[first]"), :html_safe? | |
end | |
def test_object_select_html_safety | |
@post = Post.new | |
@post.written_on = Date.new(2004, 6, 15) | |
assert_predicate date_select("post", "written_on", default: Time.local(2006, 9, 19, 15, 16, 35), include_blank: true), :html_safe? | |
assert_predicate time_select("post", "written_on", ignore_date: true), :html_safe? | |
end | |
def test_time_tag_with_date | |
date = Date.new(2013, 2, 20) | |
expected = '<time datetime="2013-02-20">February 20, 2013</time>' | |
assert_equal expected, time_tag(date) | |
end | |
def test_time_tag_with_time | |
time = Time.new(2013, 2, 20, 0, 0, 0, "+00:00") | |
expected = '<time datetime="2013-02-20T00:00:00+00:00">February 20, 2013 00:00</time>' | |
assert_equal expected, time_tag(time) | |
end | |
def test_time_tag_with_given_text | |
assert_match(/<time.*>Right now<\/time>/, time_tag(Time.now, "Right now")) | |
end | |
def test_time_tag_with_given_block | |
assert_match(/<time.*><span>Right now<\/span><\/time>/, time_tag(Time.now) { raw("<span>Right now</span>") }) | |
end | |
def test_time_tag_with_different_format | |
time = Time.new(2013, 2, 20, 0, 0, 0, "+00:00") | |
expected = '<time datetime="2013-02-20T00:00:00+00:00">20 Feb 00:00</time>' | |
assert_equal expected, time_tag(time, format: :short) | |
end | |
end |
# frozen_string_literal: true | |
require "active_support/core_ext/time/calculations" | |
module ActionView | |
module Helpers | |
module Tags # :nodoc: | |
class DateSelect < Base # :nodoc: | |
def initialize(object_name, method_name, template_object, options, html_options) | |
@html_options = html_options | |
super(object_name, method_name, template_object, options) | |
end | |
def render | |
error_wrapping(datetime_selector(@options, @html_options).public_send("select_#{select_type}").html_safe) | |
end | |
class << self | |
def select_type | |
@select_type ||= name.split("::").last.sub("Select", "").downcase | |
end | |
end | |
private | |
def select_type | |
self.class.select_type | |
end | |
def datetime_selector(options, html_options) | |
datetime = options.fetch(:selected) { value || default_datetime(options) } | |
@auto_index ||= nil | |
options = options.dup | |
options[:field_name] = @method_name | |
options[:include_position] = true | |
options[:prefix] ||= @object_name | |
options[:index] = @auto_index if @auto_index && !options.has_key?(:index) | |
DateTimeSelector.new(datetime, options, html_options) | |
end | |
def default_datetime(options) | |
return if options[:include_blank] || options[:prompt] | |
case options[:default] | |
when nil | |
Time.current | |
when Date, Time | |
options[:default] | |
else | |
default = options[:default].dup | |
# Rename :minute and :second to :min and :sec | |
default[:min] ||= default[:minute] | |
default[:sec] ||= default[:second] | |
time = Time.current | |
[:year, :month, :day, :hour, :min, :sec].each do |key| | |
default[key] ||= time.public_send(key) | |
end | |
Time.utc( | |
default[:year], default[:month], default[:day], | |
default[:hour], default[:min], default[:sec] | |
) | |
end | |
end | |
end | |
end | |
end | |
end |
# frozen_string_literal: true | |
module ActionView | |
module Helpers | |
module Tags # :nodoc: | |
class DatetimeField < TextField # :nodoc: | |
def render | |
options = @options.stringify_keys | |
options["value"] ||= format_date(value) | |
options["min"] = format_date(datetime_value(options["min"])) | |
options["max"] = format_date(datetime_value(options["max"])) | |
@options = options | |
super | |
end | |
private | |
def format_date(value) | |
raise NotImplementedError | |
end | |
def datetime_value(value) | |
if value.is_a? String | |
DateTime.parse(value) rescue nil | |
else | |
value | |
end | |
end | |
end | |
end | |
end | |
end |
# frozen_string_literal: true | |
module ActionView | |
module Helpers | |
module Tags # :nodoc: | |
class DatetimeLocalField < DatetimeField # :nodoc: | |
class << self | |
def field_type | |
@field_type ||= "datetime-local" | |
end | |
end | |
private | |
def format_date(value) | |
value&.strftime("%Y-%m-%dT%T") | |
end | |
end | |
end | |
end | |
end |
# frozen_string_literal: true | |
module ActionView | |
module Helpers | |
module Tags # :nodoc: | |
class DatetimeSelect < DateSelect # :nodoc: | |
end | |
end | |
end | |
end |
# frozen_string_literal: true | |
require "action_view/helpers/tag_helper" | |
module ActionView | |
# = Action View Debug Helper | |
# | |
# Provides a set of methods for making it easier to debug Rails objects. | |
module Helpers # :nodoc: | |
module DebugHelper | |
include TagHelper | |
# Returns a YAML representation of +object+ wrapped with <pre> and </pre>. | |
# If the object cannot be converted to YAML using +to_yaml+, +inspect+ will be called instead. | |
# Useful for inspecting an object at the time of rendering. | |
# | |
# @user = User.new({ username: 'testing', password: 'xyz', age: 42}) | |
# debug(@user) | |
# # => | |
# <pre class='debug_dump'>--- !ruby/object:User | |
# attributes: | |
# updated_at: | |
# username: testing | |
# age: 42 | |
# password: xyz | |
# created_at: | |
# </pre> | |
def debug(object) | |
Marshal.dump(object) | |
object = ERB::Util.html_escape(object.to_yaml) | |
content_tag(:pre, object, class: "debug_dump") | |
rescue # errors from Marshal or YAML | |
# Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback | |
content_tag(:code, object.inspect, class: "debug_dump") | |
end | |
end | |
end | |
end |
# frozen_string_literal: true | |
require "active_record_unit" | |
require "nokogiri" | |
class DebugHelperTest < ActionView::TestCase | |
def test_debug | |
company = Company.new(name: "firebase") | |
output = debug(company) | |
assert_match "name: name", output | |
assert_match "value_before_type_cast: firebase", output | |
assert_match "active_record_yaml_version: 2", output | |
end | |
def test_debug_with_marshal_error | |
obj = -> { } | |
assert_match obj.inspect, Nokogiri.XML(debug(obj)).content | |
end | |
end |
# frozen_string_literal: true | |
require "concurrent/map" | |
require "action_view/path_set" | |
require "action_view/render_parser" | |
module ActionView | |
class DependencyTracker # :nodoc: | |
extend ActiveSupport::Autoload | |
autoload :ERBTracker | |
autoload :RipperTracker | |
@trackers = Concurrent::Map.new | |
def self.find_dependencies(name, template, view_paths = nil) | |
tracker = @trackers[template.handler] | |
return [] unless tracker | |
tracker.call(name, template, view_paths) | |
end | |
def self.register_tracker(extension, tracker) | |
handler = Template.handler_for_extension(extension) | |
if tracker.respond_to?(:supports_view_paths?) | |
@trackers[handler] = tracker | |
else | |
@trackers[handler] = lambda { |name, template, _| | |
tracker.call(name, template) | |
} | |
end | |
end | |
def self.remove_tracker(handler) | |
@trackers.delete(handler) | |
end | |
register_tracker :erb, ERBTracker | |
end | |
end |
# frozen_string_literal: true | |
require "abstract_unit" | |
require "action_view/dependency_tracker" | |
class NeckbeardTracker | |
def self.call(name, template) | |
["foo/#{name}"] | |
end | |
end | |
class FakeTemplate | |
attr_reader :source, :handler | |
def initialize(source, handler = Neckbeard) | |
@source, @handler = source, handler | |
if handler == :erb | |
@handler = ActionView::Template::Handlers::ERB.new | |
end | |
end | |
def type | |
["text/html"] | |
end | |
end | |
Neckbeard = lambda { |template, source| source } | |
Bowtie = lambda { |template, source| source } | |
class DependencyTrackerTest < ActionView::TestCase | |
def tracker | |
ActionView::DependencyTracker | |
end | |
def setup | |
ActionView::Template.register_template_handler :neckbeard, Neckbeard | |
tracker.register_tracker(:neckbeard, NeckbeardTracker) | |
end | |
def teardown | |
ActionView::Template.unregister_template_handler :neckbeard | |
tracker.remove_tracker(:neckbeard) | |
end | |
def test_finds_tracker_by_template_handler | |
template = FakeTemplate.new("boo/hoo") | |
dependencies = tracker.find_dependencies("boo/hoo", template) | |
assert_equal ["foo/boo/hoo"], dependencies | |
end | |
def test_returns_empty_array_if_no_tracker_is_found | |
template = FakeTemplate.new("boo/hoo", Bowtie) | |
dependencies = tracker.find_dependencies("boo/hoo", template) | |
assert_equal [], dependencies | |
end | |
end | |
# Tests run with both ERBTracker and RipperTracker | |
module SharedTrackerTests | |
def test_dependency_of_erb_template_with_number_in_filename | |
template = FakeTemplate.new("<%= render 'messages/message123' %>", :erb) | |
tracker = make_tracker("messages/_message123", template) | |
assert_equal ["messages/message123"], tracker.dependencies | |
end | |
def test_dependency_of_template_partial_with_layout | |
template = FakeTemplate.new("<%= render partial: 'messages/show', layout: 'messages/layout' %>", :erb) | |
tracker = make_tracker("multiple/_dependencies", template) | |
assert_equal ["messages/layout", "messages/show"], tracker.dependencies.sort | |
end | |
def test_dependency_of_template_layout_standalone | |
template = FakeTemplate.new("<%= render layout: 'messages/layout' do %>", :erb) | |
tracker = make_tracker("messages/layout", template) | |
assert_equal ["messages/layout"], tracker.dependencies | |
end | |
def test_finds_dependency_in_correct_directory | |
template = FakeTemplate.new("<%= render(message.topic) %>", :erb) | |
tracker = make_tracker("messages/_message", template) | |
assert_equal ["topics/topic"], tracker.dependencies | |
end | |
def test_finds_dependency_in_correct_directory_with_underscore | |
template = FakeTemplate.new("<%= render(message_type.messages) %>", :erb) | |
tracker = make_tracker("message_types/_message_type", template) | |
assert_equal ["messages/message"], tracker.dependencies | |
end | |
def test_dependency_of_erb_template_with_no_spaces_after_render | |
template = FakeTemplate.new("<%= render'messages/message' %>", :erb) | |
tracker = make_tracker("messages/_message", template) | |
assert_equal ["messages/message"], tracker.dependencies | |
end | |
def test_finds_no_dependency_when_render_begins_the_name_of_an_identifier | |
template = FakeTemplate.new("<%= rendering 'it useless' %>", :erb) | |
tracker = make_tracker("resources/_resource", template) | |
assert_equal [], tracker.dependencies | |
end | |
def test_finds_no_dependency_when_render_ends_the_name_of_another_method | |
template = FakeTemplate.new("<%= surrender 'to reason' %>", :erb) | |
tracker = make_tracker("resources/_resource", template) | |
assert_equal [], tracker.dependencies | |
end | |
def test_finds_dependency_on_multiline_render_calls | |
template = FakeTemplate.new("<%= | |
render object: @all_posts, | |
partial: 'posts' %>", :erb) | |
tracker = make_tracker("some/_little_posts", template) | |
assert_equal ["some/posts"], tracker.dependencies | |
end | |
def test_finds_multiple_unrelated_odd_dependencies | |
template = FakeTemplate.new(" | |
<%= render('shared/header', title: 'Title') %> | |
<h2>Section title</h2> | |
<%= render@section %> | |
", :erb) | |
tracker = make_tracker("multiple/_dependencies", template) | |
assert_equal ["shared/header", "sections/section"], tracker.dependencies | |
end | |
def test_finds_dependencies_for_all_kinds_of_identifiers | |
template = FakeTemplate.new(" | |
<%= render $globals %> | |
<%= render @instance_variables %> | |
<%= render @@class_variables %> | |
", :erb) | |
tracker = make_tracker("identifiers/_all", template) | |
assert_equal [ | |
"globals/global", | |
"instance_variables/instance_variable", | |
"class_variables/class_variable" | |
], tracker.dependencies | |
end | |
def test_finds_dependencies_on_method_chains | |
template = FakeTemplate.new("<%= render @parent.child.grandchildren %>", :erb) | |
tracker = make_tracker("method/_chains", template) | |
assert_equal ["grandchildren/grandchild"], tracker.dependencies | |
end | |
def test_finds_dependencies_with_special_characters | |
template = FakeTemplate.new("<%= render partial: 'ピカチュウ', object: @pokémon %>", :erb) | |
tracker = make_tracker("special/_characters", template) | |
assert_equal ["special/ピカチュウ"], tracker.dependencies | |
end | |
def test_finds_dependencies_with_quotes_within | |
template = FakeTemplate.new(%{ | |
<%= render "single/quote's" %> | |
<%= render 'double/quote"s' %> | |
}, :erb) | |
tracker = make_tracker("quotes/_single_and_double", template) | |
assert_equal ["single/quote's", 'double/quote"s'], tracker.dependencies | |
end | |
def test_finds_dependencies_with_extra_spaces | |
template = FakeTemplate.new(%{ | |
<%= render "header" %> | |
<%= render partial: "form" %> | |
<%= render @message %> | |
<%= render ( @message.events ) %> | |
<%= render :collection => @message.comments, | |
:partial => "comments/comment" %> | |
}, :erb) | |
tracker = make_tracker("spaces/_extra", template) | |
assert_equal [ | |
"spaces/header", | |
"spaces/form", | |
"messages/message", | |
"events/event", | |
"comments/comment" | |
], tracker.dependencies | |
end | |
def test_dependencies_with_interpolation | |
template = FakeTemplate.new(%q{ | |
<%= render "double/#{quote}" %> | |
<%= render 'single/#{quote}' %> | |
}, :erb) | |
tracker = make_tracker("interpolation/_string", template) | |
assert_equal ["single/\#{quote}"], tracker.dependencies | |
end | |
end | |
class ERBTrackerTest < Minitest::Test | |
include SharedTrackerTests | |
def make_tracker(name, template) | |
ActionView::DependencyTracker::ERBTracker.new(name, template) | |
end | |
end | |
class RipperTrackerTest < Minitest::Test | |
include SharedTrackerTests | |
def make_tracker(name, template) | |
ActionView::DependencyTracker::RipperTracker.new(name, template) | |
end | |
def test_dependencies_skip_unknown_options | |
template = FakeTemplate.new(%{ | |
<%= render partial: "unknown_render_call", unknown_render_option: "yes" %> | |
}, :erb) | |
tracker = make_tracker("interpolation/_string", template) | |
assert_equal [], tracker.dependencies | |
end | |
def test_dependencies_finds_spacer_templates | |
template = FakeTemplate.new(%{ | |
<%= render partial: "messages/message", collection: books, spacer_template: "messages/message_spacer" %> | |
}, :erb) | |
tracker = make_tracker("messages/show", template) | |
assert_equal ["messages/message_spacer", "messages/message"], tracker.dependencies | |
end | |
def test_dependencies_skip_commented_out_renders | |
template = FakeTemplate.new(%{ | |
<%# render "messages/legacy_message" %> | |
}, :erb) | |
tracker = make_tracker("messages/show", template) | |
assert_equal [], tracker.dependencies | |
end | |
end |
# frozen_string_literal: true | |
class Developer < ActiveRecord::Base | |
has_and_belongs_to_many :projects | |
has_many :replies | |
has_many :topics, through: :replies | |
accepts_nested_attributes_for :projects | |
end |
# frozen_string_literal: true | |
require "action_view/dependency_tracker" | |
module ActionView | |
class Digestor | |
@@digest_mutex = Mutex.new | |
class << self | |
# Supported options: | |
# | |
# * <tt>name</tt> - Template name | |
# * <tt>format</tt> - Template format | |
# * <tt>finder</tt> - An instance of <tt>ActionView::LookupContext</tt> | |
# * <tt>dependencies</tt> - An array of dependent views | |
def digest(name:, format: nil, finder:, dependencies: nil) | |
if dependencies.nil? || dependencies.empty? | |
cache_key = "#{name}.#{format}" | |
else | |
dependencies_suffix = dependencies.flatten.tap(&:compact!).join(".") | |
cache_key = "#{name}.#{format}.#{dependencies_suffix}" | |
end | |
# this is a correctly done double-checked locking idiom | |
# (Concurrent::Map's lookups have volatile semantics) | |
finder.digest_cache[cache_key] || @@digest_mutex.synchronize do | |
finder.digest_cache.fetch(cache_key) do # re-check under lock | |
path = TemplatePath.parse(name) | |
root = tree(path.to_s, finder, path.partial?) | |
dependencies.each do |injected_dep| | |
root.children << Injected.new(injected_dep, nil, nil) | |
end if dependencies | |
finder.digest_cache[cache_key] = root.digest(finder) | |
end | |
end | |
end | |
def logger | |
ActionView::Base.logger || NullLogger | |
end | |
# Create a dependency tree for template named +name+. | |
def tree(name, finder, partial = false, seen = {}) | |
logical_name = name.gsub(%r|/_|, "/") | |
interpolated = name.include?("#") | |
path = TemplatePath.parse(name) | |
if !interpolated && (template = find_template(finder, path.name, [path.prefix], partial, [])) | |
if node = seen[template.identifier] # handle cycles in the tree | |
node | |
else | |
node = seen[template.identifier] = Node.create(name, logical_name, template, partial) | |
deps = DependencyTracker.find_dependencies(name, template, finder.view_paths) | |
deps.uniq { |n| n.gsub(%r|/_|, "/") }.each do |dep_file| | |
node.children << tree(dep_file, finder, true, seen) | |
end | |
node | |
end | |
else | |
unless interpolated # Dynamic template partial names can never be tracked | |
logger.error " Couldn't find template for digesting: #{name}" | |
end | |
seen[name] ||= Missing.new(name, logical_name, nil) | |
end | |
end | |
private | |
def find_template(finder, name, prefixes, partial, keys) | |
finder.disable_cache do | |
finder.find_all(name, prefixes, partial, keys).first | |
end | |
end | |
end | |
class Node | |
attr_reader :name, :logical_name, :template, :children | |
def self.create(name, logical_name, template, partial) | |
klass = partial ? Partial : Node | |
klass.new(name, logical_name, template, []) | |
end | |
def initialize(name, logical_name, template, children = []) | |
@name = name | |
@logical_name = logical_name | |
@template = template | |
@children = children | |
end | |
def digest(finder, stack = []) | |
ActiveSupport::Digest.hexdigest("#{template.source}-#{dependency_digest(finder, stack)}") | |
end | |
def dependency_digest(finder, stack) | |
children.map do |node| | |
if stack.include?(node) | |
false | |
else | |
finder.digest_cache[node.name] ||= begin | |
stack.push node | |
node.digest(finder, stack).tap { stack.pop } | |
end | |
end | |
end.join("-") | |
end | |
def to_dep_map | |
children.any? ? { name => children.map(&:to_dep_map) } : name | |
end | |
end | |
class Partial < Node; end | |
class Missing < Node | |
def digest(finder, _ = []) "" end | |
end | |
class Injected < Node | |
def digest(finder, _ = []) name end | |
end | |
class NullLogger | |
def self.debug(_); end | |
def self.error(_); end | |
end | |
end | |
end |
# frozen_string_literal: true | |
require "abstract_unit" | |
require "fileutils" | |
require "action_view/dependency_tracker" | |
class FixtureFinder < ActionView::LookupContext | |
FIXTURES_DIR = File.expand_path("../fixtures/digestor", __dir__) | |
def self.build(details = {}) | |
new(ActionView::PathSet.new(["digestor", "digestor/api"]), details, []) | |
end | |
end | |
class ActionView::Digestor::Node | |
def flatten | |
[self] + children.flat_map(&:flatten) | |
end | |
end | |
class TemplateDigestorTest < ActionView::TestCase | |
def setup | |
@cwd = Dir.pwd | |
@tmp_dir = Dir.mktmpdir | |
ActionView::LookupContext::DetailsKey.clear | |
FileUtils.cp_r FixtureFinder::FIXTURES_DIR, @tmp_dir | |
Dir.chdir @tmp_dir | |
end | |
def teardown | |
Dir.chdir @cwd | |
FileUtils.rm_r @tmp_dir | |
end | |
def test_top_level_change_reflected | |
assert_digest_difference("messages/show") do | |
change_template("messages/show") | |
end | |
end | |
def test_explicit_dependency | |
assert_digest_difference("messages/show") do | |
change_template("messages/_message") | |
end | |
end | |
def test_explicit_dependency_in_multiline_erb_tag | |
assert_digest_difference("messages/show") do | |
change_template("messages/_form") | |
end | |
end | |
def test_explicit_dependency_wildcard | |
assert_digest_difference("events/index") do | |
change_template("events/_completed") | |
end | |
end | |
def test_explicit_dependency_wildcard_picks_up_added_file | |
disable_resolver_caching do | |
assert_digest_difference("events/index") do | |
add_template("events/_uncompleted") | |
end | |
end | |
end | |
def test_explicit_dependency_wildcard_picks_up_removed_file | |
disable_resolver_caching do | |
add_template("events/_subscribers_changed") | |
assert_digest_difference("events/index") do | |
remove_template("events/_subscribers_changed") | |
end | |
end | |
end | |
def test_second_level_dependency | |
assert_digest_difference("messages/show") do | |
change_template("comments/_comments") | |
end | |
end | |
def test_second_level_dependency_within_same_directory | |
assert_digest_difference("messages/show") do | |
change_template("messages/_header") | |
end | |
end | |
def test_third_level_dependency | |
assert_digest_difference("messages/show") do | |
change_template("comments/_comment") | |
end | |
end | |
def test_directory_depth_dependency | |
assert_digest_difference("level/below/index") do | |
change_template("level/below/_header") | |
end | |
end | |
def test_logging_of_missing_template | |
assert_logged "Couldn't find template for digesting: messages/something_missing" do | |
digest("messages/show") | |
end | |
end | |
def test_logging_of_missing_template_ending_with_number | |
assert_logged "Couldn't find template for digesting: messages/something_missing_1" do | |
digest("messages/show") | |
end | |
end | |
def test_logging_of_missing_template_for_dependencies | |
assert_logged "Couldn't find template for digesting: messages/something_missing" do | |
dependencies("messages/something_missing") | |
end | |
end | |
def test_logging_of_missing_template_for_nested_dependencies | |
assert_logged "Couldn't find template for digesting: messages/something_missing" do | |
nested_dependencies("messages/something_missing") | |
end | |
end | |
def test_getting_of_singly_nested_dependencies | |
singly_nested_dependencies = ["messages/header", "messages/form", "messages/message", "events/event", "comments/comment"] | |
assert_equal singly_nested_dependencies, nested_dependencies("messages/edit") | |
end | |
def test_getting_of_doubly_nested_dependencies | |
doubly_nested = [{ "comments/comments" => ["comments/comment"] }, "messages/message"] | |
assert_equal doubly_nested, nested_dependencies("messages/peek") | |
end | |
def test_nested_template_directory | |
assert_digest_difference("messages/show") do | |
change_template("messages/actions/_move") | |
end | |
end | |
def test_nested_template_deps | |
nested_deps = ["messages/header", { "comments/comments" => ["comments/comment"] }, "messages/actions/move", "events/event", "messages/something_missing", "messages/something_missing_1", "messages/message", "messages/form"] | |
assert_equal nested_deps, nested_dependencies("messages/show") | |
end | |
def test_nested_template_deps_with_non_default_rendered_format | |
nested_deps = [{ "comments/comments" => ["comments/comment"] }] | |
assert_equal nested_deps, nested_dependencies("messages/thread") | |
end | |
def test_template_formats_of_nested_deps_with_non_default_rendered_format | |
@finder = finder.with_prepended_formats([:json]) | |
assert_equal [:json], tree_template_formats("messages/thread").uniq | |
end | |
def test_template_formats_of_dependencies_with_same_logical_name_and_different_rendered_format | |
assert_equal [:html], tree_template_formats("messages/show").uniq | |
end | |
def test_template_dependencies_with_fallback_from_js_to_html_format | |
assert_equal ["comments/comment"], dependencies("comments/show") | |
end | |
def test_template_digest_with_fallback_from_js_to_html_format | |
assert_digest_difference("comments/show") do | |
change_template("comments/_comment") | |
end | |
end | |
def test_recursion_in_renders | |
assert digest("level/recursion") # assert recursion is possible | |
assert_not_nil digest("level/recursion") # assert digest is stored | |
end | |
def test_chaining_the_top_template_on_recursion | |
assert digest("level/recursion") # assert recursion is possible | |
assert_digest_difference("level/recursion") do | |
change_template("level/recursion") | |
end | |
assert_not_nil digest("level/recursion") # assert digest is stored | |
end | |
def test_chaining_the_partial_template_on_recursion | |
assert digest("level/recursion") # assert recursion is possible | |
assert_digest_difference("level/recursion") do | |
change_template("level/_recursion") | |
end | |
assert_not_nil digest("level/recursion") # assert digest is stored | |
end | |
def test_dont_generate_a_digest_for_missing_templates | |
assert_equal "", digest("nothing/there") | |
end | |
def test_collection_dependency | |
assert_digest_difference("messages/index") do | |
change_template("messages/_message") | |
end | |
assert_digest_difference("messages/index") do | |
change_template("events/_event") | |
end | |
end | |
def test_collection_derived_from_record_dependency | |
assert_digest_difference("messages/show") do | |
change_template("events/_event") | |
end | |
end | |
def test_details_are_included_in_cache_key | |
# Cache the template digest. | |
@finder = FixtureFinder.build(formats: [:html]) | |
old_digest = digest("events/_event") | |
# Change the template; the cached digest remains unchanged. | |
change_template("events/_event") | |
# The details are changed, so a new cache key is generated. | |
@finder = FixtureFinder.build | |
# The cache is busted. | |
assert_not_equal old_digest, digest("events/_event") | |
end | |
def test_extra_whitespace_in_render_partial | |
assert_digest_difference("messages/edit") do | |
change_template("messages/_form") | |
end | |
end | |
def test_extra_whitespace_in_render_named_partial | |
assert_digest_difference("messages/edit") do | |
change_template("messages/_header") | |
end | |
end | |
def test_extra_whitespace_in_render_record | |
assert_digest_difference("messages/edit") do | |
change_template("messages/_message") | |
end | |
end | |
def test_extra_whitespace_in_render_with_parenthesis | |
assert_digest_difference("messages/edit") do | |
change_template("events/_event") | |
end | |
end | |
def test_old_style_hash_in_render_invocation | |
assert_digest_difference("messages/edit") do | |
change_template("comments/_comment") | |
end | |
end | |
def test_variants | |
assert_digest_difference("messages/new", variants: [:iphone]) do | |
change_template("messages/new", :iphone) | |
change_template("messages/_header", :iphone) | |
end | |
end | |
def test_dependencies_via_options_results_in_different_digest | |
digest_plain = digest("comments/_comment") | |
digest_fridge = digest("comments/_comment", dependencies: ["fridge"]) | |
digest_phone = digest("comments/_comment", dependencies: ["phone"]) | |
digest_fridge_phone = digest("comments/_comment", dependencies: ["fridge", "phone"]) | |
assert_not_equal digest_plain, digest_fridge | |
assert_not_equal digest_plain, digest_phone | |
assert_not_equal digest_plain, digest_fridge_phone | |
assert_not_equal digest_fridge, digest_phone | |
assert_not_equal digest_fridge, digest_fridge_phone | |
assert_not_equal digest_phone, digest_fridge_phone | |
end | |
def test_different_formats_with_same_logical_template_names_results_in_different_digests | |
html_digest = digest("comments/_comment", format: :html) | |
json_digest = digest("comments/_comment", format: :json) | |
assert_not_equal html_digest, json_digest | |
end | |
def test_digest_cache_cleanup_with_recursion | |
first_digest = digest("level/_recursion") | |
second_digest = digest("level/_recursion") | |
assert first_digest | |
# If the cache is cleaned up correctly, subsequent digests should return the same | |
assert_equal first_digest, second_digest | |
end | |
def test_digest_cache_cleanup_with_recursion_and_template_caching_off | |
disable_resolver_caching do | |
first_digest = digest("level/_recursion") | |
second_digest = digest("level/_recursion") | |
assert first_digest | |
# If the cache is cleaned up correctly, subsequent digests should return the same | |
assert_equal first_digest, second_digest | |
end | |
end | |
private | |
def assert_logged(message) | |
old_logger = ActionView::Base.logger | |
log = StringIO.new | |
ActionView::Base.logger = Logger.new(log) | |
begin | |
yield | |
log.rewind | |
assert_match message, log.read | |
ensure | |
ActionView::Base.logger = old_logger | |
end | |
end | |
def assert_digest_difference(template_name, options = {}) | |
previous_digest = digest(template_name, options) | |
finder.view_paths.each(&:clear_cache) | |
finder.digest_cache.clear | |
yield | |
assert_not_equal previous_digest, digest(template_name, options), "digest didn't change" | |
finder.digest_cache.clear | |
finder.view_paths.each(&:clear_cache) | |
end | |
def digest(template_name, options = {}) | |
options = options.dup | |
finder_options = options.extract!(:variants, :format) | |
finder.variants = finder_options[:variants] || [] | |
finder_with_formats = if finder_options[:format] | |
finder.with_prepended_formats(Array(finder_options[:format])) | |
else | |
finder | |
end | |
ActionView::Digestor.digest(name: template_name, format: finder_options[:format], finder: finder_with_formats, dependencies: (options[:dependencies] || [])) | |
end | |
def dependencies(template_name) | |
tree = ActionView::Digestor.tree(template_name, finder) | |
tree.children.map(&:name) | |
end | |
def nested_dependencies(template_name) | |
tree = ActionView::Digestor.tree(template_name, finder) | |
tree.children.map(&:to_dep_map) | |
end | |
def tree_template_formats(template_name) | |
tree = ActionView::Digestor.tree(template_name, finder) | |
tree.flatten.filter_map { |node| node.template&.format } | |
end | |
def disable_resolver_caching | |
old_caching, ActionView::Resolver.caching = ActionView::Resolver.caching, false | |
yield | |
ensure | |
ActionView::Resolver.caching = old_caching | |
end | |
def finder | |
@finder ||= FixtureFinder.build | |
end | |
def change_template(template_name, variant = nil) | |
variant = "+#{variant}" if variant.present? | |
File.open("digestor/#{template_name}.html#{variant}.erb", "w") do |f| | |
f.write "\nTHIS WAS CHANGED!" | |
end | |
end | |
alias_method :add_template, :change_template | |
def remove_template(template_name) | |
File.delete("digestor/#{template_name}.html.erb") | |
end | |
end |
# frozen_string_literal: true | |
module ActionView | |
module Helpers | |
module Tags # :nodoc: | |
class EmailField < TextField # :nodoc: | |
end | |
end | |
end | |
end |
# frozen_string_literal: true | |
module ActionView | |
class Template | |
module Handlers | |
class ERB | |
autoload :Erubi, "action_view/template/handlers/erb/erubi" | |
# Specify trim mode for the ERB compiler. Defaults to '-'. | |
# See ERB documentation for suitable values. | |
class_attribute :erb_trim_mode, default: "-" | |
# Default implementation used. | |
class_attribute :erb_implementation, default: Erubi | |
# Do not escape templates of these mime types. | |
class_attribute :escape_ignore_list, default: ["text/plain"] | |
# Strip trailing newlines from rendered output | |
class_attribute :strip_trailing_newlines, default: false | |
ENCODING_TAG = Regexp.new("\\A(<%#{ENCODING_FLAG}-?%>)[ \\t]*") | |
def self.call(template, source) | |
new.call(template, source) | |
end | |
def supports_streaming? | |
true | |
end | |
def handles_encoding? | |
true | |
end | |
def call(template, source) | |
# First, convert to BINARY, so in case the encoding is | |
# wrong, we can still find an encoding tag | |
# (<%# encoding %>) inside the String using a regular | |
# expression | |
template_source = source.b | |
erb = template_source.gsub(ENCODING_TAG, "") | |
encoding = $2 | |
erb.force_encoding valid_encoding(source.dup, encoding) | |
# Always make sure we return a String in the default_internal | |
erb.encode! | |
# Strip trailing newlines from the template if enabled | |
erb.chomp! if strip_trailing_newlines | |
options = { | |
escape: (self.class.escape_ignore_list.include? template.type), | |
trim: (self.class.erb_trim_mode == "-") | |
} | |
if ActionView::Base.annotate_rendered_view_with_filenames && template.format == :html | |
options[:preamble] = "@output_buffer.safe_append='<!-- BEGIN #{template.short_identifier} -->';" | |
options[:postamble] = "@output_buffer.safe_append='<!-- END #{template.short_identifier} -->';@output_buffer.to_s" | |
end | |
self.class.erb_implementation.new(erb, options).src | |
end | |
private | |
def valid_encoding(string, encoding) | |
# If a magic encoding comment was found, tag the | |
# String with this encoding. This is for a case | |
# where the original String was assumed to be, | |
# for instance, UTF-8, but a magic comment | |
# proved otherwise | |
string.force_encoding(encoding) if encoding | |
# If the String is valid, return the encoding we found | |
return string.encoding if string.valid_encoding? | |
# Otherwise, raise an exception | |
raise WrongEncodingError.new(string, string.encoding) | |
end | |
end | |
end | |
end | |
end |
# frozen_string_literal: true | |
module ActionView | |
class DependencyTracker # :nodoc: | |
class ERBTracker # :nodoc: | |
EXPLICIT_DEPENDENCY = /# Template Dependency: (\S+)/ | |
# A valid ruby identifier - suitable for class, method and specially variable names | |
IDENTIFIER = / | |
[[:alpha:]_] # at least one uppercase letter, lowercase letter or underscore | |
[[:word:]]* # followed by optional letters, numbers or underscores | |
/x | |
# Any kind of variable name. e.g. @instance, @@class, $global or local. | |
# Possibly following a method call chain | |
VARIABLE_OR_METHOD_CHAIN = / | |
(?:\$|@{1,2})? # optional global, instance or class variable indicator | |
(?:#{IDENTIFIER}\.)* # followed by an optional chain of zero-argument method calls | |
(?<dynamic>#{IDENTIFIER}) # and a final valid identifier, captured as DYNAMIC | |
/x | |
# A simple string literal. e.g. "School's out!" | |
STRING = / | |
(?<quote>['"]) # an opening quote | |
(?<static>.*?) # with anything inside, captured as STATIC | |
\k<quote> # and a matching closing quote | |
/x | |
# Part of any hash containing the :partial key | |
PARTIAL_HASH_KEY = / | |
(?:\bpartial:|:partial\s*=>) # partial key in either old or new style hash syntax | |
\s* # followed by optional spaces | |
/x | |
# Part of any hash containing the :layout key | |
LAYOUT_HASH_KEY = / | |
(?:\blayout:|:layout\s*=>) # layout key in either old or new style hash syntax | |
\s* # followed by optional spaces | |
/x | |
# Matches: | |
# partial: "comments/comment", collection: @all_comments => "comments/comment" | |
# (object: @single_comment, partial: "comments/comment") => "comments/comment" | |
# | |
# "comments/comments" | |
# 'comments/comments' | |
# ('comments/comments') | |
# | |
# (@topic) => "topics/topic" | |
# topics => "topics/topic" | |
# (message.topics) => "topics/topic" | |
RENDER_ARGUMENTS = /\A | |
(?:\s*\(?\s*) # optional opening paren surrounded by spaces | |
(?:.*?#{PARTIAL_HASH_KEY}|#{LAYOUT_HASH_KEY})? # optional hash, up to the partial or layout key declaration | |
(?:#{STRING}|#{VARIABLE_OR_METHOD_CHAIN}) # finally, the dependency name of interest | |
/xm | |
LAYOUT_DEPENDENCY = /\A | |
(?:\s*\(?\s*) # optional opening paren surrounded by spaces | |
(?:.*?#{LAYOUT_HASH_KEY}) # check if the line has layout key declaration | |
(?:#{STRING}|#{VARIABLE_OR_METHOD_CHAIN}) # finally, the dependency name of interest | |
/xm | |
def self.supports_view_paths? # :nodoc: | |
true | |
end | |
def self.call(name, template, view_paths = nil) | |
new(name, template, view_paths).dependencies | |
end | |
def initialize(name, template, view_paths = nil) | |
@name, @template, @view_paths = name, template, view_paths | |
end | |
def dependencies | |
render_dependencies + explicit_dependencies | |
end | |
attr_reader :name, :template | |
private :name, :template | |
private | |
def source | |
template.source | |
end | |
def directory | |
name.split("/")[0..-2].join("/") | |
end | |
def render_dependencies | |
render_dependencies = [] | |
render_calls = source.split(/\brender\b/).drop(1) | |
render_calls.each do |arguments| | |
add_dependencies(render_dependencies, arguments, LAYOUT_DEPENDENCY) | |
add_dependencies(render_dependencies, arguments, RENDER_ARGUMENTS) | |
end | |
render_dependencies.uniq | |
end | |
def add_dependencies(render_dependencies, arguments, pattern) | |
arguments.scan(pattern) do | |
match = Regexp.last_match | |
add_dynamic_dependency(render_dependencies, match[:dynamic]) | |
add_static_dependency(render_dependencies, match[:static], match[:quote]) | |
end | |
end | |
def add_dynamic_dependency(dependencies, dependency) | |
if dependency | |
dependencies << "#{dependency.pluralize}/#{dependency.singularize}" | |
end | |
end | |
def add_static_dependency(dependencies, dependency, quote_type) | |
if quote_type == '"' | |
# Ignore if there is interpolation | |
return if dependency.include?('#{') | |
end | |
if dependency | |
if dependency.include?("/") | |
dependencies << dependency | |
else | |
dependencies << "#{directory}/#{dependency}" | |
end | |
end | |
end | |
def resolve_directories(wildcard_dependencies) | |
return [] unless @view_paths | |
return [] if wildcard_dependencies.empty? | |
# Remove trailing "/*" | |
prefixes = wildcard_dependencies.map { |query| query[0..-3] } | |
@view_paths.flat_map(&:all_template_paths).uniq.filter_map { |path| | |
path.to_s if prefixes.include?(path.prefix) | |
}.sort | |
end | |
def explicit_dependencies | |
dependencies = source.scan(EXPLICIT_DEPENDENCY).flatten.uniq | |
wildcards, explicits = dependencies.partition { |dependency| dependency.end_with?("/*") } | |
(explicits + resolve_directories(wildcards)).uniq | |
end | |
end | |
end | |
end |
# frozen_string_literal: true | |
require "abstract_unit" | |
require "active_support/json" | |
class ErbUtilTest < ActiveSupport::TestCase | |
include ERB::Util | |
ERB::Util::HTML_ESCAPE.each do |given, expected| | |
define_method "test_html_escape_#{expected.gsub(/\W/, '')}" do | |
assert_equal expected, html_escape(given) | |
end | |
end | |
ERB::Util::JSON_ESCAPE.each do |given, expected| | |
define_method "test_json_escape_#{expected.gsub(/\W/, '')}" do | |
assert_equal ERB::Util::JSON_ESCAPE[given], json_escape(given) | |
end | |
end | |
HTML_ESCAPE_TEST_CASES = [ | |
["<br>", "<br>"], | |
["a & b", "a & b"], | |
['"quoted" string', ""quoted" string"], | |
["'quoted' string", "'quoted' string"], | |
[ | |
'<script type="application/javascript">alert("You are \'pwned\'!")</script>', | |
"<script type="application/javascript">alert("You are 'pwned'!")</script>" | |
] | |
] | |
JSON_ESCAPE_TEST_CASES = [ | |
["1", "1"], | |
["null", "null"], | |
['"&"', '"\u0026"'], | |
['"</script>"', '"\u003c/script\u003e"'], | |
['["</script>"]', '["\u003c/script\u003e"]'], | |
['{"name":"</script>"}', '{"name":"\u003c/script\u003e"}'], | |
[%({"name":"d\u2028h\u2029h"}), '{"name":"d\u2028h\u2029h"}'] | |
] | |
def test_html_escape | |
HTML_ESCAPE_TEST_CASES.each do |(raw, expected)| | |
assert_equal expected, html_escape(raw) | |
end | |
end | |
def test_json_escape | |
JSON_ESCAPE_TEST_CASES.each do |(raw, expected)| | |
assert_equal expected, json_escape(raw) | |
end | |
end | |
def test_json_escape_does_not_alter_json_string_meaning | |
JSON_ESCAPE_TEST_CASES.each do |(raw, _)| | |
expected = ActiveSupport::JSON.decode(raw) | |
if expected.nil? | |
assert_nil ActiveSupport::JSON.decode(json_escape(raw)) | |
else | |
assert_equal expected, ActiveSupport::JSON.decode(json_escape(raw)) | |
end | |
end | |
end | |
def test_json_escape_is_idempotent | |
JSON_ESCAPE_TEST_CASES.each do |(raw, _)| | |
assert_equal json_escape(raw), json_escape(json_escape(raw)) | |
end | |
end | |
def test_json_escape_returns_unsafe_strings_when_passed_unsafe_strings | |
value = json_escape("asdf") | |
assert_not_predicate value, :html_safe? | |
end | |
def test_json_escape_returns_safe_strings_when_passed_safe_strings | |
value = json_escape("asdf".html_safe) | |
assert_predicate value, :html_safe? | |
end | |
def test_html_escape_is_html_safe | |
escaped = h("<p>") | |
assert_equal "<p>", escaped | |
assert_predicate escaped, :html_safe? | |
end | |
def test_html_escape_passes_html_escape_unmodified | |
escaped = h("<p>".html_safe) | |
assert_equal "<p>", escaped | |
assert_predicate escaped, :html_safe? | |
end | |
def test_rest_in_ascii | |
(0..127).to_a.map(&:chr).each do |chr| | |
next if %('"&<>).include?(chr) | |
assert_equal chr, html_escape(chr) | |
end | |
end | |
def test_html_escape_once | |
assert_equal "1 <>&"' 2 & 3", html_escape_once('1 <>&"\' 2 & 3') | |
assert_equal " ' ' λ λ " ' < > ", html_escape_once(" ' ' λ λ \" ' < > ") | |
end | |
def test_html_escape_once_returns_unsafe_strings_when_passed_unsafe_strings | |
value = html_escape_once("1 < 2 & 3") | |
assert_not_predicate value, :html_safe? | |
end | |
def test_html_escape_once_returns_safe_strings_when_passed_safe_strings | |
value = html_escape_once("1 < 2 & 3".html_safe) | |
assert_predicate value, :html_safe? | |
end | |
end |
# frozen_string_literal: true | |
require "abstract_unit" | |
require "action_view/template/handlers/erb/erubi" | |
class ErubiTest < ActiveSupport::TestCase | |
test "can configure bufvar" do | |
template = <<~ERB | |
foo | |
<%= "foo".upcase %> | |
<%== "foo".length %> | |
ERB | |
baseline = ActionView::Template::Handlers::ERB::Erubi.new(template) | |
erubi = ActionView::Template::Handlers::ERB::Erubi.new(template, bufvar: "boofer") | |
assert_equal baseline.src.gsub("#{baseline.bufvar}.", "boofer."), erubi.src | |
end | |
end |
# frozen_string_literal: true | |
require "active_support/core_ext/enumerable" | |
module ActionView | |
# = Action View Errors | |
class ActionViewError < StandardError # :nodoc: | |
end | |
class EncodingError < StandardError # :nodoc: | |
end | |
class WrongEncodingError < EncodingError # :nodoc: | |
def initialize(string, encoding) | |
@string, @encoding = string, encoding | |
end | |
def message | |
@string.force_encoding(Encoding::ASCII_8BIT) | |
"Your template was not saved as valid #{@encoding}. Please " \ | |
"either specify #{@encoding} as the encoding for your template " \ | |
"in your text editor, or mark the template with its " \ | |
"encoding by inserting the following as the first line " \ | |
"of the template:\n\n# encoding: <name of correct encoding>.\n\n" \ | |
"The source of your template was:\n\n#{@string}" | |
end | |
end | |
class MissingTemplate < ActionViewError # :nodoc: | |
attr_reader :path, :paths, :prefixes, :partial | |
def initialize(paths, path, prefixes, partial, details, *) | |
if partial && path.present? | |
path = path.sub(%r{([^/]+)$}, "_\\1") | |
end | |
@path = path | |
@paths = paths | |
@prefixes = Array(prefixes) | |
@partial = partial | |
template_type = if partial | |
"partial" | |
elsif /layouts/i.match?(path) | |
"layout" | |
else | |
"template" | |
end | |
searched_paths = @prefixes.map { |prefix| [prefix, path].join("/") } | |
out = "Missing #{template_type} #{searched_paths.join(", ")} with #{details.inspect}.\n\nSearched in:\n" | |
out += paths.compact.map { |p| " * #{p.to_s.inspect}\n" }.join | |
super out | |
end | |
if defined?(DidYouMean::Correctable) && defined?(DidYouMean::Jaro) | |
include DidYouMean::Correctable | |
class Results # :nodoc: | |
Result = Struct.new(:path, :score) | |
def initialize(size) | |
@size = size | |
@results = [] | |
end | |
def to_a | |
@results.map(&:path) | |
end | |
def should_record?(score) | |
if @results.size < @size | |
true | |
else | |
score < @results.last.score | |
end | |
end | |
def add(path, score) | |
if should_record?(score) | |
@results << Result.new(path, score) | |
@results.sort_by!(&:score) | |
@results.pop if @results.size > @size | |
end | |
end | |
end | |
# Apps may have thousands of candidate templates so we attempt to | |
# generate the suggestions as efficiently as possible. | |
# First we split templates into prefixes and basenames, so that those can | |
# be matched separately. | |
def corrections | |
candidates = paths.flat_map(&:all_template_paths).uniq | |
if partial | |
candidates.select!(&:partial?) | |
else | |
candidates.reject!(&:partial?) | |
end | |
# Group by possible prefixes | |
files_by_dir = candidates.group_by(&:prefix) | |
files_by_dir.transform_values! do |files| | |
files.map do |file| | |
# Remove prefix | |
File.basename(file.to_s) | |
end | |
end | |
# No suggestions if there's an exact match, but wrong details | |
if prefixes.any? { |prefix| files_by_dir[prefix]&.include?(path) } | |
return [] | |
end | |
cached_distance = Hash.new do |h, args| | |
h[args] = -DidYouMean::Jaro.distance(*args) | |
end | |
results = Results.new(6) | |
files_by_dir.keys.index_with do |dirname| | |
prefixes.map do |prefix| | |
cached_distance[[prefix, dirname]] | |
end.min | |
end.sort_by(&:last).each do |dirname, dirweight| | |
# If our directory's score makes it impossible to find a better match | |
# we can prune this search branch. | |
next unless results.should_record?(dirweight - 1.0) | |
files = files_by_dir[dirname] | |
files.each do |file| | |
fileweight = cached_distance[[path, file]] | |
score = dirweight + fileweight | |
results.add(File.join(dirname, file), score) | |
end | |
end | |
if partial | |
results.to_a.map { |res| res.sub(%r{_([^/]+)\z}, "\\1") } | |
else | |
results.to_a | |
end | |
end | |
end | |
end | |
class Template | |
# The Template::Error exception is raised when the compilation or rendering of the template | |
# fails. This exception then gathers a bunch of intimate details and uses it to report a | |
# precise exception message. | |
class Error < ActionViewError # :nodoc: | |
SOURCE_CODE_RADIUS = 3 | |
# Override to prevent #cause resetting during re-raise. | |
attr_reader :cause | |
def initialize(template) | |
super($!.message) | |
set_backtrace($!.backtrace) | |
@cause = $! | |
@template, @sub_templates = template, nil | |
end | |
def file_name | |
@template.identifier | |
end | |
def sub_template_message | |
if @sub_templates | |
"Trace of template inclusion: " + | |
@sub_templates.collect(&:inspect).join(", ") | |
else | |
"" | |
end | |
end | |
def source_extract(indentation = 0) | |
return [] unless num = line_number | |
num = num.to_i | |
source_code = @template.encode!.split("\n") | |
start_on_line = [ num - SOURCE_CODE_RADIUS - 1, 0 ].max | |
end_on_line = [ num + SOURCE_CODE_RADIUS - 1, source_code.length].min | |
indent = end_on_line.to_s.size + indentation | |
return [] unless source_code = source_code[start_on_line..end_on_line] | |
formatted_code_for(source_code, start_on_line, indent) | |
end | |
def sub_template_of(template_path) | |
@sub_templates ||= [] | |
@sub_templates << template_path | |
end | |
def line_number | |
@line_number ||= | |
if file_name | |
regexp = /#{Regexp.escape File.basename(file_name)}:(\d+)/ | |
$1 if message =~ regexp || backtrace.find { |line| line =~ regexp } | |
end | |
end | |
def annotated_source_code | |
source_extract(4) | |
end | |
private | |
def source_location | |
if line_number | |
"on line ##{line_number} of " | |
else | |
"in " | |
end + file_name | |
end | |
def formatted_code_for(source_code, line_counter, indent) | |
indent_template = "%#{indent}s: %s" | |
source_code.map do |line| | |
line_counter += 1 | |
indent_template % [line_counter, line] | |
end | |
end | |
end | |
end | |
TemplateError = Template::Error | |
class SyntaxErrorInTemplate < TemplateError # :nodoc: | |
def initialize(template, offending_code_string) | |
@offending_code_string = offending_code_string | |
super(template) | |
end | |
def message | |
<<~MESSAGE | |
Encountered a syntax error while rendering template: check #{@offending_code_string} | |
MESSAGE | |
end | |
def annotated_source_code | |
@offending_code_string.split("\n").map.with_index(1) { |line, index| | |
indentation = " " * 4 | |
"#{index}:#{indentation}#{line}" | |
} | |
end | |
end | |
end |
# frozen_string_literal: true | |
require "erubi" | |
module ActionView | |
class Template | |
module Handlers | |
class ERB | |
class Erubi < ::Erubi::Engine | |
# :nodoc: all | |
def initialize(input, properties = {}) | |
@newline_pending = 0 | |
# Dup properties so that we don't modify argument | |
properties = Hash[properties] | |
properties[:bufvar] ||= "@output_buffer" | |
properties[:preamble] ||= "" | |
properties[:postamble] ||= "#{properties[:bufvar]}.to_s" | |
properties[:escapefunc] = "" | |
super | |
end | |
def evaluate(action_view_erb_handler_context) | |
src = @src | |
view = Class.new(ActionView::Base) { | |
include action_view_erb_handler_context._routes.url_helpers | |
class_eval("define_method(:_template) { |local_assigns, output_buffer| #{src} }", defined?(@filename) ? @filename : "(erubi)", 0) | |
}.empty | |
view._run(:_template, nil, {}, ActionView::OutputBuffer.new) | |
end | |
private | |
def add_text(text) | |
return if text.empty? | |
if text == "\n" | |
@newline_pending += 1 | |
else | |
src << bufvar << ".safe_append='" | |
src << "\n" * @newline_pending if @newline_pending > 0 | |
src << text.gsub(/['\\]/, '\\\\\&') | |
src << "'.freeze;" | |
@newline_pending = 0 | |
end | |
end | |
BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/ | |
def add_expression(indicator, code) | |
flush_newline_if_pending(src) | |
if (indicator == "==") || @escape | |
src << bufvar << ".safe_expr_append=" | |
else | |
src << bufvar << ".append=" | |
end | |
if BLOCK_EXPR.match?(code) | |
src << " " << code | |
else | |
src << "(" << code << ");" | |
end | |
end | |
def add_code(code) | |
flush_newline_if_pending(src) | |
super | |
end | |
def add_postamble(_) | |
flush_newline_if_pending(src) | |
super | |
end | |
def flush_newline_if_pending(src) | |
if @newline_pending > 0 | |
src << bufvar << ".safe_append='#{"\n" * @newline_pending}'.freeze;" | |
@newline_pending = 0 | |
end | |
end | |
end | |
end | |
end | |
end | |
end |
# frozen_string_literal: true | |
require "active_model" | |
Customer = Struct.new(:name, :id) do | |
extend ActiveModel::Naming | |
include ActiveModel::Conversion | |
undef_method :to_json | |
def to_xml(options = {}) | |
if options[:builder] | |
options[:builder].name name | |
else | |
"<name>#{name}</name>" | |
end | |
end | |
def to_js(options = {}) | |
"name: #{name.inspect}" | |
end | |
alias :to_text :to_js | |
def errors | |
[] | |
end | |
def persisted? | |
id.present? | |
end | |
def cache_key | |
name.to_s | |
end | |
end | |
class BadCustomer < Customer; end | |
class GoodCustomer < Customer; end | |
Post = Struct.new(:title, :author_name, :body, :secret, :persisted, :written_on, :cost) do | |
extend ActiveModel::Naming | |
include ActiveModel::Conversion | |
extend ActiveModel::Translation | |
alias_method :secret?, :secret | |
alias_method :persisted?, :persisted | |
def initialize(*args) | |
super | |
@persisted = false | |
end | |
attr_accessor :author | |
def author_attributes=(attributes); end | |
attr_accessor :comments, :comment_ids | |
def comments_attributes=(attributes); end | |
attr_accessor :tags | |
def tags_attributes=(attributes); end | |
end | |
class PostDelegator < Post | |
def to_model | |
PostDelegate.new | |
end | |
end | |
class PostDelegate < Post | |
def self.human_attribute_name(attribute) | |
"Delegate #{super}" | |
end | |
def model_name | |
ActiveModel::Name.new(self.class) | |
end | |
end | |
class Comment | |
extend ActiveModel::Naming | |
include ActiveModel::Conversion | |
attr_reader :id | |
attr_reader :post_id | |
def initialize(id = nil, post_id = nil); @id, @post_id = id, post_id end | |
def to_key; id ? [id] : nil end | |
def save; @id = 1; @post_id = 1 end | |
def persisted?; @id.present? end | |
def to_param; @id && @id.to_s; end | |
def name | |
@id.nil? ? "new #{self.class.name.downcase}" : "#{self.class.name.downcase} ##{@id}" | |
end | |
attr_accessor :relevances | |
def relevances_attributes=(attributes); end | |
attr_accessor :body | |
end | |
class Tag | |
extend ActiveModel::Naming | |
include ActiveModel::Conversion | |
attr_reader :id | |
attr_reader :post_id | |
def initialize(id = nil, post_id = nil); @id, @post_id = id, post_id end | |
def to_key; id ? [id] : nil end | |
def save; @id = 1; @post_id = 1 end | |
def persisted?; @id.present? end | |
def to_param; @id && @id.to_s; end | |
def value | |
@id.nil? ? "new #{self.class.name.downcase}" : "#{self.class.name.downcase} ##{@id}" | |
end | |
attr_accessor :relevances | |
def relevances_attributes=(attributes); end | |
end | |
class CommentRelevance | |
extend ActiveModel::Naming | |
include ActiveModel::Conversion | |
attr_reader :id | |
attr_reader :comment_id | |
def initialize(id = nil, comment_id = nil); @id, @comment_id = id, comment_id end | |
def to_key; id ? [id] : nil end | |
def save; @id = 1; @comment_id = 1 end | |
def persisted?; @id.present? end | |
def to_param; @id && @id.to_s; end | |
def value | |
@id.nil? ? "new #{self.class.name.downcase}" : "#{self.class.name.downcase} ##{@id}" | |
end | |
end | |
class TagRelevance | |
extend ActiveModel::Naming | |
include ActiveModel::Conversion | |
attr_reader :id | |
attr_reader :tag_id | |
def initialize(id = nil, tag_id = nil); @id, @tag_id = id, tag_id end | |
def to_key; id ? [id] : nil end | |
def save; @id = 1; @tag_id = 1 end | |
def persisted?; @id.present? end | |
def to_param; @id && @id.to_s; end | |
def value | |
@id.nil? ? "new #{self.class.name.downcase}" : "#{self.class.name.downcase} ##{@id}" | |
end | |
end | |
class Author < Comment | |
attr_accessor :post | |
def post_attributes=(attributes); end | |
end | |
class HashBackedAuthor < Hash | |
extend ActiveModel::Naming | |
include ActiveModel::Conversion | |
def persisted?; false; end | |
def name | |
"hash backed author" | |
end | |
end | |
module Blog | |
def self.use_relative_model_naming? | |
true | |
end | |
Post = Struct.new(:title, :id) do | |
extend ActiveModel::Naming | |
include ActiveModel::Conversion | |
def persisted? | |
id.present? | |
end | |
end | |
end | |
class ArelLike | |
def to_ary | |
true | |
end | |
def each(&block) | |
a = Array.new(2) { |id| Comment.new(id + 1) } | |
a.each(&block) | |
end | |
end | |
Car = Struct.new(:color) | |
class Plane | |
attr_reader :to_key | |
def model_name | |
OpenStruct.new param_key: "airplane" | |
end | |
def save | |
@to_key = [1] | |
end | |
end |
# frozen_string_literal: true | |
module ActionView | |
class Template | |
module Sources | |
class File | |
def initialize(filename) | |
@filename = filename | |
end | |
def to_s | |
::File.binread @filename | |
end | |
end | |
end | |
end | |
end |
# frozen_string_literal: true | |
module ActionView | |
module Helpers | |
module Tags # :nodoc: | |
class FileField < TextField # :nodoc: | |
def render | |
include_hidden = @options.delete(:include_hidden) | |
options = @options.stringify_keys | |
add_default_name_and_id(options) | |
if options["multiple"] && include_hidden | |
hidden_field_for_multiple_file(options) + super | |
else | |
super | |
end | |
end | |
private | |
def hidden_field_for_multiple_file(options) | |
tag("input", "name" => options["name"], "type" => "hidden", "value" => "", "autocomplete" => "off") | |
end | |
end | |
end | |
end | |
end |
# frozen_string_literal: true | |
require "abstract_unit" | |
require "template/resolver_shared_tests" | |
class FileSystemResolverTest < ActiveSupport::TestCase | |
include ResolverSharedTests | |
def resolver | |
ActionView::FileSystemResolver.new(tmpdir) | |
end | |
end |
# frozen_string_literal: true | |
require "abstract_unit" | |
class FixtureResolverTest < ActiveSupport::TestCase | |
def test_should_return_empty_list_for_unknown_path | |
resolver = ActionView::FixtureResolver.new() | |
templates = resolver.find_all("path", "arbitrary", false, locale: [], formats: [:html], variants: [], handlers: []) | |
assert_equal [], templates, "expected an empty list of templates" | |
end | |
def test_should_return_template_for_declared_path | |
resolver = ActionView::FixtureResolver.new("arbitrary/path.erb" => "this text") | |
templates = resolver.find_all("path", "arbitrary", false, locale: [], formats: [:html], variants: [], handlers: [:erb]) | |
assert_equal 1, templates.size, "expected one template" | |
assert_equal "this text", templates.first.source | |
assert_equal "arbitrary/path", templates.first.virtual_path | |
assert_nil templates.first.format | |
end | |
def test_should_match_templates_with_variants | |
resolver = ActionView::FixtureResolver.new("arbitrary/path.html+variant.erb" => "this text") | |
templates = resolver.find_all("path", "arbitrary", false, locale: [], formats: [:html], variants: [:variant], handlers: [:erb]) | |
assert_equal 1, templates.size, "expected one template" | |
assert_equal "this text", templates.first.source | |
assert_equal "arbitrary/path", templates.first.virtual_path | |
assert_equal :html, templates.first.format | |
assert_equal "variant", templates.first.variant | |
end | |
def test_should_match_locales | |
resolver = ActionView::FixtureResolver.new("arbitrary/path.erb" => "this text", "arbitrary/path.fr.erb" => "ce texte") | |
en = resolver.find_all("path", "arbitrary", false, locale: [:en], formats: [:html], variants: [], handlers: [:erb]) | |
fr = resolver.find_all("path", "arbitrary", false, locale: [:fr], formats: [:html], variants: [], handlers: [:erb]) | |
assert_equal 1, en.size | |
assert_equal 2, fr.size | |
assert_equal "this text", en[0].source | |
assert_equal "ce texte", fr[0].source | |
assert_equal "this text", fr[1].source | |
end | |
def test_should_return_all_variants_for_any | |
resolver = ActionView::FixtureResolver.new("arbitrary/path.html.erb" => "this html", "arbitrary/path.html+variant.erb" => "this text") | |
templates = resolver.find_all("path", "arbitrary", false, locale: [], formats: [:html], variants: [], handlers: [:erb]) | |
assert_equal 1, templates.size, "expected one template" | |
assert_equal "this html", templates.first.source | |
templates = resolver.find_all("path", "arbitrary", false, locale: [], formats: [:html], variants: :any, handlers: [:erb]) | |
assert_equal 2, templates.size, "expected all templates" | |
end | |
end |
# frozen_string_literal: true | |
require "active_support/core_ext/string/output_safety" | |
module ActionView | |
class OutputFlow # :nodoc: | |
attr_reader :content | |
def initialize | |
@content = Hash.new { |h, k| h[k] = ActiveSupport::SafeBuffer.new } | |
end | |
# Called by _layout_for to read stored values. | |
def get(key) | |
@content[key] | |
end | |
# Called by each renderer object to set the layout contents. | |
def set(key, value) | |
@content[key] = ActiveSupport::SafeBuffer.new(value.to_s) | |
end | |
# Called by content_for | |
def append(key, value) | |
@content[key] << value.to_s | |
end | |
alias_method :append!, :append | |
end | |
class StreamingFlow < OutputFlow # :nodoc: | |
def initialize(view, fiber) | |
@view = view | |
@parent = nil | |
@child = view.output_buffer | |
@content = view.view_flow.content | |
@fiber = fiber | |
@root = Fiber.current.object_id | |
end | |
# Try to get stored content. If the content | |
# is not available and we're inside the layout fiber, | |
# then it will begin waiting for the given key and yield. | |
def get(key) | |
return super if @content.key?(key) | |
if inside_fiber? | |
view = @view | |
begin | |
@waiting_for = key | |
view.output_buffer, @parent = @child, view.output_buffer | |
Fiber.yield | |
ensure | |
@waiting_for = nil | |
view.output_buffer, @child = @parent, view.output_buffer | |
end | |
end | |
super | |
end | |
# Appends the contents for the given key. This is called | |
# by providing and resuming back to the fiber, | |
# if that's the key it's waiting for. | |
def append!(key, value) | |
super | |
@fiber.resume if @waiting_for == key | |
end | |
private | |
def inside_fiber? | |
Fiber.current.object_id != @root | |
end | |
end | |
end |
# frozen_string_literal: true | |
require "abstract_unit" | |
Category = Struct.new(:id, :name) | |
class FormCollectionsHelperTest < ActionView::TestCase | |
def assert_no_select(selector, value = nil) | |
assert_select(selector, text: value, count: 0) | |
end | |
def with_collection_radio_buttons(*args, &block) | |
@output_buffer = collection_radio_buttons(*args, &block) | |
end | |
def with_collection_check_boxes(*args, &block) | |
@output_buffer = collection_check_boxes(*args, &block) | |
end | |
# COLLECTION RADIO BUTTONS | |
test "collection radio accepts a collection and generates inputs from value method" do | |
with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s | |
assert_select "input[type=radio][value=true]#user_active_true" | |
assert_select "input[type=radio][value=false]#user_active_false" | |
end | |
test "collection radio accepts a collection and generates inputs from label method" do | |
with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s | |
assert_select "label[for=user_active_true]", "true" | |
assert_select "label[for=user_active_false]", "false" | |
end | |
test "collection radio handles camelized collection values for labels correctly" do | |
with_collection_radio_buttons :user, :active, ["Yes", "No"], :to_s, :to_s | |
assert_select "label[for=user_active_yes]", "Yes" | |
assert_select "label[for=user_active_no]", "No" | |
end | |
test "collection radio generates labels for non-English values correctly" do | |
with_collection_radio_buttons :user, :title, ["Господин", "Госпожа"], :to_s, :to_s | |
assert_select "input[type=radio]#user_title_господин" | |
assert_select "label[for=user_title_господин]", "Господин" | |
end | |
test "collection radio should sanitize collection values for labels correctly" do | |
with_collection_radio_buttons :user, :name, ["$0.99", "$1.99"], :to_s, :to_s | |
assert_select "label[for=user_name_0_99]", "$0.99" | |
assert_select "label[for=user_name_1_99]", "$1.99" | |
end | |
test "collection radio correctly builds unique DOM IDs for float values" do | |
with_collection_radio_buttons :user, :name, [1.0, 10], :to_s, :to_s | |
assert_select "label[for=user_name_1_0]", "1.0" | |
assert_select "label[for=user_name_10]", "10" | |
assert_select 'input#user_name_1_0[type=radio][value="1.0"]' | |
assert_select 'input#user_name_10[type=radio][value="10"]' | |
end | |
test "collection radio accepts checked item" do | |
with_collection_radio_buttons :user, :active, [[1, true], [0, false]], :last, :first, checked: true | |
assert_select "input[type=radio][value=true][checked=checked]" | |
assert_no_select "input[type=radio][value=false][checked=checked]" | |
end | |
test "collection radio accepts multiple disabled items" do | |
collection = [[1, true], [0, false], [2, "other"]] | |
with_collection_radio_buttons :user, :active, collection, :last, :first, disabled: [true, false] | |
assert_select "input[type=radio][value=true][disabled=disabled]" | |
assert_select "input[type=radio][value=false][disabled=disabled]" | |
assert_no_select "input[type=radio][value=other][disabled=disabled]" | |
end | |
test "collection radio accepts single disabled item" do | |
collection = [[1, true], [0, false]] | |
with_collection_radio_buttons :user, :active, collection, :last, :first, disabled: true | |
assert_select "input[type=radio][value=true][disabled=disabled]" | |
assert_no_select "input[type=radio][value=false][disabled=disabled]" | |
end | |
test "collection radio accepts multiple readonly items" do | |
collection = [[1, true], [0, false], [2, "other"]] | |
with_collection_radio_buttons :user, :active, collection, :last, :first, readonly: [true, false] | |
assert_select "input[type=radio][value=true][readonly=readonly]" | |
assert_select "input[type=radio][value=false][readonly=readonly]" | |
assert_no_select "input[type=radio][value=other][readonly=readonly]" | |
end | |
test "collection radio accepts single readonly item" do | |
collection = [[1, true], [0, false]] | |
with_collection_radio_buttons :user, :active, collection, :last, :first, readonly: true | |
assert_select "input[type=radio][value=true][readonly=readonly]" | |
assert_no_select "input[type=radio][value=false][readonly=readonly]" | |
end | |
test "collection radio accepts html options as input" do | |
collection = [[1, true], [0, false]] | |
with_collection_radio_buttons :user, :active, collection, :last, :first, {}, { class: "special-radio" } | |
assert_select "input[type=radio][value=true].special-radio#user_active_true" | |
assert_select "input[type=radio][value=false].special-radio#user_active_false" | |
end | |
test "collection radio accepts html options as the last element of array" do | |
collection = [[1, true, { class: "foo" }], [0, false, { class: "bar" }]] | |
with_collection_radio_buttons :user, :active, collection, :second, :first | |
assert_select "input[type=radio][value=true].foo#user_active_true" | |
assert_select "input[type=radio][value=false].bar#user_active_false" | |
end | |
test "collection radio sets the label class defined inside the block" do | |
collection = [[1, true, { class: "foo" }], [0, false, { class: "bar" }]] | |
with_collection_radio_buttons :user, :active, collection, :second, :first do |b| | |
b.label(class: "collection_radio_buttons") | |
end | |
assert_select "label.collection_radio_buttons[for=user_active_true]" | |
assert_select "label.collection_radio_buttons[for=user_active_false]" | |
end | |
test "collection radio does not include the input class in the respective label" do | |
collection = [[1, true, { class: "foo" }], [0, false, { class: "bar" }]] | |
with_collection_radio_buttons :user, :active, collection, :second, :first | |
assert_no_select "label.foo[for=user_active_true]" | |
assert_no_select "label.bar[for=user_active_false]" | |
end | |
test "collection radio does not wrap input inside the label" do | |
with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s | |
assert_select "input[type=radio] + label" | |
assert_no_select "label input" | |
end | |
test "collection radio accepts a block to render the label as radio button wrapper" do | |
with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s do |b| | |
b.label { b.radio_button } | |
end | |
assert_select "label[for=user_active_true] > input#user_active_true[type=radio]" | |
assert_select "label[for=user_active_false] > input#user_active_false[type=radio]" | |
end | |
test "collection radio accepts a block to change the order of label and radio button" do | |
with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s do |b| | |
b.label + b.radio_button | |
end | |
assert_select "label[for=user_active_true] + input#user_active_true[type=radio]" | |
assert_select "label[for=user_active_false] + input#user_active_false[type=radio]" | |
end | |
test "collection radio with block helpers accept extra html options" do | |
with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s do |b| | |
b.label(class: "radio_button") + b.radio_button(class: "radio_button") | |
end | |
assert_select "label.radio_button[for=user_active_true] + input#user_active_true.radio_button[type=radio]" | |
assert_select "label.radio_button[for=user_active_false] + input#user_active_false.radio_button[type=radio]" | |
end | |
test "collection radio with block helpers allows access to current text and value" do | |
with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s do |b| | |
b.label("data-value": b.value) { b.radio_button + b.text } | |
end | |
assert_select "label[for=user_active_true][data-value=true]", "true" do | |
assert_select "input#user_active_true[type=radio]" | |
end | |
assert_select "label[for=user_active_false][data-value=false]", "false" do | |
assert_select "input#user_active_false[type=radio]" | |
end | |
end | |
test "collection radio with block helpers allows access to the current object item in the collection to access extra properties" do | |
with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s do |b| | |
b.label(class: b.object) { b.radio_button + b.text } | |
end | |
assert_select "label.true[for=user_active_true]", "true" do | |
assert_select "input#user_active_true[type=radio]" | |
end | |
assert_select "label.false[for=user_active_false]", "false" do | |
assert_select "input#user_active_false[type=radio]" | |
end | |
end | |
test "collection radio buttons with fields for" do | |
collection = [Category.new(1, "Category 1"), Category.new(2, "Category 2")] | |
@output_buffer = fields_for(:post) do |p| | |
p.collection_radio_buttons :category_id, collection, :id, :name | |
end | |
assert_select 'input#post_category_id_1[type=radio][value="1"]' | |
assert_select 'input#post_category_id_2[type=radio][value="2"]' | |
assert_select "label[for=post_category_id_1]", "Category 1" | |
assert_select "label[for=post_category_id_2]", "Category 2" | |
end | |
test "collection radio accepts checked item which has a value of false" do | |
with_collection_radio_buttons :user, :active, [[1, true], [0, false]], :last, :first, checked: false | |
assert_no_select "input[type=radio][value=true][checked=checked]" | |
assert_select "input[type=radio][value=false][checked=checked]" | |
end | |
test "collection radio buttons generates only one hidden field for the entire collection, to ensure something will be sent back to the server when posting an empty collection" do | |
collection = [Category.new(1, "Category 1"), Category.new(2, "Category 2")] | |
with_collection_radio_buttons :user, :category_ids, collection, :id, :name | |
assert_select "input[type=hidden][name='user[category_ids]'][value=''][autocomplete='off']", count: 1 | |
end | |
test "collection radio buttons generates a hidden field using the given :name in :html_options" do | |
collection = [Category.new(1, "Category 1"), Category.new(2, "Category 2")] | |
with_collection_radio_buttons :user, :category_ids, collection, :id, :name, {}, { name: "user[other_category_ids]" } | |
assert_select "input[type=hidden][name='user[other_category_ids]'][value=''][autocomplete='off']", count: 1 | |
end | |
test "collection radio buttons generates a hidden field with index if it was provided" do | |
collection = [Category.new(1, "Category 1"), Category.new(2, "Category 2")] | |
with_collection_radio_buttons :user, :category_ids, collection, :id, :name, index: 322 | |
assert_select "input[type=hidden][name='user[322][category_ids]'][value=''][autocomplete='off']", count: 1 | |
end | |
test "collection radio buttons does not generate a hidden field if include_hidden option is false" do | |
collection = [Category.new(1, "Category 1"), Category.new(2, "Category 2")] | |
with_collection_radio_buttons :user, :category_ids, collection, :id, :name, include_hidden: false | |
assert_select "input[type=hidden][name='user[category_ids]'][value='']", count: 0 | |
end | |
test "collection radio buttons does not generate a hidden field if include_hidden option is false with key as string" do | |
collection = [Category.new(1, "Category 1"), Category.new(2, "Category 2")] | |
with_collection_radio_buttons :user, :category_ids, collection, :id, :name, "include_hidden" => false | |
assert_select "input[type=hidden][name='user[category_ids]'][value='']", count: 0 | |
end | |
# COLLECTION CHECK BOXES | |
test "collection check boxes accepts a collection and generate a series of checkboxes for value method" do | |
collection = [Category.new(1, "Category 1"), Category.new(2, "Category 2")] | |
with_collection_check_boxes :user, :category_ids, collection, :id, :name | |
assert_select 'input#user_category_ids_1[type=checkbox][value="1"]' | |
assert_select 'input#user_category_ids_2[type=checkbox][value="2"]' | |
end | |
test "collection check boxes generates only one hidden field for the entire collection, to ensure something will be sent back to the server when posting an empty collection" do | |
collection = [Category.new(1, "Category 1"), Category.new(2, "Category 2")] | |
with_collection_check_boxes :user, :category_ids, collection, :id, :name | |
assert_select "input[type=hidden][name='user[category_ids][]'][value=''][autocomplete='off']", count: 1 | |
end | |
test "collection check boxes generates a hidden field using the given :name in :html_options" do | |
collection = [Category.new(1, "Category 1"), Category.new(2, "Category 2")] | |
with_collection_check_boxes :user, :category_ids, collection, :id, :name, {}, { name: "user[other_category_ids][]" } | |
assert_select "input[type=hidden][name='user[other_category_ids][]'][value=''][autocomplete='off']", count: 1 | |
end | |
test "collection check boxes generates a hidden field with index if it was provided" do | |
collection = [Category.new(1, "Category 1"), Category.new(2, "Category 2")] | |
with_collection_check_boxes :user, :category_ids, collection, :id, :name, index: 322 | |
assert_select "input[type=hidden][name='user[322][category_ids][]'][value=''][autocomplete='off']", count: 1 | |
end | |
test "collection check boxes does not generate a hidden field if include_hidden option is false" do | |
collection = [Category.new(1, "Category 1"), Category.new(2, "Category 2")] | |
with_collection_check_boxes :user, :category_ids, collection, :id, :name, include_hidden: false | |
assert_select "input[type=hidden][name='user[category_ids][]'][value='']", count: 0 | |
end | |
test "collection check boxes does not generate a hidden field if include_hidden option is false with key as string" do | |
collection = [Category.new(1, "Category 1"), Category.new(2, "Category 2")] | |
with_collection_check_boxes :user, :category_ids, collection, :id, :name, "include_hidden" => false | |
assert_select "input[type=hidden][name='user[category_ids][]'][value='']", count: 0 | |
end | |
test "collection check boxes accepts a collection and generate a series of checkboxes with labels for label method" do | |
collection = [Category.new(1, "Category 1"), Category.new(2, "Category 2")] | |
with_collection_check_boxes :user, :category_ids, collection, :id, :name | |
assert_select "label[for=user_category_ids_1]", "Category 1" | |
assert_select "label[for=user_category_ids_2]", "Category 2" | |
end | |
test "collection check boxes handles camelized collection values for labels correctly" do | |
with_collection_check_boxes :user, :active, ["Yes", "No"], :to_s, :to_s | |
assert_select "label[for=user_active_yes]", "Yes" | |
assert_select "label[for=user_active_no]", "No" | |
end | |
test "collection check box should sanitize collection values for labels correctly" do | |
with_collection_check_boxes :user, :name, ["$0.99", "$1.99"], :to_s, :to_s | |
assert_select "label[for=user_name_0_99]", "$0.99" | |
assert_select "label[for=user_name_1_99]", "$1.99" | |
end | |
test "collection check boxes correctly builds unique DOM IDs for float values" do | |
with_collection_check_boxes :user, :name, [1.0, 10], :to_s, :to_s | |
assert_select "label[for=user_name_1_0]", "1.0" | |
assert_select "label[for=user_name_10]", "10" | |
assert_select 'input#user_name_1_0[type=checkbox][value="1.0"]' | |
assert_select 'input#user_name_10[type=checkbox][value="10"]' | |
end | |
test "collection check boxes generates labels for non-English values correctly" do | |
with_collection_check_boxes :user, :title, ["Господин", "Госпожа"], :to_s, :to_s | |
assert_select "input[type=checkbox]#user_title_господин" | |
assert_select "label[for=user_title_господин]", "Господин" | |
end | |
test "collection check boxes accepts html options as the last element of array" do | |
collection = [[1, "Category 1", { class: "foo" }], [2, "Category 2", { class: "bar" }]] | |
with_collection_check_boxes :user, :active, collection, :first, :second | |
assert_select 'input[type=checkbox][value="1"].foo' | |
assert_select 'input[type=checkbox][value="2"].bar' | |
end | |
test "collection check boxes propagates input id to the label for attribute" do | |
collection = [[1, "Category 1", { id: "foo" }], [2, "Category 2", { id: "bar" }]] | |
with_collection_check_boxes :user, :active, collection, :first, :second | |
assert_select 'input[type=checkbox][value="1"]#foo' | |
assert_select 'input[type=checkbox][value="2"]#bar' | |
assert_select "label[for=foo]" | |
assert_select "label[for=bar]" | |
end | |
test "collection check boxes sets the label class defined inside the block" do | |
collection = [[1, "Category 1", { class: "foo" }], [2, "Category 2", { class: "bar" }]] | |
with_collection_check_boxes :user, :active, collection, :second, :first do |b| | |
b.label(class: "collection_check_boxes") | |
end | |
assert_select "label.collection_check_boxes[for=user_active_category_1]" | |
assert_select "label.collection_check_boxes[for=user_active_category_2]" | |
end | |
test "collection check boxes does not include the input class in the respective label" do | |
collection = [[1, "Category 1", { class: "foo" }], [2, "Category 2", { class: "bar" }]] | |
with_collection_check_boxes :user, :active, collection, :second, :first | |
assert_no_select "label.foo[for=user_active_category_1]" | |
assert_no_select "label.bar[for=user_active_category_2]" | |
end | |
test "collection check boxes accepts selected values as :checked option" do | |
collection = (1..3).map { |i| [i, "Category #{i}"] } | |
with_collection_check_boxes :user, :category_ids, collection, :first, :last, checked: [1, 3] | |
assert_select 'input[type=checkbox][value="1"][checked=checked]' | |
assert_select 'input[type=checkbox][value="3"][checked=checked]' | |
assert_no_select 'input[type=checkbox][value="2"][checked=checked]' | |
end | |
test "collection check boxes accepts selected string values as :checked option" do | |
collection = (1..3).map { |i| [i, "Category #{i}"] } | |
with_collection_check_boxes :user, :category_ids, collection, :first, :last, checked: ["1", "3"] | |
assert_select 'input[type=checkbox][value="1"][checked=checked]' | |
assert_select 'input[type=checkbox][value="3"][checked=checked]' | |
assert_no_select 'input[type=checkbox][value="2"][checked=checked]' | |
end | |
test "collection check boxes accepts a single checked value" do | |
collection = (1..3).map { |i| [i, "Category #{i}"] } | |
with_collection_check_boxes :user, :category_ids, collection, :first, :last, checked: 3 | |
assert_select 'input[type=checkbox][value="3"][checked=checked]' | |
assert_no_select 'input[type=checkbox][value="1"][checked=checked]' | |
assert_no_select 'input[type=checkbox][value="2"][checked=checked]' | |
end | |
test "collection check boxes accepts selected values as :checked option and override the model values" do | |
user = Struct.new(:category_ids).new(2) | |
collection = (1..3).map { |i| [i, "Category #{i}"] } | |
@output_buffer = fields_for(:user, user) do |p| | |
p.collection_check_boxes :category_ids, collection, :first, :last, checked: [1, 3] | |
end | |
assert_select 'input[type=checkbox][value="1"][checked=checked]' | |
assert_select 'input[type=checkbox][value="3"][checked=checked]' | |
assert_no_select 'input[type=checkbox][value="2"][checked=checked]' | |
end | |
test "collection check boxes accepts multiple disabled items" do | |
collection = (1..3).map { |i| [i, "Category #{i}"] } | |
with_collection_check_boxes :user, :category_ids, collection, :first, :last, disabled: [1, 3] | |
assert_select 'input[type=checkbox][value="1"][disabled=disabled]' | |
assert_select 'input[type=checkbox][value="3"][disabled=disabled]' | |
assert_no_select 'input[type=checkbox][value="2"][disabled=disabled]' | |
end | |
test "collection check boxes accepts single disabled item" do | |
collection = (1..3).map { |i| [i, "Category #{i}"] } | |
with_collection_check_boxes :user, :category_ids, collection, :first, :last, disabled: 1 | |
assert_select 'input[type=checkbox][value="1"][disabled=disabled]' | |
assert_no_select 'input[type=checkbox][value="3"][disabled=disabled]' | |
assert_no_select 'input[type=checkbox][value="2"][disabled=disabled]' | |
end | |
test "collection check boxes accepts a proc to disabled items" do | |
collection = (1..3).map { |i| [i, "Category #{i}"] } | |
with_collection_check_boxes :user, :category_ids, collection, :first, :last, disabled: proc { |i| i.first == 1 } | |
assert_select 'input[type=checkbox][value="1"][disabled=disabled]' | |
assert_no_select 'input[type=checkbox][value="3"][disabled=disabled]' | |
assert_no_select 'input[type=checkbox][value="2"][disabled=disabled]' | |
end | |
test "collection check boxes accepts multiple readonly items" do | |
collection = (1..3).map { |i| [i, "Category #{i}"] } | |
with_collection_check_boxes :user, :category_ids, collection, :first, :last, readonly: [1, 3] | |
assert_select 'input[type=checkbox][value="1"][readonly=readonly]' | |
assert_select 'input[type=checkbox][value="3"][readonly=readonly]' | |
assert_no_select 'input[type=checkbox][value="2"][readonly=readonly]' | |
end | |
test "collection check boxes accepts single readonly item" do | |
collection = (1..3).map { |i| [i, "Category #{i}"] } | |
with_collection_check_boxes :user, :category_ids, collection, :first, :last, readonly: 1 | |
assert_select 'input[type=checkbox][value="1"][readonly=readonly]' | |
assert_no_select 'input[type=checkbox][value="3"][readonly=readonly]' | |
assert_no_select 'input[type=checkbox][value="2"][readonly=readonly]' | |
end | |
test "collection check boxes accepts a proc to readonly items" do | |
collection = (1..3).map { |i| [i, "Category #{i}"] } | |
with_collection_check_boxes :user, :category_ids, collection, :first, :last, readonly: proc { |i| i.first == 1 } | |
assert_select 'input[type=checkbox][value="1"][readonly=readonly]' | |
assert_no_select 'input[type=checkbox][value="3"][readonly=readonly]' | |
assert_no_select 'input[type=checkbox][value="2"][readonly=readonly]' | |
end | |
test "collection check boxes accepts html options" do | |
collection = [[1, "Category 1"], [2, "Category 2"]] | |
with_collection_check_boxes :user, :category_ids, collection, :first, :last, {}, { class: "check" } | |
assert_select 'input.check[type=checkbox][value="1"]' | |
assert_select 'input.check[type=checkbox][value="2"]' | |
end | |
test "collection check boxes with fields for" do | |
collection = [Category.new(1, "Category 1"), Category.new(2, "Category 2")] | |
@output_buffer = fields_for(:post) do |p| | |
p.collection_check_boxes :category_ids, collection, :id, :name | |
end | |
assert_select 'input#post_category_ids_1[type=checkbox][value="1"]' | |
assert_select 'input#post_category_ids_2[type=checkbox][value="2"]' | |
assert_select "label[for=post_category_ids_1]", "Category 1" | |
assert_select "label[for=post_category_ids_2]", "Category 2" | |
end | |
test "collection check boxes does not wrap input inside the label" do | |
with_collection_check_boxes :user, :active, [true, false], :to_s, :to_s | |
assert_select "input[type=checkbox] + label" | |
assert_no_select "label input" | |
end | |
test "collection check boxes accepts a block to render the label as check box wrapper" do | |
with_collection_check_boxes :user, :active, [true, false], :to_s, :to_s do |b| | |
b.label { b.check_box } | |
end | |
assert_select "label[for=user_active_true] > input#user_active_true[type=checkbox]" | |
assert_select "label[for=user_active_false] > input#user_active_false[type=checkbox]" | |
end | |
test "collection check boxes accepts a block to change the order of label and check box" do | |
with_collection_check_boxes :user, :active, [true, false], :to_s, :to_s do |b| | |
b.label + b.check_box | |
end | |
assert_select "label[for=user_active_true] + input#user_active_true[type=checkbox]" | |
assert_select "label[for=user_active_false] + input#user_active_false[type=checkbox]" | |
end | |
test "collection check boxes with block helpers accept extra html options" do | |
with_collection_check_boxes :user, :active, [true, false], :to_s, :to_s do |b| | |
b.label(class: "check_box") + b.check_box(class: "check_box") | |
end | |
assert_select "label.check_box[for=user_active_true] + input#user_active_true.check_box[type=checkbox]" | |
assert_select "label.check_box[for=user_active_false] + input#user_active_false.check_box[type=checkbox]" | |
end | |
test "collection check boxes with block helpers allows access to current text and value" do | |
with_collection_check_boxes :user, :active, [true, false], :to_s, :to_s do |b| | |
b.label("data-value": b.value) { b.check_box + b.text } | |
end | |
assert_select "label[for=user_active_true][data-value=true]", "true" do | |
assert_select "input#user_active_true[type=checkbox]" | |
end | |
assert_select "label[for=user_active_false][data-value=false]", "false" do | |
assert_select "input#user_active_false[type=checkbox]" | |
end | |
end | |
test "collection check boxes with block helpers allows access to the current object item in the collection to access extra properties" do | |
with_collection_check_boxes :user, :active, [true, false], :to_s, :to_s do |b| | |
b.label(class: b.object) { b.check_box + b.text } | |
end | |
assert_select "label.true[for=user_active_true]", "true" do | |
assert_select "input#user_active_true[type=checkbox]" | |
end | |
assert_select "label.false[for=user_active_false]", "false" do | |
assert_select "input#user_active_false[type=checkbox]" | |
end | |
end | |
end |
# frozen_string_literal: true | |
require "abstract_unit" | |
require "template/erb/helper" | |
module ERBTest | |
class TagHelperTest < BlockTestCase | |
test "form_for works" do | |
routes = ActionDispatch::Routing::RouteSet.new | |
routes.draw do | |
get "/blah/update", to: "blah#update" | |
end | |
output = render_content "form_for(:staticpage, :url => {:controller => 'blah', :action => 'update'})", "", routes | |
assert_match %r{<form.*action="/blah/update".*method="post">.*</form>}, output | |
end | |
end | |
end |
# frozen_string_literal: true | |
require "cgi" | |
require "action_view/helpers/date_helper" | |
require "action_view/helpers/url_helper" | |
require "action_view/helpers/form_tag_helper" | |
require "action_view/helpers/active_model_helper" | |
require "action_view/model_naming" | |
require "action_view/record_identifier" | |
require "active_support/core_ext/module/attribute_accessors" | |
require "active_support/core_ext/hash/slice" | |
require "active_support/core_ext/string/output_safety" | |
require "active_support/core_ext/string/inflections" | |
module ActionView | |
# = Action View Form Helpers | |
module Helpers # :nodoc: | |
# Form helpers are designed to make working with resources much easier | |
# compared to using vanilla HTML. | |
# | |
# Typically, a form designed to create or update a resource reflects the | |
# identity of the resource in several ways: (i) the URL that the form is | |
# sent to (the form element's +action+ attribute) should result in a request | |
# being routed to the appropriate controller action (with the appropriate <tt>:id</tt> | |
# parameter in the case of an existing resource), (ii) input fields should | |
# be named in such a way that in the controller their values appear in the | |
# appropriate places within the +params+ hash, and (iii) for an existing record, | |
# when the form is initially displayed, input fields corresponding to attributes | |
# of the resource should show the current values of those attributes. | |
# | |
# In Rails, this is usually achieved by creating the form using +form_for+ and | |
# a number of related helper methods. +form_for+ generates an appropriate <tt>form</tt> | |
# tag and yields a form builder object that knows the model the form is about. | |
# Input fields are created by calling methods defined on the form builder, which | |
# means they are able to generate the appropriate names and default values | |
# corresponding to the model attributes, as well as convenient IDs, etc. | |
# Conventions in the generated field names allow controllers to receive form data | |
# nicely structured in +params+ with no effort on your side. | |
# | |
# For example, to create a new person you typically set up a new instance of | |
# +Person+ in the <tt>PeopleController#new</tt> action, <tt>@person</tt>, and | |
# in the view template pass that object to +form_for+: | |
# | |
# <%= form_for @person do |f| %> | |
# <%= f.label :first_name %>: | |
# <%= f.text_field :first_name %><br /> | |
# | |
# <%= f.label :last_name %>: | |
# <%= f.text_field :last_name %><br /> | |
# | |
# <%= f.submit %> | |
# <% end %> | |
# | |
# The HTML generated for this would be (modulus formatting): | |
# | |
# <form action="/people" class="new_person" id="new_person" method="post"> | |
# <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" /> | |
# <label for="person_first_name">First name</label>: | |
# <input id="person_first_name" name="person[first_name]" type="text" /><br /> | |
# | |
# <label for="person_last_name">Last name</label>: | |
# <input id="person_last_name" name="person[last_name]" type="text" /><br /> | |
# | |
# <input name="commit" type="submit" value="Create Person" /> | |
# </form> | |
# | |
# As you see, the HTML reflects knowledge about the resource in several spots, | |
# like the path the form should be submitted to, or the names of the input fields. | |
# | |
# In particular, thanks to the conventions followed in the generated field names, the | |
# controller gets a nested hash <tt>params[:person]</tt> with the person attributes | |
# set in the form. That hash is ready to be passed to <tt>Person.new</tt>: | |
# | |
# @person = Person.new(params[:person]) | |
# if @person.save | |
# # success | |
# else | |
# # error handling | |
# end | |
# | |
# Interestingly, the exact same view code in the previous example can be used to edit | |
# a person. If <tt>@person</tt> is an existing record with name "John Smith" and ID 256, | |
# the code above as is would yield instead: | |
# | |
# <form action="/people/256" class="edit_person" id="edit_person_256" method="post"> | |
# <input name="_method" type="hidden" value="patch" /> | |
# <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" /> | |
# <label for="person_first_name">First name</label>: | |
# <input id="person_first_name" name="person[first_name]" type="text" value="John" /><br /> | |
# | |
# <label for="person_last_name">Last name</label>: | |
# <input id="person_last_name" name="person[last_name]" type="text" value="Smith" /><br /> | |
# | |
# <input name="commit" type="submit" value="Update Person" /> | |
# </form> | |
# | |
# Note that the endpoint, default values, and submit button label are tailored for <tt>@person</tt>. | |
# That works that way because the involved helpers know whether the resource is a new record or not, | |
# and generate HTML accordingly. | |
# | |
# The controller would receive the form data again in <tt>params[:person]</tt>, ready to be | |
# passed to <tt>Person#update</tt>: | |
# | |
# if @person.update(params[:person]) | |
# # success | |
# else | |
# # error handling | |
# end | |
# | |
# That's how you typically work with resources. | |
module FormHelper | |
extend ActiveSupport::Concern | |
include FormTagHelper | |
include UrlHelper | |
include ModelNaming | |
include RecordIdentifier | |
attr_internal :default_form_builder | |
# Creates a form that allows the user to create or update the attributes | |
# of a specific model object. | |
# | |
# The method can be used in several slightly different ways, depending on | |
# how much you wish to rely on Rails to infer automatically from the model | |
# how the form should be constructed. For a generic model object, a form | |
# can be created by passing +form_for+ a string or symbol representing | |
# the object we are concerned with: | |
# | |
# <%= form_for :person do |f| %> | |
# First name: <%= f.text_field :first_name %><br /> | |
# Last name : <%= f.text_field :last_name %><br /> | |
# Biography : <%= f.text_area :biography %><br /> | |
# Admin? : <%= f.check_box :admin %><br /> | |
# <%= f.submit %> | |
# <% end %> | |
# | |
# The variable +f+ yielded to the block is a FormBuilder object that | |
# incorporates the knowledge about the model object represented by | |
# <tt>:person</tt> passed to +form_for+. Methods defined on the FormBuilder | |
# are used to generate fields bound to this model. Thus, for example, | |
# | |
# <%= f.text_field :first_name %> | |
# | |
# will get expanded to | |
# | |
# <%= text_field :person, :first_name %> | |
# | |
# which results in an HTML <tt><input></tt> tag whose +name+ attribute is | |
# <tt>person[first_name]</tt>. This means that when the form is submitted, | |
# the value entered by the user will be available in the controller as | |
# <tt>params[:person][:first_name]</tt>. | |
# | |
# For fields generated in this way using the FormBuilder, | |
# if <tt>:person</tt> also happens to be the name of an instance variable | |
# <tt>@person</tt>, the default value of the field shown when the form is | |
# initially displayed (e.g. in the situation where you are editing an | |
# existing record) will be the value of the corresponding attribute of | |
# <tt>@person</tt>. | |
# | |
# The rightmost argument to +form_for+ is an | |
# optional hash of options - | |
# | |
# * <tt>:url</tt> - The URL the form is to be submitted to. This may be | |
# represented in the same way as values passed to +url_for+ or +link_to+. | |
# So for example you may use a named route directly. When the model is | |
# represented by a string or symbol, as in the example above, if the | |
# <tt>:url</tt> option is not specified, by default the form will be | |
# sent back to the current URL (We will describe below an alternative | |
# resource-oriented usage of +form_for+ in which the URL does not need | |
# to be specified explicitly). | |
# * <tt>:namespace</tt> - A namespace for your form to ensure uniqueness of | |
# id attributes on form elements. The namespace attribute will be prefixed | |
# with underscore on the generated HTML id. | |
# * <tt>:method</tt> - The method to use when submitting the form, usually | |
# either "get" or "post". If "patch", "put", "delete", or another verb | |
# is used, a hidden input with name <tt>_method</tt> is added to | |
# simulate the verb over post. | |
# * <tt>:authenticity_token</tt> - Authenticity token to use in the form. | |
# Use only if you need to pass custom authenticity token string, or to | |
# not add authenticity_token field at all (by passing <tt>false</tt>). | |
# Remote forms may omit the embedded authenticity token by setting | |
# <tt>config.action_view.embed_authenticity_token_in_remote_forms = false</tt>. | |
# This is helpful when you're fragment-caching the form. Remote forms | |
# get the authenticity token from the <tt>meta</tt> tag, so embedding is | |
# unnecessary unless you support browsers without JavaScript. | |
# * <tt>:remote</tt> - If set to true, will allow the Unobtrusive | |
# JavaScript drivers to control the submit behavior. | |
# * <tt>:enforce_utf8</tt> - If set to false, a hidden input with name | |
# utf8 is not output. | |
# * <tt>:html</tt> - Optional HTML attributes for the form tag. | |
# | |
# Also note that +form_for+ doesn't create an exclusive scope. It's still | |
# possible to use both the stand-alone FormHelper methods and methods | |
# from FormTagHelper. For example: | |
# | |
# <%= form_for :person do |f| %> | |
# First name: <%= f.text_field :first_name %> | |
# Last name : <%= f.text_field :last_name %> | |
# Biography : <%= text_area :person, :biography %> | |
# Admin? : <%= check_box_tag "person[admin]", "1", @person.company.admin? %> | |
# <%= f.submit %> | |
# <% end %> | |
# | |
# This also works for the methods in FormOptionsHelper and DateHelper that | |
# are designed to work with an object as base, like | |
# FormOptionsHelper#collection_select and DateHelper#datetime_select. | |
# | |
# === #form_for with a model object | |
# | |
# In the examples above, the object to be created or edited was | |
# represented by a symbol passed to +form_for+, and we noted that | |
# a string can also be used equivalently. It is also possible, however, | |
# to pass a model object itself to +form_for+. For example, if <tt>@post</tt> | |
# is an existing record you wish to edit, you can create the form using | |
# | |
# <%= form_for @post do |f| %> | |
# ... | |
# <% end %> | |
# | |
# This behaves in almost the same way as outlined previously, with a | |
# couple of small exceptions. First, the prefix used to name the input | |
# elements within the form (hence the key that denotes them in the +params+ | |
# hash) is actually derived from the object's _class_, e.g. <tt>params[:post]</tt> | |
# if the object's class is +Post+. However, this can be overwritten using | |
# the <tt>:as</tt> option, e.g. - | |
# | |
# <%= form_for(@person, as: :client) do |f| %> | |
# ... | |
# <% end %> | |
# | |
# would result in <tt>params[:client]</tt>. | |
# | |
# Secondly, the field values shown when the form is initially displayed | |
# are taken from the attributes of the object passed to +form_for+, | |
# regardless of whether the object is an instance | |
# variable. So, for example, if we had a _local_ variable +post+ | |
# representing an existing record, | |
# | |
# <%= form_for post do |f| %> | |
# ... | |
# <% end %> | |
# | |
# would produce a form with fields whose initial state reflect the current | |
# values of the attributes of +post+. | |
# | |
# === Resource-oriented style | |
# | |
# In the examples just shown, although not indicated explicitly, we still | |
# need to use the <tt>:url</tt> option in order to specify where the | |
# form is going to be sent. However, further simplification is possible | |
# if the record passed to +form_for+ is a _resource_, i.e. it corresponds | |
# to a set of RESTful routes, e.g. defined using the +resources+ method | |
# in <tt>config/routes.rb</tt>. In this case Rails will simply infer the | |
# appropriate URL from the record itself. For example, | |
# | |
# <%= form_for @post do |f| %> | |
# ... | |
# <% end %> | |
# | |
# is then equivalent to something like: | |
# | |
# <%= form_for @post, as: :post, url: post_path(@post), method: :patch, html: { class: "edit_post", id: "edit_post_45" } do |f| %> | |
# ... | |
# <% end %> | |
# | |
# And for a new record | |
# | |
# <%= form_for(Post.new) do |f| %> | |
# ... | |
# <% end %> | |
# | |
# is equivalent to something like: | |
# | |
# <%= form_for @post, as: :post, url: posts_path, html: { class: "new_post", id: "new_post" } do |f| %> | |
# ... | |
# <% end %> | |
# | |
# However you can still overwrite individual conventions, such as: | |
# | |
# <%= form_for(@post, url: super_posts_path) do |f| %> | |
# ... | |
# <% end %> | |
# | |
# You can omit the <tt>action</tt> attribute by passing <tt>url: false</tt>: | |
# | |
# <%= form_for(@post, url: false) do |f| %> | |
# ... | |
# <% end %> | |
# | |
# You can also set the answer format, like this: | |
# | |
# <%= form_for(@post, format: :json) do |f| %> | |
# ... | |
# <% end %> | |
# | |
# For namespaced routes, like +admin_post_url+: | |
# | |
# <%= form_for([:admin, @post]) do |f| %> | |
# ... | |
# <% end %> | |
# | |
# If your resource has associations defined, for example, you want to add comments | |
# to the document given that the routes are set correctly: | |
# | |
# <%= form_for([@document, @comment]) do |f| %> | |
# ... | |
# <% end %> | |
# | |
# Where <tt>@document = Document.find(params[:id])</tt> and | |
# <tt>@comment = Comment.new</tt>. | |
# | |
# === Setting the method | |
# | |
# You can force the form to use the full array of HTTP verbs by setting | |
# | |
# method: (:get|:post|:patch|:put|:delete) | |
# | |
# in the options hash. If the verb is not GET or POST, which are natively | |
# supported by HTML forms, the form will be set to POST and a hidden input | |
# called _method will carry the intended verb for the server to interpret. | |
# | |
# === Unobtrusive JavaScript | |
# | |
# Specifying: | |
# | |
# remote: true | |
# | |
# in the options hash creates a form that will allow the unobtrusive JavaScript drivers to modify its | |
# behavior. The form submission will work just like a regular submission as viewed by the receiving | |
# side (all elements available in <tt>params</tt>). | |
# | |
# Example: | |
# | |
# <%= form_for(@post, remote: true) do |f| %> | |
# ... | |
# <% end %> | |
# | |
# The HTML generated for this would be: | |
# | |
# <form action='http://www.example.com' method='post' data-remote='true'> | |
# <input name='_method' type='hidden' value='patch' /> | |
# ... | |
# </form> | |
# | |
# === Setting HTML options | |
# | |
# You can set data attributes directly by passing in a data hash, but all other HTML options must be wrapped in | |
# the HTML key. Example: | |
# | |
# <%= form_for(@post, data: { behavior: "autosave" }, html: { name: "go" }) do |f| %> | |
# ... | |
# <% end %> | |
# | |
# The HTML generated for this would be: | |
# | |
# <form action='http://www.example.com' method='post' data-behavior='autosave' name='go'> | |
# <input name='_method' type='hidden' value='patch' /> | |
# ... | |
# </form> | |
# | |
# === Removing hidden model id's | |
# | |
# The form_for method automatically includes the model id as a hidden field in the form. | |
# This is used to maintain the correlation between the form data and its associated model. | |
# Some ORM systems do not use IDs on nested models so in this case you want to be able | |
# to disable the hidden id. | |
# | |
# In the following example the Post model has many Comments stored within it in a NoSQL database, | |
# thus there is no primary key for comments. | |
# | |
# Example: | |
# | |
# <%= form_for(@post) do |f| %> | |
# <%= f.fields_for(:comments, include_id: false) do |cf| %> | |
# ... | |
# <% end %> | |
# <% end %> | |
# | |
# === Customized form builders | |
# | |
# You can also build forms using a customized FormBuilder class. Subclass | |
# FormBuilder and override or define some more helpers, then use your | |
# custom builder. For example, let's say you made a helper to | |
# automatically add labels to form inputs. | |
# | |
# <%= form_for @person, url: { action: "create" }, builder: LabellingFormBuilder do |f| %> | |
# <%= f.text_field :first_name %> | |
# <%= f.text_field :last_name %> | |
# <%= f.text_area :biography %> | |
# <%= f.check_box :admin %> | |
# <%= f.submit %> | |
# <% end %> | |
# | |
# In this case, if you use this: | |
# | |
# <%= render f %> | |
# | |
# The rendered template is <tt>people/_labelling_form</tt> and the local | |
# variable referencing the form builder is called | |
# <tt>labelling_form</tt>. | |
# | |
# The custom FormBuilder class is automatically merged with the options | |
# of a nested fields_for call, unless it's explicitly set. | |
# | |
# In many cases you will want to wrap the above in another helper, so you | |
# could do something like the following: | |
# | |
# def labelled_form_for(record_or_name_or_array, *args, &block) | |
# options = args.extract_options! | |
# form_for(record_or_name_or_array, *(args << options.merge(builder: LabellingFormBuilder)), &block) | |
# end | |
# | |
# If you don't need to attach a form to a model instance, then check out | |
# FormTagHelper#form_tag. | |
# | |
# === Form to external resources | |
# | |
# When you build forms to external resources sometimes you need to set an authenticity token or just render a form | |
# without it, for example when you submit data to a payment gateway number and types of fields could be limited. | |
# | |
# To set an authenticity token you need to pass an <tt>:authenticity_token</tt> parameter | |
# | |
# <%= form_for @invoice, url: external_url, authenticity_token: 'external_token' do |f| %> | |
# ... | |
# <% end %> | |
# | |
# If you don't want to an authenticity token field be rendered at all just pass <tt>false</tt>: | |
# | |
# <%= form_for @invoice, url: external_url, authenticity_token: false do |f| %> | |
# ... | |
# <% end %> | |
def form_for(record, options = {}, &block) | |
raise ArgumentError, "Missing block" unless block_given? | |
case record | |
when String, Symbol | |
model = nil | |
object_name = record | |
else | |
model = convert_to_model(record) | |
object = _object_for_form_builder(record) | |
raise ArgumentError, "First argument in form cannot contain nil or be empty" unless object | |
object_name = options[:as] || model_name_from_record_or_class(object).param_key | |
apply_form_for_options!(object, options) | |
end | |
remote = options.delete(:remote) | |
if remote && !embed_authenticity_token_in_remote_forms && options[:authenticity_token].blank? | |
options[:authenticity_token] = false | |
end | |
options[:model] = model | |
options[:scope] = object_name | |
options[:local] = !remote | |
options[:skip_default_ids] = false | |
options[:allow_method_names_outside_object] = options.fetch(:allow_method_names_outside_object, false) | |
form_with(**options, &block) | |
end | |
def apply_form_for_options!(object, options) # :nodoc: | |
object = convert_to_model(object) | |
as = options[:as] | |
namespace = options[:namespace] | |
action = object.respond_to?(:persisted?) && object.persisted? ? :edit : :new | |
options[:html] ||= {} | |
options[:html].reverse_merge!( | |
class: as ? "#{action}_#{as}" : dom_class(object, action), | |
id: (as ? [namespace, action, as] : [namespace, dom_id(object, action)]).compact.join("_").presence, | |
) | |
end | |
private :apply_form_for_options! | |
mattr_accessor :form_with_generates_remote_forms, default: true | |
mattr_accessor :form_with_generates_ids, default: false | |
mattr_accessor :multiple_file_field_include_hidden, default: false | |
# Creates a form tag based on mixing URLs, scopes, or models. | |
# | |
# # Using just a URL: | |
# <%= form_with url: posts_path do |form| %> | |
# <%= form.text_field :title %> | |
# <% end %> | |
# # => | |
# <form action="/posts" method="post"> | |
# <input type="text" name="title"> | |
# </form> | |
# | |
# # With an intentionally empty URL: | |
# <%= form_with url: false do |form| %> | |
# <%= form.text_field :title %> | |
# <% end %> | |
# # => | |
# <form method="post" data-remote="true"> | |
# <input type="text" name="title"> | |
# </form> | |
# | |
# # Adding a scope prefixes the input field names: | |
# <%= form_with scope: :post, url: posts_path do |form| %> | |
# <%= form.text_field :title %> | |
# <% end %> | |
# # => | |
# <form action="/posts" method="post"> | |
# <input type="text" name="post[title]"> | |
# </form> | |
# | |
# # Using a model infers both the URL and scope: | |
# <%= form_with model: Post.new do |form| %> | |
# <%= form.text_field :title %> | |
# <% end %> | |
# # => | |
# <form action="/posts" method="post"> | |
# <input type="text" name="post[title]"> | |
# </form> | |
# | |
# # An existing model makes an update form and fills out field values: | |
# <%= form_with model: Post.first do |form| %> | |
# <%= form.text_field :title %> | |
# <% end %> | |
# # => | |
# <form action="/posts/1" method="post"> | |
# <input type="hidden" name="_method" value="patch"> | |
# <input type="text" name="post[title]" value="<the title of the post>"> | |
# </form> | |
# | |
# # Though the fields don't have to correspond to model attributes: | |
# <%= form_with model: Cat.new do |form| %> | |
# <%= form.text_field :cats_dont_have_gills %> | |
# <%= form.text_field :but_in_forms_they_can %> | |
# <% end %> | |
# # => | |
# <form action="/cats" method="post"> | |
# <input type="text" name="cat[cats_dont_have_gills]"> | |
# <input type="text" name="cat[but_in_forms_they_can]"> | |
# </form> | |
# | |
# The parameters in the forms are accessible in controllers according to | |
# their name nesting. So inputs named +title+ and <tt>post[title]</tt> are | |
# accessible as <tt>params[:title]</tt> and <tt>params[:post][:title]</tt> | |
# respectively. | |
# | |
# For ease of comparison the examples above left out the submit button, | |
# as well as the auto generated hidden fields that enable UTF-8 support | |
# and adds an authenticity token needed for cross site request forgery | |
# protection. | |
# | |
# === Resource-oriented style | |
# | |
# In many of the examples just shown, the +:model+ passed to +form_with+ | |
# is a _resource_. It corresponds to a set of RESTful routes, most likely | |
# defined via +resources+ in <tt>config/routes.rb</tt>. | |
# | |
# So when passing such a model record, Rails infers the URL and method. | |
# | |
# <%= form_with model: @post do |form| %> | |
# ... | |
# <% end %> | |
# | |
# is then equivalent to something like: | |
# | |
# <%= form_with scope: :post, url: post_path(@post), method: :patch do |form| %> | |
# ... | |
# <% end %> | |
# | |
# And for a new record | |
# | |
# <%= form_with model: Post.new do |form| %> | |
# ... | |
# <% end %> | |
# | |
# is equivalent to something like: | |
# | |
# <%= form_with scope: :post, url: posts_path do |form| %> | |
# ... | |
# <% end %> | |
# | |
# ==== +form_with+ options | |
# | |
# * <tt>:url</tt> - The URL the form submits to. Akin to values passed to | |
# +url_for+ or +link_to+. For example, you may use a named route | |
# directly. When a <tt>:scope</tt> is passed without a <tt>:url</tt> the | |
# form just submits to the current URL. | |
# * <tt>:method</tt> - The method to use when submitting the form, usually | |
# either "get" or "post". If "patch", "put", "delete", or another verb | |
# is used, a hidden input named <tt>_method</tt> is added to | |
# simulate the verb over post. | |
# * <tt>:format</tt> - The format of the route the form submits to. | |
# Useful when submitting to another resource type, like <tt>:json</tt>. | |
# Skipped if a <tt>:url</tt> is passed. | |
# * <tt>:scope</tt> - The scope to prefix input field names with and | |
# thereby how the submitted parameters are grouped in controllers. | |
# * <tt>:namespace</tt> - A namespace for your form to ensure uniqueness of | |
# id attributes on form elements. The namespace attribute will be prefixed | |
# with underscore on the generated HTML id. | |
# * <tt>:model</tt> - A model object to infer the <tt>:url</tt> and | |
# <tt>:scope</tt> by, plus fill out input field values. | |
# So if a +title+ attribute is set to "Ahoy!" then a +title+ input | |
# field's value would be "Ahoy!". | |
# If the model is a new record a create form is generated, if an | |
# existing record, however, an update form is generated. | |
# Pass <tt>:scope</tt> or <tt>:url</tt> to override the defaults. | |
# E.g. turn <tt>params[:post]</tt> into <tt>params[:article]</tt>. | |
# * <tt>:authenticity_token</tt> - Authenticity token to use in the form. | |
# Override with a custom authenticity token or pass <tt>false</tt> to | |
# skip the authenticity token field altogether. | |
# Useful when submitting to an external resource like a payment gateway | |
# that might limit the valid fields. | |
# Remote forms may omit the embedded authenticity token by setting | |
# <tt>config.action_view.embed_authenticity_token_in_remote_forms = false</tt>. | |
# This is helpful when fragment-caching the form. Remote forms | |
# get the authenticity token from the <tt>meta</tt> tag, so embedding is | |
# unnecessary unless you support browsers without JavaScript. | |
# * <tt>:local</tt> - Whether to use standard HTTP form submission. | |
# When set to <tt>true</tt>, the form is submitted via standard HTTP. | |
# When set to <tt>false</tt>, the form is submitted as a "remote form", which | |
# is handled by Rails UJS as an XHR. When unspecified, the behavior is derived | |
# from <tt>config.action_view.form_with_generates_remote_forms</tt> where the | |
# config's value is actually the inverse of what <tt>local</tt>'s value would be. | |
# As of Rails 6.1, that configuration option defaults to <tt>false</tt> | |
# (which has the equivalent effect of passing <tt>local: true</tt>). | |
# In previous versions of Rails, that configuration option defaults to | |
# <tt>true</tt> (the equivalent of passing <tt>local: false</tt>). | |
# * <tt>:skip_enforcing_utf8</tt> - If set to true, a hidden input with name | |
# utf8 is not output. | |
# * <tt>:builder</tt> - Override the object used to build the form. | |
# * <tt>:id</tt> - Optional HTML id attribute. | |
# * <tt>:class</tt> - Optional HTML class attribute. | |
# * <tt>:data</tt> - Optional HTML data attributes. | |
# * <tt>:html</tt> - Other optional HTML attributes for the form tag. | |
# | |
# === Examples | |
# | |
# When not passing a block, +form_with+ just generates an opening form tag. | |
# | |
# <%= form_with(model: @post, url: super_posts_path) %> | |
# <%= form_with(model: @post, scope: :article) %> | |
# <%= form_with(model: @post, format: :json) %> | |
# <%= form_with(model: @post, authenticity_token: false) %> # Disables the token. | |
# | |
# For namespaced routes, like +admin_post_url+: | |
# | |
# <%= form_with(model: [ :admin, @post ]) do |form| %> | |
# ... | |
# <% end %> | |
# | |
# If your resource has associations defined, for example, you want to add comments | |
# to the document given that the routes are set correctly: | |
# | |
# <%= form_with(model: [ @document, Comment.new ]) do |form| %> | |
# ... | |
# <% end %> | |
# | |
# Where <tt>@document = Document.find(params[:id])</tt>. | |
# | |
# === Mixing with other form helpers | |
# | |
# While +form_with+ uses a FormBuilder object it's possible to mix and | |
# match the stand-alone FormHelper methods and methods | |
# from FormTagHelper: | |
# | |
# <%= form_with scope: :person do |form| %> | |
# <%= form.text_field :first_name %> | |
# <%= form.text_field :last_name %> | |
# | |
# <%= text_area :person, :biography %> | |
# <%= check_box_tag "person[admin]", "1", @person.company.admin? %> | |
# | |
# <%= form.submit %> | |
# <% end %> | |
# | |
# Same goes for the methods in FormOptionsHelper and DateHelper designed | |
# to work with an object as a base, like | |
# FormOptionsHelper#collection_select and DateHelper#datetime_select. | |
# | |
# === Setting the method | |
# | |
# You can force the form to use the full array of HTTP verbs by setting | |
# | |
# method: (:get|:post|:patch|:put|:delete) | |
# | |
# in the options hash. If the verb is not GET or POST, which are natively | |
# supported by HTML forms, the form will be set to POST and a hidden input | |
# called _method will carry the intended verb for the server to interpret. | |
# | |
# === Setting HTML options | |
# | |
# You can set data attributes directly in a data hash, but HTML options | |
# besides id and class must be wrapped in an HTML key: | |
# | |
# <%= form_with(model: @post, data: { behavior: "autosave" }, html: { name: "go" }) do |form| %> | |
# ... | |
# <% end %> | |
# | |
# generates | |
# | |
# <form action="/posts/123" method="post" data-behavior="autosave" name="go"> | |
# <input name="_method" type="hidden" value="patch" /> | |
# ... | |
# </form> | |
# | |
# === Removing hidden model id's | |
# | |
# The +form_with+ method automatically includes the model id as a hidden field in the form. | |
# This is used to maintain the correlation between the form data and its associated model. | |
# Some ORM systems do not use IDs on nested models so in this case you want to be able | |
# to disable the hidden id. | |
# | |
# In the following example the Post model has many Comments stored within it in a NoSQL database, | |
# thus there is no primary key for comments. | |
# | |
# <%= form_with(model: @post) do |form| %> | |
# <%= form.fields(:comments, skip_id: true) do |fields| %> | |
# ... | |
# <% end %> | |
# <% end %> | |
# | |
# === Customized form builders | |
# | |
# You can also build forms using a customized FormBuilder class. Subclass | |
# FormBuilder and override or define some more helpers, then use your | |
# custom builder. For example, let's say you made a helper to | |
# automatically add labels to form inputs. | |
# | |
# <%= form_with model: @person, url: { action: "create" }, builder: LabellingFormBuilder do |form| %> | |
# <%= form.text_field :first_name %> | |
# <%= form.text_field :last_name %> | |
# <%= form.text_area :biography %> | |
# <%= form.check_box :admin %> | |
# <%= form.submit %> | |
# <% end %> | |
# | |
# In this case, if you use: | |
# | |
# <%= render form %> | |
# | |
# The rendered template is <tt>people/_labelling_form</tt> and the local | |
# variable referencing the form builder is called | |
# <tt>labelling_form</tt>. | |
# | |
# The custom FormBuilder class is automatically merged with the options | |
# of a nested +fields+ call, unless it's explicitly set. | |
# | |
# In many cases you will want to wrap the above in another helper, so you | |
# could do something like the following: | |
# | |
# def labelled_form_with(**options, &block) | |
# form_with(**options.merge(builder: LabellingFormBuilder), &block) | |
# end | |
def form_with(model: nil, scope: nil, url: nil, format: nil, **options, &block) | |
options = { allow_method_names_outside_object: true, skip_default_ids: !form_with_generates_ids }.merge!(options) | |
if model | |
if url != false | |
url ||= if format.nil? | |
polymorphic_path(model, {}) | |
else | |
polymorphic_path(model, format: format) | |
end | |
end | |
model = _object_for_form_builder(model) | |
scope ||= model_name_from_record_or_class(model).param_key | |
end | |
if block_given? | |
builder = instantiate_builder(scope, model, options) | |
output = capture(builder, &block) | |
options[:multipart] ||= builder.multipart? | |
html_options = html_options_for_form_with(url, model, **options) | |
form_tag_with_body(html_options, output) | |
else | |
html_options = html_options_for_form_with(url, model, **options) | |
form_tag_html(html_options) | |
end | |
end | |
# Creates a scope around a specific model object like form_for, but | |
# doesn't create the form tags themselves. This makes fields_for suitable | |
# for specifying additional model objects in the same form. | |
# | |
# Although the usage and purpose of +fields_for+ is similar to +form_for+'s, | |
# its method signature is slightly different. Like +form_for+, it yields | |
# a FormBuilder object associated with a particular model object to a block, | |
# and within the block allows methods to be called on the builder to | |
# generate fields associated with the model object. Fields may reflect | |
# a model object in two ways - how they are named (hence how submitted | |
# values appear within the +params+ hash in the controller) and what | |
# default values are shown when the form the fields appear in is first | |
# displayed. In order for both of these features to be specified independently, | |
# both an object name (represented by either a symbol or string) and the | |
# object itself can be passed to the method separately - | |
# | |
# <%= form_for @person do |person_form| %> | |
# First name: <%= person_form.text_field :first_name %> | |
# Last name : <%= person_form.text_field :last_name %> | |
# | |
# <%= fields_for :permission, @person.permission do |permission_fields| %> | |
# Admin? : <%= permission_fields.check_box :admin %> | |
# <% end %> | |
# | |
# <%= person_form.submit %> | |
# <% end %> | |
# | |
# In this case, the checkbox field will be represented by an HTML +input+ | |
# tag with the +name+ attribute <tt>permission[admin]</tt>, and the submitted | |
# value will appear in the controller as <tt>params[:permission][:admin]</tt>. | |
# If <tt>@person.permission</tt> is an existing record with an attribute | |
# +admin+, the initial state of the checkbox when first displayed will | |
# reflect the value of <tt>@person.permission.admin</tt>. | |
# | |
# Often this can be simplified by passing just the name of the model | |
# object to +fields_for+ - | |
# | |
# <%= fields_for :permission do |permission_fields| %> | |
# Admin?: <%= permission_fields.check_box :admin %> | |
# <% end %> | |
# | |
# ...in which case, if <tt>:permission</tt> also happens to be the name of an | |
# instance variable <tt>@permission</tt>, the initial state of the input | |
# field will reflect the value of that variable's attribute <tt>@permission.admin</tt>. | |
# | |
# Alternatively, you can pass just the model object itself (if the first | |
# argument isn't a string or symbol +fields_for+ will realize that the | |
# name has been omitted) - | |
# | |
# <%= fields_for @person.permission do |permission_fields| %> | |
# Admin?: <%= permission_fields.check_box :admin %> | |
# <% end %> | |
# | |
# and +fields_for+ will derive the required name of the field from the | |
# _class_ of the model object, e.g. if <tt>@person.permission</tt>, is | |
# of class +Permission+, the field will still be named <tt>permission[admin]</tt>. | |
# | |
# Note: This also works for the methods in FormOptionsHelper and | |
# DateHelper that are designed to work with an object as base, like | |
# FormOptionsHelper#collection_select and DateHelper#datetime_select. | |
# | |
# === Nested Attributes Examples | |
# | |
# When the object belonging to the current scope has a nested attribute | |
# writer for a certain attribute, fields_for will yield a new scope | |
# for that attribute. This allows you to create forms that set or change | |
# the attributes of a parent object and its associations in one go. | |
# | |
# Nested attribute writers are normal setter methods named after an | |
# association. The most common way of defining these writers is either | |
# with +accepts_nested_attributes_for+ in a model definition or by | |
# defining a method with the proper name. For example: the attribute | |
# writer for the association <tt>:address</tt> is called | |
# <tt>address_attributes=</tt>. | |
# | |
# Whether a one-to-one or one-to-many style form builder will be yielded | |
# depends on whether the normal reader method returns a _single_ object | |
# or an _array_ of objects. | |
# | |
# ==== One-to-one | |
# | |
# Consider a Person class which returns a _single_ Address from the | |
# <tt>address</tt> reader method and responds to the | |
# <tt>address_attributes=</tt> writer method: | |
# | |
# class Person | |
# def address | |
# @address | |
# end | |
# | |
# def address_attributes=(attributes) | |
# # Process the attributes hash | |
# end | |
# end | |
# | |
# This model can now be used with a nested fields_for, like so: | |
# | |
# <%= form_for @person do |person_form| %> | |
# ... | |
# <%= person_form.fields_for :address do |address_fields| %> | |
# Street : <%= address_fields.text_field :street %> | |
# Zip code: <%= address_fields.text_field :zip_code %> | |
# <% end %> | |
# ... | |
# <% end %> | |
# | |
# When address is already an association on a Person you can use | |
# +accepts_nested_attributes_for+ to define the writer method for you: | |
# | |
# class Person < ActiveRecord::Base | |
# has_one :address | |
# accepts_nested_attributes_for :address | |
# end | |
# | |
# If you want to destroy the associated model through the form, you have | |
# to enable it first using the <tt>:allow_destroy</tt> option for | |
# +accepts_nested_attributes_for+: | |
# | |
# class Person < ActiveRecord::Base | |
# has_one :address | |
# accepts_nested_attributes_for :address, allow_destroy: true | |
# end | |
# | |
# Now, when you use a form element with the <tt>_destroy</tt> parameter, | |
# with a value that evaluates to +true+, you will destroy the associated | |
# model (e.g. 1, '1', true, or 'true'): | |
# | |
# <%= form_for @person do |person_form| %> | |
# ... | |
# <%= person_form.fields_for :address do |address_fields| %> | |
# ... | |
# Delete: <%= address_fields.check_box :_destroy %> | |
# <% end %> | |
# ... | |
# <% end %> | |
# | |
# ==== One-to-many | |
# | |
# Consider a Person class which returns an _array_ of Project instances | |
# from the <tt>projects</tt> reader method and responds to the | |
# <tt>projects_attributes=</tt> writer method: | |
# | |
# class Person | |
# def projects | |
# [@project1, @project2] | |
# end | |
# | |
# def projects_attributes=(attributes) | |
# # Process the attributes hash | |
# end | |
# end | |
# | |
# Note that the <tt>projects_attributes=</tt> writer method is in fact | |
# required for fields_for to correctly identify <tt>:projects</tt> as a | |
# collection, and the correct indices to be set in the form markup. | |
# | |
# When projects is already an association on Person you can use | |
# +accepts_nested_attributes_for+ to define the writer method for you: | |
# | |
# class Person < ActiveRecord::Base | |
# has_many :projects | |
# accepts_nested_attributes_for :projects | |
# end | |
# | |
# This model can now be used with a nested fields_for. The block given to | |
# the nested fields_for call will be repeated for each instance in the | |
# collection: | |
# | |
# <%= form_for @person do |person_form| %> | |
# ... | |
# <%= person_form.fields_for :projects do |project_fields| %> | |
# <% if project_fields.object.active? %> | |
# Name: <%= project_fields.text_field :name %> | |
# <% end %> | |
# <% end %> | |
# ... | |
# <% end %> | |
# | |
# It's also possible to specify the instance to be used: | |
# | |
# <%= form_for @person do |person_form| %> | |
# ... | |
# <% @person.projects.each do |project| %> | |
# <% if project.active? %> | |
# <%= person_form.fields_for :projects, project do |project_fields| %> | |
# Name: <%= project_fields.text_field :name %> | |
# <% end %> | |
# <% end %> | |
# <% end %> | |
# ... | |
# <% end %> | |
# | |
# Or a collection to be used: | |
# | |
# <%= form_for @person do |person_form| %> | |
# ... | |
# <%= person_form.fields_for :projects, @active_projects do |project_fields| %> | |
# Name: <%= project_fields.text_field :name %> | |
# <% end %> | |
# ... | |
# <% end %> | |
# | |
# If you want to destroy any of the associated models through the | |
# form, you have to enable it first using the <tt>:allow_destroy</tt> | |
# option for +accepts_nested_attributes_for+: | |
# | |
# class Person < ActiveRecord::Base | |
# has_many :projects | |
# accepts_nested_attributes_for :projects, allow_destroy: true | |
# end | |
# | |
# This will allow you to specify which models to destroy in the | |
# attributes hash by adding a form element for the <tt>_destroy</tt> | |
# parameter with a value that evaluates to +true+ | |
# (e.g. 1, '1', true, or 'true'): | |
# | |
# <%= form_for @person do |person_form| %> | |
# ... | |
# <%= person_form.fields_for :projects do |project_fields| %> | |
# Delete: <%= project_fields.check_box :_destroy %> | |
# <% end %> | |
# ... | |
# <% end %> | |
# | |
# When a collection is used you might want to know the index of each | |
# object into the array. For this purpose, the <tt>index</tt> method | |
# is available in the FormBuilder object. | |
# | |
# <%= form_for @person do |person_form| %> | |
# ... | |
# <%= person_form.fields_for :projects do |project_fields| %> | |
# Project #<%= project_fields.index %> | |
# ... | |
# <% end %> | |
# ... | |
# <% end %> | |
# | |
# Note that fields_for will automatically generate a hidden field | |
# to store the ID of the record if it responds to <tt>persisted?</tt>. | |
# There are circumstances where this hidden field is not needed and you | |
# can pass <tt>include_id: false</tt> to prevent fields_for from | |
# rendering it automatically. | |
def fields_for(record_name, record_object = nil, options = {}, &block) | |
options = { model: record_object, allow_method_names_outside_object: false, skip_default_ids: false }.merge!(options) | |
fields(record_name, **options, &block) | |
end | |
# Scopes input fields with either an explicit scope or model. | |
# Like +form_with+ does with <tt>:scope</tt> or <tt>:model</tt>, | |
# except it doesn't output the form tags. | |
# | |
# # Using a scope prefixes the input field names: | |
# <%= fields :comment do |fields| %> | |
# <%= fields.text_field :body %> | |
# <% end %> | |
# # => <input type="text" name="comment[body]"> | |
# | |
# # Using a model infers the scope and assigns field values: | |
# <%= fields model: Comment.new(body: "full bodied") do |fields| %> | |
# <%= fields.text_field :body %> | |
# <% end %> | |
# # => <input type="text" name="comment[body]" value="full bodied"> | |
# | |
# # Using +fields+ with +form_with+: | |
# <%= form_with model: @post do |form| %> | |
# <%= form.text_field :title %> | |
# | |
# <%= form.fields :comment do |fields| %> | |
# <%= fields.text_field :body %> | |
# <% end %> | |
# <% end %> | |
# | |
# Much like +form_with+ a FormBuilder instance associated with the scope | |
# or model is yielded, so any generated field names are prefixed with | |
# either the passed scope or the scope inferred from the <tt>:model</tt>. | |
# | |
# === Mixing with other form helpers | |
# | |
# While +form_with+ uses a FormBuilder object it's possible to mix and | |
# match the stand-alone FormHelper methods and methods | |
# from FormTagHelper: | |
# | |
# <%= fields model: @comment do |fields| %> | |
# <%= fields.text_field :body %> | |
# | |
# <%= text_area :commenter, :biography %> | |
# <%= check_box_tag "comment[all_caps]", "1", @comment.commenter.hulk_mode? %> | |
# <% end %> | |
# | |
# Same goes for the methods in FormOptionsHelper and DateHelper designed | |
# to work with an object as a base, like | |
# FormOptionsHelper#collection_select and DateHelper#datetime_select. | |
def fields(scope = nil, model: nil, **options, &block) | |
options = { allow_method_names_outside_object: true, skip_default_ids: !form_with_generates_ids }.merge!(options) | |
if model | |
model = _object_for_form_builder(model) | |
scope ||= model_name_from_record_or_class(model).param_key | |
end | |
builder = instantiate_builder(scope, model, options) | |
capture(builder, &block) | |
end | |
# Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object | |
# assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation | |
# is found in the current I18n locale (through <tt>helpers.label.<modelname>.<attribute></tt>) or you specify it explicitly. | |
# Additional options on the label tag can be passed as a hash with +options+. These options will be tagged | |
# onto the HTML as an HTML element attribute as in the example shown, except for the <tt>:value</tt> option, which is designed to | |
# target labels for radio_button tags (where the value is used in the ID of the input tag). | |
# | |
# ==== Examples | |
# label(:post, :title) | |
# # => <label for="post_title">Title</label> | |
# | |
# You can localize your labels based on model and attribute names. | |
# For example you can define the following in your locale (e.g. en.yml) | |
# | |
# helpers: | |
# label: | |
# post: | |
# body: "Write your entire text here" | |
# | |
# Which then will result in | |
# | |
# label(:post, :body) | |
# # => <label for="post_body">Write your entire text here</label> | |
# | |
# Localization can also be based purely on the translation of the attribute-name | |
# (if you are using ActiveRecord): | |
# | |
# activerecord: | |
# attributes: | |
# post: | |
# cost: "Total cost" | |
# | |
# label(:post, :cost) | |
# # => <label for="post_cost">Total cost</label> | |
# | |
# label(:post, :title, "A short title") | |
# # => <label for="post_title">A short title</label> | |
# | |
# label(:post, :title, "A short title", class: "title_label") | |
# # => <label for="post_title" class="title_label">A short title</label> | |
# | |
# label(:post, :privacy, "Public Post", value: "public") | |
# # => <label for="post_privacy_public">Public Post</label> | |
# | |
# label(:post, :cost) do |translation| | |
# content_tag(:span, translation, class: "cost_label") | |
# end | |
# # => <label for="post_cost"><span class="cost_label">Total cost</span></label> | |
# | |
# label(:post, :cost) do |builder| | |
# content_tag(:span, builder.translation, class: "cost_label") | |
# end | |
# # => <label for="post_cost"><span class="cost_label">Total cost</span></label> | |
# | |
# label(:post, :terms) do | |
# raw('Accept <a href="/terms">Terms</a>.') | |
# end | |
# # => <label for="post_terms">Accept <a href="/terms">Terms</a>.</label> | |
def label(object_name, method, content_or_options = nil, options = nil, &block) | |
Tags::Label.new(object_name, method, self, content_or_options, options).render(&block) | |
end | |
# Returns an input tag of the "text" type tailored for accessing a specified attribute (identified by +method+) on an object | |
# assigned to the template (identified by +object+). Additional options on the input tag can be passed as a | |
# hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example | |
# shown. | |
# | |
# ==== Examples | |
# text_field(:post, :title, size: 20) | |
# # => <input type="text" id="post_title" name="post[title]" size="20" value="#{@post.title}" /> | |
# | |
# text_field(:post, :title, class: "create_input") | |
# # => <input type="text" id="post_title" name="post[title]" value="#{@post.title}" class="create_input" /> | |
# | |
# text_field(:post, :title, maxlength: 30, class: "title_input") | |
# # => <input type="text" id="post_title" name="post[title]" maxlength="30" size="30" value="#{@post.title}" class="title_input" /> | |
# | |
# text_field(:session, :user, onchange: "if ($('#session_user').val() === 'admin') { alert('Your login cannot be admin!'); }") | |
# # => <input type="text" id="session_user" name="session[user]" value="#{@session.user}" onchange="if ($('#session_user').val() === 'admin') { alert('Your login cannot be admin!'); }"/> | |
# | |
# text_field(:snippet, :code, size: 20, class: 'code_input') | |
# # => <input type="text" id="snippet_code" name="snippet[code]" size="20" value="#{@snippet.code}" class="code_input" /> | |
def text_field(object_name, method, options = {}) | |
Tags::TextField.new(object_name, method, self, options).render | |
end | |
# Returns an input tag of the "password" type tailored for accessing a specified attribute (identified by +method+) on an object | |
# assigned to the template (identified by +object+). Additional options on the input tag can be passed as a | |
# hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example | |
# shown. For security reasons this field is blank by default; pass in a value via +options+ if this is not desired. | |
# | |
# ==== Examples | |
# password_field(:login, :pass, size: 20) | |
# # => <input type="password" id="login_pass" name="login[pass]" size="20" /> | |
# | |
# password_field(:account, :secret, class: "form_input", value: @account.secret) | |
# # => <input type="password" id="account_secret" name="account[secret]" value="#{@account.secret}" class="form_input" /> | |
# | |
# password_field(:user, :password, onchange: "if ($('#user_password').val().length > 30) { alert('Your password needs to be shorter!'); }") | |
# # => <input type="password" id="user_password" name="user[password]" onchange="if ($('#user_password').val().length > 30) { alert('Your password needs to be shorter!'); }"/> | |
# | |
# password_field(:account, :pin, size: 20, class: 'form_input') | |
# # => <input type="password" id="account_pin" name="account[pin]" size="20" class="form_input" /> | |
def password_field(object_name, method, options = {}) | |
Tags::PasswordField.new(object_name, method, self, options).render | |
end | |
# Returns a hidden input tag tailored for accessing a specified attribute (identified by +method+) on an object | |
# assigned to the template (identified by +object+). Additional options on the input tag can be passed as a | |
# hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example | |
# shown. | |
# | |
# ==== Examples | |
# hidden_field(:signup, :pass_confirm) | |
# # => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="#{@signup.pass_confirm}" /> | |
# | |
# hidden_field(:post, :tag_list) | |
# # => <input type="hidden" id="post_tag_list" name="post[tag_list]" value="#{@post.tag_list}" /> | |
# | |
# hidden_field(:user, :token) | |
# # => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" /> | |
def hidden_field(object_name, method, options = {}) | |
Tags::HiddenField.new(object_name, method, self, options).render | |
end | |
# Returns a file upload input tag tailored for accessing a specified attribute (identified by +method+) on an object | |
# assigned to the template (identified by +object+). Additional options on the input tag can be passed as a | |
# hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example | |
# shown. | |
# | |
# Using this method inside a +form_for+ block will set the enclosing form's encoding to <tt>multipart/form-data</tt>. | |
# | |
# ==== Options | |
# * Creates standard HTML attributes for the tag. | |
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input. | |
# * <tt>:multiple</tt> - If set to true, *in most updated browsers* the user will be allowed to select multiple files. | |
# * <tt>:include_hidden</tt> - When <tt>multiple: true</tt> and <tt>include_hidden: true</tt>, the field will be prefixed with an <tt><input type="hidden"></tt> field with an empty value to support submitting an empty collection of files. | |
# * <tt>:accept</tt> - If set to one or multiple mime-types, the user will be suggested a filter when choosing a file. You still need to set up model validations. | |
# | |
# ==== Examples | |
# file_field(:user, :avatar) | |
# # => <input type="file" id="user_avatar" name="user[avatar]" /> | |
# | |
# file_field(:post, :image, multiple: true) | |
# # => <input type="file" id="post_image" name="post[image][]" multiple="multiple" /> | |
# | |
# file_field(:post, :attached, accept: 'text/html') | |
# # => <input accept="text/html" type="file" id="post_attached" name="post[attached]" /> | |
# | |
# file_field(:post, :image, accept: 'image/png,image/gif,image/jpeg') | |
# # => <input type="file" id="post_image" name="post[image]" accept="image/png,image/gif,image/jpeg" /> | |
# | |
# file_field(:attachment, :file, class: 'file_input') | |
# # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" /> | |
def file_field(object_name, method, options = {}) | |
options = { include_hidden: multiple_file_field_include_hidden }.merge!(options) | |
Tags::FileField.new(object_name, method, self, convert_direct_upload_option_to_url(options.dup)).render | |
end | |
# Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+) | |
# on an object assigned to the template (identified by +object+). Additional options on the input tag can be passed as a | |
# hash with +options+. | |
# | |
# ==== Examples | |
# text_area(:post, :body, cols: 20, rows: 40) | |
# # => <textarea cols="20" rows="40" id="post_body" name="post[body]"> | |
# # #{@post.body} | |
# # </textarea> | |
# | |
# text_area(:comment, :text, size: "20x30") | |
# # => <textarea cols="20" rows="30" id="comment_text" name="comment[text]"> | |
# # #{@comment.text} | |
# # </textarea> | |
# | |
# text_area(:application, :notes, cols: 40, rows: 15, class: 'app_input') | |
# # => <textarea cols="40" rows="15" id="application_notes" name="application[notes]" class="app_input"> | |
# # #{@application.notes} | |
# # </textarea> | |
# | |
# text_area(:entry, :body, size: "20x20", disabled: 'disabled') | |
# # => <textarea cols="20" rows="20" id="entry_body" name="entry[body]" disabled="disabled"> | |
# # #{@entry.body} | |
# # </textarea> | |
def text_area(object_name, method, options = {}) | |
Tags::TextArea.new(object_name, method, self, options).render | |
end | |
# Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object | |
# assigned to the template (identified by +object+). This object must be an instance object (@object) and not a local object. | |
# It's intended that +method+ returns an integer and if that integer is above zero, then the checkbox is checked. | |
# Additional options on the input tag can be passed as a hash with +options+. The +checked_value+ defaults to 1 | |
# while the default +unchecked_value+ is set to 0 which is convenient for boolean values. | |
# | |
# ==== Options | |
# | |
# * Any standard HTML attributes for the tag can be passed in, for example +:class+. | |
# * <tt>:checked</tt> - +true+ or +false+ forces the state of the checkbox to be checked or not. | |
# * <tt>:include_hidden</tt> - If set to false, the auxiliary hidden field described below will not be generated. | |
# | |
# ==== Gotcha | |
# | |
# The HTML specification says unchecked check boxes are not successful, and | |
# thus web browsers do not send them. Unfortunately this introduces a gotcha: | |
# if an +Invoice+ model has a +paid+ flag, and in the form that edits a paid | |
# invoice the user unchecks its check box, no +paid+ parameter is sent. So, | |
# any mass-assignment idiom like | |
# | |
# @invoice.update(params[:invoice]) | |
# | |
# wouldn't update the flag. | |
# | |
# To prevent this the helper generates an auxiliary hidden field before | |
# every check box. The hidden field has the same name and its | |
# attributes mimic an unchecked check box. | |
# | |
# This way, the client either sends only the hidden field (representing | |
# the check box is unchecked), or both fields. Since the HTML specification | |
# says key/value pairs have to be sent in the same order they appear in the | |
# form, and parameters extraction gets the last occurrence of any repeated | |
# key in the query string, that works for ordinary forms. | |
# | |
# Unfortunately that workaround does not work when the check box goes | |
# within an array-like parameter, as in | |
# | |
# <%= fields_for "project[invoice_attributes][]", invoice, index: nil do |form| %> | |
# <%= form.check_box :paid %> | |
# ... | |
# <% end %> | |
# | |
# because parameter name repetition is precisely what Rails seeks to distinguish | |
# the elements of the array. For each item with a checked check box you | |
# get an extra ghost item with only that attribute, assigned to "0". | |
# | |
# In that case it is preferable to either use +check_box_tag+ or to use | |
# hashes instead of arrays. | |
# | |
# ==== Examples | |
# | |
# # Let's say that @post.validated? is 1: | |
# check_box("post", "validated") | |
# # => <input name="post[validated]" type="hidden" value="0" /> | |
# # <input checked="checked" type="checkbox" id="post_validated" name="post[validated]" value="1" /> | |
# | |
# # Let's say that @puppy.gooddog is "no": | |
# check_box("puppy", "gooddog", {}, "yes", "no") | |
# # => <input name="puppy[gooddog]" type="hidden" value="no" /> | |
# # <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" /> | |
# | |
# check_box("eula", "accepted", { class: 'eula_check' }, "yes", "no") | |
# # => <input name="eula[accepted]" type="hidden" value="no" /> | |
# # <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" /> | |
def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0") | |
Tags::CheckBox.new(object_name, method, self, checked_value, unchecked_value, options).render | |
end | |
# Returns a radio button tag for accessing a specified attribute (identified by +method+) on an object | |
# assigned to the template (identified by +object+). If the current value of +method+ is +tag_value+ the | |
# radio button will be checked. | |
# | |
# To force the radio button to be checked pass <tt>checked: true</tt> in the | |
# +options+ hash. You may pass HTML options there as well. | |
# | |
# # Let's say that @post.category returns "rails": | |
# radio_button("post", "category", "rails") | |
# radio_button("post", "category", "java") | |
# # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" /> | |
# # <input type="radio" id="post_category_java" name="post[category]" value="java" /> | |
# | |
# # Let's say that @user.receive_newsletter returns "no": | |
# radio_button("user", "receive_newsletter", "yes") | |
# radio_button("user", "receive_newsletter", "no") | |
# # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" /> | |
# # <input type="radio" id="user_receive_newsletter_no" name="user[receive_newsletter]" value="no" checked="checked" /> | |
def radio_button(object_name, method, tag_value, options = {}) | |
Tags::RadioButton.new(object_name, method, self, tag_value, options).render | |
end | |
# Returns a text_field of type "color". | |
# | |
# color_field("car", "color") | |
# # => <input id="car_color" name="car[color]" type="color" value="#000000" /> | |
def color_field(object_name, method, options = {}) | |
Tags::ColorField.new(object_name, method, self, options).render | |
end | |
# Returns an input of type "search" for accessing a specified attribute (identified by +method+) on an object | |
# assigned to the template (identified by +object_name+). Inputs of type "search" may be styled differently by | |
# some browsers. | |
# | |
# search_field(:user, :name) | |
# # => <input id="user_name" name="user[name]" type="search" /> | |
# search_field(:user, :name, autosave: false) | |
# # => <input autosave="false" id="user_name" name="user[name]" type="search" /> | |
# search_field(:user, :name, results: 3) | |
# # => <input id="user_name" name="user[name]" results="3" type="search" /> | |
# # Assume request.host returns "www.example.com" | |
# search_field(:user, :name, autosave: true) | |
# # => <input autosave="com.example.www" id="user_name" name="user[name]" results="10" type="search" /> | |
# search_field(:user, :name, onsearch: true) | |
# # => <input id="user_name" incremental="true" name="user[name]" onsearch="true" type="search" /> | |
# search_field(:user, :name, autosave: false, onsearch: true) | |
# # => <input autosave="false" id="user_name" incremental="true" name="user[name]" onsearch="true" type="search" /> | |
# search_field(:user, :name, autosave: true, onsearch: true) | |
# # => <input autosave="com.example.www" id="user_name" incremental="true" name="user[name]" onsearch="true" results="10" type="search" /> | |
def search_field(object_name, method, options = {}) | |
Tags::SearchField.new(object_name, method, self, options).render | |
end | |
# Returns a text_field of type "tel". | |
# | |
# telephone_field("user", "phone") | |
# # => <input id="user_phone" name="user[phone]" type="tel" /> | |
# | |
def telephone_field(object_name, method, options = {}) | |
Tags::TelField.new(object_name, method, self, options).render | |
end | |
# aliases telephone_field | |
alias phone_field telephone_field | |
# Returns a text_field of type "date". | |
# | |
# date_field("user", "born_on") | |
# # => <input id="user_born_on" name="user[born_on]" type="date" /> | |
# | |
# The default value is generated by trying to call +strftime+ with "%Y-%m-%d" | |
# on the object's value, which makes it behave as expected for instances | |
# of DateTime and ActiveSupport::TimeWithZone. You can still override that | |
# by passing the "value" option explicitly, e.g. | |
# | |
# @user.born_on = Date.new(1984, 1, 27) | |
# date_field("user", "born_on", value: "1984-05-12") | |
# # => <input id="user_born_on" name="user[born_on]" type="date" value="1984-05-12" /> | |
# | |
# You can create values for the "min" and "max" attributes by passing | |
# instances of Date or Time to the options hash. | |
# | |
# date_field("user", "born_on", min: Date.today) | |
# # => <input id="user_born_on" name="user[born_on]" type="date" min="2014-05-20" /> | |
# | |
# Alternatively, you can pass a String formatted as an ISO8601 date as the | |
# values for "min" and "max." | |
# | |
# date_field("user", "born_on", min: "2014-05-20") | |
# # => <input id="user_born_on" name="user[born_on]" type="date" min="2014-05-20" /> | |
# | |
def date_field(object_name, method, options = {}) | |
Tags::DateField.new(object_name, method, self, options).render | |
end | |
# Returns a text_field of type "time". | |
# | |
# The default value is generated by trying to call +strftime+ with "%T.%L" | |
# on the object's value. If you pass <tt>include_seconds: false</tt>, it will be | |
# formatted by trying to call +strftime+ with "%H:%M" on the object's value. | |
# It is also possible to override this by passing the "value" option. | |
# | |
# === Options | |
# * Accepts same options as time_field_tag | |
# | |
# === Example | |
# time_field("task", "started_at") | |
# # => <input id="task_started_at" name="task[started_at]" type="time" /> | |
# | |
# You can create values for the "min" and "max" attributes by passing | |
# instances of Date or Time to the options hash. | |
# | |
# time_field("task", "started_at", min: Time.now) | |
# # => <input id="task_started_at" name="task[started_at]" type="time" min="01:00:00.000" /> | |
# | |
# Alternatively, you can pass a String formatted as an ISO8601 time as the | |
# values for "min" and "max." | |
# | |
# time_field("task", "started_at", min: "01:00:00") | |
# # => <input id="task_started_at" name="task[started_at]" type="time" min="01:00:00.000" /> | |
# | |
# By default, provided times will be formatted including seconds. You can render just the hour | |
# and minute by passing <tt>include_seconds: false</tt>. Some browsers will render a simpler UI | |
# if you exclude seconds in the timestamp format. | |
# | |
# time_field("task", "started_at", value: Time.now, include_seconds: false) | |
# # => <input id="task_started_at" name="task[started_at]" type="time" value="01:00" /> | |
def time_field(object_name, method, options = {}) | |
Tags::TimeField.new(object_name, method, self, options).render | |
end | |
# Returns a text_field of type "datetime-local". | |
# | |
# datetime_field("user", "born_on") | |
# # => <input id="user_born_on" name="user[born_on]" type="datetime-local" /> | |
# | |
# The default value is generated by trying to call +strftime+ with "%Y-%m-%dT%T" | |
# on the object's value, which makes it behave as expected for instances | |
# of DateTime and ActiveSupport::TimeWithZone. | |
# | |
# @user.born_on = Date.new(1984, 1, 12) | |
# datetime_field("user", "born_on") | |
# # => <input id="user_born_on" name="user[born_on]" type="datetime-local" value="1984-01-12T00:00:00" /> | |
# | |
# You can create values for the "min" and "max" attributes by passing | |
# instances of Date or Time to the options hash. | |
# | |
# datetime_field("user", "born_on", min: Date.today) | |
# # => <input id="user_born_on" name="user[born_on]" type="datetime-local" min="2014-05-20T00:00:00.000" /> | |
# | |
# Alternatively, you can pass a String formatted as an ISO8601 datetime as | |
# the values for "min" and "max." | |
# | |
# datetime_field("user", "born_on", min: "2014-05-20T00:00:00") | |
# # => <input id="user_born_on" name="user[born_on]" type="datetime-local" min="2014-05-20T00:00:00.000" /> | |
# | |
def datetime_field(object_name, method, options = {}) | |
Tags::DatetimeLocalField.new(object_name, method, self, options).render | |
end | |
alias datetime_local_field datetime_field | |
# Returns a text_field of type "month". | |
# | |
# month_field("user", "born_on") | |
# # => <input id="user_born_on" name="user[born_on]" type="month" /> | |
# | |
# The default value is generated by trying to call +strftime+ with "%Y-%m" | |
# on the object's value, which makes it behave as expected for instances | |
# of DateTime and ActiveSupport::TimeWithZone. | |
# | |
# @user.born_on = Date.new(1984, 1, 27) | |
# month_field("user", "born_on") | |
# # => <input id="user_born_on" name="user[born_on]" type="date" value="1984-01" /> | |
# | |
def month_field(object_name, method, options = {}) | |
Tags::MonthField.new(object_name, method, self, options).render | |
end | |
# Returns a text_field of type "week". | |
# | |
# week_field("user", "born_on") | |
# # => <input id="user_born_on" name="user[born_on]" type="week" /> | |
# | |
# The default value is generated by trying to call +strftime+ with "%Y-W%W" | |
# on the object's value, which makes it behave as expected for instances | |
# of DateTime and ActiveSupport::TimeWithZone. | |
# | |
# @user.born_on = Date.new(1984, 5, 12) | |
# week_field("user", "born_on") | |
# # => <input id="user_born_on" name="user[born_on]" type="date" value="1984-W19" /> | |
# | |
def week_field(object_name, method, options = {}) | |
Tags::WeekField.new(object_name, method, self, options).render | |
end | |
# Returns a text_field of type "url". | |
# | |
# url_field("user", "homepage") | |
# # => <input id="user_homepage" name="user[homepage]" type="url" /> | |
# | |
def url_field(object_name, method, options = {}) | |
Tags::UrlField.new(object_name, method, self, options).render | |
end | |
# Returns a text_field of type "email". | |
# | |
# email_field("user", "address") | |
# # => <input id="user_address" name="user[address]" type="email" /> | |
# | |
def email_field(object_name, method, options = {}) | |
Tags::EmailField.new(object_name, method, self, options).render | |
end | |
# Returns an input tag of type "number". | |
# | |
# ==== Options | |
# * Accepts same options as number_field_tag | |
def number_field(object_name, method, options = {}) | |
Tags::NumberField.new(object_name, method, self, options).render | |
end | |
# Returns an input tag of type "range". | |
# | |
# ==== Options | |
# * Accepts same options as range_field_tag | |
def range_field(object_name, method, options = {}) | |
Tags::RangeField.new(object_name, method, self, options).render | |
end | |
def _object_for_form_builder(object) # :nodoc: | |
object.is_a?(Array) ? object.last : object | |
end | |
private | |
def html_options_for_form_with(url_for_options = nil, model = nil, html: {}, local: !form_with_generates_remote_forms, | |
skip_enforcing_utf8: nil, **options) | |
html_options = options.slice(:id, :class, :multipart, :method, :data, :authenticity_token).merge!(html) | |
html_options[:remote] = html.delete(:remote) || !local | |
html_options[:method] ||= :patch if model.respond_to?(:persisted?) && model.persisted? | |
if skip_enforcing_utf8.nil? | |
if options.key?(:enforce_utf8) | |
html_options[:enforce_utf8] = options[:enforce_utf8] | |
end | |
else | |
html_options[:enforce_utf8] = !skip_enforcing_utf8 | |
end | |
html_options_for_form(url_for_options.nil? ? {} : url_for_options, html_options) | |
end | |
def instantiate_builder(record_name, record_object, options) | |
case record_name | |
when String, Symbol | |
object = record_object | |
object_name = record_name | |
else | |
object = record_name | |
object_name = model_name_from_record_or_class(object).param_key if object | |
end | |
builder = options[:builder] || default_form_builder_class | |
builder.new(object_name, object, self, options) | |
end | |
def default_form_builder_class | |
builder = default_form_builder || ActionView::Base.default_form_builder | |
builder.respond_to?(:constantize) ? builder.constantize : builder | |
end | |
end | |
# A +FormBuilder+ object is associated with a particular model object and | |
# allows you to generate fields associated with the model object. The | |
# +FormBuilder+ object is yielded when using +form_for+ or +fields_for+. | |
# For example: | |
# | |
# <%= form_for @person do |person_form| %> | |
# Name: <%= person_form.text_field :name %> | |
# Admin: <%= person_form.check_box :admin %> | |
# <% end %> | |
# | |
# In the above block, a +FormBuilder+ object is yielded as the | |
# +person_form+ variable. This allows you to generate the +text_field+ | |
# and +check_box+ fields by specifying their eponymous methods, which | |
# modify the underlying template and associates the <tt>@person</tt> model object | |
# with the form. | |
# | |
# The +FormBuilder+ object can be thought of as serving as a proxy for the | |
# methods in the +FormHelper+ module. This class, however, allows you to | |
# call methods with the model object you are building the form for. | |
# | |
# You can create your own custom FormBuilder templates by subclassing this | |
# class. For example: | |
# | |
# class MyFormBuilder < ActionView::Helpers::FormBuilder | |
# def div_radio_button(method, tag_value, options = {}) | |
# @template.content_tag(:div, | |
# @template.radio_button( | |
# @object_name, method, tag_value, objectify_options(options) | |
# ) | |
# ) | |
# end | |
# end | |
# | |
# The above code creates a new method +div_radio_button+ which wraps a div | |
# around the new radio button. Note that when options are passed in, you | |
# must call +objectify_options+ in order for the model object to get | |
# correctly passed to the method. If +objectify_options+ is not called, | |
# then the newly created helper will not be linked back to the model. | |
# | |
# The +div_radio_button+ code from above can now be used as follows: | |
# | |
# <%= form_for @person, :builder => MyFormBuilder do |f| %> | |
# I am a child: <%= f.div_radio_button(:admin, "child") %> | |
# I am an adult: <%= f.div_radio_button(:admin, "adult") %> | |
# <% end -%> | |
# | |
# The standard set of helper methods for form building are located in the | |
# +field_helpers+ class attribute. | |
class FormBuilder | |
include ModelNaming | |
# The methods which wrap a form helper call. | |
class_attribute :field_helpers, default: [ | |
:fields_for, :fields, :label, :text_field, :password_field, | |
:hidden_field, :file_field, :text_area, :check_box, | |
:radio_button, :color_field, :search_field, | |
:telephone_field, :phone_field, :date_field, | |
:time_field, :datetime_field, :datetime_local_field, | |
:month_field, :week_field, :url_field, :email_field, | |
:number_field, :range_field | |
] | |
attr_accessor :object_name, :object, :options | |
attr_reader :multipart, :index | |
alias :multipart? :multipart | |
def multipart=(multipart) | |
@multipart = multipart | |
if parent_builder = @options[:parent_builder] | |
parent_builder.multipart = multipart | |
end | |
end | |
def self._to_partial_path | |
@_to_partial_path ||= name.demodulize.underscore.sub!(/_builder$/, "") | |
end | |
def to_partial_path | |
self.class._to_partial_path | |
end | |
def to_model | |
self | |
end | |
def initialize(object_name, object, template, options) | |
@nested_child_index = {} | |
@object_name, @object, @template, @options = object_name, object, template, options | |
@default_options = @options ? @options.slice(:index, :namespace, :skip_default_ids, :allow_method_names_outside_object) : {} | |
@default_html_options = @default_options.except(:skip_default_ids, :allow_method_names_outside_object) | |
convert_to_legacy_options(@options) | |
if @object_name&.end_with?("[]") | |
if (object ||= @template.instance_variable_get("@#{@object_name[0..-3]}")) && object.respond_to?(:to_param) | |
@auto_index = object.to_param | |
else | |
raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}" | |
end | |
end | |
@multipart = nil | |
@index = options[:index] || options[:child_index] | |
end | |
# Generate an HTML <tt>id</tt> attribute value. | |
# | |
# return the <tt><form></tt> element's <tt>id</tt> attribute. | |
# | |
# <%= form_for @post do |f| %> | |
# <%# ... %> | |
# | |
# <% content_for :sticky_footer do %> | |
# <%= form.button(form: f.id) %> | |
# <% end %> | |
# <% end %> | |
# | |
# In the example above, the <tt>:sticky_footer</tt> content area will | |
# exist outside of the <tt><form></tt> element. By declaring the | |
# <tt>form</tt> HTML attribute, we hint to the browser that the generated | |
# <tt><button></tt> element should be treated as the <tt><form></tt> | |
# element's submit button, regardless of where it exists in the DOM. | |
def id | |
options.dig(:html, :id) | |
end | |
# Generate an HTML <tt>id</tt> attribute value for the given field | |
# | |
# Return the value generated by the <tt>FormBuilder</tt> for the given | |
# attribute name. | |
# | |
# <%= form_for @post do |f| %> | |
# <%= f.label :title %> | |
# <%= f.text_field :title, aria: { describedby: f.field_id(:title, :error) } %> | |
# <%= tag.span("is blank", id: f.field_id(:title, :error) %> | |
# <% end %> | |
# | |
# In the example above, the <tt><input type="text"></tt> element built by | |
# the call to <tt>FormBuilder#text_field</tt> declares an | |
# <tt>aria-describedby</tt> attribute referencing the <tt><span></tt> | |
# element, sharing a common <tt>id</tt> root (<tt>post_title</tt>, in this | |
# case). | |
def field_id(method, *suffixes, namespace: @options[:namespace], index: @index) | |
@template.field_id(@object_name, method, *suffixes, namespace: namespace, index: index) | |
end | |
# Generate an HTML <tt>name</tt> attribute value for the given name and | |
# field combination | |
# | |
# Return the value generated by the <tt>FormBuilder</tt> for the given | |
# attribute name. | |
# | |
# <%= form_for @post do |f| %> | |
# <%= f.text_field :title, name: f.field_name(:title, :subtitle) %> | |
# <%# => <input type="text" name="post[title][subtitle]"> | |
# <% end %> | |
# | |
# <%= form_for @post do |f| %> | |
# <%= f.field_tag :tag, name: f.field_name(:tag, multiple: true) %> | |
# <%# => <input type="text" name="post[tag][]"> | |
# <% end %> | |
# | |
def field_name(method, *methods, multiple: false, index: @index) | |
object_name = @options.fetch(:as) { @object_name } | |
@template.field_name(object_name, method, *methods, index: index, multiple: multiple) | |
end | |
## | |
# :method: text_field | |
# | |
# :call-seq: text_field(method, options = {}) | |
# | |
# Wraps ActionView::Helpers::FormHelper#text_field for form builders: | |
# | |
# <%= form_with model: @user do |f| %> | |
# <%= f.text_field :name %> | |
# <% end %> | |
# | |
# Please refer to the documentation of the base helper for details. | |
## | |
# :method: password_field | |
# | |
# :call-seq: password_field(method, options = {}) | |
# | |
# Wraps ActionView::Helpers::FormHelper#password_field for form builders: | |
# | |
# <%= form_with model: @user do |f| %> | |
# <%= f.password_field :password %> | |
# <% end %> | |
# | |
# Please refer to the documentation of the base helper for details. | |
## | |
# :method: text_area | |
# | |
# :call-seq: text_area(method, options = {}) | |
# | |
# Wraps ActionView::Helpers::FormHelper#text_area for form builders: | |
# | |
# <%= form_with model: @user do |f| %> | |
# <%= f.text_area :detail %> | |
# <% end %> | |
# | |
# Please refer to the documentation of the base helper for details. | |
## | |
# :method: color_field | |
# | |
# :call-seq: color_field(method, options = {}) | |
# | |
# Wraps ActionView::Helpers::FormHelper#color_field for form builders: | |
# | |
# <%= form_with model: @user do |f| %> | |
# <%= f.color_field :favorite_color %> | |
# <% end %> | |
# | |
# Please refer to the documentation of the base helper for details. | |
## | |
# :method: search_field | |
# | |
# :call-seq: search_field(method, options = {}) | |
# | |
# Wraps ActionView::Helpers::FormHelper#search_field for form builders: | |
# | |
# <%= form_with model: @user do |f| %> | |
# <%= f.search_field :name %> | |
# <% end %> | |
# | |
# Please refer to the documentation of the base helper for details. | |
## | |
# :method: telephone_field | |
# | |
# :call-seq: telephone_field(method, options = {}) | |
# | |
# Wraps ActionView::Helpers::FormHelper#telephone_field for form builders: | |
# | |
# <%= form_with model: @user do |f| %> | |
# <%= f.telephone_field :phone %> | |
# <% end %> | |
# | |
# Please refer to the documentation of the base helper for details. | |
## | |
# :method: phone_field | |
# | |
# :call-seq: phone_field(method, options = {}) | |
# | |
# Wraps ActionView::Helpers::FormHelper#phone_field for form builders: | |
# | |
# <%= form_with model: @user do |f| %> | |
# <%= f.phone_field :phone %> | |
# <% end %> | |
# | |
# Please refer to the documentation of the base helper for details. | |
## | |
# :method: date_field | |
# | |
# :call-seq: date_field(method, options = {}) | |
# | |
# Wraps ActionView::Helpers::FormHelper#date_field for form builders: | |
# | |
# <%= form_with model: @user do |f| %> | |
# <%= f.date_field :born_on %> | |
# <% end %> | |
# | |
# Please refer to the documentation of the base helper for details. | |
## | |
# :method: time_field | |
# | |
# :call-seq: time_field(method, options = {}) | |
# | |
# Wraps ActionView::Helpers::FormHelper#time_field for form builders: | |
# | |
# <%= form_with model: @user do |f| %> | |
# <%= f.time_field :born_at %> | |
# <% end %> | |
# | |
# Please refer to the documentation of the base helper for details. | |
## | |
# :method: datetime_field | |
# | |
# :call-seq: datetime_field(method, options = {}) | |
# | |
# Wraps ActionView::Helpers::FormHelper#datetime_field for form builders: | |
# | |
# <%= form_with model: @user do |f| %> | |
# <%= f.datetime_field :graduation_day %> | |
# <% end %> | |
# | |
# Please refer to the documentation of the base helper for details. | |
## | |
# :method: datetime_local_field | |
# | |
# :call-seq: datetime_local_field(method, options = {}) | |
# | |
# Wraps ActionView::Helpers::FormHelper#datetime_local_field for form builders: | |
# | |
# <%= form_with model: @user do |f| %> | |
# <%= f.datetime_local_field :graduation_day %> | |
# <% end %> | |
# | |
# Please refer to the documentation of the base helper for details. | |
## | |
# :method: month_field | |
# | |
# :call-seq: month_field(method, options = {}) | |
# | |
# Wraps ActionView::Helpers::FormHelper#month_field for form builders: | |
# | |
# <%= form_with model: @user do |f| %> | |
# <%= f.month_field :birthday_month %> | |
# <% end %> | |
# | |
# Please refer to the documentation of the base helper for details. | |
## | |
# :method: week_field | |
# | |
# :call-seq: week_field(method, options = {}) | |
# | |
# Wraps ActionView::Helpers::FormHelper#week_field for form builders: | |
# | |
# <%= form_with model: @user do |f| %> | |
# <%= f.week_field :birthday_week %> | |
# <% end %> | |
# | |
# Please refer to the documentation of the base helper for details. | |
## | |
# :method: url_field | |
# | |
# :call-seq: url_field(method, options = {}) | |
# | |
# Wraps ActionView::Helpers::FormHelper#url_field for form builders: | |
# | |
# <%= form_with model: @user do |f| %> | |
# <%= f.url_field :homepage %> | |
# <% end %> | |
# | |
# Please refer to the documentation of the base helper for details. | |
## | |
# :method: email_field | |
# | |
# :call-seq: email_field(method, options = {}) | |
# | |
# Wraps ActionView::Helpers::FormHelper#email_field for form builders: | |
# | |
# <%= form_with model: @user do |f| %> | |
# <%= f.email_field :address %> | |
# <% end %> | |
# | |
# Please refer to the documentation of the base helper for details. | |
## | |
# :method: number_field | |
# | |
# :call-seq: number_field(method, options = {}) | |
# | |
# Wraps ActionView::Helpers::FormHelper#number_field for form builders: | |
# | |
# <%= form_with model: @user do |f| %> | |
# <%= f.number_field :age %> | |
# <% end %> | |
# | |
# Please refer to the documentation of the base helper for details. | |
## | |
# :method: range_field | |
# | |
# :call-seq: range_field(method, options = {}) | |
# | |
# Wraps ActionView::Helpers::FormHelper#range_field for form builders: | |
# | |
# <%= form_with model: @user do |f| %> | |
# <%= f.range_field :age %> | |
# <% end %> | |
# | |
# Please refer to the documentation of the base helper for details. | |
(field_helpers - [:label, :check_box, :radio_button, :fields_for, :fields, :hidden_field, :file_field]).each do |selector| | |
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 | |
def #{selector}(method, options = {}) # def text_field(method, options = {}) | |
@template.public_send( # @template.public_send( | |
#{selector.inspect}, # :text_field, | |
@object_name, # @object_na |
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)
(Sorry about that, but we can’t show files that are this big right now.)