assert2 0.3.8 → 0.3.9

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/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