xss_shield 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/MIT-LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2009 Novell, 2007 Trampoline Systems
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,103 @@
1
+ = XSS Shield
2
+
3
+ This Rails plugin provides automatic cross site scripting
4
+ ({XSS}[http://en.wikipedia.org/wiki/Cross-site_scripting]) protection for your
5
+ views. Once installed, you no longer have to manually and painstakingly sanitize
6
+ all your views with HTML escaping (eg. <tt><%= h(foo) %></tt>). Currently only
7
+ {ERB}[http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/index.html] templates are
8
+ supported.
9
+
10
+ For example with XSS Shield:
11
+ <%= link_to "A & B", "/foo" %>
12
+ will return a +SafeString+:
13
+ <a href="/foo">A &amp; B</a>
14
+ and not a plain, unsafe +String+:
15
+ <a href="/foo">A & B</a>
16
+
17
+ This version has been tested to work with <b><i>Rails 2.1.2</i></b>. Your milage
18
+ may vary.
19
+
20
+ DISCLAIMER: Note that while no effort is spared to ensure that this plugin works as
21
+ advertised, we cannot guarantee that all your views are 100% XSS safe. Use it at
22
+ your own risk, but remember that {bug
23
+ reports}[http://github.com/jamestyj/xss_shield/issues] and patches are welcomed.
24
+
25
+ == How it works
26
+
27
+ It works by subclassing +String+ into +SafeString+. When the ERB engine sees a
28
+ <tt><%= foo %></tt> fragment, it checks if the result of executing +foo+ is a
29
+ +SafeString+. If so, it just uses it. Otherwise the string is HTML escaped
30
+ first.
31
+
32
+ The use of +SafeString+ avoids potential double-escaping. For example, with XSS
33
+ Shield, <tt><%= @foo %></tt> is the same as <tt><%= h(@foo) %></tt>.
34
+
35
+ If your string contains HTML that you don't want to escape (and you trust it),
36
+ just append <tt>.xss_safe</tt>:
37
+ <%= "<b>foobar</b>".xss_safe %>
38
+
39
+ It would be cumbersome to require xss_safe every time you use some helper like
40
+ <tt>render(:partial)</tt> or +link_to+, so some helpers are modified to return
41
+ +SafeString+.
42
+
43
+ If you trust your helpers, you can mark them as XSS safe:
44
+
45
+ module Some::Module
46
+ mark_methods_as_xss_safe :text_field, :check_box
47
+ end
48
+
49
+ You may need to manually tweak your helpers, views and layouts to avoid
50
+ unnecessary escaping.
51
+
52
+ == Other template engines
53
+
54
+ Currently only ERB templates is supported, but support for other templating
55
+ engines should be relatively straightforward. It's mostly a matter of changing
56
+ to_s to to_xss_safe in a few places in their source.
57
+
58
+ Patches that add support for other templating engines (along with supporting
59
+ tests) are welcomed.
60
+
61
+ == Running tests
62
+
63
+ This plugin monkey patches ERB in order to do its magic, so it's a good idea to
64
+ at least run the included tests to verify that things work in your environment.
65
+
66
+ You can run the XSS Shield tests by simply running:
67
+
68
+ rake
69
+
70
+ which should generate output looking like this:
71
+
72
+ (in /xss_shield)
73
+ /usr/bin/ruby -I"lib:lib" "/usr/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake/rake_test_loader.rb" ...
74
+ Loaded suite /usr/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake/rake_test_loader
75
+ Started
76
+ ..........................................................................................
77
+ Finished in 0.163422 seconds.
78
+
79
+ 90 tests, 135 assertions, 0 failures, 0 errors
80
+
81
+ If you place this plugin inside the vendor/plugin directory of your Rails
82
+ application, the test suite will load your application environment by requiring
83
+ RAILS_ROOT/test/test_helper.rb.
84
+
85
+ Of course, you should also verify that your existing application tests still
86
+ pass with XSS Shield enabled.
87
+
88
+ == Bugs and feedback
89
+
90
+ Please report bugs and feature requests
91
+ {here}[http://github.com/jamestyj/xss_shield/issues]. Patches and suggestions
92
+ are welcomed too.
93
+
94
+ == Authors
95
+
96
+ - Updated to support Rails 2.1 and maintained by {James
97
+ Tan}[http://github.com/jamestyj], Novell.
98
+ - {Original code}[http://code.google.com/p/xss-shield/] written by {Tomasz
99
+ Wegrzanowski}[http://code.google.com/u/Tomasz.Wegrzanowski/], Trampoline Systems.
100
+
101
+ == License
102
+
103
+ Copyright (c) 2009 Novell. See MIT-LICENSE in this directory.
data/Rakefile ADDED
@@ -0,0 +1,33 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ desc 'Test the xss-shield plugin.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib'
11
+ t.pattern = 'test/**/*_test.rb'
12
+ t.verbose = true
13
+ end
14
+
15
+ begin
16
+ require 'jeweler'
17
+ Jeweler::Tasks.new do |gemspec|
18
+ gemspec.name = 'xss_shield'
19
+ gemspec.summary = 'Protect your Rails site from XSS attacks.'
20
+ gemspec.description = 'This Rails plugin provides automatic cross site ' +
21
+ 'scripting (XSS) protection for your views. Once installed, you no ' +
22
+ 'longer have to manually and painstakingly sanitize all your views ' +
23
+ 'with HTML escaping.'
24
+ gemspec.email = 'jamestyj@gmail.com'
25
+ gemspec.homepage = 'http://github.com/jamestyj/xss_shield'
26
+ gemspec.authors = [ 'James Tan' ]
27
+ end
28
+ Jeweler::GemcutterTasks.new
29
+ rescue LoadError
30
+ puts 'Jeweler (or a dependency) not available. ' +
31
+ 'Install it with: sudo gem install jeweler.'
32
+ end
33
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
data/init.rb ADDED
@@ -0,0 +1,15 @@
1
+ unless ENV['DISABLE_XSS_SHIELD']
2
+ require 'xss_shield'
3
+ else
4
+ class ::String
5
+ def xss_safe
6
+ self
7
+ end
8
+ end
9
+
10
+ class ::NilClass
11
+ def xss_safe
12
+ self
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,101 @@
1
+ # Create our own ERB compiler to handle <%= %> differently.
2
+ # See /usr/lib/ruby/1.8/erb.erb.
3
+ class XSSProtectedERB < ERB
4
+ class Compiler < ::ERB::Compiler
5
+ def compile(s)
6
+ out = Buffer.new(self)
7
+
8
+ content = ''
9
+ scanner = make_scanner(s)
10
+ scanner.scan do |token|
11
+ if scanner.stag.nil?
12
+ case token
13
+ when PercentLine
14
+ out.push("#{@put_cmd} #{content.dump}") if content.size > 0
15
+ content = ''
16
+ out.push(token.to_s)
17
+ out.cr
18
+ when :cr
19
+ out.cr
20
+ when '<%', '<%=', '<%#'
21
+ scanner.stag = token
22
+ out.push("#{@put_cmd} #{content.dump}") if content.size > 0
23
+ content = ''
24
+ when "\n"
25
+ content << "\n"
26
+ out.push("#{@put_cmd} #{content.dump}")
27
+ out.cr
28
+ content = ''
29
+ when '<%%'
30
+ content << '<%'
31
+ else
32
+ content << token
33
+ end
34
+ else
35
+ case token
36
+ when '%>'
37
+ case scanner.stag
38
+ when '<%'
39
+ if content[-1] == ?\n
40
+ content.chop!
41
+ out.push(content)
42
+ out.cr
43
+ else
44
+ out.push(content)
45
+ end
46
+ when '<%='
47
+ # NOTE: Changed lines
48
+
49
+ # Don't escape yield statements (they should already be safe)
50
+ if content =~ /^[ \t]*yield[ |\(]/
51
+ to_string = 'to_s'
52
+ else
53
+ to_string = 'to_xss_safe'
54
+ end
55
+ out.push("#{@insert_cmd}((#{content}).#{to_string})")
56
+
57
+ # NOTE: End changed lines
58
+ when '<%#'
59
+ # out.push("# #{content.dump}")
60
+ end
61
+ scanner.stag = nil
62
+ content = ''
63
+ when '%%>'
64
+ content << '%>'
65
+ else
66
+ content << token
67
+ end
68
+ end
69
+ end
70
+ out.push("#{@put_cmd} #{content.dump}") if content.size > 0
71
+ out.close
72
+ out.script
73
+ end
74
+
75
+ end
76
+
77
+ def initialize(str, safe_level=nil, trim_mode=nil, eoutvar='_erbout')
78
+ @safe_level = safe_level
79
+ # NOTE: Changed lines
80
+
81
+ compiler = XSSProtectedERB::Compiler.new(trim_mode)
82
+
83
+ # NOTE: End changed lines
84
+ set_eoutvar(compiler, eoutvar)
85
+ @src = compiler.compile(str)
86
+ @filename = nil
87
+ end
88
+ end
89
+
90
+ # Use our own ERB handler.
91
+ # See /usr/lib/ruby/gems/1.8/gems/actionpack-2.1.0/lib/action_view/template_handlers/erb.rb.
92
+ module ActionView
93
+ module TemplateHandlers
94
+ class ERB < TemplateHandler
95
+ def compile(template)
96
+ ::XSSProtectedERB.new(template.source, nil, @view.erb_trim_mode).src
97
+ end
98
+ end
99
+ end
100
+ end
101
+
@@ -0,0 +1,42 @@
1
+ class SafeString < String
2
+ def to_s
3
+ self
4
+ end
5
+ def to_xss_safe
6
+ self
7
+ end
8
+ end
9
+
10
+ class String
11
+ def xss_safe
12
+ SafeString.new(self)
13
+ end
14
+ end
15
+
16
+ class NilClass
17
+ def xss_safe
18
+ self
19
+ end
20
+ end
21
+
22
+ # ERB::Util.h and (include ERB::Util; h) are different methods
23
+ module ERB::Util
24
+ class <<self
25
+ def h_with_xss_protection(*args)
26
+ h_without_xss_protection(*args).xss_safe
27
+ end
28
+ alias_method_chain :h, :xss_protection
29
+ end
30
+
31
+ def h_with_xss_protection(*args)
32
+ h_without_xss_protection(*args).xss_safe
33
+ end
34
+ alias_method_chain :h, :xss_protection
35
+ end
36
+
37
+ class Object
38
+ def to_xss_safe
39
+ ERB::Util.h(to_s).xss_safe
40
+ end
41
+ end
42
+
@@ -0,0 +1,118 @@
1
+ class Module
2
+ def mark_methods_as_xss_safe(*ms)
3
+ ms.each do |m|
4
+ begin
5
+ instance_method("#{m}_with_xss_protection")
6
+ rescue NameError
7
+ define_method :"#{m}_with_xss_protection" do |*args|
8
+ send(:"#{m}_without_xss_protection", *args).xss_safe
9
+ end
10
+ alias_method_chain m, :xss_protection
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ # Mark known helpers as xss_safe only if their arguments are guaranteed to be
17
+ # safe. Don't mark methods that take a block as xss_safe.
18
+ class ActionView::Base
19
+ # ActionView::Helpers::AssetTagHelper
20
+ mark_methods_as_xss_safe :auto_discovery_link_tag,
21
+ :javascript_include_tag,
22
+ :stylesheet_link_tag,
23
+ :image_tag
24
+
25
+ # ActionView::Helpers::JavaScriptHelper
26
+ mark_methods_as_xss_safe :link_to_function,
27
+ :button_to_function,
28
+ :javascript_tag
29
+
30
+ # ActionView::Helpers::FormHelper
31
+ mark_methods_as_xss_safe :check_box,
32
+ :file_field,
33
+ :hidden_field,
34
+ :label,
35
+ :password_field,
36
+ :radio_button,
37
+ :text_area,
38
+ :text_field
39
+
40
+ # ActionView::Helpers::FormTagHelper
41
+ mark_methods_as_xss_safe :check_box_tag,
42
+ :file_field_tag,
43
+ :form_tag_html,
44
+ :hidden_field_tag,
45
+ :image_submit_tag,
46
+ :label_tag,
47
+ :password_field_tag,
48
+ :radio_button_tag,
49
+ :select_tag,
50
+ :submit_tag,
51
+ :text_area_tag,
52
+ :text_field_tag
53
+
54
+ # ActionView::Helpers::FormOptionsHelper
55
+ mark_methods_as_xss_safe :select,
56
+ :options_for_select,
57
+ :collection_select,
58
+ :country_select,
59
+ :time_zone_select,
60
+ :options_from_collection_for_select,
61
+ :option_groups_from_collection_for_select,
62
+ :country_options_for_select,
63
+ :time_zone_options_for_select
64
+
65
+ # ActionView::Helpers::PrototypeHelper
66
+ mark_methods_as_xss_safe :submit_to_remote
67
+
68
+ # ActionView::Helpers::ActiveRecordHelper
69
+ mark_methods_as_xss_safe :error_message_on,
70
+ :error_messages_for,
71
+ :input
72
+
73
+ # ActionView::Helpers::DateHelper
74
+ mark_methods_as_xss_safe :date_select,
75
+ :datetime_select,
76
+ :select_date,
77
+ :select_datetime,
78
+ :select_time,
79
+ :select_month,
80
+ :select_minute,
81
+ :select_hour,
82
+ :select_day,
83
+ :select_year,
84
+ :select_second,
85
+ :time_select
86
+
87
+ # ActionView::Helpers::UrlHelper
88
+ mark_methods_as_xss_safe :mail_to
89
+
90
+ # General
91
+ mark_methods_as_xss_safe :render
92
+
93
+ def link_to_with_xss_protection(text, *args)
94
+ link_to_without_xss_protection(text.to_xss_safe, *args).xss_safe
95
+ end
96
+ alias_method_chain :link_to, :xss_protection
97
+
98
+ def button_to_with_xss_protection(text, *args)
99
+ button_to_without_xss_protection(text.to_xss_safe, *args).xss_safe
100
+ end
101
+ alias_method_chain :button_to, :xss_protection
102
+ end
103
+
104
+ # AssetPackager plugin.
105
+ if defined? Synthesis
106
+ module Synthesis::AssetPackageHelper
107
+ mark_methods_as_xss_safe :stylesheet_link_merged,
108
+ :javascript_include_merged
109
+ end
110
+ end
111
+
112
+ # WillPaginate plugin.
113
+ if defined? WillPaginate
114
+ module WillPaginate::ViewHelpers
115
+ mark_methods_as_xss_safe :will_paginate
116
+ end
117
+ end
118
+
data/lib/xss_shield.rb ADDED
@@ -0,0 +1,3 @@
1
+ require 'xss_shield/safe_string'
2
+ require 'xss_shield/erb_hacks'
3
+ require 'xss_shield/secure_helpers'
@@ -0,0 +1,55 @@
1
+ require File.dirname(__FILE__) + '/../test/test_helper'
2
+
3
+ # Test that helpers from ActionView::Helpers::ActiveRecordHelper are properly
4
+ # escaped.
5
+ class ActiveRecordHelper < Test::Unit::TestCase
6
+
7
+ def setup
8
+ @errors = mock()
9
+ foobar = mock()
10
+ foobar.stubs(:collect).returns(['foo&name'])
11
+ Object.stubs(:content_columns).returns(foobar)
12
+ @foo = Object.new
13
+ @foo.stubs(:errors).returns(@errors)
14
+ @options = { :locals => { :@foo => @foo } }
15
+ end
16
+
17
+ def test_error_message_on
18
+ @errors.stubs(:on).with(:bar).returns('foo&bar')
19
+ assert_render({
20
+ %(<%= error_message_on :foo, :bar %>) => %(
21
+ <div class="formError">foo&bar</div>)
22
+ }, @options)
23
+ end
24
+
25
+ def test_error_messages_for
26
+ @errors.stubs(:count).returns(1)
27
+ @errors.stubs(:full_messages).returns('foo&bar')
28
+ assert_render({
29
+ %(<%= error_messages_for :foo %>) => %(
30
+ <div class="errorExplanation" id="errorExplanation"><h2>1 error \
31
+ prohibited this foo from being saved</h2><p>There were problems with the \
32
+ following fields:</p><ul><li>foo&bar</li></ul></div>)
33
+ }, @options)
34
+ end
35
+
36
+ def test_form
37
+ @foo.stubs(:new_record?).returns(true)
38
+ assert_render({
39
+ %(<%= form :foo %>) => %(
40
+ <form action="/test/foobar" method="post">foo&name<input name="commit" \
41
+ type="submit" value="Create"#{XHTML_TAGS}></form>)
42
+ }, @options)
43
+ end
44
+
45
+ def test_input
46
+ @foo.stubs(:column_for_attribute).returns(stub(:type => :string))
47
+ @foo.stubs(:bar).returns('foo&bar&val')
48
+ assert_render({
49
+ %(<%= input :foo, :bar %>) => %(
50
+ <input name="foo[bar]" size="30" type="text" id="foo_bar" value="\
51
+ foo&amp;bar&amp;val" />)
52
+ }, @options)
53
+ end
54
+
55
+ end
@@ -0,0 +1,32 @@
1
+ require File.dirname(__FILE__) + '/../test/test_helper'
2
+
3
+ # Test that helpers from Synthesis::AssetPackagerHelper are properly escaped.
4
+ class AssetPackagerTest < Test::Unit::TestCase
5
+
6
+ $asset_packages_yml = {
7
+ "javascripts" => [{ "base" => [ "foobar" ] }],
8
+ "stylesheets" => [{ "base" => [ "foobar" ] }]
9
+ }
10
+ include Synthesis::AssetPackageHelper
11
+
12
+ rescue NameError
13
+ puts "AssetPackager plugin not found, skipping related tests"
14
+
15
+ else
16
+
17
+ def test_stylesheet_link_merged
18
+ assert_render(
19
+ %(<%= stylesheet_link_merged :base %>) => %(
20
+ <link href="/stylesheets/foobar.css" rel="stylesheet" media="screen"\
21
+ type="text/css"#{XHTML_TAGS}>)
22
+ )
23
+ end
24
+
25
+ def test_javascript_include_merged
26
+ assert_render(
27
+ %(<%= javascript_include_merged :base %>) => %(
28
+ <script type="text/javascript" src="/javascripts/foobar.js"></script>)
29
+ )
30
+ end
31
+
32
+ end
@@ -0,0 +1,66 @@
1
+ require File.dirname(__FILE__) + '/../test/test_helper'
2
+
3
+ # Test that helpers from ActionView::Helpers::AssetTagHelper are properly
4
+ # escaped.
5
+ class AssetTagHelper < Test::Unit::TestCase
6
+
7
+ def test_auto_discovery_link_tag
8
+ assert_render(
9
+ %(<%= auto_discovery_link_tag "foo&bar" %>) => %(
10
+ <link href="/test/foobar" title="FOO&amp;BAR" rel="alternate" type="\
11
+ foo&amp;bar"#{XHTML_TAGS}>))
12
+ end
13
+
14
+ def test_image_path
15
+ assert_render(
16
+ %(<%= image_path "foo&bar" %>) => %(/images/foo&amp;bar))
17
+ end
18
+
19
+ def test_image_tag
20
+ assert_render(
21
+ %(<%= image_tag "foo&bar" %>) => %(
22
+ <img src="/images/foo&amp;bar" alt="Foo&amp;bar"#{XHTML_TAGS}>))
23
+ end
24
+
25
+ def test_javascript_include_tag
26
+ assert_render(
27
+ %(<%= javascript_include_tag "foo&bar" %>) => %(
28
+ <script type="text/javascript" src="/javascripts/foo&amp;bar.js"></script>))
29
+ end
30
+
31
+ def test_javascript_path
32
+ assert_render(
33
+ %(<%= javascript_path "foo&bar" %>) => %(/javascripts/foo&amp;bar.js))
34
+ end
35
+
36
+ # Alias for image_path.
37
+ def test_path_to_image
38
+ assert_render(
39
+ %(<%= path_to_image "foo&bar" %>) => %(/images/foo&amp;bar))
40
+ end
41
+
42
+ # Alias for javascript_path.
43
+ def test_path_to_javascript
44
+ assert_render(
45
+ %(<%= path_to_javascript "foo&bar" %>) => %(/javascripts/foo&amp;bar.js))
46
+ end
47
+
48
+ # Alias for stylesheet_path.
49
+ def test_path_to_stylesheet
50
+ assert_render(
51
+ %(<%= path_to_stylesheet "foo&bar" %>) => %(/stylesheets/foo&amp;bar.css))
52
+ end
53
+
54
+ def test_stylesheet_link_tag
55
+ assert_render(
56
+ %(<%= stylesheet_link_tag "foo&bar" %>) => %(
57
+ <link href="/stylesheets/foo&amp;bar.css" rel="stylesheet" type=\
58
+ "text/css" media="screen"#{XHTML_TAGS}>))
59
+ end
60
+
61
+ def test_stylesheet_path
62
+ assert_render(
63
+ %(<%= stylesheet_path "foo&bar" %>) => %(/stylesheets/foo&amp;bar.css))
64
+ end
65
+
66
+ end
@@ -0,0 +1,71 @@
1
+ require File.dirname(__FILE__) + '/../test/test_helper'
2
+
3
+ # Test that helpers from ActionView::Helpers::DateHelper are properly
4
+ # escaped.
5
+ class DateHelperTest < Test::Unit::TestCase
6
+
7
+ def test_date_select
8
+ assert_render_has_no_escaped_chars %(<%= date_select :foo, :created_on %>)
9
+ end
10
+
11
+ def test_datetime_select
12
+ assert_render_has_no_escaped_chars(
13
+ %(<%= datetime_select :foo, :created_on %>))
14
+ end
15
+
16
+ def test_distance_of_time_in_words
17
+ assert_render({
18
+ %(<%= distance_of_time_in_words time, time+10 %>) => %(less than a minute)
19
+ }, { :locals => { :time => Time.now } })
20
+ end
21
+
22
+ def test_distance_of_time_in_words_to_now
23
+ assert_render({
24
+ %(<%= distance_of_time_in_words 10 %>) => %(less than a minute)
25
+ })
26
+ end
27
+
28
+ def test_select_date
29
+ assert_render_has_no_escaped_chars %(<%= select_date %>)
30
+ end
31
+
32
+ def test_select_datetime
33
+ assert_render_has_no_escaped_chars %(<%= select_datetime %>)
34
+ end
35
+
36
+ def test_select_day
37
+ assert_render_has_no_escaped_chars %(<%= select_day 1 %>)
38
+ end
39
+
40
+ def test_select_hour
41
+ assert_render_has_no_escaped_chars %(<%= select_hour 1 %>)
42
+ end
43
+
44
+ def test_select_minute
45
+ assert_render_has_no_escaped_chars %(<%= select_minute 1 %>)
46
+ end
47
+
48
+ def test_select_month
49
+ assert_render_has_no_escaped_chars %(<%= select_month 1 %>)
50
+ end
51
+
52
+ def test_select_second
53
+ assert_render_has_no_escaped_chars %(<%= select_second 1 %>)
54
+ end
55
+
56
+ def test_select_time
57
+ assert_render_has_no_escaped_chars %(<%= select_time Time.now %>)
58
+ end
59
+
60
+ def test_select_year
61
+ assert_render_has_no_escaped_chars %(<%= select_year 2000 %>)
62
+ end
63
+
64
+ def test_time_ago_in_words
65
+ assert_render_has_no_escaped_chars %(<%= time_ago_in_words Time.now %>)
66
+ end
67
+
68
+ def test_time_select
69
+ assert_render_has_no_escaped_chars %(<%= time_select :foo, :bar %>)
70
+ end
71
+ end