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.
@@ -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