merb-core 0.9.12 → 0.9.13

Sign up to get free protection for your applications and to get access to all the features.
@@ -322,6 +322,7 @@ class Merb::BootLoader::BuildFramework < Merb::BootLoader
322
322
  # ==== Returns
323
323
  # nil
324
324
  def run
325
+ $:.push Merb.root unless Merb.root == File.expand_path(Dir.pwd)
325
326
  build_framework
326
327
  nil
327
328
  end
@@ -283,6 +283,7 @@ class Merb::Controller < Merb::AbstractController
283
283
  # @api public
284
284
  def url(name, *args)
285
285
  args << params
286
+ name = request.route if name == :this
286
287
  Merb::Router.url(name, *args)
287
288
  end
288
289
 
@@ -219,13 +219,17 @@ module Merb
219
219
  # ---
220
220
  # @api plugin
221
221
  def url(name, *args)
222
- unless name.is_a?(Symbol)
223
- args.unshift(name)
224
- name = :default
225
- end
226
-
227
- unless route = Merb::Router.named_routes[name]
228
- raise Merb::Router::GenerationError, "Named route not found: #{name}"
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
@@ -586,6 +586,8 @@ module Merb
586
586
  #
587
587
  # @api private
588
588
  def full_name(name)
589
+ raise Error, ":this is reserved. Please pick another name." if name == :this
590
+
589
591
  if @route
590
592
  @route.name = name
591
593
  self
@@ -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
- nested_keys = keys.map do |k|
146
- k.to_s == "id" ? ":#{singular}_id" : ":#{k}"
147
- end.join("/")
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 = [@options[:resource_prefix], key].flatten.compact
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
- raise GenerationError, "Cannot generate regexp Routes" if regexp?
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 = conditions[: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
- require 'mongrel'
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
- Dir.chdir(source_dir) do
127
- opts = args.last.is_a?(Hash) ? args.pop : {}
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 File.directory?(gems_dir = File.join(Dir.pwd, 'gems')) ||
317
- File.directory?(gems_dir = File.join(File.dirname(__FILE__), '..', 'gems'))
318
- $BUNDLE = true; Gem.clear_paths; Gem.path.unshift(gems_dir)
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]
@@ -1,8 +1,8 @@
1
- begin
2
- require "hpricot"
3
- require 'merb-core/test/test_ext/hpricot'
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
- Merb::Test::ViewHelper.send(:include, Merb::Test::Rspec::ViewMatchers)
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, type)
4
- @expected, @type = expected, type
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
- send("matches_#{@type}?", stringlike)
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
- REXML::Document.new(stringlike).root
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 matches_libxml?(stringlike)
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 LibXML::XML::Document, LibXML::XML::Node
57
+ when Nokogiri::HTML::Document, Nokogiri::XML::NodeSet
30
58
  stringlike
31
59
  when StringIO
32
- LibXML::XML::HTMLParser.string(stringlike.string).parse
60
+ Nokogiri::HTML(stringlike.string)
33
61
  else
34
- LibXML::XML::HTMLParser.string(stringlike).parse
62
+ Nokogiri::HTML(stringlike.to_s)
35
63
  end
36
- !@document.find(@expected).empty?
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
- end
89
-
90
- class MatchTag
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
- @errors[0]
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
- "Expected not to match against <#{@name} #{@attrs.map{ |a,v| "#{a}=\"#{v}\"" }.join(" ")}> tag, but it matched"
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
- attr_accessor :outer_has_tag, :inner_has_tag
172
-
173
- # ==== Parameters
174
- # tag<~to_s>:: The tag to look for.
175
- # attributes<Hash>:: Attributes for the tag (see below).
176
- def initialize(tag, attributes = {}, &blk)
177
- @tag, @attributes = tag, attributes
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
- @blk = blk unless blk.nil?
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
- !@document.search(selector).empty?
130
+ html << "/>"
212
131
  end
213
- end
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
- # ==== Returns
249
- # String:: The tag used in failure messages.
250
- def tag_for_error
251
- result = "#{@tag}#{id_for_error}#{class_for_error}#{attributes_for_error}"
252
- inner_has_tag ? result << " > #{inner_has_tag.tag_for_error}" : result
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
- @inner_has_tag
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
- # ==== Parameters
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 string to look for.
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
- def have_xpath(expected)
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
- # tag<~to_s>:: The name of the tag.
377
- # attributes<Hash>:: Tag attributes.
207
+ # expected<String>:: The XPath query to look for.
378
208
  #
379
209
  # ==== Returns
380
- # HasTag:: A new has tag matcher.
381
- #
382
- # ==== Examples
383
- # # Check for <div>
384
- # body.should have_tag("div")
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
- alias_method :with_tag, :have_tag
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