presenting 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/LICENSE +20 -0
- data/README +10 -0
- data/Rakefile +22 -0
- data/app/assets/javascript/grid.js +0 -0
- data/app/assets/javascript/search.js +13 -0
- data/app/assets/stylesheets/details-color.css +7 -0
- data/app/assets/stylesheets/details.css +10 -0
- data/app/assets/stylesheets/form.css +1 -0
- data/app/assets/stylesheets/grid-color.css +71 -0
- data/app/assets/stylesheets/grid.css +64 -0
- data/app/assets/stylesheets/search-color.css +16 -0
- data/app/assets/stylesheets/search.css +45 -0
- data/app/controllers/presentation/assets_controller.rb +42 -0
- data/app/views/presentations/_details.erb +11 -0
- data/app/views/presentations/_field_search.erb +14 -0
- data/app/views/presentations/_form.erb +20 -0
- data/app/views/presentations/_grid.erb +70 -0
- data/app/views/presentations/_search.erb +7 -0
- data/config/routes.rb +3 -0
- data/lib/presentation/base.rb +48 -0
- data/lib/presentation/details.rb +19 -0
- data/lib/presentation/field_search.rb +65 -0
- data/lib/presentation/form.rb +149 -0
- data/lib/presentation/grid.rb +160 -0
- data/lib/presentation/search.rb +9 -0
- data/lib/presenting/attribute.rb +51 -0
- data/lib/presenting/configurable.rb +10 -0
- data/lib/presenting/defaults.rb +10 -0
- data/lib/presenting/field_set.rb +26 -0
- data/lib/presenting/form_helpers.rb +51 -0
- data/lib/presenting/helpers.rb +110 -0
- data/lib/presenting/sanitize.rb +19 -0
- data/lib/presenting/search.rb +185 -0
- data/lib/presenting/sorting.rb +87 -0
- data/lib/presenting/view.rb +3 -0
- data/lib/presenting.rb +9 -0
- data/rails/init.rb +12 -0
- data/test/assets_test.rb +58 -0
- data/test/attribute_test.rb +61 -0
- data/test/configurable_test.rb +20 -0
- data/test/details_test.rb +68 -0
- data/test/field_search_test.rb +102 -0
- data/test/field_set_test.rb +46 -0
- data/test/form_test.rb +287 -0
- data/test/grid_test.rb +219 -0
- data/test/helpers_test.rb +72 -0
- data/test/presenting_test.rb +15 -0
- data/test/rails/app/controllers/application_controller.rb +15 -0
- data/test/rails/app/controllers/users_controller.rb +36 -0
- data/test/rails/app/helpers/application_helper.rb +3 -0
- data/test/rails/app/helpers/users_helper.rb +2 -0
- data/test/rails/app/models/user.rb +2 -0
- data/test/rails/app/views/layouts/application.html.erb +15 -0
- data/test/rails/app/views/users/index.html.erb +10 -0
- data/test/rails/app/views/users/new.html.erb +2 -0
- data/test/rails/app/views/users/show.html.erb +1 -0
- data/test/rails/config/boot.rb +109 -0
- data/test/rails/config/database.yml +17 -0
- data/test/rails/config/environment.rb +13 -0
- data/test/rails/config/environments/development.rb +17 -0
- data/test/rails/config/environments/production.rb +24 -0
- data/test/rails/config/environments/test.rb +22 -0
- data/test/rails/config/locales/en.yml +5 -0
- data/test/rails/config/routes.rb +5 -0
- data/test/rails/db/development.sqlite3 +0 -0
- data/test/rails/db/migrate/20090213085444_create_users.rb +13 -0
- data/test/rails/db/migrate/20090213085607_populate_users.rb +13 -0
- data/test/rails/db/schema.rb +23 -0
- data/test/rails/db/test.sqlite3 +0 -0
- data/test/rails/log/development.log +858 -0
- data/test/rails/public/404.html +30 -0
- data/test/rails/public/422.html +30 -0
- data/test/rails/public/500.html +33 -0
- data/test/rails/public/javascripts/application.js +2 -0
- data/test/rails/public/javascripts/jquery.livequery.min.js +11 -0
- data/test/rails/public/javascripts/prototype.js +4320 -0
- data/test/rails/script/console +3 -0
- data/test/rails/script/dbconsole +3 -0
- data/test/rails/script/destroy +3 -0
- data/test/rails/script/generate +3 -0
- data/test/rails/script/plugin +3 -0
- data/test/rails/script/runner +3 -0
- data/test/rails/script/server +3 -0
- data/test/sanitize_test.rb +15 -0
- data/test/search_conditions_test.rb +137 -0
- data/test/search_test.rb +30 -0
- data/test/sorting_test.rb +63 -0
- data/test/test_helper.rb +66 -0
- metadata +217 -0
@@ -0,0 +1,185 @@
|
|
1
|
+
module Presenting
|
2
|
+
class Search
|
3
|
+
include Presenting::Configurable
|
4
|
+
|
5
|
+
# I want to support three configuration formats:
|
6
|
+
#
|
7
|
+
# Search.new(:fields => [:first_name, :last_name, :email])
|
8
|
+
#
|
9
|
+
# Search.new(:fields => {
|
10
|
+
# 'first_name' => :equals,
|
11
|
+
# 'last_name' => :begins_with,
|
12
|
+
# 'email' => :not_null
|
13
|
+
# })
|
14
|
+
#
|
15
|
+
# Search.new(:fields => {
|
16
|
+
# 'fname' => {:sql => 'first_name', :pattern => :equals},
|
17
|
+
# 'lname' => {:sql => 'last_name', :pattern => :begins_with},
|
18
|
+
# 'email' => {:sql => 'email', :pattern => :not_null}
|
19
|
+
# })
|
20
|
+
def fields=(obj)
|
21
|
+
case obj
|
22
|
+
when Array
|
23
|
+
obj.each do |name| fields << name end
|
24
|
+
|
25
|
+
when Hash
|
26
|
+
obj.each do |k, v|
|
27
|
+
fields << {k => v}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def fields
|
33
|
+
@fields ||= FieldSet.new
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_sql(params, type = :simple)
|
37
|
+
send("to_#{type}_sql", params) unless params.blank?
|
38
|
+
end
|
39
|
+
|
40
|
+
protected
|
41
|
+
|
42
|
+
# handles a simple search where a given term is matched against a number of fields, and can match any of them.
|
43
|
+
# this is usually presented to the user as a single "smart" search box.
|
44
|
+
def to_simple_sql(term)
|
45
|
+
sql = fields.map(&:fragment).join(' OR ')
|
46
|
+
binds = fields.collect{|f| f.bind(term)}.compact
|
47
|
+
[sql, binds].flatten.compact
|
48
|
+
end
|
49
|
+
|
50
|
+
# handles a search setup where a user may enter a search value for any field, and anything entered must match.
|
51
|
+
# this is usually presented to the user as a set of labeled search boxes.
|
52
|
+
#
|
53
|
+
# example field terms:
|
54
|
+
# field_terms = {
|
55
|
+
# 'first_name' => {:value => 'Bob'}
|
56
|
+
# 'last_name' => {:value => 'Smith'}
|
57
|
+
# }
|
58
|
+
#
|
59
|
+
def to_field_sql(field_terms)
|
60
|
+
searched_fields = fields.select{|f| field_terms[f.name] and not field_terms[f.name][:value].blank?}
|
61
|
+
unless searched_fields.empty?
|
62
|
+
sql = searched_fields.map(&:fragment).join(' AND ')
|
63
|
+
binds = searched_fields.collect{|f| f.bind(field_terms[f.name][:value])}
|
64
|
+
|
65
|
+
[sql, binds].flatten.compact
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class FieldSet < Array
|
70
|
+
def <<(val)
|
71
|
+
if val.is_a? Hash
|
72
|
+
k, v = *val.to_a.first
|
73
|
+
opts = v.is_a?(Hash) ? v : {:pattern => v}
|
74
|
+
opts[:name] = k
|
75
|
+
else
|
76
|
+
opts = {:name => val}
|
77
|
+
end
|
78
|
+
super Field.new(opts)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# TODO: a field may require extra joins when it is searched on
|
83
|
+
# TODO: support more than just mysql (need access to a Connection for quoting and attribute conditions)
|
84
|
+
class Field
|
85
|
+
include Presenting::Configurable
|
86
|
+
|
87
|
+
# required (this is what appears in the parameter hash)
|
88
|
+
attr_reader :name
|
89
|
+
def name=(val)
|
90
|
+
@name = val.to_s
|
91
|
+
end
|
92
|
+
|
93
|
+
# sql field (default == name)
|
94
|
+
def sql
|
95
|
+
@sql ||= name
|
96
|
+
end
|
97
|
+
attr_writer :sql
|
98
|
+
|
99
|
+
# a shortcut for common operator/bind_pattern combos
|
100
|
+
def pattern=(val)
|
101
|
+
case val
|
102
|
+
when :equals
|
103
|
+
self.operator = '= ?'
|
104
|
+
self.bind_pattern = '?'
|
105
|
+
when :begins_with
|
106
|
+
self.operator = 'LIKE ?'
|
107
|
+
self.bind_pattern = '?%'
|
108
|
+
when :ends_with
|
109
|
+
self.operator = 'LIKE ?'
|
110
|
+
self.bind_pattern = '%?'
|
111
|
+
when :contains
|
112
|
+
self.operator = 'LIKE ?'
|
113
|
+
self.bind_pattern = '%?%'
|
114
|
+
when :null
|
115
|
+
self.operator = 'IS NULL'
|
116
|
+
when :not_null
|
117
|
+
self.operator = 'IS NOT NULL'
|
118
|
+
when :true
|
119
|
+
self.operator = '= ?'
|
120
|
+
self.bind_pattern = true
|
121
|
+
when :false
|
122
|
+
self.operator = '= ?'
|
123
|
+
self.bind_pattern = false
|
124
|
+
when :less_than
|
125
|
+
self.operator = '< ?'
|
126
|
+
when :less_than_or_equal_to, :not_greater_than
|
127
|
+
self.operator = '<= ?'
|
128
|
+
when :greater_than
|
129
|
+
self.operator = '> ?'
|
130
|
+
when :greater_than_or_equal_to, :not_less_than
|
131
|
+
self.operator = '>= ?'
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# the format for comparison with :sql, with an optional bind for search terms
|
136
|
+
# '= ?', 'LIKE ?', 'IN (?)', etc.
|
137
|
+
def operator
|
138
|
+
@operator ||= '= ?'
|
139
|
+
end
|
140
|
+
attr_writer :operator
|
141
|
+
|
142
|
+
# formats the term BEFORE binding into the sql
|
143
|
+
# e.g. '?', '?%', etc.
|
144
|
+
def bind_pattern
|
145
|
+
@bind_pattern ||= '?'
|
146
|
+
end
|
147
|
+
attr_writer :bind_pattern
|
148
|
+
|
149
|
+
# composes the sql fragment
|
150
|
+
def fragment
|
151
|
+
"#{sql} #{operator}"
|
152
|
+
end
|
153
|
+
|
154
|
+
# prepares the bindable term
|
155
|
+
def bind(term)
|
156
|
+
return nil unless operator.include?('?')
|
157
|
+
return bind_pattern unless bind_pattern.is_a? String
|
158
|
+
bind_pattern == '?' ? typecast(term) : bind_pattern.sub('?', typecast(term).to_s)
|
159
|
+
end
|
160
|
+
|
161
|
+
# you can set a data type for the field, which will be used to convert
|
162
|
+
# parameter values. currently this is mostly useful for :time searches.
|
163
|
+
attr_accessor :type
|
164
|
+
|
165
|
+
protected
|
166
|
+
|
167
|
+
def typecast(val)
|
168
|
+
case type
|
169
|
+
when :date
|
170
|
+
val.is_a?(String) ?
|
171
|
+
(Time.zone ? Time.zone.parse(val) : Time.parse(val)).to_date :
|
172
|
+
val
|
173
|
+
|
174
|
+
when :time, :datetime
|
175
|
+
val.is_a?(String) ?
|
176
|
+
(Time.zone ? Time.zone.parse(val) : Time.parse(val)) :
|
177
|
+
val
|
178
|
+
|
179
|
+
else
|
180
|
+
val.to_s.strip
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module Presenting
|
2
|
+
class Sorting
|
3
|
+
include Presenting::Configurable
|
4
|
+
|
5
|
+
# I want to support two configuration formats:
|
6
|
+
#
|
7
|
+
# Sorting.new(:fields => [:first_name, :last_name, :email])
|
8
|
+
#
|
9
|
+
# Sorting.new(:fields => {
|
10
|
+
# 'name' => 'CONCAT(first_name, last_name)',
|
11
|
+
# 'email' => 'email_address'
|
12
|
+
# })
|
13
|
+
def fields=(obj)
|
14
|
+
case obj
|
15
|
+
when Array
|
16
|
+
obj.each do |name| fields << name end
|
17
|
+
|
18
|
+
when Hash
|
19
|
+
obj.each do |k, v|
|
20
|
+
fields << {k => v}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def fields
|
26
|
+
@fields ||= FieldSet.new
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_sql(param)
|
30
|
+
fields.each do |field|
|
31
|
+
# search for and return the first known field
|
32
|
+
return "#{field.sql} #{desc_or_asc param[field.name]}" if param[field.name]
|
33
|
+
end unless param.blank?
|
34
|
+
# no known fields found
|
35
|
+
default
|
36
|
+
end
|
37
|
+
|
38
|
+
# The default sorting, if no known fields are found in the parameters.
|
39
|
+
# Default sorting is specified by name/direction, using an array.
|
40
|
+
#
|
41
|
+
# Example:
|
42
|
+
#
|
43
|
+
# @sorting.default = [:name, 'asc']
|
44
|
+
#
|
45
|
+
def default
|
46
|
+
@default ||= "#{fields.first.sql} ASC"
|
47
|
+
end
|
48
|
+
def default=(val)
|
49
|
+
@default = "#{fields.find{|f| f.name == val.first.to_s}.sql} #{desc_or_asc val.second}"
|
50
|
+
end
|
51
|
+
|
52
|
+
protected
|
53
|
+
|
54
|
+
def desc_or_asc(val)
|
55
|
+
val.to_s.downcase == 'desc' ? 'DESC' : 'ASC'
|
56
|
+
end
|
57
|
+
|
58
|
+
class FieldSet < Array
|
59
|
+
def <<(val)
|
60
|
+
if val.is_a? Hash
|
61
|
+
k, v = *val.to_a.first
|
62
|
+
opts = v.is_a?(Hash) ? v : {:sql => v}
|
63
|
+
opts[:name] = k
|
64
|
+
else
|
65
|
+
opts = {:name => val}
|
66
|
+
end
|
67
|
+
super Field.new(opts)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class Field
|
72
|
+
include Presenting::Configurable
|
73
|
+
|
74
|
+
# required (this is what appears in the parameter hash)
|
75
|
+
attr_reader :name
|
76
|
+
def name=(val)
|
77
|
+
@name = val.to_s
|
78
|
+
end
|
79
|
+
|
80
|
+
# sql field (default == name)
|
81
|
+
attr_writer :sql
|
82
|
+
def sql
|
83
|
+
@sql ||= self.name.to_s
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
data/lib/presenting.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
module Presenting
|
2
|
+
# prior to Rails 2.3, you need to call this manually from your config/routes.rb
|
3
|
+
def self.draw_routes(map)
|
4
|
+
map.namespace(:presentation) do |presentation|
|
5
|
+
presentation.stylesheet 'stylesheets/:id.css', :controller => 'assets', :action => 'stylesheet', :format => 'css'
|
6
|
+
presentation.javascript 'javascript/:id.js', :controller => 'assets', :action => 'javascript', :format => 'js'
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
data/rails/init.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
ActionView::Base.class_eval { include Presenting::Helpers }
|
2
|
+
ActionController::Base.const_set(:Search, Presenting::Search)
|
3
|
+
ActionController::Base.const_set(:Sorting, Presenting::Sorting)
|
4
|
+
ActionView::Helpers::FormBuilder.class_eval { include Presenting::FormHelpers }
|
5
|
+
|
6
|
+
if defined? Rails # because tests don't define the Rails module
|
7
|
+
if Rails.version.match(/^2\.2/)
|
8
|
+
ActionController::Base.view_paths << File.join(File.dirname(__FILE__), 'app', 'views')
|
9
|
+
ActiveSupport::Dependencies.load_paths << File.dirname(__FILE__) + '/app/controllers'
|
10
|
+
$LOAD_PATH.unshift File.dirname(__FILE__) + '/app/controllers/'
|
11
|
+
end
|
12
|
+
end
|
data/test/assets_test.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
require 'action_controller/test_case'
|
3
|
+
require 'action_controller/integration'
|
4
|
+
|
5
|
+
class Presentation::AssetsControllerTest < ActionController::TestCase
|
6
|
+
|
7
|
+
def setup
|
8
|
+
ActionController::Base.perform_caching = false
|
9
|
+
end
|
10
|
+
|
11
|
+
# stylesheet
|
12
|
+
|
13
|
+
def test_stylesheet_routing_recognition
|
14
|
+
assert_recognizes({:controller => "presentation/assets", :action => "stylesheet", :id => "foo", :format => "css"}, "/presentation/stylesheets/foo.css")
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_stylesheet_routing_generation
|
18
|
+
assert_generates "/presentation/stylesheets/foo.css", {:controller => "presentation/assets", :action => "stylesheet", :id => "foo", :format => "css"}
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_named_stylesheet_routes
|
22
|
+
assert_equal "/presentation/stylesheets/foo.css", presentation_stylesheet_path("foo", :format => 'css')
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_retrieving_a_named_stylesheet
|
26
|
+
get :stylesheet, :id => 'grid', :format => 'css'
|
27
|
+
assert_response :success
|
28
|
+
assert_equal @response.body, File.read(File.join(PLUGIN_ROOT, 'app', 'assets', 'stylesheets', 'grid.css'))
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_retrieving_multiple_named_stylesheets
|
32
|
+
get :stylesheet, :id => 'grid,form', :format => 'css'
|
33
|
+
assert_response :success
|
34
|
+
assert @response.body.include?(File.read(File.join(PLUGIN_ROOT, 'app', 'assets', 'stylesheets', 'grid.css')))
|
35
|
+
assert @response.body.include?(File.read(File.join(PLUGIN_ROOT, 'app', 'assets', 'stylesheets', 'form.css')))
|
36
|
+
end
|
37
|
+
|
38
|
+
# javascript
|
39
|
+
|
40
|
+
def test_javascript_routing_recognition
|
41
|
+
assert_recognizes({:controller => "presentation/assets", :action => "javascript", :id => "foo", :format => "js"}, "/presentation/javascript/foo.js")
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_javascript_routing_generation
|
45
|
+
assert_generates "/presentation/javascript/foo.js", {:controller => "presentation/assets", :action => "javascript", :id => "foo", :format => "js"}
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_named_javascript_routes
|
49
|
+
assert_equal "/presentation/javascript/foo.js", presentation_javascript_path("foo", :format => 'js')
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_retrieving_multiple_named_javascripts
|
53
|
+
get :javascript, :id => 'grid,search', :format => 'js'
|
54
|
+
assert_response :success
|
55
|
+
assert @response.body.include?(File.read(File.join(PLUGIN_ROOT, 'app', 'assets', 'javascript', 'grid.js')))
|
56
|
+
assert @response.body.include?(File.read(File.join(PLUGIN_ROOT, 'app', 'assets', 'javascript', 'search.js')))
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class AttributeTest < Presenting::Test
|
4
|
+
|
5
|
+
def setup
|
6
|
+
@a = Presenting::Attribute.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_assigning_a_symbol_name
|
10
|
+
@a.name = :foo
|
11
|
+
assert_equal "Foo", @a.name, "name is typecast to a string and titleized"
|
12
|
+
assert_equal :foo, @a.value, "value is assumed to be a symbol as well"
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_assigning_a_string_name
|
16
|
+
@a.name = "foo"
|
17
|
+
assert_equal "foo", @a.name, "name remains a string"
|
18
|
+
assert_equal "foo", @a.value, "value is assumed to be a string"
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_symbol_values
|
22
|
+
@a.value = :foo
|
23
|
+
assert_equal "bar", @a.value_from(stub('row', :foo => "bar")), "symbols are methods"
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_string_values
|
27
|
+
@a.value = "foo"
|
28
|
+
assert_equal "foo", @a.value_from(stub('row', :foo => "bar")), "strings are constant"
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_proc_values
|
32
|
+
@a.value = proc{|row| "hello"}
|
33
|
+
assert_equal "hello", @a.value_from(stub('row', :foo => "bar")), "procs are custom"
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_that_value_from_does_not_sanitizes_itself
|
37
|
+
@a.value = '<span>hello</span>'
|
38
|
+
@a.sanitize = true
|
39
|
+
assert_equal '<span>hello</span>', @a.value_from(nil)
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_hash_rows_with_symbol_values
|
43
|
+
@a.value = :foo
|
44
|
+
assert_equal 'bar', @a.value_from({:foo => 'bar'}), "symbols are hash keys"
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_sanitize_is_default_true
|
48
|
+
assert @a.sanitize?
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_assigning_a_symbol_id
|
52
|
+
@a.id = :foo
|
53
|
+
assert_equal 'foo', @a.id
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_default_id_from_complex_name
|
57
|
+
@a.name = 'Hello, World!'
|
58
|
+
assert_equal 'hello_world', @a.id
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class ConfigurableTest < Presenting::Test
|
4
|
+
class ConfigurableUser
|
5
|
+
include Presenting::Configurable
|
6
|
+
attr_accessor :first_name
|
7
|
+
attr_reader :name
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_setting_first_name_with_initializer
|
11
|
+
user = ConfigurableUser.new(:first_name => "Bob")
|
12
|
+
assert_equal 'Bob', user.first_name
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_setting_unsettable_attribute
|
16
|
+
assert_raises NoMethodError do
|
17
|
+
ConfigurableUser.new(:name => 'Bob Jenkins')
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class DetailsTest < Presenting::Test
|
4
|
+
def setup
|
5
|
+
@d = Presentation::Details.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_adding_a_field_by_name
|
9
|
+
@d.fields = ["foo"]
|
10
|
+
assert_equal "foo", @d.fields.first.name, "name is stringified"
|
11
|
+
assert_equal "foo", @d.fields.first.value, "value is assumed to be a method"
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_adding_a_field_by_name_and_value
|
15
|
+
@d.fields = [{"foo" => :bar}]
|
16
|
+
assert_equal "foo", @d.fields.first.name, "key is name"
|
17
|
+
assert_equal :bar, @d.fields.first.value, "value is value"
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_adding_a_field_by_name_and_options
|
21
|
+
@d.fields = [{"foo" => {:value => :bar}}]
|
22
|
+
assert_equal "foo", @d.fields.first.name, "key is name"
|
23
|
+
assert_equal :bar, @d.fields.first.value, "value is found and saved"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class DetailsRenderTest < Presentation::RenderTest
|
28
|
+
def setup
|
29
|
+
@presentation = Presentation::Details.new
|
30
|
+
@presentation.presentable = stub('user', :name => 'foo', :email => 'foo@example.com')
|
31
|
+
@presentation.controller = TestController.new
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_rendering_a_title
|
35
|
+
@presentation.title = 'Hello World'
|
36
|
+
|
37
|
+
assert_select '.presentation-details h3', 'Hello World'
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_rendering_an_attribute
|
41
|
+
@presentation.fields = [:email]
|
42
|
+
|
43
|
+
assert_select '.presentation-details' do
|
44
|
+
assert_select 'dl' do
|
45
|
+
assert_select 'dt', 'Email'
|
46
|
+
assert_select 'dd', 'foo@example.com'
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_rendering_sanitized_data
|
52
|
+
@presentation.fields = [
|
53
|
+
{:email => {:sanitize => false}},
|
54
|
+
{:name => {:sanitize => true}}
|
55
|
+
]
|
56
|
+
@presentation.presentable = stub('user', :name => '&', :email => '&')
|
57
|
+
|
58
|
+
assert_select '.presentation-details' do
|
59
|
+
assert_select 'dl' do
|
60
|
+
assert_select 'dt:nth-of-type(1)', 'Email'
|
61
|
+
assert_select 'dd:nth-of-type(1)', '&'
|
62
|
+
|
63
|
+
assert_select 'dt:nth-of-type(2)', 'Name'
|
64
|
+
assert_select 'dd:nth-of-type(2)', '&'
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class FieldSearchTest < Presenting::Test
|
4
|
+
def setup
|
5
|
+
@s = Presentation::FieldSearch.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_adding_a_field_by_name
|
9
|
+
@s.fields = [:foo]
|
10
|
+
assert_equal "Foo", @s.fields.first.name
|
11
|
+
assert_equal :text, @s.fields.first.type
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_adding_a_field_by_name_and_type
|
15
|
+
@s.fields = [{"foo" => :bar}]
|
16
|
+
assert_equal "foo", @s.fields.first.name
|
17
|
+
assert_equal :bar, @s.fields.first.type
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_adding_a_field_by_name_and_options
|
21
|
+
@s.fields = [{"foo" => {:type => :bar}}]
|
22
|
+
assert_equal "foo", @s.fields.first.name
|
23
|
+
assert_equal :bar, @s.fields.first.type
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class FieldSearchFieldTest < Presenting::Test
|
28
|
+
def setup
|
29
|
+
@f = Presentation::FieldSearch::Field.new
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_assigning_a_symbol_name
|
33
|
+
@f.name = :foo
|
34
|
+
assert_equal "Foo", @f.name, "name is typecast to a string and titleized"
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_assigning_a_string_name
|
38
|
+
@f.name = "foo"
|
39
|
+
assert_equal "foo", @f.name, "name remains a string"
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_default_type
|
43
|
+
assert_equal :text, @f.type
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class FieldSearchRenderingTest < Presentation::RenderTest
|
48
|
+
def setup
|
49
|
+
@presentation = Presentation::FieldSearch.new
|
50
|
+
@presentation.controller = TestController.new
|
51
|
+
@presentation.controller.request.path = '/users'
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_rendering_a_form_reuses_current_path
|
55
|
+
assert_select 'form[action=/users?][method=get]'
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_rendering_a_text_field
|
59
|
+
@presentation.fields << {:foo => :text}
|
60
|
+
assert_select "form input[type=text][name='search[foo][value]']"
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_rendering_a_text_field_with_existing_value
|
64
|
+
@presentation.fields << {:foo => :text}
|
65
|
+
@presentation.controller.params[:search] = {'foo' => {:value => 'bar'}}
|
66
|
+
assert_select "form input[type=text][name='search[foo][value]'][value=bar]"
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_rendering_a_checkbox_field
|
70
|
+
@presentation.fields << {:foo => :checkbox}
|
71
|
+
assert_select "form input[type=checkbox][name='search[foo][value]'][value=1]"
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_rendering_a_checkbox_field_with_existing_value
|
75
|
+
@presentation.fields << {:foo => :checkbox}
|
76
|
+
@presentation.controller.params[:search] = {'foo' => {:value => '1'}}
|
77
|
+
|
78
|
+
assert_select "form input[type=checkbox][name='search[foo][value]'][value=1][checked=checked]"
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_rendering_a_dropdown_field_with_existing_value
|
82
|
+
@presentation.fields << {:foo => {:type => :dropdown, :options => [["Dollar", "$"], ["Kroner", "DKK"]]}}
|
83
|
+
@presentation.controller.params[:search] = {'foo' => {:value => 'DKK'}}
|
84
|
+
|
85
|
+
assert_select "form select[name='search[foo][value]']" do
|
86
|
+
assert_select "option[value='$']"
|
87
|
+
assert_select "option[value='$'][selected='selected']", false
|
88
|
+
assert_select "option[value='DKK'][selected='selected']"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_rendering_a_dropdown_field_with_integers_and_existing_value
|
93
|
+
@presentation.fields << {:foo => {:type => :dropdown, :options => [["Dollar", 1], ["Kroner", 2]]}}
|
94
|
+
@presentation.controller.params[:search] = {'foo' => {:value => '2'}}
|
95
|
+
|
96
|
+
assert_select "form select[name='search[foo][value]']" do
|
97
|
+
assert_select "option[value='1']"
|
98
|
+
assert_select "option[value='1'][selected='selected']", false
|
99
|
+
assert_select "option[value='2'][selected='selected']"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class FieldSetTest < Presenting::Test
|
4
|
+
def setup
|
5
|
+
@set = Presenting::FieldSet.new(Presenting::Attribute, :name, :value)
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_reading_a_field_by_name
|
9
|
+
@set << "foo"
|
10
|
+
assert_equal "foo", @set["foo"].name
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_pushing_a_field_by_name
|
14
|
+
@set << "foo"
|
15
|
+
assert_equal "foo", @set.first.name
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_pushing_a_field_by_name_and_value
|
19
|
+
@set << {"foo" => :bar}
|
20
|
+
assert_equal "foo", @set.first.name
|
21
|
+
assert_equal :bar, @set.first.value
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_pushing_a_field_by_name_and_options
|
25
|
+
@set << {"foo" => {:value => :bar}}
|
26
|
+
assert_equal "foo", @set.first.name
|
27
|
+
assert_equal :bar, @set.first.value
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_adding_a_field_by_name
|
31
|
+
@set["foo"] = nil
|
32
|
+
assert_equal "foo", @set.first.name
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_adding_a_field_by_name_and_value
|
36
|
+
@set["foo"] = :bar
|
37
|
+
assert_equal "foo", @set.first.name
|
38
|
+
assert_equal :bar, @set.first.value
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_adding_a_field_by_name_and_options
|
42
|
+
@set["foo"] = {:value => :bar}
|
43
|
+
assert_equal "foo", @set.first.name
|
44
|
+
assert_equal :bar, @set.first.value
|
45
|
+
end
|
46
|
+
end
|