merb-core 0.9.12 → 0.9.13
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/lib/merb-core/bootloader.rb +1 -0
- data/lib/merb-core/controller/merb_controller.rb +1 -0
- data/lib/merb-core/dispatch/router.rb +14 -10
- data/lib/merb-core/dispatch/router/behavior.rb +2 -0
- data/lib/merb-core/dispatch/router/resources.rb +13 -8
- data/lib/merb-core/dispatch/router/route.rb +18 -9
- data/lib/merb-core/rack/adapter/abstract.rb +1 -1
- data/lib/merb-core/rack/adapter/mongrel.rb +9 -1
- data/lib/merb-core/tasks/gem_management.rb +16 -5
- data/lib/merb-core/test.rb +7 -6
- data/lib/merb-core/test/helpers.rb +1 -2
- data/lib/merb-core/test/matchers.rb +4 -1
- data/lib/merb-core/test/matchers/view_matchers.rb +101 -273
- data/lib/merb-core/test/run_spec.rb +40 -0
- data/lib/merb-core/test/run_specs.rb +32 -41
- data/lib/merb-core/test/test_ext/rspec.rb +0 -1
- data/lib/merb-core/test/webrat.rb +37 -0
- data/lib/merb-core/vendor/nokogiri/css.rb +6 -0
- data/lib/merb-core/vendor/nokogiri/css/generated_parser.rb +653 -0
- data/lib/merb-core/vendor/nokogiri/css/generated_tokenizer.rb +159 -0
- data/lib/merb-core/vendor/nokogiri/css/node.rb +95 -0
- data/lib/merb-core/vendor/nokogiri/css/parser.rb +24 -0
- data/lib/merb-core/vendor/nokogiri/css/parser.y +198 -0
- data/lib/merb-core/vendor/nokogiri/css/tokenizer.rb +9 -0
- data/lib/merb-core/vendor/nokogiri/css/tokenizer.rex +63 -0
- data/lib/merb-core/vendor/nokogiri/css/xpath_visitor.rb +159 -0
- data/lib/merb-core/version.rb +1 -1
- metadata +17 -4
- data/lib/merb-core/test/helpers/view_helper.rb +0 -121
data/lib/merb-core/bootloader.rb
CHANGED
@@ -219,13 +219,17 @@ module Merb
|
|
219
219
|
# ---
|
220
220
|
# @api plugin
|
221
221
|
def url(name, *args)
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
222
|
+
if name.is_a?(Route)
|
223
|
+
route = name
|
224
|
+
else
|
225
|
+
unless name.is_a?(Symbol)
|
226
|
+
args.unshift(name)
|
227
|
+
name = :default
|
228
|
+
end
|
229
|
+
|
230
|
+
unless route = Merb::Router.named_routes[name]
|
231
|
+
raise Merb::Router::GenerationError, "Named route not found: #{name}"
|
232
|
+
end
|
229
233
|
end
|
230
234
|
|
231
235
|
defaults = args.pop
|
@@ -263,13 +267,13 @@ module Merb
|
|
263
267
|
end
|
264
268
|
end
|
265
269
|
|
266
|
-
params << options
|
267
|
-
|
268
270
|
unless route = Merb::Router.resource_routes[key]
|
269
271
|
raise Merb::Router::GenerationError, "Resource route not found: #{args.inspect}"
|
270
272
|
end
|
273
|
+
|
274
|
+
params << options
|
271
275
|
|
272
|
-
route.generate(params, defaults)
|
276
|
+
route.generate(params, defaults, true)
|
273
277
|
end
|
274
278
|
|
275
279
|
# Add functionality to the router. This can be in the form of
|
@@ -124,13 +124,13 @@ module Merb
|
|
124
124
|
|
125
125
|
# => show
|
126
126
|
resource.match("/#{root_keys}(.:format)", match_opts.merge(:method => :get)).to(:action => "show").
|
127
|
-
name(singular).register_resource(klass_name)
|
127
|
+
name(singular).register_resource(klass_name, :identifiers => keys)
|
128
128
|
|
129
129
|
# => user defined member routes
|
130
130
|
member.each_pair do |action, method|
|
131
131
|
action = action.to_s
|
132
132
|
resource.match("/#{root_keys}/#{action}(.:format)", match_opts.merge(:method => method)).
|
133
|
-
to(:action => "#{action}").name(action, singular).register_resource(klass_name, action)
|
133
|
+
to(:action => "#{action}").name(action, singular).register_resource(klass_name, action, :identifiers => keys)
|
134
134
|
end
|
135
135
|
|
136
136
|
# => update
|
@@ -142,9 +142,11 @@ module Merb
|
|
142
142
|
to(:action => "destroy")
|
143
143
|
|
144
144
|
if block_given?
|
145
|
-
|
146
|
-
k
|
147
|
-
end
|
145
|
+
parent_keys = keys.map do |k|
|
146
|
+
k == :id ? "#{singular}_id".to_sym : k
|
147
|
+
end
|
148
|
+
|
149
|
+
nested_keys = parent_keys.map { |k| ":#{k}" }.join("/")
|
148
150
|
|
149
151
|
nested_match_opts = match_opts.except(:id)
|
150
152
|
nested_match_opts["#{singular}_id".to_sym] = match_opts[:id] if match_opts[:id]
|
@@ -160,10 +162,10 @@ module Merb
|
|
160
162
|
|
161
163
|
builders[:member] = lambda do |action, to, method|
|
162
164
|
resource.match("/#{root_keys}/#{action}(.:format)", match_opts.merge(:method => method)).
|
163
|
-
to(:action => to).name(action, singular).register_resource(klass_name, action)
|
165
|
+
to(:action => to).name(action, singular).register_resource(klass_name, action, :identifiers => keys)
|
164
166
|
end
|
165
167
|
|
166
|
-
resource.options(:name_prefix => singular, :resource_prefix => klass_name).
|
168
|
+
resource.options(:name_prefix => singular, :resource_prefix => klass_name, :parent_keys => parent_keys).
|
167
169
|
match("/#{nested_keys}", nested_match_opts).resource_block(builders, &block)
|
168
170
|
end
|
169
171
|
end # namespace
|
@@ -271,8 +273,11 @@ module Merb
|
|
271
273
|
|
272
274
|
#api private
|
273
275
|
def register_resource(*key)
|
274
|
-
key
|
276
|
+
options = extract_options_from_args!(key) || {}
|
277
|
+
key = [ @options[:resource_prefix], key ].flatten.compact
|
278
|
+
identifiers = [ @options[:parent_keys], options[:identifiers] ]
|
275
279
|
@route.resource = key
|
280
|
+
@route.resource_identifiers = identifiers.flatten.compact.map { |id| id.to_sym }
|
276
281
|
self
|
277
282
|
end
|
278
283
|
|
@@ -12,7 +12,7 @@ module Merb
|
|
12
12
|
|
13
13
|
attr_reader :conditions, :params, :segments
|
14
14
|
attr_reader :index, :variables, :name
|
15
|
-
attr_accessor :fixation
|
15
|
+
attr_accessor :fixation, :resource_identifiers
|
16
16
|
|
17
17
|
def initialize(conditions, params, deferred_procs, options = {})
|
18
18
|
@conditions, @params = conditions, params
|
@@ -23,11 +23,10 @@ module Merb
|
|
23
23
|
@redirect_url = @params[:url]
|
24
24
|
@defaults = {}
|
25
25
|
else
|
26
|
+
@generatable = true
|
26
27
|
@defaults = options[:defaults] || {}
|
27
28
|
end
|
28
29
|
|
29
|
-
# @conditional_block = conditional_block
|
30
|
-
|
31
30
|
@identifiers = options[:identifiers]
|
32
31
|
@deferred_procs = deferred_procs
|
33
32
|
@segments = []
|
@@ -39,6 +38,10 @@ module Merb
|
|
39
38
|
def regexp?
|
40
39
|
@regexp
|
41
40
|
end
|
41
|
+
|
42
|
+
def generatable?
|
43
|
+
@generatable && !regexp?
|
44
|
+
end
|
42
45
|
|
43
46
|
def allow_fixation?
|
44
47
|
@fixation
|
@@ -99,8 +102,11 @@ module Merb
|
|
99
102
|
#
|
100
103
|
# ==== Returns
|
101
104
|
# String:: The generated URL.
|
102
|
-
def generate(args = [], defaults = {})
|
103
|
-
|
105
|
+
def generate(args = [], defaults = {}, resource = false)
|
106
|
+
unless generatable?
|
107
|
+
raise GenerationError, "Cannot generate regexp Routes" if regexp?
|
108
|
+
raise GenerationError, "Cannot generate this route"
|
109
|
+
end
|
104
110
|
|
105
111
|
params = extract_options_from_args!(args) || { }
|
106
112
|
|
@@ -111,7 +117,7 @@ module Merb
|
|
111
117
|
# Support for anonymous params
|
112
118
|
unless args.empty?
|
113
119
|
# First, let's determine which variables are missing
|
114
|
-
variables = @variables - params.keys
|
120
|
+
variables = (resource ? @resource_identifiers : @variables) - params.keys
|
115
121
|
|
116
122
|
args.each do |param|
|
117
123
|
raise GenerationError, "The route has #{@variables.length} variables: #{@variables.inspect}" if variables.empty?
|
@@ -336,14 +342,17 @@ module Merb
|
|
336
342
|
|
337
343
|
def compile_conditions
|
338
344
|
@original_conditions = conditions.dup
|
339
|
-
|
340
|
-
if path
|
341
|
-
path = [path].flatten.compact
|
345
|
+
|
346
|
+
if conditions[:path] && !conditions[:path].empty?
|
347
|
+
path = conditions[:path].flatten.compact
|
342
348
|
if path = compile_path(path)
|
343
349
|
conditions[:path] = Regexp.new("^#{path}$")
|
344
350
|
else
|
345
351
|
conditions.delete(:path)
|
346
352
|
end
|
353
|
+
else
|
354
|
+
# If there is no path, we can't generate it
|
355
|
+
@generatable = false
|
347
356
|
end
|
348
357
|
end
|
349
358
|
|
@@ -253,7 +253,7 @@ module Merb
|
|
253
253
|
app = "merb#{" : #{name}" if (name && name != "merb")}"
|
254
254
|
max_port = Merb::Config[:cluster] ? (Merb::Config[:cluster] - 1) : 0
|
255
255
|
numbers = ((whoami != :worker) && (max_port > 0)) ? "#{port}..#{port + max_port}" : port
|
256
|
-
file = Merb::Config[:socket_file]
|
256
|
+
file = Merb::Config[:socket_file] % port if Merb::Config[:socket_file]
|
257
257
|
|
258
258
|
listening_on = if Merb::Config[:socket]
|
259
259
|
"socket#{'s' if max_port > 0 && whoami != :worker} #{numbers} "\
|
@@ -1,5 +1,13 @@
|
|
1
|
-
|
1
|
+
begin
|
2
|
+
require 'mongrel'
|
3
|
+
rescue LoadError => e
|
4
|
+
Merb.fatal! "Mongrel is not installed, but you are trying to use it. " \
|
5
|
+
"You need to either install mongrel or a different Ruby web " \
|
6
|
+
"server, like thin."
|
7
|
+
end
|
8
|
+
|
2
9
|
require 'merb-core/rack/handler/mongrel'
|
10
|
+
|
3
11
|
module Merb
|
4
12
|
|
5
13
|
module Rack
|
@@ -83,6 +83,7 @@ module GemManagement
|
|
83
83
|
if installer.installed_gems.empty? && exception
|
84
84
|
error "Failed to install gem '#{gem} (#{version || 'any version'})' (#{exception.message})"
|
85
85
|
end
|
86
|
+
ensure_bin_wrapper_for_installed_gems(installer.installed_gems, options)
|
86
87
|
installer.installed_gems.each do |spec|
|
87
88
|
success "Successfully installed #{spec.full_name}"
|
88
89
|
end
|
@@ -110,6 +111,7 @@ module GemManagement
|
|
110
111
|
if installer.installed_gems.empty? && exception
|
111
112
|
error "Failed to install gem '#{gem}' (#{e.message})"
|
112
113
|
end
|
114
|
+
ensure_bin_wrapper_for_installed_gems(installer.installed_gems, options)
|
113
115
|
installer.installed_gems.each do |spec|
|
114
116
|
success "Successfully installed #{spec.full_name}"
|
115
117
|
end
|
@@ -123,8 +125,8 @@ module GemManagement
|
|
123
125
|
# install_gem_from_source(source_dir, :skip => [...])
|
124
126
|
def install_gem_from_source(source_dir, *args)
|
125
127
|
installed_gems = []
|
126
|
-
|
127
|
-
|
128
|
+
opts = args.last.is_a?(Hash) ? args.pop : {}
|
129
|
+
Dir.chdir(source_dir) do
|
128
130
|
gem_name = args[0] || File.basename(source_dir)
|
129
131
|
gem_pkg_dir = File.join(source_dir, 'pkg')
|
130
132
|
gem_pkg_glob = File.join(gem_pkg_dir, "#{gem_name}-*.gem")
|
@@ -164,6 +166,8 @@ module GemManagement
|
|
164
166
|
end
|
165
167
|
end
|
166
168
|
|
169
|
+
ensure_bin_wrapper_for(opts[:install_dir], opts[:bin_dir], *installed_gems)
|
170
|
+
|
167
171
|
# Finally install the main gem
|
168
172
|
if install_pkg(Dir[gem_pkg_glob][0], opts.merge(:refresh => refresh))
|
169
173
|
installed_gems = refresh
|
@@ -293,6 +297,13 @@ module GemManagement
|
|
293
297
|
end
|
294
298
|
end
|
295
299
|
|
300
|
+
def ensure_bin_wrapper_for_installed_gems(gemspecs, options)
|
301
|
+
if options[:install_dir] && options[:bin_dir]
|
302
|
+
gems = gemspecs.map { |spec| spec.name }
|
303
|
+
ensure_bin_wrapper_for(options[:install_dir], options[:bin_dir], *gems)
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
296
307
|
private
|
297
308
|
|
298
309
|
def executable_wrapper(spec, bin_file_name, minigems = true)
|
@@ -313,9 +324,9 @@ rescue LoadError
|
|
313
324
|
require '#{then_req}'
|
314
325
|
end
|
315
326
|
|
316
|
-
if
|
317
|
-
|
318
|
-
$BUNDLE = true; Gem.clear_paths; Gem.path.
|
327
|
+
# use gems dir if ../gems exists - eg. only for ./bin/#{bin_file_name}
|
328
|
+
if File.directory?(gems_dir = File.join(File.dirname(__FILE__), '..', 'gems'))
|
329
|
+
$BUNDLE = true; Gem.clear_paths; Gem.path.replace([gems_dir])
|
319
330
|
ENV["PATH"] = "\#{File.dirname(__FILE__)}:\#{gems_dir}/bin:\#{ENV["PATH"]}"
|
320
331
|
if (local_gem = Dir[File.join(gems_dir, "specifications", "#{spec.name}-*.gemspec")].last)
|
321
332
|
version = File.basename(local_gem)[/-([\\.\\d]+)\\.gemspec$/, 1]
|
data/lib/merb-core/test.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
begin
|
2
|
-
|
3
|
-
|
4
|
-
rescue
|
5
|
-
end
|
1
|
+
# begin
|
2
|
+
# require "hpricot"
|
3
|
+
# require 'merb-core/test/test_ext/hpricot'
|
4
|
+
# rescue
|
5
|
+
# end
|
6
6
|
|
7
7
|
require 'merb-core/test/test_ext/object'
|
8
8
|
require 'merb-core/test/test_ext/string'
|
@@ -10,8 +10,9 @@ require 'merb-core/test/test_ext/string'
|
|
10
10
|
module Merb; module Test; end; end
|
11
11
|
|
12
12
|
require 'merb-core/test/helpers'
|
13
|
+
require 'merb-core/test/webrat'
|
13
14
|
|
14
15
|
if Merb.test_framework.to_s == "rspec"
|
15
16
|
require 'merb-core/test/test_ext/rspec'
|
16
17
|
require 'merb-core/test/matchers'
|
17
|
-
end
|
18
|
+
end
|
@@ -7,5 +7,4 @@ require "merb-core/test/helpers/mock_request_helper"
|
|
7
7
|
require "merb-core/test/helpers/route_helper"
|
8
8
|
require "merb-core/test/helpers/request_helper"
|
9
9
|
require "merb-core/test/helpers/multipart_request_helper"
|
10
|
-
require "merb-core/test/helpers/controller_helper"
|
11
|
-
require "merb-core/test/helpers/view_helper"
|
10
|
+
require "merb-core/test/helpers/controller_helper"
|
@@ -7,4 +7,7 @@ require "merb-core/test/matchers/request_matchers"
|
|
7
7
|
|
8
8
|
Merb::Test::ControllerHelper.send(:include, Merb::Test::Rspec::ControllerMatchers)
|
9
9
|
Merb::Test::RouteHelper.send(:include, Merb::Test::Rspec::RouteMatchers)
|
10
|
-
|
10
|
+
|
11
|
+
module Merb::Test::ViewHelper
|
12
|
+
include Merb::Test::Rspec::ViewMatchers
|
13
|
+
end
|
@@ -1,11 +1,27 @@
|
|
1
1
|
module Merb::Test::Rspec::ViewMatchers
|
2
|
+
|
2
3
|
class HaveXpath
|
3
|
-
def initialize(expected,
|
4
|
-
|
4
|
+
def initialize(expected, &block)
|
5
|
+
# Require nokogiri and fall back on rexml
|
6
|
+
begin
|
7
|
+
require "nokogiri"
|
8
|
+
rescue LoadError => e
|
9
|
+
if require "rexml/document"
|
10
|
+
require "merb-core/vendor/nokogiri/css"
|
11
|
+
warn("Standard REXML library is slow. Please consider installing nokogiri.\nUse \"sudo gem install nokogiri\"")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
@expected = expected
|
16
|
+
@block = block
|
5
17
|
end
|
6
18
|
|
7
19
|
def matches?(stringlike)
|
8
|
-
|
20
|
+
if defined?(Nokogiri::XML)
|
21
|
+
matches_nokogiri?(stringlike)
|
22
|
+
else
|
23
|
+
matches_rexml?(stringlike)
|
24
|
+
end
|
9
25
|
end
|
10
26
|
|
11
27
|
def matches_rexml?(stringlike)
|
@@ -17,23 +33,39 @@ module Merb::Test::Rspec::ViewMatchers
|
|
17
33
|
when REXML::Node
|
18
34
|
stringlike
|
19
35
|
when StringIO, String
|
20
|
-
|
36
|
+
begin
|
37
|
+
REXML::Document.new(stringlike.to_s).root
|
38
|
+
rescue REXML::ParseException => e
|
39
|
+
if e.message.include?("second root element")
|
40
|
+
REXML::Document.new("<fake-root-element>#{stringlike}</fake-root-element>").root
|
41
|
+
else
|
42
|
+
raise e
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
query.all? do |q|
|
48
|
+
matched = REXML::XPath.match(@document, q)
|
49
|
+
matched.any? && (!block_given? || matched.all?(&@block))
|
21
50
|
end
|
22
|
-
!REXML::XPath.match(@document, @expected).empty?
|
23
51
|
end
|
24
52
|
|
25
|
-
def
|
53
|
+
def matches_nokogiri?(stringlike)
|
26
54
|
stringlike = stringlike.body.to_s if stringlike.respond_to?(:body)
|
27
55
|
|
28
56
|
@document = case stringlike
|
29
|
-
when
|
57
|
+
when Nokogiri::HTML::Document, Nokogiri::XML::NodeSet
|
30
58
|
stringlike
|
31
59
|
when StringIO
|
32
|
-
|
60
|
+
Nokogiri::HTML(stringlike.string)
|
33
61
|
else
|
34
|
-
|
62
|
+
Nokogiri::HTML(stringlike.to_s)
|
35
63
|
end
|
36
|
-
|
64
|
+
@document.xpath(*query).any?
|
65
|
+
end
|
66
|
+
|
67
|
+
def query
|
68
|
+
[@expected].flatten.compact
|
37
69
|
end
|
38
70
|
|
39
71
|
# ==== Returns
|
@@ -49,238 +81,71 @@ module Merb::Test::Rspec::ViewMatchers
|
|
49
81
|
end
|
50
82
|
end
|
51
83
|
|
52
|
-
class HaveSelector
|
53
|
-
|
54
|
-
# ==== Parameters
|
55
|
-
# expected<String>:: The string to look for.
|
56
|
-
def initialize(expected)
|
57
|
-
@expected = expected
|
58
|
-
end
|
59
|
-
|
60
|
-
# ==== Parameters
|
61
|
-
# stringlike<Hpricot::Elem, StringIO, String>:: The thing to search in.
|
62
|
-
#
|
63
|
-
# ==== Returns
|
64
|
-
# Boolean:: True if there was at least one match.
|
65
|
-
def matches?(stringlike)
|
66
|
-
@document = case stringlike
|
67
|
-
when Hpricot::Elem
|
68
|
-
stringlike
|
69
|
-
when StringIO
|
70
|
-
Hpricot.parse(stringlike.string)
|
71
|
-
else
|
72
|
-
Hpricot.parse(stringlike)
|
73
|
-
end
|
74
|
-
!@document.search(@expected).empty?
|
75
|
-
end
|
84
|
+
class HaveSelector < HaveXpath
|
76
85
|
|
77
86
|
# ==== Returns
|
78
87
|
# String:: The failure message.
|
79
88
|
def failure_message
|
80
89
|
"expected following text to match selector #{@expected}:\n#{@document}"
|
81
90
|
end
|
82
|
-
|
91
|
+
|
83
92
|
# ==== Returns
|
84
93
|
# String:: The failure message to be displayed in negative matches.
|
85
94
|
def negative_failure_message
|
86
95
|
"expected following text to not match selector #{@expected}:\n#{@document}"
|
87
96
|
end
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
# ==== Parameters
|
93
|
-
# name<~to_s>:: The name of the tag to look for.
|
94
|
-
# attrs<Hash>:: Attributes to look for in the tag (see below).
|
95
|
-
#
|
96
|
-
# ==== Options (attrs)
|
97
|
-
# :content<String>:: Optional content to match.
|
98
|
-
def initialize(name, attrs)
|
99
|
-
@name, @attrs = name, attrs
|
100
|
-
@content = @attrs.delete(:content)
|
101
|
-
end
|
102
|
-
|
103
|
-
# ==== Parameters
|
104
|
-
# target<String>:: The string to look for the tag in.
|
105
|
-
#
|
106
|
-
# ==== Returns
|
107
|
-
# Boolean:: True if the tag matched.
|
108
|
-
def matches?(target)
|
109
|
-
@errors = []
|
110
|
-
unless target.include?("<#{@name}")
|
111
|
-
@errors << "Expected a <#{@name}>, but was #{target}"
|
112
|
-
end
|
113
|
-
@attrs.each do |attr, val|
|
114
|
-
unless target.include?("#{attr}=\"#{val}\"")
|
115
|
-
@errors << "Expected #{attr}=\"#{val}\", but was #{target}"
|
116
|
-
end
|
117
|
-
end
|
118
|
-
if @content
|
119
|
-
unless target.include?(">#{@content}<")
|
120
|
-
@errors << "Expected #{target} to include #{@content}"
|
121
|
-
end
|
122
|
-
end
|
123
|
-
@errors.size == 0
|
97
|
+
|
98
|
+
def query
|
99
|
+
Nokogiri::CSS::Parser.parse(*super).map { |ast| ast.to_xpath }
|
124
100
|
end
|
125
|
-
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
class HaveTag < HaveSelector
|
105
|
+
|
126
106
|
# ==== Returns
|
127
107
|
# String:: The failure message.
|
128
108
|
def failure_message
|
129
|
-
@
|
109
|
+
"expected following output to contain a #{tag_inspect} tag:\n#{@document}"
|
130
110
|
end
|
131
|
-
|
111
|
+
|
132
112
|
# ==== Returns
|
133
113
|
# String:: The failure message to be displayed in negative matches.
|
134
114
|
def negative_failure_message
|
135
|
-
"
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
class NotMatchTag
|
140
|
-
|
141
|
-
# === Parameters
|
142
|
-
# attrs<Hash>:: A set of attributes that must not be matched.
|
143
|
-
def initialize(attrs)
|
144
|
-
@attrs = attrs
|
145
|
-
end
|
146
|
-
|
147
|
-
# ==== Parameters
|
148
|
-
# target<String>:: The target to look for the match in.
|
149
|
-
#
|
150
|
-
# ==== Returns
|
151
|
-
# Boolean:: True if none of the attributes were matched.
|
152
|
-
def matches?(target)
|
153
|
-
@errors = []
|
154
|
-
@attrs.each do |attr, val|
|
155
|
-
if target.include?("#{attr}=\"#{val}\"")
|
156
|
-
@errors << "Should not include #{attr}=\"#{val}\", but was #{target}"
|
157
|
-
end
|
158
|
-
end
|
159
|
-
@errors.size == 0
|
160
|
-
end
|
161
|
-
|
162
|
-
# ==== Returns
|
163
|
-
# String:: The failure message.
|
164
|
-
def failure_message
|
165
|
-
@errors[0]
|
115
|
+
"expected following output to omit a #{tag_inspect}:\n#{@document}"
|
166
116
|
end
|
167
|
-
end
|
168
|
-
|
169
|
-
class HasTag
|
170
117
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
@id, @class = @attributes.delete(:id), @attributes.delete(:class)
|
179
|
-
@blk = blk
|
180
|
-
end
|
181
|
-
|
182
|
-
# ==== Parameters
|
183
|
-
# stringlike<Hpricot::Elem, StringIO, String>:: The thing to search in.
|
184
|
-
# &blk:: An optional block for searching in child elements using with_tag.
|
185
|
-
#
|
186
|
-
# ==== Returns
|
187
|
-
# Boolean:: True if there was at least one match.
|
188
|
-
def matches?(stringlike, &blk)
|
189
|
-
@document = case stringlike
|
190
|
-
when Hpricot::Elem
|
191
|
-
stringlike
|
192
|
-
when StringIO
|
193
|
-
Hpricot.parse(stringlike.string)
|
194
|
-
else
|
195
|
-
Hpricot.parse(stringlike)
|
118
|
+
def tag_inspect
|
119
|
+
options = @expected.last.dup
|
120
|
+
content = options.delete(:content)
|
121
|
+
|
122
|
+
html = "<#{@expected.first}"
|
123
|
+
options.each do |k,v|
|
124
|
+
html << " #{k}='#{v}'"
|
196
125
|
end
|
197
126
|
|
198
|
-
|
199
|
-
|
200
|
-
unless @blk.nil?
|
201
|
-
!@document.search(selector).select do |ele|
|
202
|
-
begin
|
203
|
-
@blk.call ele
|
204
|
-
true
|
205
|
-
rescue Spec::Expectations::ExpectationNotMetError
|
206
|
-
@error_message = "#{tag_for_error}:\n" + $!.message
|
207
|
-
false
|
208
|
-
end
|
209
|
-
end.empty?
|
127
|
+
if content
|
128
|
+
html << ">#{content}</#{@expected.first}>"
|
210
129
|
else
|
211
|
-
|
130
|
+
html << "/>"
|
212
131
|
end
|
213
|
-
|
214
|
-
|
215
|
-
# ==== Returns
|
216
|
-
# String:: The complete selector for element queries.
|
217
|
-
def selector
|
218
|
-
@selector = @outer_has_tag ? @outer_has_tag.selector : ''
|
219
|
-
|
220
|
-
@selector << "//#{@tag}#{id_selector}#{class_selector}"
|
221
|
-
@selector << @attributes.map{|a, v| "[@#{a}=\"#{v}\"]"}.join
|
222
|
-
end
|
223
|
-
|
224
|
-
# ==== Returns
|
225
|
-
# String:: ID selector for use in element queries.
|
226
|
-
def id_selector
|
227
|
-
"##{@id}" if @id
|
228
|
-
end
|
229
|
-
|
230
|
-
# ==== Returns
|
231
|
-
# String:: Class selector for use in element queries.
|
232
|
-
def class_selector
|
233
|
-
".#{@class}" if @class
|
234
|
-
end
|
235
|
-
|
236
|
-
# ==== Returns
|
237
|
-
# String:: The failure message.
|
238
|
-
def failure_message
|
239
|
-
@error_message || "expected following output to contain a #{tag_for_error} tag:\n#{@document}"
|
240
|
-
end
|
241
|
-
|
242
|
-
# ==== Returns
|
243
|
-
# String:: The failure message to be displayed in negative matches.
|
244
|
-
def negative_failure_message
|
245
|
-
@error_message || "expected following output to omit a #{tag_for_error} tag:\n#{@document}"
|
132
|
+
|
133
|
+
html
|
246
134
|
end
|
247
135
|
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
end
|
254
|
-
|
255
|
-
# ==== Returns
|
256
|
-
# String:: ID for the error tag.
|
257
|
-
def id_for_error
|
258
|
-
"##{@id}" unless @id.nil?
|
259
|
-
end
|
260
|
-
|
261
|
-
# ==== Returns
|
262
|
-
# String:: Class for the error tag.
|
263
|
-
def class_for_error
|
264
|
-
".#{@class}" unless @class.nil?
|
265
|
-
end
|
266
|
-
|
267
|
-
# ==== Returns
|
268
|
-
# String:: Class for the error tag.
|
269
|
-
def attributes_for_error
|
270
|
-
@attributes.map{|a,v| "[#{a}=\"#{v}\"]"}.join
|
271
|
-
end
|
272
|
-
|
273
|
-
# Search for a child tag within a have_tag block.
|
274
|
-
#
|
275
|
-
# ==== Parameters
|
276
|
-
# tag<~to_s>:: The tag to look for.
|
277
|
-
# attributes<Hash>:: Attributes for the tag (see below).
|
278
|
-
def with_tag(name, attrs={})
|
279
|
-
@inner_has_tag = HasTag.new(name, attrs)
|
280
|
-
@inner_has_tag.outer_has_tag = self
|
136
|
+
def query
|
137
|
+
options = @expected.last.dup
|
138
|
+
selector = @expected.first.to_s
|
139
|
+
|
140
|
+
selector << ":contains('#{options.delete(:content)}')" if options[:content]
|
281
141
|
|
282
|
-
|
142
|
+
options.each do |key, value|
|
143
|
+
selector << "[#{key}='#{value}']"
|
144
|
+
end
|
145
|
+
|
146
|
+
Nokogiri::CSS::Parser.parse(selector).map { |ast| ast.to_xpath }
|
283
147
|
end
|
148
|
+
|
284
149
|
end
|
285
150
|
|
286
151
|
class HasContent
|
@@ -289,6 +154,7 @@ module Merb::Test::Rspec::ViewMatchers
|
|
289
154
|
end
|
290
155
|
|
291
156
|
def matches?(element)
|
157
|
+
element = element.body.to_s if element.respond_to?(:body)
|
292
158
|
@element = element
|
293
159
|
|
294
160
|
case @content
|
@@ -320,83 +186,45 @@ module Merb::Test::Rspec::ViewMatchers
|
|
320
186
|
end
|
321
187
|
end
|
322
188
|
end
|
323
|
-
|
324
|
-
# ==== Parameters
|
325
|
-
# name<~to_s>:: The name of the tag to look for.
|
326
|
-
# attrs<Hash>:: Attributes to look for in the tag (see below).
|
327
|
-
#
|
328
|
-
# ==== Options (attrs)
|
329
|
-
# :content<String>:: Optional content to match.
|
330
|
-
#
|
331
|
-
# ==== Returns
|
332
|
-
# MatchTag:: A new match tag matcher.
|
333
|
-
def match_tag(name, attrs={})
|
334
|
-
MatchTag.new(name, attrs)
|
335
|
-
end
|
336
189
|
|
337
|
-
#
|
338
|
-
# attrs<Hash>:: A set of attributes that must not be matched.
|
190
|
+
# Matches HTML content against a CSS 3 selector.
|
339
191
|
#
|
340
|
-
# ==== Returns
|
341
|
-
# NotMatchTag:: A new not match tag matcher.
|
342
|
-
def not_match_tag(attrs)
|
343
|
-
NotMatchTag.new(attrs)
|
344
|
-
end
|
345
|
-
|
346
192
|
# ==== Parameters
|
347
|
-
# expected<String>:: The
|
193
|
+
# expected<String>:: The CSS selector to look for.
|
348
194
|
#
|
349
195
|
# ==== Returns
|
350
196
|
# HaveSelector:: A new have selector matcher.
|
197
|
+
# ---
|
198
|
+
# @api public
|
351
199
|
def have_selector(expected)
|
352
200
|
HaveSelector.new(expected)
|
353
201
|
end
|
354
202
|
alias_method :match_selector, :have_selector
|
355
203
|
|
356
|
-
|
357
|
-
begin
|
358
|
-
require "libxml"
|
359
|
-
type = "libxml"
|
360
|
-
rescue LoadError => e
|
361
|
-
if require "rexml/document" # show warning only once
|
362
|
-
warn(<<-WARN_TEXT)
|
363
|
-
Standard REXML library is slow. Please consider to install libxml-ruby.
|
364
|
-
Use "sudo gem install libxml-ruby"
|
365
|
-
WARN_TEXT
|
366
|
-
end
|
367
|
-
type = "rexml"
|
368
|
-
end
|
369
|
-
HaveXpath.new(expected, type)
|
370
|
-
end
|
371
|
-
alias_method :match_xpath, :have_xpath
|
372
|
-
|
373
|
-
# RSpec matcher to test for the presence of tags.
|
204
|
+
# Matches HTML content against an XPath query
|
374
205
|
#
|
375
206
|
# ==== Parameters
|
376
|
-
#
|
377
|
-
# attributes<Hash>:: Tag attributes.
|
207
|
+
# expected<String>:: The XPath query to look for.
|
378
208
|
#
|
379
209
|
# ==== Returns
|
380
|
-
#
|
381
|
-
#
|
382
|
-
#
|
383
|
-
|
384
|
-
|
385
|
-
#
|
386
|
-
# # Check for <span id="notice">
|
387
|
-
# body.should have_tag("span", :id => :notice)
|
388
|
-
#
|
389
|
-
# # Check for <h1 id="foo" class="bar">
|
390
|
-
# body.should have_tag(:h2, :class => "bar", :id => "foo")
|
391
|
-
#
|
392
|
-
# # Check for <div attr="val">
|
393
|
-
# body.should have_tag(:div, :attr => :val)
|
394
|
-
def have_tag(tag, attributes = {}, &blk)
|
395
|
-
HasTag.new(tag, attributes, &blk)
|
210
|
+
# HaveXpath:: A new have xpath matcher.
|
211
|
+
# ---
|
212
|
+
# @api public
|
213
|
+
def have_xpath(expected)
|
214
|
+
HaveXpath.new(expected)
|
396
215
|
end
|
397
|
-
|
398
|
-
|
216
|
+
alias_method :match_xpath, :have_xpath
|
217
|
+
|
218
|
+
def have_tag(name, attributes = {})
|
219
|
+
HaveTag.new([name, attributes])
|
220
|
+
end
|
221
|
+
alias_method :match_tag, :have_tag
|
399
222
|
|
223
|
+
# Matches the contents of an HTML document with
|
224
|
+
# whatever string is supplied
|
225
|
+
#
|
226
|
+
# ---
|
227
|
+
# @api public
|
400
228
|
def contain(content)
|
401
229
|
HasContent.new(content)
|
402
230
|
end
|