assert2 0.3.8 → 0.3.9

Sign up to get free protection for your applications and to get access to all the features.
data/lib/assert2/xhtml.rb CHANGED
@@ -56,191 +56,192 @@ requirements:
56
56
  - at fault time, the matcher prints out the failing elements
57
57
  and their immediate context.
58
58
 
59
- First, we take care of the paperwork. This spec works with Yuri's
60
- sample website. I add Nokogiri, for our XML engine:
61
59
  =end
62
60
 
63
61
  require 'nokogiri'
64
62
 
65
- =begin
66
- That block after "response.body.should be_html_with" answers
67
- Yuri's question. Any HTML we can think of, we can specify
68
- it in there.
63
+ class Nokogiri::XML::Node
69
64
 
70
- If we inject a fault, such as :name => 'user[first_nome]', we
71
- get this diagnostic:
65
+ class XPathYielder
66
+ def initialize(method_name, &block)
67
+ self.class.send :define_method, method_name do |*args|
68
+ raise 'must call with block' unless block
69
+ block.call(*args)
70
+ end
71
+ end
72
+ end
72
73
 
73
- <input type="text" name="user[first_nome]">
74
- does not match
75
- <fieldset>
76
- <legend>Personal Information</legend>
77
- <ol>
78
- <li id="control_user_first_name">
79
- <label for="user_first_name">First name</label>
80
- <input type="text" name="user[first_name]" id="user_first_name">
81
- </li>
82
- </ol>
83
- </fieldset>
74
+ def xpath_with_callback(path, method_name, &block)
75
+ xpath path, XPathYielder.new(method_name, &block)
76
+ end
84
77
 
85
- The diagnostic only reported the fault's immediate
86
- context - the <fieldset> where the matcher sought the
87
- errant <input> field. It would not, for example, spew
88
- an entire website into our faces.
78
+ end
89
79
 
90
- To support that specification, we will create a new
91
- RSpec "matcher":
92
- =end
80
+ class BeHtmlWith
93
81
 
94
- class BeHtmlWith
95
-
96
- def matches?(stwing, &block)
97
- # @scope.wrap_expectation self do
98
- begin
99
- bwock = block || @block || proc{}
100
- builder = Nokogiri::HTML::Builder.new(&bwock)
101
- match = builder.doc.root
102
- doc = Nokogiri::HTML(stwing)
103
- @last_match = 0
104
- @failure_message = match_nodes(match, doc)
105
- return @failure_message.nil?
106
- end
107
- # end
82
+ def get_texts(element)
83
+ element.xpath('text()').map{|x|x.to_s.strip}.reject{|x|x==''}.compact
84
+ end
85
+
86
+ def match_text(ref, sam)
87
+ ref_text = get_texts(ref)
88
+ # TODO regices?
89
+ ref_text.empty? or ( ref_text - get_texts(sam) ).empty?
90
+ end
91
+
92
+ def match_attributes_and_text(reference, sample)
93
+ reference.attribute_nodes.each do |attr|
94
+ sample[attr.name] == attr.value or return false
108
95
  end
109
96
 
110
- =begin
111
- The trick up our sleeve is Nokogiri::HTML::Builder. We passed
112
- the matching block into it - that's where all the 'form',
113
- 'fieldset', 'input', etc. elements came from. And this trick
114
- exposes both our target page and our matched elements to the
115
- full power of Nokogiri. Schema validation, for example, would
116
- be very easy.
117
-
118
- The matches? method works by building two DOMs, and forcing
119
- our page's DOM to satisfy each element, attribute, and text
120
- in our specification's DOM.
121
-
122
- To match nodes, we first find all nodes, by name, below
123
- the current node. Note that match_nodes() recurses. Then
124
- we throw away all nodes that don't satisfy our matching
125
- criteria.
126
-
127
- We pick the first node that passes that check, and
128
- then recursively match its children to each child,
129
- if any, from our matching node.
130
- =end
131
-
132
- # TODO does a multi-modal top axis work?
133
-
134
- def match_nodes(match, doc)
135
- node = doc.xpath("descendant::#{match.name.sub(/\!$/, '')}").
136
- select{|n| resemble(match, n) }.
137
- first or return complaint(match, doc)
138
-
139
- this_match = node.xpath('preceding::*').length
140
-
141
- if @last_match > this_match
142
- return complaint(match, doc, 'node is out of specified order!')
143
- end
144
-
145
- @last_match = this_match
146
-
147
- # http://www.zvon.org/xxl/XPathTutorial/Output/example18.html
148
- # The preceding axis contains all nodes in the same document
149
- # as the context node that are before the context node in
150
- # document order, excluding any ancestors and excluding
151
- # attribute nodes and namespace nodes
152
-
153
- #p [node.name, node.text]
154
- # p node.path if lastest
155
- #p node.text
156
- # p lastest.path if lastest
157
-
158
- # TODO try xpath('*')
159
- match.children.grep(Nokogiri::XML::Element).each do |child|
160
- issue = match_nodes(child, node) and
161
- return issue
162
- end
97
+ return match_text(reference, sample)
98
+ end
163
99
 
164
- return nil
100
+ def elements_equal(element_1, element_2)
101
+ raise 'programming error: mismatched elements' unless element_1.document == element_2.document
102
+ element_1.path == element_2.path
103
+ end
104
+
105
+ # end # TODO more "elements" less "nodes"
106
+
107
+ def collect_samples(elements, index)
108
+ samples = elements.find_all do |element|
109
+ match_attributes_and_text(@references[index], element)
165
110
  end
166
-
167
- =begin
168
- At any point in that recursion, if we can't find a match,
169
- we build a string describing that situation, and pass it
170
- back up the call stack. This immediately stops any iterating
171
- and recursing underway!
172
-
173
- Two nodes "resemble" each other if their names are the
174
- same (naturally!); if your matching element's
175
- attributes are a subset of your page's element's
176
- attributes, and if their text is similar:
177
- =end
178
111
 
179
- def resemble(match, node)
180
- keys = match.attributes.keys
181
- node_keys = valuate(node.attributes.select{|k,v| keys.include? k })
182
- match_keys = valuate(match.attributes)
183
- node_keys == match_keys or return false
112
+ @first_samples += samples if samples.any? and index == 0
113
+ return samples
114
+ end
115
+
116
+ attr_accessor :doc,
117
+ :scope
118
+
119
+ def matches?(stwing, &block)
120
+ @scope.wrap_expectation self do # TODO put that back online
121
+ begin
122
+ bwock = block || @block || proc{} # TODO what to do with no block? validate?
123
+ builder = Nokogiri::HTML::Builder.new(&bwock)
124
+ @doc = Nokogiri::HTML(stwing)
125
+ @reason = nil
126
+
127
+ builder.doc.children.each do |child|
128
+ @first_samples = []
129
+ # TODO warn if child is text
130
+ path = build_deep_xpath(child)
131
+ next if path == "//html[ refer(., '0') ]" # CONSIDER wtf is this?
132
+
133
+ matchers = doc.root.xpath_with_callback path, :refer do |elements, index|
134
+ collect_samples(elements, index.to_i)
135
+ end
136
+
137
+ if matchers.empty?
138
+ @first_samples << @doc.root if @first_samples.empty? # TODO test the first_samples system
139
+ @failure_message = complain_about(builder.doc.root, @first_samples)
140
+ return false
141
+ end # TODO use or lose @reason
142
+ end
184
143
 
185
- # TODO try
186
- # match_text = match.xpath('text()').map{|x|x.to_s}
187
- # node_text = match.xpath('text()').map{|x|x.to_s}
144
+ # TODO complain if too many matchers
188
145
 
189
- match_text = match.children.grep(Nokogiri::XML::Text).map{|t| t.to_s.strip }
190
- node_text = node .children.grep(Nokogiri::XML::Text).map{|t| t.to_s.strip }
191
- match_text.empty? or 0 == ( match_text - node_text ).length
146
+ return true
147
+ end
192
148
  end
149
+ end
193
150
 
194
- =begin
195
- That method cannot simply compare node.text, because Nokogiri
196
- conglomerates all that node's descendants' texts together, and
197
- these would gum up our search. So those elaborate lines with
198
- grep() and map() serve to extract all the current node's
199
- immediate textual children, then compare them as sets.
200
-
201
- Put another way, <form> does not appear to contain "First name".
202
- Specifications can only match text by declaring their immediate
203
- parent.
204
-
205
- The remaining support methods are self-explanatory. They
206
- prepare Node attributes for comparison, build our diagnostics,
207
- and plug our matcher object into RSpec:
208
- =end
209
-
210
- def valuate(attributes)
211
- attributes.inject({}) do |h,(k,v)|
212
- h.merge(k => v.value)
213
- end # this converts objects to strings, so our Hashes
214
- end # can compare for equality
215
-
216
- def complaint(node, match, berate = nil)
217
- "\n #{berate}".rstrip +
218
- "\n\n#{node.to_html}\n" +
219
- " does not match\n\n" +
220
- match.to_html
221
- end
151
+ def build_deep_xpath(element)
152
+ @references = []
153
+ return '//' + build_xpath(element)
154
+ end
155
+
156
+ attr_reader :references
222
157
 
223
- attr_accessor :failure_message
224
-
225
- def negative_failure_message
226
- "yack yack yack"
158
+ def build_xpath(element)
159
+ path = element.name.sub(/\!$/, '')
160
+ element_kids = element.children.grep(Nokogiri::XML::Element)
161
+ path << "[ refer(., '#{@references.length}')"
162
+ @references << element
163
+
164
+ if element_kids.any?
165
+ path << ' and ' +
166
+ element_kids.map{|child|
167
+ './descendant::' + build_xpath(child)
168
+ }.join(' and ')
227
169
  end
170
+
171
+ path << ' ]'
172
+ return path
173
+ end
174
+
175
+ def complain_about(refered, samples, reason = nil) # TODO put argumnets in order
176
+ reason = " (#{reason})" if reason
177
+ "\nCould not find this reference#{reason}...\n\n" +
178
+ refered.to_html +
179
+ "\n\n...in these sample(s)...\n\n" + # TODO how many samples?
180
+ samples.map{|s|s.to_html}.join("\n\n...or...\n\n")
181
+ end
182
+
183
+ def count_elements_to_node(container, element)
184
+ return 0 if elements_equal(container, element)
185
+ count = 0
228
186
 
229
- def initialize(scope, &block)
230
- @scope, @block = scope, block
187
+ container.children.each do |child|
188
+ sub_count = count_elements_to_node(child, element)
189
+ return count + sub_count if sub_count
190
+ count += 1
231
191
  end
192
+
193
+ return nil
194
+ end # TODO use or lose these
195
+
196
+ # TODO does a multi-modal top axis work?
197
+ # TODO this_match = node.xpath('preceding::*').length
198
+
199
+ # http://www.zvon.org/xxl/XPathTutorial/Output/example18.html
200
+ # The preceding axis contains all nodes in the same document
201
+ # as the context node that are before the context node in
202
+ # document order, excluding any ancestors and excluding
203
+ # attribute nodes and namespace nodes
204
+
205
+ attr_accessor :failure_message
206
+
207
+ def negative_failure_message
208
+ "TODO"
209
+ end
210
+
211
+ def initialize(scope, &block)
212
+ @scope, @block = scope, block
213
+ end
214
+
215
+ def self.create(stwing)
216
+ bhw = BeHtmlWith.new(nil)
217
+ bhw.doc = Nokogiri::HTML(stwing)
218
+ return bhw
232
219
  end
233
220
 
234
- module Test::Unit::Assertions
221
+ end
222
+
223
+
224
+ module Test; module Unit; module Assertions
225
+
226
+ def wrap_expectation whatever; yield; end unless defined? wrap_expectation
227
+
235
228
  def assert_xhtml(xhtml = @response.body, &block) # TODO merge
236
- _assert_xml(xhtml) # , XML::HTMLParser)
237
229
  if block
238
- # require 'should_be_html_with_spec'
239
230
  matcher = BeHtmlWith.new(self, &block)
240
231
  matcher.matches?(xhtml, &block)
241
232
  message = matcher.failure_message
242
233
  flunk message if message.to_s != ''
234
+ # return matcher.builder.doc.to_html # TODO return something reasonable
235
+ else
236
+ _assert_xml(xhtml)
237
+ return @xdoc
243
238
  end
244
- return @xdoc
245
239
  end
246
- end
240
+
241
+ end; end; end
242
+
243
+ module Spec; module Matchers
244
+ def be_html_with(&block)
245
+ BeHtmlWith.new(self, &block)
246
+ end
247
+ end; end
@@ -56,191 +56,193 @@ requirements:
56
56
  - at fault time, the matcher prints out the failing elements
57
57
  and their immediate context.
58
58
 
59
- First, we take care of the paperwork. This spec works with Yuri's
60
- sample website. I add Nokogiri, for our XML engine:
61
59
  =end
62
60
 
63
61
  require 'nokogiri'
64
62
 
65
- =begin
66
- That block after "response.body.should be_html_with" answers
67
- Yuri's question. Any HTML we can think of, we can specify
68
- it in there.
63
+ class Nokogiri::XML::Node
69
64
 
70
- If we inject a fault, such as :name => 'user[first_nome]', we
71
- get this diagnostic:
65
+ class XPathYielder
66
+ def initialize(method_name, &block)
67
+ self.class.send :define_method, method_name do |*args|
68
+ raise 'must call with block' unless block
69
+ block.call(*args)
70
+ end
71
+ end
72
+ end
72
73
 
73
- <input type="text" name="user[first_nome]">
74
- does not match
75
- <fieldset>
76
- <legend>Personal Information</legend>
77
- <ol>
78
- <li id="control_user_first_name">
79
- <label for="user_first_name">First name</label>
80
- <input type="text" name="user[first_name]" id="user_first_name">
81
- </li>
82
- </ol>
83
- </fieldset>
74
+ def xpath_with_callback(path, method_name, &block)
75
+ xpath path, XPathYielder.new(method_name, &block)
76
+ end
84
77
 
85
- The diagnostic only reported the fault's immediate
86
- context - the <fieldset> where the matcher sought the
87
- errant <input> field. It would not, for example, spew
88
- an entire website into our faces.
78
+ end
89
79
 
90
- To support that specification, we will create a new
91
- RSpec "matcher":
92
- =end
80
+ class BeHtmlWith
93
81
 
94
- class BeHtmlWith
95
-
96
- def matches?(stwing, &block)
97
- # @scope.wrap_expectation self do
98
- begin
99
- bwock = block || @block || proc{}
100
- builder = Nokogiri::HTML::Builder.new(&bwock)
101
- match = builder.doc.root
102
- doc = Nokogiri::HTML(stwing)
103
- @last_match = 0
104
- @failure_message = match_nodes(match, doc)
105
- return @failure_message.nil?
106
- end
107
- # end
82
+ def get_texts(element)
83
+ element.xpath('text()').map{|x|x.to_s.strip}.reject{|x|x==''}.compact
84
+ end
85
+
86
+ def match_text(ref, sam)
87
+ ref_text = get_texts(ref)
88
+ # TODO regices?
89
+ ref_text.empty? or ( ref_text - get_texts(sam) ).empty?
90
+ end
91
+
92
+ def match_attributes_and_text(reference, sample)
93
+ reference.attribute_nodes.each do |attr|
94
+ sample[attr.name] == attr.value or return false
108
95
  end
109
96
 
110
- =begin
111
- The trick up our sleeve is Nokogiri::HTML::Builder. We passed
112
- the matching block into it - that's where all the 'form',
113
- 'fieldset', 'input', etc. elements came from. And this trick
114
- exposes both our target page and our matched elements to the
115
- full power of Nokogiri. Schema validation, for example, would
116
- be very easy.
117
-
118
- The matches? method works by building two DOMs, and forcing
119
- our page's DOM to satisfy each element, attribute, and text
120
- in our specification's DOM.
121
-
122
- To match nodes, we first find all nodes, by name, below
123
- the current node. Note that match_nodes() recurses. Then
124
- we throw away all nodes that don't satisfy our matching
125
- criteria.
126
-
127
- We pick the first node that passes that check, and
128
- then recursively match its children to each child,
129
- if any, from our matching node.
130
- =end
131
-
132
- # TODO does a multi-modal top axis work?
133
-
134
- def match_nodes(match, doc)
135
- node = doc.xpath("descendant::#{match.name.sub(/\!$/, '')}").
136
- select{|n| resemble(match, n) }.
137
- first or return complaint(match, doc)
138
-
139
- this_match = node.xpath('preceding::*').length
140
-
141
- if @last_match > this_match
142
- return complaint(match, doc, 'node is out of specified order!')
143
- end
144
-
145
- @last_match = this_match
146
-
147
- # http://www.zvon.org/xxl/XPathTutorial/Output/example18.html
148
- # The preceding axis contains all nodes in the same document
149
- # as the context node that are before the context node in
150
- # document order, excluding any ancestors and excluding
151
- # attribute nodes and namespace nodes
152
-
153
- #p [node.name, node.text]
154
- # p node.path if lastest
155
- #p node.text
156
- # p lastest.path if lastest
157
-
158
- # TODO try xpath('*')
159
- match.children.grep(Nokogiri::XML::Element).each do |child|
160
- issue = match_nodes(child, node) and
161
- return issue
162
- end
97
+ return match_text(reference, sample)
98
+ end
163
99
 
164
- return nil
100
+ def elements_equal(element_1, element_2)
101
+ raise 'programming error: mismatched elements' unless element_1.document == element_2.document
102
+ element_1.path == element_2.path
103
+ end
104
+
105
+ # end # TODO more "elements" less "nodes"
106
+
107
+ def collect_samples(elements, index)
108
+ samples = elements.find_all do |element|
109
+ match_attributes_and_text(@references[index], element)
165
110
  end
166
-
167
- =begin
168
- At any point in that recursion, if we can't find a match,
169
- we build a string describing that situation, and pass it
170
- back up the call stack. This immediately stops any iterating
171
- and recursing underway!
172
-
173
- Two nodes "resemble" each other if their names are the
174
- same (naturally!); if your matching element's
175
- attributes are a subset of your page's element's
176
- attributes, and if their text is similar:
177
- =end
178
111
 
179
- def resemble(match, node)
180
- keys = match.attributes.keys
181
- node_keys = valuate(node.attributes.select{|k,v| keys.include? k })
182
- match_keys = valuate(match.attributes)
183
- node_keys == match_keys or return false
112
+ @first_samples += samples if samples.any? and index == 0
113
+ return samples
114
+ end
115
+
116
+ attr_accessor :doc,
117
+ :scope
118
+
119
+ def matches?(stwing, &block)
120
+ @scope.wrap_expectation self do # TODO put that back online
121
+ begin
122
+ bwock = block || @block || proc{} # TODO what to do with no block? validate?
123
+ builder = Nokogiri::HTML::Builder.new(&bwock)
124
+ puts builder.doc.root.to_html
125
+ @doc = Nokogiri::HTML(stwing)
126
+ @reason = nil
127
+
128
+ builder.doc.children.each do |child|
129
+ @first_samples = []
130
+ # TODO warn if child is text
131
+ path = build_deep_xpath(child)
132
+ next if path == "//html[ refer(., '0') ]" # CONSIDER wtf is this?
133
+
134
+ matchers = doc.root.xpath_with_callback path, :refer do |elements, index|
135
+ collect_samples(elements, index.to_i)
136
+ end
137
+
138
+ if matchers.empty?
139
+ @first_samples << @doc.root if @first_samples.empty? # TODO test the first_samples system
140
+ @failure_message = complain_about(builder.doc.root, @first_samples)
141
+ return false
142
+ end # TODO use or lose @reason
143
+ end
184
144
 
185
- # TODO try
186
- # match_text = match.xpath('text()').map{|x|x.to_s}
187
- # node_text = match.xpath('text()').map{|x|x.to_s}
145
+ # TODO complain if too many matchers
188
146
 
189
- match_text = match.children.grep(Nokogiri::XML::Text).map{|t| t.to_s.strip }
190
- node_text = node .children.grep(Nokogiri::XML::Text).map{|t| t.to_s.strip }
191
- match_text.empty? or 0 == ( match_text - node_text ).length
147
+ return true
148
+ end
192
149
  end
150
+ end
193
151
 
194
- =begin
195
- That method cannot simply compare node.text, because Nokogiri
196
- conglomerates all that node's descendants' texts together, and
197
- these would gum up our search. So those elaborate lines with
198
- grep() and map() serve to extract all the current node's
199
- immediate textual children, then compare them as sets.
200
-
201
- Put another way, <form> does not appear to contain "First name".
202
- Specifications can only match text by declaring their immediate
203
- parent.
204
-
205
- The remaining support methods are self-explanatory. They
206
- prepare Node attributes for comparison, build our diagnostics,
207
- and plug our matcher object into RSpec:
208
- =end
209
-
210
- def valuate(attributes)
211
- attributes.inject({}) do |h,(k,v)|
212
- h.merge(k => v.value)
213
- end # this converts objects to strings, so our Hashes
214
- end # can compare for equality
215
-
216
- def complaint(node, match, berate = nil)
217
- "\n #{berate}".rstrip +
218
- "\n\n#{node.to_html}\n" +
219
- " does not match\n\n" +
220
- match.to_html
221
- end
152
+ def build_deep_xpath(element)
153
+ @references = []
154
+ return '//' + build_xpath(element)
155
+ end
156
+
157
+ attr_reader :references
222
158
 
223
- attr_accessor :failure_message
224
-
225
- def negative_failure_message
226
- "yack yack yack"
159
+ def build_xpath(element)
160
+ path = element.name.sub(/\!$/, '')
161
+ element_kids = element.children.grep(Nokogiri::XML::Element)
162
+ path << "[ refer(., '#{@references.length}')"
163
+ @references << element
164
+
165
+ if element_kids.any?
166
+ path << ' and ' +
167
+ element_kids.map{|child|
168
+ './descendant::' + build_xpath(child)
169
+ }.join(' and ')
227
170
  end
171
+
172
+ path << ' ]'
173
+ return path
174
+ end
175
+
176
+ def complain_about(refered, samples, reason = nil) # TODO put argumnets in order
177
+ reason = " (#{reason})" if reason
178
+ "\nCould not find this reference#{reason}...\n\n" +
179
+ refered.to_html +
180
+ "\n\n...in these sample(s)...\n\n" + # TODO how many samples?
181
+ samples.map{|s|s.to_html}.join("\n\n...or...\n\n")
182
+ end
183
+
184
+ def count_elements_to_node(container, element)
185
+ return 0 if elements_equal(container, element)
186
+ count = 0
228
187
 
229
- def initialize(scope, &block)
230
- @scope, @block = scope, block
188
+ container.children.each do |child|
189
+ sub_count = count_elements_to_node(child, element)
190
+ return count + sub_count if sub_count
191
+ count += 1
231
192
  end
193
+
194
+ return nil
195
+ end # TODO use or lose these
196
+
197
+ # TODO does a multi-modal top axis work?
198
+ # TODO this_match = node.xpath('preceding::*').length
199
+
200
+ # http://www.zvon.org/xxl/XPathTutorial/Output/example18.html
201
+ # The preceding axis contains all nodes in the same document
202
+ # as the context node that are before the context node in
203
+ # document order, excluding any ancestors and excluding
204
+ # attribute nodes and namespace nodes
205
+
206
+ attr_accessor :failure_message
207
+
208
+ def negative_failure_message
209
+ "TODO"
210
+ end
211
+
212
+ def initialize(scope, &block)
213
+ @scope, @block = scope, block
214
+ end
215
+
216
+ def self.create(stwing)
217
+ bhw = BeHtmlWith.new(nil)
218
+ bhw.doc = Nokogiri::HTML(stwing)
219
+ return bhw
232
220
  end
233
221
 
234
- module Test::Unit::Assertions
222
+ end
223
+
224
+
225
+ module Test; module Unit; module Assertions
226
+
227
+ def wrap_expectation whatever; yield; end unless defined? wrap_expectation
228
+
235
229
  def assert_xhtml(xhtml = @response.body, &block) # TODO merge
236
- _assert_xml(xhtml) # , XML::HTMLParser)
237
230
  if block
238
- # require 'should_be_html_with_spec'
239
231
  matcher = BeHtmlWith.new(self, &block)
240
232
  matcher.matches?(xhtml, &block)
241
233
  message = matcher.failure_message
242
- flunk message if message.any?
234
+ flunk message if message.to_s != ''
235
+ # return matcher.builder.doc.to_html # TODO return something reasonable
236
+ else
237
+ _assert_xml(xhtml)
238
+ return @xdoc
243
239
  end
244
- return @xdoc
245
240
  end
246
- end
241
+
242
+ end; end; end
243
+
244
+ module Spec; module Matchers
245
+ def be_html_with(&block)
246
+ BeHtmlWith.new(self, &block)
247
+ end
248
+ end; end
data/lib/assert2/xpath.rb CHANGED
@@ -131,8 +131,7 @@ module Test; module Unit; module Assertions
131
131
  former_xdoc = @xdoc
132
132
  apa = AssertXPathArguments.new(path, id, options)
133
133
  node = @xdoc.xpath(apa.xpath) #, nil, apa.subs)
134
- # TODO advise Nokogiri to provide substitution arguments
135
-
134
+
136
135
  add_diagnostic :clear do
137
136
  diagnostic = "xpath: #{ apa.xpath.inspect }\n"
138
137
  diagnostic << "arguments: #{ apa.subs.pretty_inspect }\n" if apa.subs.any?
@@ -0,0 +1,213 @@
1
+ require 'test/unit'
2
+ require 'assert2'
3
+ require 'rexml/document'
4
+ require 'rexml/entity'
5
+ require 'rexml/formatters/pretty'
6
+ require 'nokogiri' # must be installed to use xpath{}!
7
+
8
+ module Test; module Unit; module Assertions
9
+
10
+ def assert_xhtml(xhtml)
11
+ return _assert_xml(xhtml) # , XML::HTMLParser)
12
+ end
13
+
14
+ def _assert_xml(xml) #, parser = XML::Parser)
15
+ if false
16
+ xp = parser.new()
17
+ xp.string = xml
18
+
19
+ if XML.respond_to? :'default_pedantic_parser='
20
+ XML.default_pedantic_parser = true
21
+ else
22
+ XML::Parser.default_pedantic_parser = true
23
+ end # CONSIDER uh, figure out the best libxml-ruby??
24
+
25
+ @xdoc = xp.parse.root
26
+ else
27
+ # CONSIDER figure out how entities are supposed to work!!
28
+ xml = xml.gsub('&mdash;', '--')
29
+ doc = REXML::Document.new(xml)
30
+ @xdoc = doc.root
31
+ end
32
+ end
33
+
34
+ def assert_xhtml_(xhtml)
35
+ return _assert_xml_(xhtml) # , XML::HTMLParser)
36
+ end
37
+
38
+ def _assert_xml_(xml) #, parser = XML::Parser)
39
+ if false
40
+ xp = parser.new()
41
+ xp.string = xml
42
+
43
+ if XML.respond_to? :'default_pedantic_parser='
44
+ XML.default_pedantic_parser = true
45
+ else
46
+ XML::Parser.default_pedantic_parser = true
47
+ end # CONSIDER uh, figure out the best libxml-ruby??
48
+
49
+ @xdoc = xp.parse.root
50
+ else
51
+ # TODO figure out how entities are supposed to work!!
52
+ xml = xml.gsub('&mdash;', '--')
53
+ doc = Nokogiri::XML(xml)
54
+ @xdoc = doc.root
55
+ end
56
+ end
57
+
58
+ class AssertXPathArguments
59
+
60
+ def initialize(path = '', id = nil, options = {})
61
+ @subs = {}
62
+ @xpath = ''
63
+ to_xpath(path, id, options)
64
+ end
65
+
66
+ attr_reader :subs
67
+ attr_reader :xpath
68
+
69
+ def to_conditions(hash)
70
+ xml_attribute_name = /^[a-z][_a-z0-9]+$/i # CONSIDER is that an XML attribute name match?
71
+
72
+ @xpath << hash.map{|k, v|
73
+ sk = k.to_s
74
+ sk = '_text' if sk == '.' or k == 46
75
+ k = '.' if k == 46 and RUBY_VERSION < '1.9.0'
76
+ @subs[sk] = v.to_s
77
+ "#{ '@' if k.to_s =~ xml_attribute_name }#{k} = $#{sk}"
78
+ }.join(' and ')
79
+ end
80
+
81
+ def to_predicate(hash, options)
82
+ hash = { :id => hash } if hash.kind_of? Symbol
83
+ hash.merge! options
84
+ @xpath << '[ '
85
+ to_conditions(hash)
86
+ @xpath << ' ]'
87
+ end
88
+
89
+ def to_xpath(path, id, options)
90
+ @xpath = path
91
+ @xpath = "descendant-or-self::#{ @xpath }" if @xpath.kind_of? Symbol
92
+ to_predicate(id, options) if id
93
+ end
94
+
95
+ end
96
+
97
+ # if node = @xdoc.find_first(path) ## for libxml
98
+ # def node.text
99
+ # find_first('text()').to_s
100
+ # end
101
+
102
+ def xpath(path, id = nil, options = {}, &block)
103
+ former_xdoc = @xdoc
104
+ apa = AssertXPathArguments.new(path, id, options)
105
+ node = REXML::XPath.first(@xdoc, apa.xpath, nil, apa.subs)
106
+
107
+ add_diagnostic :clear do
108
+ diagnostic = "xpath: #{ apa.xpath.inspect }\n"
109
+ diagnostic << "arguments: #{ apa.subs.pretty_inspect }\n" if apa.subs.any?
110
+ diagnostic + "xml context:\n" + indent_xml
111
+ end
112
+
113
+ if node
114
+ def node.[](symbol)
115
+ return attributes[symbol.to_s]
116
+ end
117
+ end
118
+
119
+ if block
120
+ assert_('this xpath cannot find a node', :keep_diagnostics => true){ node }
121
+ assert_ nil, :args => [@xdoc = node], :keep_diagnostics => true, &block # TODO need the _ ?
122
+ end
123
+
124
+ return node
125
+ # TODO raid http://thebogles.com/blog/an-hpricot-style-interface-to-libxml/
126
+ ensure
127
+ @xdoc = former_xdoc
128
+ end # TODO trap LibXML::XML::XPath::InvalidPath and explicate it's an XPath problem
129
+
130
+ def xpath_(path, id = nil, options = {}, &block)
131
+ former_xdoc = @xdoc
132
+ apa = AssertXPathArguments.new(path, id, options)
133
+ node = @xdoc.xpath(apa.xpath) #, nil, apa.subs)
134
+ # TODO advise Nokogiri to provide substitution arguments
135
+
136
+ add_diagnostic :clear do
137
+ diagnostic = "xpath: #{ apa.xpath.inspect }\n"
138
+ diagnostic << "arguments: #{ apa.subs.pretty_inspect }\n" if apa.subs.any?
139
+ diagnostic + "xml context:\n" + indent_xml
140
+ end
141
+
142
+ if node
143
+ def node.[](symbol)
144
+ return attributes[symbol.to_s]
145
+ end
146
+ end
147
+
148
+ if block
149
+ assert_('this xpath cannot find a node', :keep_diagnostics => true){ node }
150
+ assert_ nil, :args => [@xdoc = node], :keep_diagnostics => true, &block # TODO need the _ ?
151
+ end
152
+
153
+ return node
154
+ # TODO raid http://thebogles.com/blog/an-hpricot-style-interface-to-libxml/
155
+ ensure
156
+ @xdoc = former_xdoc
157
+ end # TODO trap LibXML::XML::XPath::InvalidPath and explicate it's an XPath problem
158
+
159
+ def indent_xml(node = @xdoc)
160
+ bar = REXML::Formatters::Pretty.new
161
+ out = String.new
162
+ bar.write(node, out)
163
+ return out
164
+ end
165
+
166
+ end; end; end
167
+
168
+
169
+ if RUBY_VERSION <= '1.8.6'
170
+
171
+ module REXML
172
+ module Formatters
173
+ class Pretty
174
+
175
+ private
176
+
177
+ # see http://www.google.com/codesearch/p?hl=en#Ezb_-tQR858/test_libs/rexml_fix.rb
178
+ # for less info about this fix...
179
+
180
+ def wrap(string, width)
181
+ # Recursivly wrap string at width.
182
+ return string if string.length <= width
183
+ place = string.rindex(/\s+/, width) # Position in string with last ' ' before cutoff
184
+ return string if place.nil?
185
+ return string[0,place] + "\n" + wrap(string[place+1..-1], width)
186
+ end
187
+
188
+ end
189
+ end
190
+
191
+ class Element
192
+ # this patches http://www.germane-software.com/projects/rexml/ticket/128
193
+ def write(output=$stdout, indent=-1, transitive=false, ie_hack=false)
194
+ Kernel.warn("#{self.class.name}.write is deprecated. See REXML::Formatters")
195
+ formatter = if indent > -1
196
+ if transitive
197
+ require "rexml/formatters/transitive"
198
+ REXML::Formatters::Transitive.new( indent, ie_hack )
199
+ else
200
+ REXML::Formatters::Pretty.new( indent, ie_hack )
201
+ end
202
+ else
203
+ REXML::Formatters::Default.new( ie_hack )
204
+ end
205
+ formatter.write( self, output )
206
+ end
207
+ end
208
+ end
209
+
210
+ end
211
+
212
+
213
+ require '../../test/assert_xhtml_suite.rb' if $0 == __FILE__ and File.exist?('../../test/assert_xhtml_suite.rb')
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: assert2
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.8
4
+ version: 0.3.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Phlip
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-03-11 00:00:00 -07:00
12
+ date: 2009-03-15 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -25,6 +25,7 @@ files:
25
25
  - lib/assert2
26
26
  - lib/assert2/xhtml.rb~
27
27
  - lib/assert2/flunk.rb
28
+ - lib/assert2/xpath.rb~
28
29
  - lib/assert2/rubynode_reflector.rb
29
30
  - lib/assert2/xpath.rb
30
31
  - lib/assert2/ripper_reflector.rb